From c19adf3684f52433d2324c45bb56bba01fad7531 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 7 Aug 2019 17:31:23 +0300 Subject: [PATCH 001/133] Version set to 3.0.0-SNAPSHOT --- application/pom.xml | 2 +- common/dao-api/pom.xml | 2 +- common/data/pom.xml | 2 +- common/message/pom.xml | 2 +- common/pom.xml | 2 +- common/queue/pom.xml | 2 +- common/transport/coap/pom.xml | 2 +- common/transport/http/pom.xml | 2 +- common/transport/mqtt/pom.xml | 2 +- common/transport/pom.xml | 2 +- common/transport/transport-api/pom.xml | 2 +- common/util/pom.xml | 2 +- dao/pom.xml | 2 +- msa/black-box-tests/pom.xml | 2 +- msa/js-executor/pom.xml | 2 +- msa/pom.xml | 2 +- msa/tb-node/pom.xml | 2 +- msa/tb/pom.xml | 2 +- msa/transport/coap/pom.xml | 2 +- msa/transport/http/pom.xml | 2 +- msa/transport/mqtt/pom.xml | 2 +- msa/transport/pom.xml | 2 +- msa/web-ui/pom.xml | 2 +- netty-mqtt/pom.xml | 4 ++-- pom.xml | 2 +- rule-engine/pom.xml | 2 +- rule-engine/rule-engine-api/pom.xml | 2 +- rule-engine/rule-engine-components/pom.xml | 2 +- tools/pom.xml | 2 +- transport/coap/pom.xml | 2 +- transport/http/pom.xml | 2 +- transport/mqtt/pom.xml | 2 +- transport/pom.xml | 2 +- ui/pom.xml | 2 +- 34 files changed, 35 insertions(+), 35 deletions(-) diff --git a/application/pom.xml b/application/pom.xml index 24c3b6a950..23f2554ad4 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 3.0.0-SNAPSHOT thingsboard application diff --git a/common/dao-api/pom.xml b/common/dao-api/pom.xml index 05cc589eb2..7f25f8c64f 100644 --- a/common/dao-api/pom.xml +++ b/common/dao-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 3.0.0-SNAPSHOT common org.thingsboard.common diff --git a/common/data/pom.xml b/common/data/pom.xml index be1b6393fb..9e8ec16339 100644 --- a/common/data/pom.xml +++ b/common/data/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 3.0.0-SNAPSHOT common org.thingsboard.common diff --git a/common/message/pom.xml b/common/message/pom.xml index 10b0ec7494..5d03951f69 100644 --- a/common/message/pom.xml +++ b/common/message/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 3.0.0-SNAPSHOT common org.thingsboard.common diff --git a/common/pom.xml b/common/pom.xml index 7492cb3199..51773f306f 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 3.0.0-SNAPSHOT thingsboard common diff --git a/common/queue/pom.xml b/common/queue/pom.xml index 7f09972f52..79d27ae4a6 100644 --- a/common/queue/pom.xml +++ b/common/queue/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 3.0.0-SNAPSHOT common org.thingsboard.common diff --git a/common/transport/coap/pom.xml b/common/transport/coap/pom.xml index 604c97b0fb..bf63a29922 100644 --- a/common/transport/coap/pom.xml +++ b/common/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 2.4.1-SNAPSHOT + 3.0.0-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/http/pom.xml b/common/transport/http/pom.xml index 0757263353..bd4b164928 100644 --- a/common/transport/http/pom.xml +++ b/common/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 2.4.1-SNAPSHOT + 3.0.0-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/mqtt/pom.xml b/common/transport/mqtt/pom.xml index 936416229a..48df10e16f 100644 --- a/common/transport/mqtt/pom.xml +++ b/common/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 2.4.1-SNAPSHOT + 3.0.0-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/pom.xml b/common/transport/pom.xml index dc00a7f55a..31cd2d74fe 100644 --- a/common/transport/pom.xml +++ b/common/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 3.0.0-SNAPSHOT common org.thingsboard.common diff --git a/common/transport/transport-api/pom.xml b/common/transport/transport-api/pom.xml index 826d00787d..dcc44c861d 100644 --- a/common/transport/transport-api/pom.xml +++ b/common/transport/transport-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 2.4.1-SNAPSHOT + 3.0.0-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/util/pom.xml b/common/util/pom.xml index d6c1d8afd3..3590de1ad8 100644 --- a/common/util/pom.xml +++ b/common/util/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 3.0.0-SNAPSHOT common org.thingsboard.common diff --git a/dao/pom.xml b/dao/pom.xml index be764b97eb..49b659334e 100644 --- a/dao/pom.xml +++ b/dao/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 3.0.0-SNAPSHOT thingsboard dao diff --git a/msa/black-box-tests/pom.xml b/msa/black-box-tests/pom.xml index e36ef1fd42..1e249fd45e 100644 --- a/msa/black-box-tests/pom.xml +++ b/msa/black-box-tests/pom.xml @@ -21,7 +21,7 @@ org.thingsboard - 2.4.1-SNAPSHOT + 3.0.0-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/js-executor/pom.xml b/msa/js-executor/pom.xml index 278e005c10..e4faee234c 100644 --- a/msa/js-executor/pom.xml +++ b/msa/js-executor/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 3.0.0-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/pom.xml b/msa/pom.xml index 004109f926..920963603a 100644 --- a/msa/pom.xml +++ b/msa/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 3.0.0-SNAPSHOT thingsboard msa diff --git a/msa/tb-node/pom.xml b/msa/tb-node/pom.xml index 1028283c50..b58bfb7927 100644 --- a/msa/tb-node/pom.xml +++ b/msa/tb-node/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 3.0.0-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/tb/pom.xml b/msa/tb/pom.xml index 6ccc50639a..0a55218bd3 100644 --- a/msa/tb/pom.xml +++ b/msa/tb/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 3.0.0-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/transport/coap/pom.xml b/msa/transport/coap/pom.xml index f948de66b3..ad316116df 100644 --- a/msa/transport/coap/pom.xml +++ b/msa/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 2.4.1-SNAPSHOT + 3.0.0-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/http/pom.xml b/msa/transport/http/pom.xml index 7b148ae9d2..1325fe6628 100644 --- a/msa/transport/http/pom.xml +++ b/msa/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 2.4.1-SNAPSHOT + 3.0.0-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/mqtt/pom.xml b/msa/transport/mqtt/pom.xml index f29c0e9ee0..3720831d62 100644 --- a/msa/transport/mqtt/pom.xml +++ b/msa/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 2.4.1-SNAPSHOT + 3.0.0-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/pom.xml b/msa/transport/pom.xml index 5bf9fbe7e1..5fc5fec96d 100644 --- a/msa/transport/pom.xml +++ b/msa/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 3.0.0-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/web-ui/pom.xml b/msa/web-ui/pom.xml index 801d3e6727..43cf3a0e88 100644 --- a/msa/web-ui/pom.xml +++ b/msa/web-ui/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 3.0.0-SNAPSHOT msa org.thingsboard.msa diff --git a/netty-mqtt/pom.xml b/netty-mqtt/pom.xml index c7eb97b0dd..baa7e91d8c 100644 --- a/netty-mqtt/pom.xml +++ b/netty-mqtt/pom.xml @@ -19,12 +19,12 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 3.0.0-SNAPSHOT thingsboard org.thingsboard netty-mqtt - 2.4.1-SNAPSHOT + 3.0.0-SNAPSHOT jar Netty MQTT Client diff --git a/pom.xml b/pom.xml index 65657d6fa7..82c91deaf4 100755 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard thingsboard - 2.4.1-SNAPSHOT + 3.0.0-SNAPSHOT pom Thingsboard diff --git a/rule-engine/pom.xml b/rule-engine/pom.xml index 1fe931962e..0d445762d7 100644 --- a/rule-engine/pom.xml +++ b/rule-engine/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 3.0.0-SNAPSHOT thingsboard rule-engine diff --git a/rule-engine/rule-engine-api/pom.xml b/rule-engine/rule-engine-api/pom.xml index 8874c051ee..99c30b4c9e 100644 --- a/rule-engine/rule-engine-api/pom.xml +++ b/rule-engine/rule-engine-api/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 3.0.0-SNAPSHOT rule-engine org.thingsboard.rule-engine diff --git a/rule-engine/rule-engine-components/pom.xml b/rule-engine/rule-engine-components/pom.xml index 5c040a389f..bd1a79e8ed 100644 --- a/rule-engine/rule-engine-components/pom.xml +++ b/rule-engine/rule-engine-components/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 3.0.0-SNAPSHOT rule-engine org.thingsboard.rule-engine diff --git a/tools/pom.xml b/tools/pom.xml index 7402fe4708..a1d183fe07 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 3.0.0-SNAPSHOT thingsboard tools diff --git a/transport/coap/pom.xml b/transport/coap/pom.xml index f3960dce9b..67fbee67f8 100644 --- a/transport/coap/pom.xml +++ b/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 3.0.0-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/http/pom.xml b/transport/http/pom.xml index 01b5d0be5a..54b95ea122 100644 --- a/transport/http/pom.xml +++ b/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 3.0.0-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/mqtt/pom.xml b/transport/mqtt/pom.xml index 0849406295..63dede47ff 100644 --- a/transport/mqtt/pom.xml +++ b/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 3.0.0-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/pom.xml b/transport/pom.xml index 6d56741015..3e04e1fb44 100644 --- a/transport/pom.xml +++ b/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 3.0.0-SNAPSHOT thingsboard transport diff --git a/ui/pom.xml b/ui/pom.xml index 27caa78236..09f6236454 100644 --- a/ui/pom.xml +++ b/ui/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.1-SNAPSHOT + 3.0.0-SNAPSHOT thingsboard org.thingsboard From cdb9520ccfe9487399bf6eb6df092e9eddbaabfb Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 7 Aug 2019 18:48:01 +0300 Subject: [PATCH 002/133] Remove Entities NoSql DAO --- .../CassandraEntityDatabaseSchemaService.java | 30 - .../src/main/resources/thingsboard.yml | 5 +- .../controller/ControllerNoSqlTestSuite.java | 40 - .../nosql/AdminControllerNoSqlTest.java | 26 - .../nosql/AssetControllerNoSqlTest.java | 27 - .../nosql/AuditLogControllerNoSqlTest.java | 23 - .../nosql/AuthControllerNoSqlTest.java | 26 - ...omponentDescriptorControllerNoSqlTest.java | 26 - .../nosql/CustomerControllerNoSqlTest.java | 26 - .../nosql/DashboardControllerNoSqlTest.java | 26 - .../nosql/DeviceControllerNoSqlTest.java | 26 - .../nosql/EntityViewControllerNoSqlTest.java | 26 - .../nosql/TenantControllerNoSqlTest.java | 26 - .../nosql/UserControllerNoSqlTest.java | 26 - .../nosql/WidgetTypeControllerNoSqlTest.java | 26 - .../WidgetsBundleControllerNoSqlTest.java | 26 - .../server/mqtt/MqttNoSqlTestSuite.java | 11 +- .../rules/RuleEngineNoSqlTestSuite.java | 43 -- .../RuleEngineFlowNoSqlIntegrationTest.java | 26 - ...leEngineLifecycleNoSqlIntegrationTest.java | 26 - .../server/system/SystemNoSqlTestSuite.java | 41 - .../system/nosql/DeviceApiNoSqlTest.java | 27 - .../thingsboard/server/dao/util/SqlDao.java | 1 - .../server/dao/alarm/CassandraAlarmDao.java | 139 ---- .../server/dao/asset/CassandraAssetDao.java | 191 ----- .../CassandraBaseAttributesDao.java | 204 ----- .../server/dao/audit/AuditLogQueryCursor.java | 70 -- .../dao/audit/CassandraAuditLogDao.java | 359 --------- .../CassandraBaseComponentDescriptorDao.java | 175 ----- .../dao/customer/CassandraCustomerDao.java | 77 -- .../dao/dashboard/CassandraDashboardDao.java | 39 - .../dashboard/CassandraDashboardInfoDao.java | 91 --- .../device/CassandraDeviceCredentialsDao.java | 70 -- .../server/dao/device/CassandraDeviceDao.java | 191 ----- .../entityview/CassandraEntityViewDao.java | 186 ----- .../dao/event/CassandraBaseEventDao.java | 215 ------ .../dao/model/nosql/AdminSettingsEntity.java | 97 --- .../server/dao/model/nosql/AlarmEntity.java | 254 ------- .../server/dao/model/nosql/AssetEntity.java | 170 ----- .../dao/model/nosql/AuditLogEntity.java | 155 ---- .../nosql/ComponentDescriptorEntity.java | 171 ----- .../dao/model/nosql/CustomerEntity.java | 243 ------ .../dao/model/nosql/DashboardEntity.java | 174 ----- .../dao/model/nosql/DashboardInfoEntity.java | 158 ---- .../model/nosql/DeviceCredentialsEntity.java | 130 ---- .../server/dao/model/nosql/DeviceEntity.java | 168 ----- .../dao/model/nosql/EntityViewEntity.java | 164 ---- .../server/dao/model/nosql/EventEntity.java | 117 --- .../dao/model/nosql/RuleChainEntity.java | 182 ----- .../dao/model/nosql/RuleNodeEntity.java | 167 ---- .../server/dao/model/nosql/TenantEntity.java | 242 ------ .../model/nosql/UserCredentialsEntity.java | 140 ---- .../server/dao/model/nosql/UserEntity.java | 200 ----- .../dao/model/nosql/WidgetTypeEntity.java | 148 ---- .../dao/model/nosql/WidgetsBundleEntity.java | 156 ---- .../dao/model/type/ActionStatusCodec.java | 26 - .../dao/model/type/ActionTypeCodec.java | 26 - .../dao/model/type/AlarmSeverityCodec.java | 27 - .../dao/model/type/AlarmStatusCodec.java | 27 - .../server/dao/model/type/AuthorityCodec.java | 27 - .../dao/model/type/ComponentScopeCodec.java | 27 - .../dao/model/type/ComponentTypeCodec.java | 27 - .../type/DeviceCredentialsTypeCodec.java | 27 - .../model/type/RelationTypeGroupCodec.java | 27 - .../dao/nosql/CassandraAbstractDao.java | 18 +- .../server/dao/relation/BaseRelationDao.java | 381 ---------- .../dao/rule/CassandraRuleChainDao.java | 63 -- .../server/dao/rule/CassandraRuleNodeDao.java | 42 -- .../settings/CassandraAdminSettingsDao.java | 59 -- .../server/dao/tenant/CassandraTenantDao.java | 61 -- .../dao/user/CassandraUserCredentialsDao.java | 83 -- .../server/dao/user/CassandraUserDao.java | 89 --- .../dao/widget/CassandraWidgetTypeDao.java | 81 -- .../dao/widget/CassandraWidgetsBundleDao.java | 103 --- .../resources/cassandra/schema-entities.cql | 714 ------------------ .../main/resources/cassandra/system-data.cql | 44 -- .../server/dao/CustomCassandraCQLUnit.java | 1 + .../server/dao/NoSqlDaoServiceTestSuite.java | 13 +- .../nosql/AttributesServiceNoSqlTest.java | 23 - .../event/nosql/EventServiceNoSqlTest.java | 23 - .../nosql/AdminSettingsServiceNoSqlTest.java | 23 - .../service/nosql/AlarmServiceNoSqlTest.java | 23 - .../service/nosql/AssetServiceNoSqlTest.java | 23 - .../nosql/CustomerServiceNoSqlTest.java | 23 - .../nosql/DashboardServiceNoSqlTest.java | 23 - ...DeviceCredentialCacheServiceNoSqlTest.java | 23 - .../DeviceCredentialServiceNoSqlTest.java | 23 - .../service/nosql/DeviceServiceNoSqlTest.java | 23 - .../service/nosql/RelationCacheNoSqlTest.java | 23 - .../nosql/RelationServiceNoSqlTest.java | 23 - .../nosql/RuleChainServiceNoSqlTest.java | 23 - .../service/nosql/TenantServiceNoSqlTest.java | 23 - .../service/nosql/UserServiceNoSqlTest.java | 23 - .../nosql/WidgetTypeServiceNoSqlTest.java | 23 - .../nosql/WidgetsBundleServiceNoSqlTest.java | 23 - .../test/resources/cassandra/system-test.cql | 27 - dao/src/test/resources/nosql-test.properties | 14 +- dao/src/test/resources/sql-test.properties | 1 - .../resources/sql/system-data.sql | 0 msa/js-executor/package-lock.json | 14 +- msa/web-ui/package-lock.json | 18 +- .../TbSaveToCustomCassandraTableNode.java | 26 +- ui/package-lock.json | 324 ++++---- 103 files changed, 214 insertions(+), 8423 deletions(-) delete mode 100644 application/src/main/java/org/thingsboard/server/service/install/CassandraEntityDatabaseSchemaService.java delete mode 100644 application/src/test/java/org/thingsboard/server/controller/ControllerNoSqlTestSuite.java delete mode 100644 application/src/test/java/org/thingsboard/server/controller/nosql/AdminControllerNoSqlTest.java delete mode 100644 application/src/test/java/org/thingsboard/server/controller/nosql/AssetControllerNoSqlTest.java delete mode 100644 application/src/test/java/org/thingsboard/server/controller/nosql/AuditLogControllerNoSqlTest.java delete mode 100644 application/src/test/java/org/thingsboard/server/controller/nosql/AuthControllerNoSqlTest.java delete mode 100644 application/src/test/java/org/thingsboard/server/controller/nosql/ComponentDescriptorControllerNoSqlTest.java delete mode 100644 application/src/test/java/org/thingsboard/server/controller/nosql/CustomerControllerNoSqlTest.java delete mode 100644 application/src/test/java/org/thingsboard/server/controller/nosql/DashboardControllerNoSqlTest.java delete mode 100644 application/src/test/java/org/thingsboard/server/controller/nosql/DeviceControllerNoSqlTest.java delete mode 100644 application/src/test/java/org/thingsboard/server/controller/nosql/EntityViewControllerNoSqlTest.java delete mode 100644 application/src/test/java/org/thingsboard/server/controller/nosql/TenantControllerNoSqlTest.java delete mode 100644 application/src/test/java/org/thingsboard/server/controller/nosql/UserControllerNoSqlTest.java delete mode 100644 application/src/test/java/org/thingsboard/server/controller/nosql/WidgetTypeControllerNoSqlTest.java delete mode 100644 application/src/test/java/org/thingsboard/server/controller/nosql/WidgetsBundleControllerNoSqlTest.java delete mode 100644 application/src/test/java/org/thingsboard/server/rules/RuleEngineNoSqlTestSuite.java delete mode 100644 application/src/test/java/org/thingsboard/server/rules/flow/nosql/RuleEngineFlowNoSqlIntegrationTest.java delete mode 100644 application/src/test/java/org/thingsboard/server/rules/lifecycle/nosql/RuleEngineLifecycleNoSqlIntegrationTest.java delete mode 100644 application/src/test/java/org/thingsboard/server/system/SystemNoSqlTestSuite.java delete mode 100644 application/src/test/java/org/thingsboard/server/system/nosql/DeviceApiNoSqlTest.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/alarm/CassandraAlarmDao.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/asset/CassandraAssetDao.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/attributes/CassandraBaseAttributesDao.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogQueryCursor.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/audit/CassandraAuditLogDao.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/component/CassandraBaseComponentDescriptorDao.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/customer/CassandraCustomerDao.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/dashboard/CassandraDashboardDao.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/dashboard/CassandraDashboardInfoDao.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/device/CassandraDeviceCredentialsDao.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/device/CassandraDeviceDao.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/entityview/CassandraEntityViewDao.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/event/CassandraBaseEventDao.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/nosql/AdminSettingsEntity.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/nosql/AlarmEntity.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/nosql/AssetEntity.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/nosql/AuditLogEntity.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/nosql/ComponentDescriptorEntity.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/nosql/CustomerEntity.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardEntity.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardInfoEntity.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/nosql/DeviceCredentialsEntity.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/nosql/DeviceEntity.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/nosql/EntityViewEntity.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/nosql/EventEntity.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/nosql/RuleChainEntity.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/nosql/RuleNodeEntity.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/nosql/TenantEntity.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/nosql/UserCredentialsEntity.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/nosql/UserEntity.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/nosql/WidgetTypeEntity.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/nosql/WidgetsBundleEntity.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/type/ActionStatusCodec.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/type/ActionTypeCodec.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/type/AlarmSeverityCodec.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/type/AlarmStatusCodec.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/type/AuthorityCodec.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/type/ComponentScopeCodec.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/type/ComponentTypeCodec.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/type/DeviceCredentialsTypeCodec.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/type/RelationTypeGroupCodec.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationDao.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleChainDao.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleNodeDao.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/settings/CassandraAdminSettingsDao.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/tenant/CassandraTenantDao.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/user/CassandraUserCredentialsDao.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/user/CassandraUserDao.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/widget/CassandraWidgetTypeDao.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/widget/CassandraWidgetsBundleDao.java delete mode 100644 dao/src/main/resources/cassandra/schema-entities.cql delete mode 100644 dao/src/main/resources/cassandra/system-data.cql delete mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/attributes/nosql/AttributesServiceNoSqlTest.java delete mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/event/nosql/EventServiceNoSqlTest.java delete mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/nosql/AdminSettingsServiceNoSqlTest.java delete mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/nosql/AlarmServiceNoSqlTest.java delete mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/nosql/AssetServiceNoSqlTest.java delete mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/nosql/CustomerServiceNoSqlTest.java delete mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/nosql/DashboardServiceNoSqlTest.java delete mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/nosql/DeviceCredentialCacheServiceNoSqlTest.java delete mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/nosql/DeviceCredentialServiceNoSqlTest.java delete mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/nosql/DeviceServiceNoSqlTest.java delete mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/nosql/RelationCacheNoSqlTest.java delete mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/nosql/RelationServiceNoSqlTest.java delete mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/nosql/RuleChainServiceNoSqlTest.java delete mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/nosql/TenantServiceNoSqlTest.java delete mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/nosql/UserServiceNoSqlTest.java delete mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/nosql/WidgetTypeServiceNoSqlTest.java delete mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/nosql/WidgetsBundleServiceNoSqlTest.java delete mode 100644 dao/src/test/resources/cassandra/system-test.cql rename dao/src/{main => test}/resources/sql/system-data.sql (100%) diff --git a/application/src/main/java/org/thingsboard/server/service/install/CassandraEntityDatabaseSchemaService.java b/application/src/main/java/org/thingsboard/server/service/install/CassandraEntityDatabaseSchemaService.java deleted file mode 100644 index ab45798e0e..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/install/CassandraEntityDatabaseSchemaService.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright © 2016-2019 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.install; - -import org.springframework.context.annotation.Profile; -import org.springframework.stereotype.Service; -import org.thingsboard.server.dao.util.NoSqlDao; - -@Service -@NoSqlDao -@Profile("install") -public class CassandraEntityDatabaseSchemaService extends CassandraAbstractDatabaseSchemaService - implements EntityDatabaseSchemaService { - public CassandraEntityDatabaseSchemaService() { - super("schema-entities.cql"); - } -} diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 13c0ad767e..01f6f130ee 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -116,11 +116,8 @@ dashboard: database: ts_max_intervals: "${DATABASE_TS_MAX_INTERVALS:700}" # Max number of DB queries generated by single API call to fetch telemetry records - entities: - type: "${DATABASE_ENTITIES_TYPE:sql}" # cassandra OR sql ts: - type: "${DATABASE_TS_TYPE:sql}" # cassandra OR sql (for hybrid mode, only this value should be cassandra) - + type: "${DATABASE_TS_TYPE:sql}" # cassandra OR sql # Cassandra driver configuration parameters cassandra: diff --git a/application/src/test/java/org/thingsboard/server/controller/ControllerNoSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/controller/ControllerNoSqlTestSuite.java deleted file mode 100644 index 3ada840bda..0000000000 --- a/application/src/test/java/org/thingsboard/server/controller/ControllerNoSqlTestSuite.java +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright © 2016-2019 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 org.cassandraunit.dataset.cql.ClassPathCQLDataSet; -import org.junit.ClassRule; -import org.junit.extensions.cpsuite.ClasspathSuite; -import org.junit.runner.RunWith; -import org.thingsboard.server.dao.CustomCassandraCQLUnit; - -import java.util.Arrays; - -@RunWith(ClasspathSuite.class) -@ClasspathSuite.ClassnameFilters({ - "org.thingsboard.server.controller.nosql.*Test"}) -public class ControllerNoSqlTestSuite { - - @ClassRule - public static CustomCassandraCQLUnit cassandraUnit = - new CustomCassandraCQLUnit( - Arrays.asList( - new ClassPathCQLDataSet("cassandra/schema-ts.cql", false, false), - new ClassPathCQLDataSet("cassandra/schema-entities.cql", false, false), - new ClassPathCQLDataSet("cassandra/system-data.cql", false, false), - new ClassPathCQLDataSet("cassandra/system-test.cql", false, false)), - "cassandra-test.yaml", 30000l); -} diff --git a/application/src/test/java/org/thingsboard/server/controller/nosql/AdminControllerNoSqlTest.java b/application/src/test/java/org/thingsboard/server/controller/nosql/AdminControllerNoSqlTest.java deleted file mode 100644 index cfee252551..0000000000 --- a/application/src/test/java/org/thingsboard/server/controller/nosql/AdminControllerNoSqlTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright © 2016-2019 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.nosql; - -import org.thingsboard.server.controller.BaseAdminControllerTest; -import org.thingsboard.server.dao.service.DaoNoSqlTest; - -/** - * Created by Valerii Sosliuk on 6/28/2017. - */ -@DaoNoSqlTest -public class AdminControllerNoSqlTest extends BaseAdminControllerTest { -} diff --git a/application/src/test/java/org/thingsboard/server/controller/nosql/AssetControllerNoSqlTest.java b/application/src/test/java/org/thingsboard/server/controller/nosql/AssetControllerNoSqlTest.java deleted file mode 100644 index c30e042baf..0000000000 --- a/application/src/test/java/org/thingsboard/server/controller/nosql/AssetControllerNoSqlTest.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright © 2016-2019 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.nosql; - -import org.thingsboard.server.controller.BaseAssetControllerTest; -import org.thingsboard.server.dao.service.DaoNoSqlTest; -import org.thingsboard.server.dao.util.NoSqlDao; - -/** - * Created by Valerii Sosliuk on 6/28/2017. - */ -@DaoNoSqlTest -public class AssetControllerNoSqlTest extends BaseAssetControllerTest { -} diff --git a/application/src/test/java/org/thingsboard/server/controller/nosql/AuditLogControllerNoSqlTest.java b/application/src/test/java/org/thingsboard/server/controller/nosql/AuditLogControllerNoSqlTest.java deleted file mode 100644 index b573f99aeb..0000000000 --- a/application/src/test/java/org/thingsboard/server/controller/nosql/AuditLogControllerNoSqlTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2019 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.nosql; - -import org.thingsboard.server.controller.BaseAuditLogControllerTest; -import org.thingsboard.server.dao.service.DaoNoSqlTest; - -@DaoNoSqlTest -public class AuditLogControllerNoSqlTest extends BaseAuditLogControllerTest { -} diff --git a/application/src/test/java/org/thingsboard/server/controller/nosql/AuthControllerNoSqlTest.java b/application/src/test/java/org/thingsboard/server/controller/nosql/AuthControllerNoSqlTest.java deleted file mode 100644 index 4f120eb10d..0000000000 --- a/application/src/test/java/org/thingsboard/server/controller/nosql/AuthControllerNoSqlTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright © 2016-2019 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.nosql; - -import org.thingsboard.server.controller.BaseAuthControllerTest; -import org.thingsboard.server.dao.service.DaoNoSqlTest; - -/** - * Created by Valerii Sosliuk on 6/28/2017. - */ -@DaoNoSqlTest -public class AuthControllerNoSqlTest extends BaseAuthControllerTest { -} diff --git a/application/src/test/java/org/thingsboard/server/controller/nosql/ComponentDescriptorControllerNoSqlTest.java b/application/src/test/java/org/thingsboard/server/controller/nosql/ComponentDescriptorControllerNoSqlTest.java deleted file mode 100644 index 6eb42c4326..0000000000 --- a/application/src/test/java/org/thingsboard/server/controller/nosql/ComponentDescriptorControllerNoSqlTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright © 2016-2019 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.nosql; - -import org.thingsboard.server.controller.BaseComponentDescriptorControllerTest; -import org.thingsboard.server.dao.service.DaoNoSqlTest; - -/** - * Created by Valerii Sosliuk on 6/28/2017. - */ -@DaoNoSqlTest -public class ComponentDescriptorControllerNoSqlTest extends BaseComponentDescriptorControllerTest { -} diff --git a/application/src/test/java/org/thingsboard/server/controller/nosql/CustomerControllerNoSqlTest.java b/application/src/test/java/org/thingsboard/server/controller/nosql/CustomerControllerNoSqlTest.java deleted file mode 100644 index 7e516309bb..0000000000 --- a/application/src/test/java/org/thingsboard/server/controller/nosql/CustomerControllerNoSqlTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright © 2016-2019 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.nosql; - -import org.thingsboard.server.controller.BaseCustomerControllerTest; -import org.thingsboard.server.dao.service.DaoNoSqlTest; - -/** - * Created by Valerii Sosliuk on 6/28/2017. - */ -@DaoNoSqlTest -public class CustomerControllerNoSqlTest extends BaseCustomerControllerTest { -} diff --git a/application/src/test/java/org/thingsboard/server/controller/nosql/DashboardControllerNoSqlTest.java b/application/src/test/java/org/thingsboard/server/controller/nosql/DashboardControllerNoSqlTest.java deleted file mode 100644 index af774d96bf..0000000000 --- a/application/src/test/java/org/thingsboard/server/controller/nosql/DashboardControllerNoSqlTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright © 2016-2019 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.nosql; - -import org.thingsboard.server.controller.BaseDashboardControllerTest; -import org.thingsboard.server.dao.service.DaoNoSqlTest; - -/** - * Created by Valerii Sosliuk on 6/28/2017. - */ -@DaoNoSqlTest -public class DashboardControllerNoSqlTest extends BaseDashboardControllerTest { -} diff --git a/application/src/test/java/org/thingsboard/server/controller/nosql/DeviceControllerNoSqlTest.java b/application/src/test/java/org/thingsboard/server/controller/nosql/DeviceControllerNoSqlTest.java deleted file mode 100644 index 0d10bd7254..0000000000 --- a/application/src/test/java/org/thingsboard/server/controller/nosql/DeviceControllerNoSqlTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright © 2016-2019 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.nosql; - -import org.thingsboard.server.controller.BaseDeviceControllerTest; -import org.thingsboard.server.dao.service.DaoNoSqlTest; - -/** - * Created by Valerii Sosliuk on 6/28/2017. - */ -@DaoNoSqlTest -public class DeviceControllerNoSqlTest extends BaseDeviceControllerTest { -} diff --git a/application/src/test/java/org/thingsboard/server/controller/nosql/EntityViewControllerNoSqlTest.java b/application/src/test/java/org/thingsboard/server/controller/nosql/EntityViewControllerNoSqlTest.java deleted file mode 100644 index 5455930ca3..0000000000 --- a/application/src/test/java/org/thingsboard/server/controller/nosql/EntityViewControllerNoSqlTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright © 2016-2019 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.nosql; - -import org.thingsboard.server.controller.BaseEntityViewControllerTest; -import org.thingsboard.server.dao.service.DaoNoSqlTest; - -/** - * Created by Victor Basanets on 8/27/2017. - */ -@DaoNoSqlTest -public class EntityViewControllerNoSqlTest extends BaseEntityViewControllerTest { -} diff --git a/application/src/test/java/org/thingsboard/server/controller/nosql/TenantControllerNoSqlTest.java b/application/src/test/java/org/thingsboard/server/controller/nosql/TenantControllerNoSqlTest.java deleted file mode 100644 index 8bfa1226fb..0000000000 --- a/application/src/test/java/org/thingsboard/server/controller/nosql/TenantControllerNoSqlTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright © 2016-2019 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.nosql; - -import org.thingsboard.server.controller.BaseTenantControllerTest; -import org.thingsboard.server.dao.service.DaoNoSqlTest; - -/** - * Created by Valerii Sosliuk on 6/28/2017. - */ -@DaoNoSqlTest -public class TenantControllerNoSqlTest extends BaseTenantControllerTest { -} diff --git a/application/src/test/java/org/thingsboard/server/controller/nosql/UserControllerNoSqlTest.java b/application/src/test/java/org/thingsboard/server/controller/nosql/UserControllerNoSqlTest.java deleted file mode 100644 index 5817571abc..0000000000 --- a/application/src/test/java/org/thingsboard/server/controller/nosql/UserControllerNoSqlTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright © 2016-2019 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.nosql; - -import org.thingsboard.server.controller.BaseUserControllerTest; -import org.thingsboard.server.dao.service.DaoNoSqlTest; - -/** - * Created by Valerii Sosliuk on 6/28/2017. - */ -@DaoNoSqlTest -public class UserControllerNoSqlTest extends BaseUserControllerTest { -} diff --git a/application/src/test/java/org/thingsboard/server/controller/nosql/WidgetTypeControllerNoSqlTest.java b/application/src/test/java/org/thingsboard/server/controller/nosql/WidgetTypeControllerNoSqlTest.java deleted file mode 100644 index aaa9584aba..0000000000 --- a/application/src/test/java/org/thingsboard/server/controller/nosql/WidgetTypeControllerNoSqlTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright © 2016-2019 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.nosql; - -import org.thingsboard.server.controller.BaseWidgetTypeControllerTest; -import org.thingsboard.server.dao.service.DaoNoSqlTest; - -/** - * Created by Valerii Sosliuk on 6/28/2017. - */ -@DaoNoSqlTest -public class WidgetTypeControllerNoSqlTest extends BaseWidgetTypeControllerTest { -} diff --git a/application/src/test/java/org/thingsboard/server/controller/nosql/WidgetsBundleControllerNoSqlTest.java b/application/src/test/java/org/thingsboard/server/controller/nosql/WidgetsBundleControllerNoSqlTest.java deleted file mode 100644 index ce4b617da2..0000000000 --- a/application/src/test/java/org/thingsboard/server/controller/nosql/WidgetsBundleControllerNoSqlTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright © 2016-2019 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.nosql; - -import org.thingsboard.server.controller.BaseWidgetsBundleControllerTest; -import org.thingsboard.server.dao.service.DaoNoSqlTest; - -/** - * Created by Valerii Sosliuk on 6/28/2017. - */ -@DaoNoSqlTest -public class WidgetsBundleControllerNoSqlTest extends BaseWidgetsBundleControllerTest { -} diff --git a/application/src/test/java/org/thingsboard/server/mqtt/MqttNoSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/mqtt/MqttNoSqlTestSuite.java index a3b38a2956..b27fa8ac47 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/MqttNoSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/MqttNoSqlTestSuite.java @@ -20,6 +20,7 @@ import org.junit.ClassRule; import org.junit.extensions.cpsuite.ClasspathSuite; import org.junit.runner.RunWith; import org.thingsboard.server.dao.CustomCassandraCQLUnit; +import org.thingsboard.server.dao.CustomSqlUnit; import java.util.Arrays; @@ -28,12 +29,16 @@ import java.util.Arrays; "org.thingsboard.server.mqtt.*.nosql.*Test"}) public class MqttNoSqlTestSuite { + @ClassRule + public static CustomSqlUnit sqlUnit = new CustomSqlUnit( + Arrays.asList("sql/schema-entities.sql", "sql/system-data.sql"), + "sql/drop-all-tables.sql", + "nosql-test.properties"); + @ClassRule public static CustomCassandraCQLUnit cassandraUnit = new CustomCassandraCQLUnit( Arrays.asList( - new ClassPathCQLDataSet("cassandra/schema-ts.cql", false, false), - new ClassPathCQLDataSet("cassandra/schema-entities.cql", false, false), - new ClassPathCQLDataSet("cassandra/system-data.cql", false, false)), + new ClassPathCQLDataSet("cassandra/schema-ts.cql", false, false)), "cassandra-test.yaml", 30000l); } diff --git a/application/src/test/java/org/thingsboard/server/rules/RuleEngineNoSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/rules/RuleEngineNoSqlTestSuite.java deleted file mode 100644 index dbd5ec9303..0000000000 --- a/application/src/test/java/org/thingsboard/server/rules/RuleEngineNoSqlTestSuite.java +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright © 2016-2019 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.rules; - -import org.cassandraunit.dataset.cql.ClassPathCQLDataSet; -import org.junit.ClassRule; -import org.junit.extensions.cpsuite.ClasspathSuite; -import org.junit.runner.RunWith; -import org.thingsboard.server.dao.CustomCassandraCQLUnit; -import org.thingsboard.server.dao.CustomSqlUnit; - -import java.util.Arrays; - -@RunWith(ClasspathSuite.class) -@ClasspathSuite.ClassnameFilters({ - "org.thingsboard.server.rules.flow.nosql.*Test", - "org.thingsboard.server.rules.lifecycle.nosql.*Test" -}) -public class RuleEngineNoSqlTestSuite { - - @ClassRule - public static CustomCassandraCQLUnit cassandraUnit = - new CustomCassandraCQLUnit( - Arrays.asList( - new ClassPathCQLDataSet("cassandra/schema-ts.cql", false, false), - new ClassPathCQLDataSet("cassandra/schema-entities.cql", false, false), - new ClassPathCQLDataSet("cassandra/system-data.cql", false, false)), - "cassandra-test.yaml", 30000l); - -} diff --git a/application/src/test/java/org/thingsboard/server/rules/flow/nosql/RuleEngineFlowNoSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/rules/flow/nosql/RuleEngineFlowNoSqlIntegrationTest.java deleted file mode 100644 index cbcdcfd015..0000000000 --- a/application/src/test/java/org/thingsboard/server/rules/flow/nosql/RuleEngineFlowNoSqlIntegrationTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright © 2016-2019 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.rules.flow.nosql; - -import org.thingsboard.server.dao.service.DaoNoSqlTest; -import org.thingsboard.server.rules.flow.AbstractRuleEngineFlowIntegrationTest; - -/** - * Created by Valerii Sosliuk on 8/22/2017. - */ -@DaoNoSqlTest -public class RuleEngineFlowNoSqlIntegrationTest extends AbstractRuleEngineFlowIntegrationTest { -} diff --git a/application/src/test/java/org/thingsboard/server/rules/lifecycle/nosql/RuleEngineLifecycleNoSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/rules/lifecycle/nosql/RuleEngineLifecycleNoSqlIntegrationTest.java deleted file mode 100644 index 4d77f32638..0000000000 --- a/application/src/test/java/org/thingsboard/server/rules/lifecycle/nosql/RuleEngineLifecycleNoSqlIntegrationTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright © 2016-2019 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.rules.lifecycle.nosql; - -import org.thingsboard.server.dao.service.DaoNoSqlTest; -import org.thingsboard.server.rules.lifecycle.AbstractRuleEngineLifecycleIntegrationTest; - -/** - * Created by Valerii Sosliuk on 8/22/2017. - */ -@DaoNoSqlTest -public class RuleEngineLifecycleNoSqlIntegrationTest extends AbstractRuleEngineLifecycleIntegrationTest { -} diff --git a/application/src/test/java/org/thingsboard/server/system/SystemNoSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/system/SystemNoSqlTestSuite.java deleted file mode 100644 index cbe3290a07..0000000000 --- a/application/src/test/java/org/thingsboard/server/system/SystemNoSqlTestSuite.java +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright © 2016-2019 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.system; - -import org.cassandraunit.dataset.cql.ClassPathCQLDataSet; -import org.junit.ClassRule; -import org.junit.extensions.cpsuite.ClasspathSuite; -import org.junit.runner.RunWith; -import org.thingsboard.server.dao.CustomCassandraCQLUnit; - -import java.util.Arrays; - -/** - * @author Andrew Shvayka - */ -@RunWith(ClasspathSuite.class) -@ClasspathSuite.ClassnameFilters({"org.thingsboard.server.system.*NoSqlTest"}) -public class SystemNoSqlTestSuite { - - @ClassRule - public static CustomCassandraCQLUnit cassandraUnit = - new CustomCassandraCQLUnit( - Arrays.asList( - new ClassPathCQLDataSet("cassandra/schema-ts.cql", false, false), - new ClassPathCQLDataSet("cassandra/schema-entities.cql", false, false), - new ClassPathCQLDataSet("cassandra/system-data.cql", false, false)), - "cassandra-test.yaml", 30000l); -} diff --git a/application/src/test/java/org/thingsboard/server/system/nosql/DeviceApiNoSqlTest.java b/application/src/test/java/org/thingsboard/server/system/nosql/DeviceApiNoSqlTest.java deleted file mode 100644 index 1673b34394..0000000000 --- a/application/src/test/java/org/thingsboard/server/system/nosql/DeviceApiNoSqlTest.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright © 2016-2019 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.system.nosql; - -import org.thingsboard.server.dao.service.DaoNoSqlTest; -import org.thingsboard.server.dao.util.NoSqlDao; -import org.thingsboard.server.system.BaseHttpDeviceApiTest; - -/** - * Created by Valerii Sosliuk on 6/27/2017. - */ -@DaoNoSqlTest -public class DeviceApiNoSqlTest extends BaseHttpDeviceApiTest { -} diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/util/SqlDao.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/util/SqlDao.java index 45782f9a69..45eb4089ab 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/util/SqlDao.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/util/SqlDao.java @@ -17,6 +17,5 @@ package org.thingsboard.server.dao.util; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -@ConditionalOnProperty(prefix = "database.entities", value = "type", havingValue = "sql") public @interface SqlDao { } diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/CassandraAlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/CassandraAlarmDao.java deleted file mode 100644 index a152a7a401..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/CassandraAlarmDao.java +++ /dev/null @@ -1,139 +0,0 @@ -/** - * Copyright © 2016-2019 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.alarm; - -import com.datastax.driver.core.Statement; -import com.datastax.driver.core.querybuilder.QueryBuilder; -import com.datastax.driver.core.querybuilder.Select; -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.Autowired; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.alarm.Alarm; -import org.thingsboard.server.common.data.alarm.AlarmInfo; -import org.thingsboard.server.common.data.alarm.AlarmQuery; -import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; -import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.relation.EntityRelation; -import org.thingsboard.server.common.data.relation.RelationTypeGroup; -import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.model.nosql.AlarmEntity; -import org.thingsboard.server.dao.nosql.CassandraAbstractModelDao; -import org.thingsboard.server.dao.relation.RelationDao; -import org.thingsboard.server.dao.util.NoSqlDao; - -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; -import static com.datastax.driver.core.querybuilder.QueryBuilder.select; -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_BY_ID_VIEW_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_TYPE_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_TENANT_ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_TYPE_PROPERTY; - -@Component -@Slf4j -@NoSqlDao -public class CassandraAlarmDao extends CassandraAbstractModelDao implements AlarmDao { - - @Autowired - private RelationDao relationDao; - - @Override - protected Class getColumnFamilyClass() { - return AlarmEntity.class; - } - - @Override - protected String getColumnFamilyName() { - return ALARM_COLUMN_FAMILY_NAME; - } - - protected boolean isDeleteOnSave() { - return false; - } - - @Override - public Alarm save(TenantId tenantId, Alarm alarm) { - log.debug("Save asset [{}] ", alarm); - return super.save(tenantId, alarm); - } - - @Override - public Boolean deleteAlarm(TenantId tenantId, Alarm alarm) { - Statement delete = QueryBuilder.delete().all().from(getColumnFamilyName()).where(eq(ModelConstants.ID_PROPERTY, alarm.getId().getId())) - .and(eq(ALARM_TENANT_ID_PROPERTY, tenantId.getId())) - .and(eq(ALARM_ORIGINATOR_ID_PROPERTY, alarm.getOriginator().getId())) - .and(eq(ALARM_ORIGINATOR_TYPE_PROPERTY, alarm.getOriginator().getEntityType())) - .and(eq(ALARM_TYPE_PROPERTY, alarm.getType())); - log.debug("Remove request: {}", delete.toString()); - return executeWrite(tenantId, delete).wasApplied(); - } - - @Override - public ListenableFuture findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type) { - Select select = select().from(ALARM_COLUMN_FAMILY_NAME); - Select.Where query = select.where(); - query.and(eq(ALARM_TENANT_ID_PROPERTY, tenantId.getId())); - query.and(eq(ALARM_ORIGINATOR_ID_PROPERTY, originator.getId())); - query.and(eq(ALARM_ORIGINATOR_TYPE_PROPERTY, originator.getEntityType())); - query.and(eq(ALARM_TYPE_PROPERTY, type)); - query.limit(1); - query.orderBy(QueryBuilder.asc(ModelConstants.ALARM_TYPE_PROPERTY), QueryBuilder.desc(ModelConstants.ID_PROPERTY)); - return findOneByStatementAsync(tenantId, query); - } - - @Override - public ListenableFuture> findAlarms(TenantId tenantId, AlarmQuery query) { - log.trace("Try to find alarms by entity [{}], searchStatus [{}], status [{}] and pageLink [{}]", query.getAffectedEntityId(), query.getSearchStatus(), query.getStatus(), query.getPageLink()); - EntityId affectedEntity = query.getAffectedEntityId(); - String searchStatusName; - if (query.getSearchStatus() == null && query.getStatus() == null) { - searchStatusName = AlarmSearchStatus.ANY.name(); - } else if (query.getSearchStatus() != null) { - searchStatusName = query.getSearchStatus().name(); - } else { - searchStatusName = query.getStatus().name(); - } - String relationType = BaseAlarmService.ALARM_RELATION_PREFIX + searchStatusName; - ListenableFuture> relations = relationDao.findRelations(tenantId, affectedEntity, relationType, RelationTypeGroup.ALARM, EntityType.ALARM, query.getPageLink()); - return Futures.transformAsync(relations, input -> { - List> alarmFutures = new ArrayList<>(input.size()); - for (EntityRelation relation : input) { - alarmFutures.add(Futures.transform( - findAlarmByIdAsync(tenantId, relation.getTo().getId()), - AlarmInfo::new)); - } - return Futures.successfulAsList(alarmFutures); - }); - } - - @Override - public ListenableFuture findAlarmByIdAsync(TenantId tenantId, UUID key) { - log.debug("Get alarm by id {}", key); - Select.Where query = select().from(ALARM_BY_ID_VIEW_NAME).where(eq(ModelConstants.ID_PROPERTY, key)); - query.limit(1); - log.trace("Execute query {}", query); - return findOneByStatementAsync(tenantId, query); - } -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/CassandraAssetDao.java b/dao/src/main/java/org/thingsboard/server/dao/asset/CassandraAssetDao.java deleted file mode 100644 index deba1cc639..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/CassandraAssetDao.java +++ /dev/null @@ -1,191 +0,0 @@ -/** - * Copyright © 2016-2019 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.asset; - -import com.datastax.driver.core.ResultSet; -import com.datastax.driver.core.ResultSetFuture; -import com.datastax.driver.core.Statement; -import com.datastax.driver.core.querybuilder.Select; -import com.datastax.driver.mapping.Result; -import com.google.common.base.Function; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.EntitySubtype; -import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.asset.Asset; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageLink; -import org.thingsboard.server.dao.DaoUtil; -import org.thingsboard.server.dao.model.EntitySubtypeEntity; -import org.thingsboard.server.dao.model.nosql.AssetEntity; -import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao; -import org.thingsboard.server.dao.util.NoSqlDao; - -import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.UUID; - -import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; -import static com.datastax.driver.core.querybuilder.QueryBuilder.in; -import static com.datastax.driver.core.querybuilder.QueryBuilder.select; -import static org.thingsboard.server.dao.model.ModelConstants.ASSET_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.ASSET_BY_CUSTOMER_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.ASSET_BY_TENANT_AND_NAME_VIEW_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.ASSET_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.ASSET_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.ASSET_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.ASSET_CUSTOMER_ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ASSET_NAME_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ASSET_TENANT_ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ASSET_TYPE_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_SUBTYPE_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_SUBTYPE_ENTITY_TYPE_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_SUBTYPE_TENANT_ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY; - -@Component -@Slf4j -@NoSqlDao -public class CassandraAssetDao extends CassandraAbstractSearchTextDao implements AssetDao { - - @Override - protected Class getColumnFamilyClass() { - return AssetEntity.class; - } - - @Override - protected String getColumnFamilyName() { - return ASSET_COLUMN_FAMILY_NAME; - } - - @Override - public Asset save(TenantId tenantId, Asset domain) { - Asset savedAsset = super.save(tenantId, domain); - EntitySubtype entitySubtype = new EntitySubtype(savedAsset.getTenantId(), EntityType.ASSET, savedAsset.getType()); - EntitySubtypeEntity entitySubtypeEntity = new EntitySubtypeEntity(entitySubtype); - Statement saveStatement = cluster.getMapper(EntitySubtypeEntity.class).saveQuery(entitySubtypeEntity); - executeWrite(tenantId, saveStatement); - return savedAsset; - } - - @Override - public List findAssetsByTenantId(UUID tenantId, TextPageLink pageLink) { - log.debug("Try to find assets by tenantId [{}] and pageLink [{}]", tenantId, pageLink); - List assetEntities = findPageWithTextSearch(new TenantId(tenantId), ASSET_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME, - Collections.singletonList(eq(ASSET_TENANT_ID_PROPERTY, tenantId)), pageLink); - - log.trace("Found assets [{}] by tenantId [{}] and pageLink [{}]", assetEntities, tenantId, pageLink); - return DaoUtil.convertDataList(assetEntities); - } - - @Override - public List findAssetsByTenantIdAndType(UUID tenantId, String type, TextPageLink pageLink) { - log.debug("Try to find assets by tenantId [{}], type [{}] and pageLink [{}]", tenantId, type, pageLink); - List assetEntities = findPageWithTextSearch(new TenantId(tenantId), ASSET_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME, - Arrays.asList(eq(ASSET_TYPE_PROPERTY, type), - eq(ASSET_TENANT_ID_PROPERTY, tenantId)), pageLink); - log.trace("Found assets [{}] by tenantId [{}], type [{}] and pageLink [{}]", assetEntities, tenantId, type, pageLink); - return DaoUtil.convertDataList(assetEntities); - } - - public ListenableFuture> findAssetsByTenantIdAndIdsAsync(UUID tenantId, List assetIds) { - log.debug("Try to find assets by tenantId [{}] and asset Ids [{}]", tenantId, assetIds); - Select select = select().from(getColumnFamilyName()); - Select.Where query = select.where(); - query.and(eq(ASSET_TENANT_ID_PROPERTY, tenantId)); - query.and(in(ID_PROPERTY, assetIds)); - return findListByStatementAsync(new TenantId(tenantId), query); - } - - @Override - public List findAssetsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink) { - log.debug("Try to find assets by tenantId [{}], customerId[{}] and pageLink [{}]", tenantId, customerId, pageLink); - List assetEntities = findPageWithTextSearch(new TenantId(tenantId), ASSET_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME, - Arrays.asList(eq(ASSET_CUSTOMER_ID_PROPERTY, customerId), - eq(ASSET_TENANT_ID_PROPERTY, tenantId)), - pageLink); - - log.trace("Found assets [{}] by tenantId [{}], customerId [{}] and pageLink [{}]", assetEntities, tenantId, customerId, pageLink); - return DaoUtil.convertDataList(assetEntities); - } - - @Override - public List findAssetsByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, TextPageLink pageLink) { - log.debug("Try to find assets by tenantId [{}], customerId [{}], type [{}] and pageLink [{}]", tenantId, customerId, type, pageLink); - List assetEntities = findPageWithTextSearch(new TenantId(tenantId), ASSET_BY_CUSTOMER_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME, - Arrays.asList(eq(ASSET_TYPE_PROPERTY, type), - eq(ASSET_CUSTOMER_ID_PROPERTY, customerId), - eq(ASSET_TENANT_ID_PROPERTY, tenantId)), - pageLink); - - log.trace("Found assets [{}] by tenantId [{}], customerId [{}], type [{}] and pageLink [{}]", assetEntities, tenantId, customerId, type, pageLink); - return DaoUtil.convertDataList(assetEntities); - } - - @Override - public ListenableFuture> findAssetsByTenantIdAndCustomerIdAndIdsAsync(UUID tenantId, UUID customerId, List assetIds) { - log.debug("Try to find assets by tenantId [{}], customerId [{}] and asset Ids [{}]", tenantId, customerId, assetIds); - Select select = select().from(getColumnFamilyName()); - Select.Where query = select.where(); - query.and(eq(ASSET_TENANT_ID_PROPERTY, tenantId)); - query.and(eq(ASSET_CUSTOMER_ID_PROPERTY, customerId)); - query.and(in(ID_PROPERTY, assetIds)); - return findListByStatementAsync(new TenantId(tenantId), query); - } - - @Override - public Optional findAssetsByTenantIdAndName(UUID tenantId, String assetName) { - Select select = select().from(ASSET_BY_TENANT_AND_NAME_VIEW_NAME); - Select.Where query = select.where(); - query.and(eq(ASSET_TENANT_ID_PROPERTY, tenantId)); - query.and(eq(ASSET_NAME_PROPERTY, assetName)); - AssetEntity assetEntity = (AssetEntity) findOneByStatement(new TenantId(tenantId), query); - return Optional.ofNullable(DaoUtil.getData(assetEntity)); - } - - @Override - public ListenableFuture> findTenantAssetTypesAsync(UUID tenantId) { - Select select = select().from(ENTITY_SUBTYPE_COLUMN_FAMILY_NAME); - Select.Where query = select.where(); - query.and(eq(ENTITY_SUBTYPE_TENANT_ID_PROPERTY, tenantId)); - query.and(eq(ENTITY_SUBTYPE_ENTITY_TYPE_PROPERTY, EntityType.ASSET)); - query.setConsistencyLevel(cluster.getDefaultReadConsistencyLevel()); - ResultSetFuture resultSetFuture = executeAsyncRead(new TenantId(tenantId), query); - return Futures.transform(resultSetFuture, new Function>() { - @Nullable - @Override - public List apply(@Nullable ResultSet resultSet) { - Result result = cluster.getMapper(EntitySubtypeEntity.class).map(resultSet); - if (result != null) { - List entitySubtypes = new ArrayList<>(); - result.all().forEach((entitySubtypeEntity) -> - entitySubtypes.add(entitySubtypeEntity.toEntitySubtype()) - ); - return entitySubtypes; - } else { - return Collections.emptyList(); - } - } - }); - } - -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/CassandraBaseAttributesDao.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/CassandraBaseAttributesDao.java deleted file mode 100644 index b4ea0e61ef..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/attributes/CassandraBaseAttributesDao.java +++ /dev/null @@ -1,204 +0,0 @@ -/** - * Copyright © 2016-2019 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.attributes; - -import com.datastax.driver.core.BoundStatement; -import com.datastax.driver.core.PreparedStatement; -import com.datastax.driver.core.ResultSet; -import com.datastax.driver.core.Row; -import com.datastax.driver.core.Statement; -import com.datastax.driver.core.querybuilder.QueryBuilder; -import com.datastax.driver.core.querybuilder.Select; -import com.google.common.base.Function; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.kv.AttributeKvEntry; -import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; -import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.nosql.CassandraAbstractAsyncDao; -import org.thingsboard.server.dao.timeseries.CassandraBaseTimeseriesDao; -import org.thingsboard.server.dao.util.NoSqlDao; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - -import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; -import static com.datastax.driver.core.querybuilder.QueryBuilder.select; -import static org.thingsboard.server.dao.model.ModelConstants.ATTRIBUTES_KV_CF; -import static org.thingsboard.server.dao.model.ModelConstants.ATTRIBUTE_KEY_COLUMN; -import static org.thingsboard.server.dao.model.ModelConstants.ATTRIBUTE_TYPE_COLUMN; -import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_ID_COLUMN; -import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_TYPE_COLUMN; -import static org.thingsboard.server.dao.model.ModelConstants.LAST_UPDATE_TS_COLUMN; - -/** - * @author Andrew Shvayka - */ -@Component -@Slf4j -@NoSqlDao -public class CassandraBaseAttributesDao extends CassandraAbstractAsyncDao implements AttributesDao { - - private PreparedStatement saveStmt; - - @PostConstruct - public void init() { - super.startExecutor(); - } - - @PreDestroy - public void stop() { - super.stopExecutor(); - } - - @Override - public ListenableFuture> find(TenantId tenantId, EntityId entityId, String attributeType, String attributeKey) { - Select.Where select = select().from(ATTRIBUTES_KV_CF) - .where(eq(ENTITY_TYPE_COLUMN, entityId.getEntityType())) - .and(eq(ENTITY_ID_COLUMN, entityId.getId())) - .and(eq(ATTRIBUTE_TYPE_COLUMN, attributeType)) - .and(eq(ATTRIBUTE_KEY_COLUMN, attributeKey)); - log.trace("Generated query [{}] for entityId {} and key {}", select, entityId, attributeKey); - return Futures.transform(executeAsyncRead(tenantId, select), (Function>) input -> - Optional.ofNullable(convertResultToAttributesKvEntry(attributeKey, input.one())) - , readResultsProcessingExecutor); - } - - @Override - public ListenableFuture> find(TenantId tenantId, EntityId entityId, String attributeType, Collection attributeKeys) { - List>> entries = new ArrayList<>(); - attributeKeys.forEach(attributeKey -> entries.add(find(tenantId, entityId, attributeType, attributeKey))); - return Futures.transform(Futures.allAsList(entries), (Function>, ? extends List>) input -> { - List result = new ArrayList<>(); - input.stream().filter(opt -> opt.isPresent()).forEach(opt -> result.add(opt.get())); - return result; - }, readResultsProcessingExecutor); - } - - - @Override - public ListenableFuture> findAll(TenantId tenantId, EntityId entityId, String attributeType) { - Select.Where select = select().from(ATTRIBUTES_KV_CF) - .where(eq(ENTITY_TYPE_COLUMN, entityId.getEntityType())) - .and(eq(ENTITY_ID_COLUMN, entityId.getId())) - .and(eq(ATTRIBUTE_TYPE_COLUMN, attributeType)); - log.trace("Generated query [{}] for entityId {} and attributeType {}", select, entityId, attributeType); - return Futures.transform(executeAsyncRead(tenantId, select), (Function>) input -> - convertResultToAttributesKvEntryList(input) - , readResultsProcessingExecutor); - } - - @Override - public ListenableFuture save(TenantId tenantId, EntityId entityId, String attributeType, AttributeKvEntry attribute) { - BoundStatement stmt = getSaveStmt().bind(); - stmt.setString(0, entityId.getEntityType().name()); - stmt.setUUID(1, entityId.getId()); - stmt.setString(2, attributeType); - stmt.setString(3, attribute.getKey()); - stmt.setLong(4, attribute.getLastUpdateTs()); - stmt.setString(5, attribute.getStrValue().orElse(null)); - Optional booleanValue = attribute.getBooleanValue(); - if (booleanValue.isPresent()) { - stmt.setBool(6, booleanValue.get()); - } else { - stmt.setToNull(6); - } - Optional longValue = attribute.getLongValue(); - if (longValue.isPresent()) { - stmt.setLong(7, longValue.get()); - } else { - stmt.setToNull(7); - } - Optional doubleValue = attribute.getDoubleValue(); - if (doubleValue.isPresent()) { - stmt.setDouble(8, doubleValue.get()); - } else { - stmt.setToNull(8); - } - log.trace("Generated save stmt [{}] for entityId {} and attributeType {} and attribute", stmt, entityId, attributeType, attribute); - return getFuture(executeAsyncWrite(tenantId, stmt), rs -> null); - } - - @Override - public ListenableFuture> removeAll(TenantId tenantId, EntityId entityId, String attributeType, List keys) { - List> futures = keys - .stream() - .map(key -> delete(tenantId, entityId, attributeType, key)) - .collect(Collectors.toList()); - return Futures.allAsList(futures); - } - - private ListenableFuture delete(TenantId tenantId, EntityId entityId, String attributeType, String key) { - Statement delete = QueryBuilder.delete().all().from(ModelConstants.ATTRIBUTES_KV_CF) - .where(eq(ENTITY_TYPE_COLUMN, entityId.getEntityType())) - .and(eq(ENTITY_ID_COLUMN, entityId.getId())) - .and(eq(ATTRIBUTE_TYPE_COLUMN, attributeType)) - .and(eq(ATTRIBUTE_KEY_COLUMN, key)); - log.debug("Remove request: {}", delete.toString()); - return getFuture(executeAsyncWrite(tenantId, delete), rs -> null); - } - - private PreparedStatement getSaveStmt() { - if (saveStmt == null) { - saveStmt = prepare("INSERT INTO " + ModelConstants.ATTRIBUTES_KV_CF + - "(" + ENTITY_TYPE_COLUMN + - "," + ENTITY_ID_COLUMN + - "," + ATTRIBUTE_TYPE_COLUMN + - "," + ATTRIBUTE_KEY_COLUMN + - "," + LAST_UPDATE_TS_COLUMN + - "," + ModelConstants.STRING_VALUE_COLUMN + - "," + ModelConstants.BOOLEAN_VALUE_COLUMN + - "," + ModelConstants.LONG_VALUE_COLUMN + - "," + ModelConstants.DOUBLE_VALUE_COLUMN + - ")" + - " VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)"); - } - return saveStmt; - } - - private AttributeKvEntry convertResultToAttributesKvEntry(String key, Row row) { - AttributeKvEntry attributeEntry = null; - if (row != null) { - long lastUpdateTs = row.get(LAST_UPDATE_TS_COLUMN, Long.class); - attributeEntry = new BaseAttributeKvEntry(CassandraBaseTimeseriesDao.toKvEntry(row, key), lastUpdateTs); - } - return attributeEntry; - } - - private List convertResultToAttributesKvEntryList(ResultSet resultSet) { - List rows = resultSet.all(); - List entries = new ArrayList<>(rows.size()); - if (!rows.isEmpty()) { - rows.forEach(row -> { - String key = row.getString(ModelConstants.ATTRIBUTE_KEY_COLUMN); - AttributeKvEntry kvEntry = convertResultToAttributesKvEntry(key, row); - if (kvEntry != null) { - entries.add(kvEntry); - } - }); - } - return entries; - } -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogQueryCursor.java b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogQueryCursor.java deleted file mode 100644 index 4ea9829a21..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogQueryCursor.java +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Copyright © 2016-2019 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.audit; - -import lombok.Getter; -import org.thingsboard.server.common.data.page.TimePageLink; -import org.thingsboard.server.dao.model.nosql.AuditLogEntity; - -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -public class AuditLogQueryCursor { - @Getter - private final UUID tenantId; - @Getter - private final List data; - @Getter - private final TimePageLink pageLink; - - private final List partitions; - - private int partitionIndex; - private int currentLimit; - - public AuditLogQueryCursor(UUID tenantId, TimePageLink pageLink, List partitions) { - this.tenantId = tenantId; - this.partitions = partitions; - this.partitionIndex = partitions.size() - 1; - this.data = new ArrayList<>(); - this.currentLimit = pageLink.getLimit(); - this.pageLink = pageLink; - } - - public boolean hasNextPartition() { - return partitionIndex >= 0; - } - - public boolean isFull() { - return currentLimit <= 0; - } - - public long getNextPartition() { - long partition = partitions.get(partitionIndex); - partitionIndex--; - return partition; - } - - public int getCurrentLimit() { - return currentLimit; - } - - public void addData(List newData) { - currentLimit -= newData.size(); - data.addAll(newData); - } -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/audit/CassandraAuditLogDao.java b/dao/src/main/java/org/thingsboard/server/dao/audit/CassandraAuditLogDao.java deleted file mode 100644 index 6ffaf1ea00..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/audit/CassandraAuditLogDao.java +++ /dev/null @@ -1,359 +0,0 @@ -/** - * Copyright © 2016-2019 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.audit; - -import com.datastax.driver.core.BoundStatement; -import com.datastax.driver.core.PreparedStatement; -import com.datastax.driver.core.ResultSet; -import com.datastax.driver.core.ResultSetFuture; -import com.datastax.driver.core.querybuilder.QueryBuilder; -import com.datastax.driver.core.querybuilder.Select; -import com.google.common.base.Function; -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.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.env.Environment; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.audit.AuditLog; -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.id.UserId; -import org.thingsboard.server.common.data.page.TimePageLink; -import org.thingsboard.server.dao.DaoUtil; -import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.model.nosql.AuditLogEntity; -import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTimeDao; -import org.thingsboard.server.dao.timeseries.TsPartitionDate; -import org.thingsboard.server.dao.util.NoSqlDao; - -import javax.annotation.Nullable; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; -import java.time.Instant; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.ZoneOffset; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.StringJoiner; -import java.util.UUID; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.stream.Collectors; - -import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; -import static org.thingsboard.server.dao.model.ModelConstants.AUDIT_LOG_BY_CUSTOMER_ID_CF; -import static org.thingsboard.server.dao.model.ModelConstants.AUDIT_LOG_BY_ENTITY_ID_CF; -import static org.thingsboard.server.dao.model.ModelConstants.AUDIT_LOG_BY_TENANT_ID_CF; -import static org.thingsboard.server.dao.model.ModelConstants.AUDIT_LOG_BY_USER_ID_CF; -import static org.thingsboard.server.dao.model.ModelConstants.AUDIT_LOG_COLUMN_FAMILY_NAME; - -@Component -@Slf4j -@NoSqlDao -public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao implements AuditLogDao { - - private static final String INSERT_INTO = "INSERT INTO "; - - @Autowired - private Environment environment; - - @Override - protected Class getColumnFamilyClass() { - return AuditLogEntity.class; - } - - @Override - protected String getColumnFamilyName() { - return AUDIT_LOG_COLUMN_FAMILY_NAME; - } - - protected ExecutorService readResultsProcessingExecutor; - - @Value("${audit-log.by_tenant_partitioning}") - private String partitioning; - private TsPartitionDate tsFormat; - - @Value("${audit-log.default_query_period}") - private Integer defaultQueryPeriodInDays; - - private PreparedStatement partitionInsertStmt; - private PreparedStatement saveByTenantStmt; - private PreparedStatement saveByTenantIdAndUserIdStmt; - private PreparedStatement saveByTenantIdAndEntityIdStmt; - private PreparedStatement saveByTenantIdAndCustomerIdStmt; - - private boolean isInstall() { - return environment.acceptsProfiles("install"); - } - - @PostConstruct - public void init() { - if (!isInstall()) { - Optional partition = TsPartitionDate.parse(partitioning); - if (partition.isPresent()) { - tsFormat = partition.get(); - } else { - log.warn("Incorrect configuration of partitioning {}", partitioning); - throw new RuntimeException("Failed to parse partitioning property: " + partitioning + "!"); - } - } - readResultsProcessingExecutor = Executors.newCachedThreadPool(); - } - - @PreDestroy - public void stopExecutor() { - if (readResultsProcessingExecutor != null) { - readResultsProcessingExecutor.shutdownNow(); - } - } - - private ListenableFuture getFuture(ResultSetFuture future, java.util.function.Function transformer) { - return Futures.transform(future, new Function() { - @Nullable - @Override - public T apply(@Nullable ResultSet input) { - return transformer.apply(input); - } - }, readResultsProcessingExecutor); - } - - @Override - public ListenableFuture saveByTenantId(AuditLog auditLog) { - log.debug("Save saveByTenantId [{}] ", auditLog); - - long partition = toPartitionTs(LocalDate.now().atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli()); - BoundStatement stmt = getSaveByTenantStmt().bind(); - stmt = setSaveStmtVariables(stmt, auditLog, partition); - return getFuture(executeAsyncWrite(auditLog.getTenantId(), stmt), rs -> null); - } - - @Override - public ListenableFuture saveByTenantIdAndEntityId(AuditLog auditLog) { - log.debug("Save saveByTenantIdAndEntityId [{}] ", auditLog); - - BoundStatement stmt = getSaveByTenantIdAndEntityIdStmt().bind(); - stmt = setSaveStmtVariables(stmt, auditLog, -1); - return getFuture(executeAsyncWrite(auditLog.getTenantId(), stmt), rs -> null); - } - - @Override - public ListenableFuture saveByTenantIdAndCustomerId(AuditLog auditLog) { - log.debug("Save saveByTenantIdAndCustomerId [{}] ", auditLog); - - BoundStatement stmt = getSaveByTenantIdAndCustomerIdStmt().bind(); - stmt = setSaveStmtVariables(stmt, auditLog, -1); - return getFuture(executeAsyncWrite(auditLog.getTenantId(), stmt), rs -> null); - } - - @Override - public ListenableFuture saveByTenantIdAndUserId(AuditLog auditLog) { - log.debug("Save saveByTenantIdAndUserId [{}] ", auditLog); - - BoundStatement stmt = getSaveByTenantIdAndUserIdStmt().bind(); - stmt = setSaveStmtVariables(stmt, auditLog, -1); - return getFuture(executeAsyncWrite(auditLog.getTenantId(), stmt), rs -> null); - } - - private BoundStatement setSaveStmtVariables(BoundStatement stmt, AuditLog auditLog, long partition) { - stmt.setUUID(0, auditLog.getId().getId()) - .setUUID(1, auditLog.getTenantId().getId()) - .setUUID(2, auditLog.getCustomerId().getId()) - .setUUID(3, auditLog.getEntityId().getId()) - .setString(4, auditLog.getEntityId().getEntityType().name()) - .setString(5, auditLog.getEntityName()) - .setUUID(6, auditLog.getUserId().getId()) - .setString(7, auditLog.getUserName()) - .setString(8, auditLog.getActionType().name()) - .setString(9, auditLog.getActionData() != null ? auditLog.getActionData().toString() : null) - .setString(10, auditLog.getActionStatus().name()) - .setString(11, auditLog.getActionFailureDetails()); - if (partition > -1) { - stmt.setLong(12, partition); - } - return stmt; - } - - @Override - public ListenableFuture savePartitionsByTenantId(AuditLog auditLog) { - log.debug("Save savePartitionsByTenantId [{}] ", auditLog); - - long partition = toPartitionTs(LocalDate.now().atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli()); - - BoundStatement stmt = getPartitionInsertStmt().bind(); - stmt = stmt.setUUID(0, auditLog.getTenantId().getId()) - .setLong(1, partition); - return getFuture(executeAsyncWrite(auditLog.getTenantId(), stmt), rs -> null); - } - - private PreparedStatement getSaveByTenantStmt() { - if (saveByTenantStmt == null) { - saveByTenantStmt = getSaveByTenantIdAndCFName(ModelConstants.AUDIT_LOG_BY_TENANT_ID_CF, true); - } - return saveByTenantStmt; - } - - private PreparedStatement getSaveByTenantIdAndEntityIdStmt() { - if (saveByTenantIdAndEntityIdStmt == null) { - saveByTenantIdAndEntityIdStmt = getSaveByTenantIdAndCFName(ModelConstants.AUDIT_LOG_BY_ENTITY_ID_CF, false); - } - return saveByTenantIdAndEntityIdStmt; - } - - private PreparedStatement getSaveByTenantIdAndCustomerIdStmt() { - if (saveByTenantIdAndCustomerIdStmt == null) { - saveByTenantIdAndCustomerIdStmt = getSaveByTenantIdAndCFName(ModelConstants.AUDIT_LOG_BY_CUSTOMER_ID_CF, false); - } - return saveByTenantIdAndCustomerIdStmt; - } - - private PreparedStatement getSaveByTenantIdAndUserIdStmt() { - if (saveByTenantIdAndUserIdStmt == null) { - saveByTenantIdAndUserIdStmt = getSaveByTenantIdAndCFName(ModelConstants.AUDIT_LOG_BY_USER_ID_CF, false); - } - return saveByTenantIdAndUserIdStmt; - } - - private PreparedStatement getSaveByTenantIdAndCFName(String cfName, boolean hasPartition) { - List columnsList = new ArrayList(); - columnsList.add(ModelConstants.AUDIT_LOG_ID_PROPERTY); - columnsList.add(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY); - columnsList.add(ModelConstants.AUDIT_LOG_CUSTOMER_ID_PROPERTY); - columnsList.add(ModelConstants.AUDIT_LOG_ENTITY_ID_PROPERTY); - columnsList.add(ModelConstants.AUDIT_LOG_ENTITY_TYPE_PROPERTY); - columnsList.add(ModelConstants.AUDIT_LOG_ENTITY_NAME_PROPERTY); - columnsList.add(ModelConstants.AUDIT_LOG_USER_ID_PROPERTY); - columnsList.add(ModelConstants.AUDIT_LOG_USER_NAME_PROPERTY); - columnsList.add(ModelConstants.AUDIT_LOG_ACTION_TYPE_PROPERTY); - columnsList.add(ModelConstants.AUDIT_LOG_ACTION_DATA_PROPERTY); - columnsList.add(ModelConstants.AUDIT_LOG_ACTION_STATUS_PROPERTY); - columnsList.add(ModelConstants.AUDIT_LOG_ACTION_FAILURE_DETAILS_PROPERTY); - if (hasPartition) { - columnsList.add(ModelConstants.AUDIT_LOG_PARTITION_PROPERTY); - } - StringJoiner values = new StringJoiner(","); - for (int i = 0; i < columnsList.size(); i++) { - values.add("?"); - } - String statementString = INSERT_INTO + cfName + " (" + String.join(",", columnsList) + ") VALUES (" + values.toString() + ")"; - return prepare(statementString); - } - - private PreparedStatement getPartitionInsertStmt() { - if (partitionInsertStmt == null) { - partitionInsertStmt = prepare(INSERT_INTO + ModelConstants.AUDIT_LOG_BY_TENANT_ID_PARTITIONS_CF + - "(" + ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY + - "," + ModelConstants.AUDIT_LOG_PARTITION_PROPERTY + ")" + - " VALUES(?, ?)"); - } - return partitionInsertStmt; - } - - private long toPartitionTs(long ts) { - LocalDateTime time = LocalDateTime.ofInstant(Instant.ofEpochMilli(ts), ZoneOffset.UTC); - return tsFormat.truncatedTo(time).toInstant(ZoneOffset.UTC).toEpochMilli(); - } - - @Override - public List findAuditLogsByTenantIdAndEntityId(UUID tenantId, EntityId entityId, TimePageLink pageLink) { - log.trace("Try to find audit logs by tenant [{}], entity [{}] and pageLink [{}]", tenantId, entityId, pageLink); - List entities = findPageWithTimeSearch(new TenantId(tenantId), AUDIT_LOG_BY_ENTITY_ID_CF, - Arrays.asList(eq(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY, tenantId), - eq(ModelConstants.AUDIT_LOG_ENTITY_TYPE_PROPERTY, entityId.getEntityType()), - eq(ModelConstants.AUDIT_LOG_ENTITY_ID_PROPERTY, entityId.getId())), - pageLink); - log.trace("Found audit logs by tenant [{}], entity [{}] and pageLink [{}]", tenantId, entityId, pageLink); - return DaoUtil.convertDataList(entities); - } - - @Override - public List findAuditLogsByTenantIdAndCustomerId(UUID tenantId, CustomerId customerId, TimePageLink pageLink) { - log.trace("Try to find audit logs by tenant [{}], customer [{}] and pageLink [{}]", tenantId, customerId, pageLink); - List entities = findPageWithTimeSearch(new TenantId(tenantId), AUDIT_LOG_BY_CUSTOMER_ID_CF, - Arrays.asList(eq(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY, tenantId), - eq(ModelConstants.AUDIT_LOG_CUSTOMER_ID_PROPERTY, customerId.getId())), - pageLink); - log.trace("Found audit logs by tenant [{}], customer [{}] and pageLink [{}]", tenantId, customerId, pageLink); - return DaoUtil.convertDataList(entities); - } - - @Override - public List findAuditLogsByTenantIdAndUserId(UUID tenantId, UserId userId, TimePageLink pageLink) { - log.trace("Try to find audit logs by tenant [{}], user [{}] and pageLink [{}]", tenantId, userId, pageLink); - List entities = findPageWithTimeSearch(new TenantId(tenantId), AUDIT_LOG_BY_USER_ID_CF, - Arrays.asList(eq(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY, tenantId), - eq(ModelConstants.AUDIT_LOG_USER_ID_PROPERTY, userId.getId())), - pageLink); - log.trace("Found audit logs by tenant [{}], user [{}] and pageLink [{}]", tenantId, userId, pageLink); - return DaoUtil.convertDataList(entities); - } - - @Override - public List findAuditLogsByTenantId(UUID tenantId, TimePageLink pageLink) { - log.trace("Try to find audit logs by tenant [{}] and pageLink [{}]", tenantId, pageLink); - - long minPartition; - if (pageLink.getStartTime() != null && pageLink.getStartTime() != 0) { - minPartition = toPartitionTs(pageLink.getStartTime()); - } else { - minPartition = toPartitionTs(LocalDate.now().minusDays(defaultQueryPeriodInDays).atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli()); - } - - long maxPartition; - if (pageLink.getEndTime() != null && pageLink.getEndTime() != 0) { - maxPartition = toPartitionTs(pageLink.getEndTime()); - } else { - maxPartition = toPartitionTs(LocalDate.now().atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli()); - } - - List partitions = fetchPartitions(tenantId, minPartition, maxPartition) - .all() - .stream() - .map(row -> row.getLong(ModelConstants.PARTITION_COLUMN)) - .collect(Collectors.toList()); - - AuditLogQueryCursor cursor = new AuditLogQueryCursor(tenantId, pageLink, partitions); - List entities = fetchSequentiallyWithLimit(cursor); - log.trace("Found audit logs by tenant [{}] and pageLink [{}]", tenantId, pageLink); - return DaoUtil.convertDataList(entities); - } - - private List fetchSequentiallyWithLimit(AuditLogQueryCursor cursor) { - if (cursor.isFull() || !cursor.hasNextPartition()) { - return cursor.getData(); - } else { - cursor.addData(findPageWithTimeSearch(new TenantId(cursor.getTenantId()), AUDIT_LOG_BY_TENANT_ID_CF, - Arrays.asList(eq(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY, cursor.getTenantId()), - eq(ModelConstants.AUDIT_LOG_PARTITION_PROPERTY, cursor.getNextPartition())), - cursor.getPageLink())); - return fetchSequentiallyWithLimit(cursor); - } - } - - private ResultSet fetchPartitions(UUID tenantId, long minPartition, long maxPartition) { - Select.Where select = QueryBuilder.select(ModelConstants.AUDIT_LOG_PARTITION_PROPERTY).from(ModelConstants.AUDIT_LOG_BY_TENANT_ID_PARTITIONS_CF) - .where(eq(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY, tenantId)); - select.and(QueryBuilder.gte(ModelConstants.PARTITION_COLUMN, minPartition)); - select.and(QueryBuilder.lte(ModelConstants.PARTITION_COLUMN, maxPartition)); - return executeRead(new TenantId(tenantId), select); - } - -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/component/CassandraBaseComponentDescriptorDao.java b/dao/src/main/java/org/thingsboard/server/dao/component/CassandraBaseComponentDescriptorDao.java deleted file mode 100644 index 7fa4a35de4..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/component/CassandraBaseComponentDescriptorDao.java +++ /dev/null @@ -1,175 +0,0 @@ -/** - * Copyright © 2016-2019 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.component; - -import com.datastax.driver.core.ResultSet; -import com.datastax.driver.core.Statement; -import com.datastax.driver.core.querybuilder.QueryBuilder; -import com.datastax.driver.core.querybuilder.Select; -import com.datastax.driver.core.utils.UUIDs; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.id.ComponentDescriptorId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageLink; -import org.thingsboard.server.common.data.plugin.ComponentDescriptor; -import org.thingsboard.server.common.data.plugin.ComponentScope; -import org.thingsboard.server.common.data.plugin.ComponentType; -import org.thingsboard.server.dao.DaoUtil; -import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.model.nosql.ComponentDescriptorEntity; -import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao; -import org.thingsboard.server.dao.util.NoSqlDao; - -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.UUID; - -import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; -import static com.datastax.driver.core.querybuilder.QueryBuilder.select; - -/** - * @author Andrew Shvayka - */ -@Component -@Slf4j -@NoSqlDao -public class CassandraBaseComponentDescriptorDao extends CassandraAbstractSearchTextDao implements ComponentDescriptorDao { - - public static final String SEARCH_RESULT = "Search result: [{}]"; - - @Override - protected Class getColumnFamilyClass() { - return ComponentDescriptorEntity.class; - } - - @Override - protected String getColumnFamilyName() { - return ModelConstants.COMPONENT_DESCRIPTOR_COLUMN_FAMILY_NAME; - } - - @Override - public Optional saveIfNotExist(TenantId tenantId, ComponentDescriptor component) { - ComponentDescriptorEntity entity = new ComponentDescriptorEntity(component); - log.debug("Save component entity [{}]", entity); - Optional result = saveIfNotExist(tenantId, entity); - if (log.isTraceEnabled()) { - log.trace("Saved result: [{}] for component entity [{}]", result.isPresent(), result.orElse(null)); - } else { - log.debug("Saved result: [{}]", result.isPresent()); - } - return result; - } - - @Override - public ComponentDescriptor findById(TenantId tenantId, ComponentDescriptorId componentId) { - log.debug("Search component entity by id [{}]", componentId); - ComponentDescriptor componentDescriptor = super.findById(tenantId, componentId.getId()); - if (log.isTraceEnabled()) { - log.trace("Search result: [{}] for component entity [{}]", componentDescriptor != null, componentDescriptor); - } else { - log.debug(SEARCH_RESULT, componentDescriptor != null); - } - return componentDescriptor; - } - - @Override - public ComponentDescriptor findByClazz(TenantId tenantId, String clazz) { - log.debug("Search component entity by clazz [{}]", clazz); - Select.Where query = select().from(getColumnFamilyName()).where(eq(ModelConstants.COMPONENT_DESCRIPTOR_CLASS_PROPERTY, clazz)); - log.trace("Execute query [{}]", query); - ComponentDescriptorEntity entity = findOneByStatement(tenantId, query); - if (log.isTraceEnabled()) { - log.trace("Search result: [{}] for component entity [{}]", entity != null, entity); - } else { - log.debug(SEARCH_RESULT, entity != null); - } - return DaoUtil.getData(entity); - } - - @Override - public List findByTypeAndPageLink(TenantId tenantId, ComponentType type, TextPageLink pageLink) { - log.debug("Try to find component by type [{}] and pageLink [{}]", type, pageLink); - List entities = findPageWithTextSearch(tenantId, ModelConstants.COMPONENT_DESCRIPTOR_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME, - Arrays.asList(eq(ModelConstants.COMPONENT_DESCRIPTOR_TYPE_PROPERTY, type)), pageLink); - if (log.isTraceEnabled()) { - log.trace(SEARCH_RESULT, Arrays.toString(entities.toArray())); - } else { - log.debug(SEARCH_RESULT, entities.size()); - } - return DaoUtil.convertDataList(entities); - } - - @Override - public List findByScopeAndTypeAndPageLink(TenantId tenantId, ComponentScope scope, ComponentType type, TextPageLink pageLink) { - log.debug("Try to find component by scope [{}] and type [{}] and pageLink [{}]", scope, type, pageLink); - List entities = findPageWithTextSearch(tenantId, ModelConstants.COMPONENT_DESCRIPTOR_BY_SCOPE_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME, - Arrays.asList(eq(ModelConstants.COMPONENT_DESCRIPTOR_TYPE_PROPERTY, type), - eq(ModelConstants.COMPONENT_DESCRIPTOR_SCOPE_PROPERTY, scope.name())), pageLink); - if (log.isTraceEnabled()) { - log.trace(SEARCH_RESULT, Arrays.toString(entities.toArray())); - } else { - log.debug(SEARCH_RESULT, entities.size()); - } - return DaoUtil.convertDataList(entities); - } - - public boolean removeById(TenantId tenantId, UUID key) { - Statement delete = QueryBuilder.delete().all().from(ModelConstants.COMPONENT_DESCRIPTOR_BY_ID).where(eq(ModelConstants.ID_PROPERTY, key)); - log.debug("Remove request: {}", delete.toString()); - return executeWrite(tenantId, delete).wasApplied(); - } - - @Override - public void deleteById(TenantId tenantId, ComponentDescriptorId id) { - log.debug("Delete plugin meta-data entity by id [{}]", id); - boolean result = removeById(tenantId, id.getId()); - log.debug("Delete result: [{}]", result); - } - - @Override - public void deleteByClazz(TenantId tenantId, String clazz) { - log.debug("Delete plugin meta-data entity by id [{}]", clazz); - Statement delete = QueryBuilder.delete().all().from(getColumnFamilyName()).where(eq(ModelConstants.COMPONENT_DESCRIPTOR_CLASS_PROPERTY, clazz)); - log.debug("Remove request: {}", delete.toString()); - ResultSet resultSet = executeWrite(tenantId, delete); - log.debug("Delete result: [{}]", resultSet.wasApplied()); - } - - private Optional saveIfNotExist(TenantId tenantId, ComponentDescriptorEntity entity) { - if (entity.getId() == null) { - entity.setId(UUIDs.timeBased()); - } - - ResultSet rs = executeRead(tenantId, QueryBuilder.insertInto(getColumnFamilyName()) - .value(ModelConstants.ID_PROPERTY, entity.getId()) - .value(ModelConstants.COMPONENT_DESCRIPTOR_NAME_PROPERTY, entity.getName()) - .value(ModelConstants.COMPONENT_DESCRIPTOR_CLASS_PROPERTY, entity.getClazz()) - .value(ModelConstants.COMPONENT_DESCRIPTOR_TYPE_PROPERTY, entity.getType()) - .value(ModelConstants.COMPONENT_DESCRIPTOR_SCOPE_PROPERTY, entity.getScope()) - .value(ModelConstants.COMPONENT_DESCRIPTOR_CONFIGURATION_DESCRIPTOR_PROPERTY, entity.getConfigurationDescriptor()) - .value(ModelConstants.COMPONENT_DESCRIPTOR_ACTIONS_PROPERTY, entity.getActions()) - .value(ModelConstants.SEARCH_TEXT_PROPERTY, entity.getSearchText()) - .ifNotExists() - ); - if (rs.wasApplied()) { - return Optional.of(DaoUtil.getData(entity)); - } else { - return Optional.empty(); - } - } -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/customer/CassandraCustomerDao.java b/dao/src/main/java/org/thingsboard/server/dao/customer/CassandraCustomerDao.java deleted file mode 100644 index fb5197b99b..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/customer/CassandraCustomerDao.java +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Copyright © 2016-2019 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.customer; - -import com.datastax.driver.core.querybuilder.Select; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.Customer; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageLink; -import org.thingsboard.server.dao.DaoUtil; -import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.model.nosql.CustomerEntity; -import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao; -import org.thingsboard.server.dao.util.NoSqlDao; - -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.UUID; - -import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; -import static com.datastax.driver.core.querybuilder.QueryBuilder.select; -import static org.thingsboard.server.dao.model.ModelConstants.CUSTOMER_BY_TENANT_AND_TITLE_VIEW_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.CUSTOMER_TENANT_ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.CUSTOMER_TITLE_PROPERTY; - -@Component -@Slf4j -@NoSqlDao -public class CassandraCustomerDao extends CassandraAbstractSearchTextDao implements CustomerDao { - - @Override - protected Class getColumnFamilyClass() { - return CustomerEntity.class; - } - - @Override - protected String getColumnFamilyName() { - return ModelConstants.CUSTOMER_COLUMN_FAMILY_NAME; - } - - @Override - public List findCustomersByTenantId(UUID tenantId, TextPageLink pageLink) { - log.debug("Try to find customers by tenantId [{}] and pageLink [{}]", tenantId, pageLink); - List customerEntities = findPageWithTextSearch(new TenantId(tenantId), ModelConstants.CUSTOMER_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME, - Arrays.asList(eq(ModelConstants.CUSTOMER_TENANT_ID_PROPERTY, tenantId)), - pageLink); - log.trace("Found customers [{}] by tenantId [{}] and pageLink [{}]", customerEntities, tenantId, pageLink); - return DaoUtil.convertDataList(customerEntities); - } - - @Override - public Optional findCustomersByTenantIdAndTitle(UUID tenantId, String title) { - Select select = select().from(CUSTOMER_BY_TENANT_AND_TITLE_VIEW_NAME); - Select.Where query = select.where(); - query.and(eq(CUSTOMER_TENANT_ID_PROPERTY, tenantId)); - query.and(eq(CUSTOMER_TITLE_PROPERTY, title)); - CustomerEntity customerEntity = findOneByStatement(new TenantId(tenantId), query); - Customer customer = DaoUtil.getData(customerEntity); - return Optional.ofNullable(customer); - } - -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/CassandraDashboardDao.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/CassandraDashboardDao.java deleted file mode 100644 index 47e04ba2c7..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/dashboard/CassandraDashboardDao.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright © 2016-2019 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.dashboard; - -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.Dashboard; -import org.thingsboard.server.dao.model.nosql.DashboardEntity; -import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao; -import org.thingsboard.server.dao.util.NoSqlDao; - -import static org.thingsboard.server.dao.model.ModelConstants.DASHBOARD_COLUMN_FAMILY_NAME; - -@Component -@NoSqlDao -public class CassandraDashboardDao extends CassandraAbstractSearchTextDao implements DashboardDao { - - @Override - protected Class getColumnFamilyClass() { - return DashboardEntity.class; - } - - @Override - protected String getColumnFamilyName() { - return DASHBOARD_COLUMN_FAMILY_NAME; - } -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/CassandraDashboardInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/CassandraDashboardInfoDao.java deleted file mode 100644 index 6038bd632a..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/dashboard/CassandraDashboardInfoDao.java +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Copyright © 2016-2019 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.dashboard; - -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.Autowired; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.DashboardInfo; -import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageLink; -import org.thingsboard.server.common.data.page.TimePageLink; -import org.thingsboard.server.common.data.relation.EntityRelation; -import org.thingsboard.server.common.data.relation.RelationTypeGroup; -import org.thingsboard.server.dao.DaoUtil; -import org.thingsboard.server.dao.model.nosql.DashboardInfoEntity; -import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao; -import org.thingsboard.server.dao.relation.RelationDao; -import org.thingsboard.server.dao.util.NoSqlDao; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.UUID; - -import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; -import static org.thingsboard.server.dao.model.ModelConstants.DASHBOARD_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.DASHBOARD_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.DASHBOARD_TENANT_ID_PROPERTY; - -@Component -@Slf4j -@NoSqlDao -public class CassandraDashboardInfoDao extends CassandraAbstractSearchTextDao implements DashboardInfoDao { - - @Autowired - private RelationDao relationDao; - - @Override - protected Class getColumnFamilyClass() { - return DashboardInfoEntity.class; - } - - @Override - protected String getColumnFamilyName() { - return DASHBOARD_COLUMN_FAMILY_NAME; - } - - @Override - public List findDashboardsByTenantId(UUID tenantId, TextPageLink pageLink) { - log.debug("Try to find dashboards by tenantId [{}] and pageLink [{}]", tenantId, pageLink); - List dashboardEntities = findPageWithTextSearch(new TenantId(tenantId), DASHBOARD_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME, - Collections.singletonList(eq(DASHBOARD_TENANT_ID_PROPERTY, tenantId)), - pageLink); - - log.trace("Found dashboards [{}] by tenantId [{}] and pageLink [{}]", dashboardEntities, tenantId, pageLink); - return DaoUtil.convertDataList(dashboardEntities); - } - - @Override - public ListenableFuture> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TimePageLink pageLink) { - log.debug("Try to find dashboards by tenantId [{}], customerId[{}] and pageLink [{}]", tenantId, customerId, pageLink); - - ListenableFuture> relations = relationDao.findRelations(new TenantId(tenantId), new CustomerId(customerId), EntityRelation.CONTAINS_TYPE, RelationTypeGroup.DASHBOARD, EntityType.DASHBOARD, pageLink); - - return Futures.transformAsync(relations, input -> { - List> dashboardFutures = new ArrayList<>(input.size()); - for (EntityRelation relation : input) { - dashboardFutures.add(findByIdAsync(new TenantId(tenantId), relation.getTo().getId())); - } - return Futures.successfulAsList(dashboardFutures); - }); - } - -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/CassandraDeviceCredentialsDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/CassandraDeviceCredentialsDao.java deleted file mode 100644 index 12527bcb00..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/device/CassandraDeviceCredentialsDao.java +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Copyright © 2016-2019 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 com.datastax.driver.core.querybuilder.Select.Where; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.security.DeviceCredentials; -import org.thingsboard.server.dao.DaoUtil; -import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.model.nosql.DeviceCredentialsEntity; -import org.thingsboard.server.dao.nosql.CassandraAbstractModelDao; -import org.thingsboard.server.dao.util.NoSqlDao; - -import java.util.UUID; - -import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; -import static com.datastax.driver.core.querybuilder.QueryBuilder.select; - -@Component -@Slf4j -@NoSqlDao -public class CassandraDeviceCredentialsDao extends CassandraAbstractModelDao implements DeviceCredentialsDao { - - @Override - protected Class getColumnFamilyClass() { - return DeviceCredentialsEntity.class; - } - - @Override - protected String getColumnFamilyName() { - return ModelConstants.DEVICE_CREDENTIALS_COLUMN_FAMILY_NAME; - } - - @Override - public DeviceCredentials findByDeviceId(TenantId tenantId, UUID deviceId) { - log.debug("Try to find device credentials by deviceId [{}] ", deviceId); - Where query = select().from(ModelConstants.DEVICE_CREDENTIALS_BY_DEVICE_COLUMN_FAMILY_NAME) - .where(eq(ModelConstants.DEVICE_CREDENTIALS_DEVICE_ID_PROPERTY, deviceId)); - log.trace("Execute query {}", query); - DeviceCredentialsEntity deviceCredentialsEntity = findOneByStatement(tenantId, query); - log.trace("Found device credentials [{}] by deviceId [{}]", deviceCredentialsEntity, deviceId); - return DaoUtil.getData(deviceCredentialsEntity); - } - - @Override - public DeviceCredentials findByCredentialsId(TenantId tenantId, String credentialsId) { - log.debug("Try to find device credentials by credentialsId [{}] ", credentialsId); - Where query = select().from(ModelConstants.DEVICE_CREDENTIALS_BY_CREDENTIALS_ID_COLUMN_FAMILY_NAME) - .where(eq(ModelConstants.DEVICE_CREDENTIALS_CREDENTIALS_ID_PROPERTY, credentialsId)); - log.trace("Execute query {}", query); - DeviceCredentialsEntity deviceCredentialsEntity = findOneByStatement(tenantId, query); - log.trace("Found device credentials [{}] by credentialsId [{}]", deviceCredentialsEntity, credentialsId); - return DaoUtil.getData(deviceCredentialsEntity); - } -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/CassandraDeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/CassandraDeviceDao.java deleted file mode 100644 index fe94db9225..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/device/CassandraDeviceDao.java +++ /dev/null @@ -1,191 +0,0 @@ -/** - * Copyright © 2016-2019 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 com.datastax.driver.core.ResultSet; -import com.datastax.driver.core.ResultSetFuture; -import com.datastax.driver.core.Statement; -import com.datastax.driver.core.querybuilder.Select; -import com.datastax.driver.mapping.Result; -import com.google.common.base.Function; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.EntitySubtype; -import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageLink; -import org.thingsboard.server.dao.DaoUtil; -import org.thingsboard.server.dao.model.EntitySubtypeEntity; -import org.thingsboard.server.dao.model.nosql.DeviceEntity; -import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao; -import org.thingsboard.server.dao.util.NoSqlDao; - -import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.UUID; - -import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; -import static com.datastax.driver.core.querybuilder.QueryBuilder.in; -import static com.datastax.driver.core.querybuilder.QueryBuilder.select; -import static org.thingsboard.server.dao.model.ModelConstants.DEVICE_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.DEVICE_BY_CUSTOMER_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.DEVICE_BY_TENANT_AND_NAME_VIEW_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.DEVICE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.DEVICE_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.DEVICE_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.DEVICE_CUSTOMER_ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.DEVICE_NAME_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.DEVICE_TENANT_ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.DEVICE_TYPE_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_SUBTYPE_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_SUBTYPE_ENTITY_TYPE_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_SUBTYPE_TENANT_ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY; - -@Component -@Slf4j -@NoSqlDao -public class CassandraDeviceDao extends CassandraAbstractSearchTextDao implements DeviceDao { - - @Override - protected Class getColumnFamilyClass() { - return DeviceEntity.class; - } - - @Override - protected String getColumnFamilyName() { - return DEVICE_COLUMN_FAMILY_NAME; - } - - @Override - public Device save(TenantId tenantId, Device domain) { - Device savedDevice = super.save(tenantId, domain); - EntitySubtype entitySubtype = new EntitySubtype(savedDevice.getTenantId(), EntityType.DEVICE, savedDevice.getType()); - EntitySubtypeEntity entitySubtypeEntity = new EntitySubtypeEntity(entitySubtype); - Statement saveStatement = cluster.getMapper(EntitySubtypeEntity.class).saveQuery(entitySubtypeEntity); - executeWrite(tenantId, saveStatement); - return savedDevice; - } - - @Override - public List findDevicesByTenantId(UUID tenantId, TextPageLink pageLink) { - log.debug("Try to find devices by tenantId [{}] and pageLink [{}]", tenantId, pageLink); - List deviceEntities = findPageWithTextSearch(new TenantId(tenantId), DEVICE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME, - Collections.singletonList(eq(DEVICE_TENANT_ID_PROPERTY, tenantId)), pageLink); - - log.trace("Found devices [{}] by tenantId [{}] and pageLink [{}]", deviceEntities, tenantId, pageLink); - return DaoUtil.convertDataList(deviceEntities); - } - - @Override - public List findDevicesByTenantIdAndType(UUID tenantId, String type, TextPageLink pageLink) { - log.debug("Try to find devices by tenantId [{}], type [{}] and pageLink [{}]", tenantId, type, pageLink); - List deviceEntities = findPageWithTextSearch(new TenantId(tenantId), DEVICE_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME, - Arrays.asList(eq(DEVICE_TYPE_PROPERTY, type), - eq(DEVICE_TENANT_ID_PROPERTY, tenantId)), pageLink); - log.trace("Found devices [{}] by tenantId [{}], type [{}] and pageLink [{}]", deviceEntities, tenantId, type, pageLink); - return DaoUtil.convertDataList(deviceEntities); - } - - @Override - public ListenableFuture> findDevicesByTenantIdAndIdsAsync(UUID tenantId, List deviceIds) { - log.debug("Try to find devices by tenantId [{}] and device Ids [{}]", tenantId, deviceIds); - Select select = select().from(getColumnFamilyName()); - Select.Where query = select.where(); - query.and(eq(DEVICE_TENANT_ID_PROPERTY, tenantId)); - query.and(in(ID_PROPERTY, deviceIds)); - return findListByStatementAsync(new TenantId(tenantId), query); - } - - @Override - public List findDevicesByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink) { - log.debug("Try to find devices by tenantId [{}], customerId[{}] and pageLink [{}]", tenantId, customerId, pageLink); - List deviceEntities = findPageWithTextSearch(new TenantId(tenantId), DEVICE_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME, - Arrays.asList(eq(DEVICE_CUSTOMER_ID_PROPERTY, customerId), - eq(DEVICE_TENANT_ID_PROPERTY, tenantId)), - pageLink); - - log.trace("Found devices [{}] by tenantId [{}], customerId [{}] and pageLink [{}]", deviceEntities, tenantId, customerId, pageLink); - return DaoUtil.convertDataList(deviceEntities); - } - - @Override - public List findDevicesByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, TextPageLink pageLink) { - log.debug("Try to find devices by tenantId [{}], customerId [{}], type [{}] and pageLink [{}]", tenantId, customerId, type, pageLink); - List deviceEntities = findPageWithTextSearch(new TenantId(tenantId), DEVICE_BY_CUSTOMER_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME, - Arrays.asList(eq(DEVICE_TYPE_PROPERTY, type), - eq(DEVICE_CUSTOMER_ID_PROPERTY, customerId), - eq(DEVICE_TENANT_ID_PROPERTY, tenantId)), - pageLink); - - log.trace("Found devices [{}] by tenantId [{}], customerId [{}], type [{}] and pageLink [{}]", deviceEntities, tenantId, customerId, type, pageLink); - return DaoUtil.convertDataList(deviceEntities); - } - - @Override - public ListenableFuture> findDevicesByTenantIdCustomerIdAndIdsAsync(UUID tenantId, UUID customerId, List deviceIds) { - log.debug("Try to find devices by tenantId [{}], customerId [{}] and device Ids [{}]", tenantId, customerId, deviceIds); - Select select = select().from(getColumnFamilyName()); - Select.Where query = select.where(); - query.and(eq(DEVICE_TENANT_ID_PROPERTY, tenantId)); - query.and(eq(DEVICE_CUSTOMER_ID_PROPERTY, customerId)); - query.and(in(ID_PROPERTY, deviceIds)); - return findListByStatementAsync(new TenantId(tenantId), query); - } - - @Override - public Optional findDeviceByTenantIdAndName(UUID tenantId, String deviceName) { - Select select = select().from(DEVICE_BY_TENANT_AND_NAME_VIEW_NAME); - Select.Where query = select.where(); - query.and(eq(DEVICE_TENANT_ID_PROPERTY, tenantId)); - query.and(eq(DEVICE_NAME_PROPERTY, deviceName)); - return Optional.ofNullable(DaoUtil.getData(findOneByStatement(new TenantId(tenantId), query))); - } - - @Override - public ListenableFuture> findTenantDeviceTypesAsync(UUID tenantId) { - Select select = select().from(ENTITY_SUBTYPE_COLUMN_FAMILY_NAME); - Select.Where query = select.where(); - query.and(eq(ENTITY_SUBTYPE_TENANT_ID_PROPERTY, tenantId)); - query.and(eq(ENTITY_SUBTYPE_ENTITY_TYPE_PROPERTY, EntityType.DEVICE)); - query.setConsistencyLevel(cluster.getDefaultReadConsistencyLevel()); - ResultSetFuture resultSetFuture = executeAsyncRead(new TenantId(tenantId), query); - return Futures.transform(resultSetFuture, new Function>() { - @Nullable - @Override - public List apply(@Nullable ResultSet resultSet) { - Result result = cluster.getMapper(EntitySubtypeEntity.class).map(resultSet); - if (result != null) { - List entitySubtypes = new ArrayList<>(); - result.all().forEach((entitySubtypeEntity) -> - entitySubtypes.add(entitySubtypeEntity.toEntitySubtype()) - ); - return entitySubtypes; - } else { - return Collections.emptyList(); - } - } - }); - } - -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/entityview/CassandraEntityViewDao.java b/dao/src/main/java/org/thingsboard/server/dao/entityview/CassandraEntityViewDao.java deleted file mode 100644 index 9935178e3f..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/entityview/CassandraEntityViewDao.java +++ /dev/null @@ -1,186 +0,0 @@ -/** - * Copyright © 2016-2019 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.entityview; - -import com.datastax.driver.core.ResultSet; -import com.datastax.driver.core.ResultSetFuture; -import com.datastax.driver.core.Statement; -import com.datastax.driver.core.querybuilder.Select; -import com.datastax.driver.mapping.Result; -import com.google.common.base.Function; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -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.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageLink; -import org.thingsboard.server.dao.DaoUtil; -import org.thingsboard.server.dao.model.EntitySubtypeEntity; -import org.thingsboard.server.dao.model.nosql.EntityViewEntity; -import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao; -import org.thingsboard.server.dao.util.NoSqlDao; - -import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.UUID; - -import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; -import static com.datastax.driver.core.querybuilder.QueryBuilder.select; -import static org.thingsboard.server.dao.model.ModelConstants.CUSTOMER_ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.DEVICE_TYPE_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_ID_COLUMN; -import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_SUBTYPE_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_SUBTYPE_ENTITY_TYPE_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_SUBTYPE_TENANT_ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_BY_TENANT_AND_CUSTOMER_AND_TYPE_CF; -import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_BY_TENANT_AND_CUSTOMER_CF; -import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_BY_TENANT_AND_ENTITY_ID_CF; -import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_BY_TENANT_AND_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_BY_TENANT_AND_SEARCH_TEXT_CF; -import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_CF; -import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_NAME_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_TABLE_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_TENANT_ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_TYPE_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.TENANT_ID_PROPERTY; - -/** - * Created by Victor Basanets on 9/06/2017. - */ -@Component -@Slf4j -@NoSqlDao -public class CassandraEntityViewDao extends CassandraAbstractSearchTextDao implements EntityViewDao { - - @Override - protected Class getColumnFamilyClass() { - return EntityViewEntity.class; - } - - @Override - protected String getColumnFamilyName() { - return ENTITY_VIEW_TABLE_FAMILY_NAME; - } - - @Override - public EntityView save(TenantId tenantId, EntityView domain) { - EntityView savedEntityView = super.save(domain.getTenantId(), domain); - EntitySubtype entitySubtype = new EntitySubtype(savedEntityView.getTenantId(), EntityType.ENTITY_VIEW, savedEntityView.getType()); - EntitySubtypeEntity entitySubtypeEntity = new EntitySubtypeEntity(entitySubtype); - Statement saveStatement = cluster.getMapper(EntitySubtypeEntity.class).saveQuery(entitySubtypeEntity); - executeWrite(tenantId, saveStatement); - return savedEntityView; - } - - @Override - public List findEntityViewsByTenantId(UUID tenantId, TextPageLink pageLink) { - log.debug("Try to find entity views by tenantId [{}] and pageLink [{}]", tenantId, pageLink); - List entityViewEntities = - findPageWithTextSearch(new TenantId(tenantId), ENTITY_VIEW_BY_TENANT_AND_SEARCH_TEXT_CF, - Collections.singletonList(eq(TENANT_ID_PROPERTY, tenantId)), pageLink); - log.trace("Found entity views [{}] by tenantId [{}] and pageLink [{}]", - entityViewEntities, tenantId, pageLink); - return DaoUtil.convertDataList(entityViewEntities); - } - - @Override - public List findEntityViewsByTenantIdAndType(UUID tenantId, String type, TextPageLink pageLink) { - log.debug("Try to find entity views by tenantId [{}], type [{}] and pageLink [{}]", tenantId, type, pageLink); - List entityViewEntities = - findPageWithTextSearch(new TenantId(tenantId), ENTITY_VIEW_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_CF, - Arrays.asList(eq(ENTITY_VIEW_TYPE_PROPERTY, type), - eq(TENANT_ID_PROPERTY, tenantId)), pageLink); - log.trace("Found entity views [{}] by tenantId [{}], type [{}] and pageLink [{}]", - entityViewEntities, tenantId, type, pageLink); - return DaoUtil.convertDataList(entityViewEntities); - } - - @Override - public Optional findEntityViewByTenantIdAndName(UUID tenantId, String name) { - Select.Where query = select().from(ENTITY_VIEW_BY_TENANT_AND_NAME).where(); - query.and(eq(ENTITY_VIEW_TENANT_ID_PROPERTY, tenantId)); - query.and(eq(ENTITY_VIEW_NAME_PROPERTY, name)); - return Optional.ofNullable(DaoUtil.getData(findOneByStatement(new TenantId(tenantId), query))); - } - - @Override - public List findEntityViewsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink) { - log.debug("Try to find entity views by tenantId [{}], customerId[{}] and pageLink [{}]", - tenantId, customerId, pageLink); - List entityViewEntities = findPageWithTextSearch(new TenantId(tenantId), - ENTITY_VIEW_BY_TENANT_AND_CUSTOMER_CF, - Arrays.asList(eq(CUSTOMER_ID_PROPERTY, customerId), eq(TENANT_ID_PROPERTY, tenantId)), - pageLink); - log.trace("Found find entity views [{}] by tenantId [{}], customerId [{}] and pageLink [{}]", - entityViewEntities, tenantId, customerId, pageLink); - return DaoUtil.convertDataList(entityViewEntities); - } - - @Override - public List findEntityViewsByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, TextPageLink pageLink) { - log.debug("Try to find entity views by tenantId [{}], customerId[{}], type [{}] and pageLink [{}]", - tenantId, customerId, type, pageLink); - List entityViewEntities = findPageWithTextSearch(new TenantId(tenantId), - ENTITY_VIEW_BY_TENANT_AND_CUSTOMER_AND_TYPE_CF, - Arrays.asList(eq(DEVICE_TYPE_PROPERTY, type), eq(CUSTOMER_ID_PROPERTY, customerId), eq(TENANT_ID_PROPERTY, tenantId)), - pageLink); - log.trace("Found find entity views [{}] by tenantId [{}], customerId [{}], type [{}] and pageLink [{}]", - entityViewEntities, tenantId, customerId, type, pageLink); - return DaoUtil.convertDataList(entityViewEntities); - } - - @Override - public ListenableFuture> findEntityViewsByTenantIdAndEntityIdAsync(UUID tenantId, UUID entityId) { - log.debug("Try to find entity views by tenantId [{}] and entityId [{}]", tenantId, entityId); - Select.Where query = select().from(ENTITY_VIEW_BY_TENANT_AND_ENTITY_ID_CF).where(); - query.and(eq(TENANT_ID_PROPERTY, tenantId)); - query.and(eq(ENTITY_ID_COLUMN, entityId)); - return findListByStatementAsync(new TenantId(tenantId), query); - } - - @Override - public ListenableFuture> findTenantEntityViewTypesAsync(UUID tenantId) { - Select select = select().from(ENTITY_SUBTYPE_COLUMN_FAMILY_NAME); - Select.Where query = select.where(); - query.and(eq(ENTITY_SUBTYPE_TENANT_ID_PROPERTY, tenantId)); - query.and(eq(ENTITY_SUBTYPE_ENTITY_TYPE_PROPERTY, EntityType.ENTITY_VIEW)); - query.setConsistencyLevel(cluster.getDefaultReadConsistencyLevel()); - ResultSetFuture resultSetFuture = executeAsyncRead(new TenantId(tenantId), query); - return Futures.transform(resultSetFuture, new Function>() { - @Nullable - @Override - public List apply(@Nullable ResultSet resultSet) { - Result result = cluster.getMapper(EntitySubtypeEntity.class).map(resultSet); - if (result != null) { - List entitySubtypes = new ArrayList<>(); - result.all().forEach((entitySubtypeEntity) -> - entitySubtypes.add(entitySubtypeEntity.toEntitySubtype()) - ); - return entitySubtypes; - } else { - return Collections.emptyList(); - } - } - }); - } -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/event/CassandraBaseEventDao.java b/dao/src/main/java/org/thingsboard/server/dao/event/CassandraBaseEventDao.java deleted file mode 100644 index d5cbcac3cf..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/event/CassandraBaseEventDao.java +++ /dev/null @@ -1,215 +0,0 @@ -/** - * Copyright © 2016-2019 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.event; - -import com.datastax.driver.core.ResultSetFuture; -import com.datastax.driver.core.querybuilder.Insert; -import com.datastax.driver.core.querybuilder.QueryBuilder; -import com.datastax.driver.core.querybuilder.Select; -import com.datastax.driver.core.utils.UUIDs; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.DataConstants; -import org.thingsboard.server.common.data.Event; -import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.EventId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TimePageLink; -import org.thingsboard.server.dao.DaoUtil; -import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.model.nosql.EventEntity; -import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTimeDao; -import org.thingsboard.server.dao.util.NoSqlDao; - -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.ExecutionException; - -import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; -import static com.datastax.driver.core.querybuilder.QueryBuilder.in; -import static com.datastax.driver.core.querybuilder.QueryBuilder.select; -import static com.datastax.driver.core.querybuilder.QueryBuilder.ttl; -import static org.thingsboard.server.dao.model.ModelConstants.*; - -@Component -@Slf4j -@NoSqlDao -public class CassandraBaseEventDao extends CassandraAbstractSearchTimeDao implements EventDao { - - private final TenantId systemTenantId = new TenantId(NULL_UUID); - - @Override - protected Class getColumnFamilyClass() { - return EventEntity.class; - } - - @Override - protected String getColumnFamilyName() { - return EVENT_COLUMN_FAMILY_NAME; - } - - @Value("${cassandra.query.events_ttl:0}") - private int eventsTtl; - - @Value("${cassandra.query.debug_events_ttl:0}") - private int debugEventsTtl; - - @Override - public Event save(TenantId tenantId, Event event) { - try { - return saveAsync(event).get(); - } catch (InterruptedException | ExecutionException e) { - throw new IllegalStateException("Could not save EventEntity", e); - } - } - - @Override - public ListenableFuture saveAsync(Event event) { - log.debug("Save event [{}] ", event); - if (event.getTenantId() == null) { - log.trace("Save system event with predefined id {}", systemTenantId); - event.setTenantId(systemTenantId); - } - if (event.getId() == null) { - event.setId(new EventId(UUIDs.timeBased())); - } - if (StringUtils.isEmpty(event.getUid())) { - event.setUid(event.getId().toString()); - } - ListenableFuture> optionalSave = saveAsync(event.getTenantId(), new EventEntity(event), false, eventsTtl); - return Futures.transform(optionalSave, opt -> opt.orElse(null)); - } - - @Override - public Optional saveIfNotExists(Event event) { - if (event.getTenantId() == null) { - log.trace("Save system event with predefined id {}", systemTenantId); - event.setTenantId(systemTenantId); - } - if (event.getId() == null) { - event.setId(new EventId(UUIDs.timeBased())); - } - return save(event.getTenantId(), new EventEntity(event), true, eventsTtl); - } - - @Override - public Event findEvent(UUID tenantId, EntityId entityId, String eventType, String eventUid) { - log.debug("Search event entity by [{}][{}][{}][{}]", tenantId, entityId, eventType, eventUid); - Select.Where query = select().from(getColumnFamilyName()).where( - eq(ModelConstants.EVENT_TENANT_ID_PROPERTY, tenantId)) - .and(eq(ModelConstants.EVENT_ENTITY_TYPE_PROPERTY, entityId.getEntityType())) - .and(eq(ModelConstants.EVENT_ENTITY_ID_PROPERTY, entityId.getId())) - .and(eq(ModelConstants.EVENT_TYPE_PROPERTY, eventType)) - .and(eq(ModelConstants.EVENT_UID_PROPERTY, eventUid)); - log.trace("Execute query [{}]", query); - EventEntity entity = findOneByStatement(new TenantId(tenantId), query); - if (log.isTraceEnabled()) { - log.trace("Search result: [{}] for event entity [{}]", entity != null, entity); - } else { - log.debug("Search result: [{}]", entity != null); - } - return DaoUtil.getData(entity); - } - - @Override - public List findEvents(UUID tenantId, EntityId entityId, TimePageLink pageLink) { - log.trace("Try to find events by tenant [{}], entity [{}]and pageLink [{}]", tenantId, entityId, pageLink); - List entities = findPageWithTimeSearch(new TenantId(tenantId), EVENT_BY_ID_VIEW_NAME, - Arrays.asList(eq(ModelConstants.EVENT_TENANT_ID_PROPERTY, tenantId), - eq(ModelConstants.EVENT_ENTITY_TYPE_PROPERTY, entityId.getEntityType()), - eq(ModelConstants.EVENT_ENTITY_ID_PROPERTY, entityId.getId())), - pageLink); - log.trace("Found events by tenant [{}], entity [{}] and pageLink [{}]", tenantId, entityId, pageLink); - return DaoUtil.convertDataList(entities); - } - - @Override - public List findEvents(UUID tenantId, EntityId entityId, String eventType, TimePageLink pageLink) { - log.trace("Try to find events by tenant [{}], entity [{}], type [{}] and pageLink [{}]", tenantId, entityId, eventType, pageLink); - List entities = findPageWithTimeSearch(new TenantId(tenantId), EVENT_BY_TYPE_AND_ID_VIEW_NAME, - Arrays.asList(eq(ModelConstants.EVENT_TENANT_ID_PROPERTY, tenantId), - eq(ModelConstants.EVENT_ENTITY_TYPE_PROPERTY, entityId.getEntityType()), - eq(ModelConstants.EVENT_ENTITY_ID_PROPERTY, entityId.getId()), - eq(ModelConstants.EVENT_TYPE_PROPERTY, eventType)), - pageLink.isAscOrder() ? QueryBuilder.asc(ModelConstants.EVENT_TYPE_PROPERTY) : - QueryBuilder.desc(ModelConstants.EVENT_TYPE_PROPERTY), - pageLink); - log.trace("Found events by tenant [{}], entity [{}], type [{}] and pageLink [{}]", tenantId, entityId, eventType, pageLink); - return DaoUtil.convertDataList(entities); - } - - @Override - public List findLatestEvents(UUID tenantId, EntityId entityId, String eventType, int limit) { - log.trace("Try to find latest events by tenant [{}], entity [{}], type [{}] and limit [{}]", tenantId, entityId, eventType, limit); - Select select = select().from(EVENT_BY_TYPE_AND_ID_VIEW_NAME); - Select.Where query = select.where(); - query.and(eq(ModelConstants.EVENT_TENANT_ID_PROPERTY, tenantId)); - query.and(eq(ModelConstants.EVENT_ENTITY_TYPE_PROPERTY, entityId.getEntityType())); - query.and(eq(ModelConstants.EVENT_ENTITY_ID_PROPERTY, entityId.getId())); - query.and(eq(ModelConstants.EVENT_TYPE_PROPERTY, eventType)); - query.limit(limit); - query.orderBy(QueryBuilder.desc(ModelConstants.EVENT_TYPE_PROPERTY), QueryBuilder.desc(ModelConstants.ID_PROPERTY)); - List entities = findListByStatement(new TenantId(tenantId), query); - return DaoUtil.convertDataList(entities); - } - - private Optional save(TenantId tenantId, EventEntity entity, boolean ifNotExists, int ttl) { - try { - return saveAsync(tenantId, entity, ifNotExists, ttl).get(); - } catch (InterruptedException | ExecutionException e) { - throw new IllegalStateException("Could not save EventEntity", e); - } - } - - private ListenableFuture> saveAsync(TenantId tenantId, EventEntity entity, boolean ifNotExists, int ttl) { - if (entity.getId() == null) { - entity.setId(UUIDs.timeBased()); - } - Insert insert = QueryBuilder.insertInto(getColumnFamilyName()) - .value(ModelConstants.ID_PROPERTY, entity.getId()) - .value(ModelConstants.EVENT_TENANT_ID_PROPERTY, entity.getTenantId()) - .value(ModelConstants.EVENT_ENTITY_TYPE_PROPERTY, entity.getEntityType()) - .value(ModelConstants.EVENT_ENTITY_ID_PROPERTY, entity.getEntityId()) - .value(ModelConstants.EVENT_TYPE_PROPERTY, entity.getEventType()) - .value(ModelConstants.EVENT_UID_PROPERTY, entity.getEventUid()) - .value(ModelConstants.EVENT_BODY_PROPERTY, entity.getBody()); - - if (ifNotExists) { - insert = insert.ifNotExists(); - } - - int selectedTtl = (entity.getEventType().equals(DataConstants.DEBUG_RULE_NODE) || - entity.getEventType().equals(DataConstants.DEBUG_RULE_CHAIN)) ? debugEventsTtl : ttl; - - if (selectedTtl > 0) { - insert.using(ttl(selectedTtl)); - } - ResultSetFuture resultSetFuture = executeAsyncWrite(tenantId, insert); - return Futures.transform(resultSetFuture, rs -> { - if (rs.wasApplied()) { - return Optional.of(DaoUtil.getData(entity)); - } else { - return Optional.empty(); - } - }); - } -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/AdminSettingsEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/AdminSettingsEntity.java deleted file mode 100644 index 8198d0ab32..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/AdminSettingsEntity.java +++ /dev/null @@ -1,97 +0,0 @@ -/** - * Copyright © 2016-2019 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.model.nosql; - -import com.datastax.driver.core.utils.UUIDs; -import com.datastax.driver.mapping.annotations.Column; -import com.datastax.driver.mapping.annotations.PartitionKey; -import com.datastax.driver.mapping.annotations.Table; -import com.fasterxml.jackson.databind.JsonNode; -import lombok.EqualsAndHashCode; -import lombok.ToString; -import org.thingsboard.server.common.data.AdminSettings; -import org.thingsboard.server.common.data.id.AdminSettingsId; -import org.thingsboard.server.dao.model.BaseEntity; -import org.thingsboard.server.dao.model.type.JsonCodec; - -import java.util.UUID; - -import static org.thingsboard.server.dao.model.ModelConstants.ADMIN_SETTINGS_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.ADMIN_SETTINGS_JSON_VALUE_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ADMIN_SETTINGS_KEY_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY; - -@Table(name = ADMIN_SETTINGS_COLUMN_FAMILY_NAME) -@EqualsAndHashCode -@ToString -public final class AdminSettingsEntity implements BaseEntity { - - @PartitionKey(value = 0) - @Column(name = ID_PROPERTY) - private UUID id; - - @Column(name = ADMIN_SETTINGS_KEY_PROPERTY) - private String key; - - @Column(name = ADMIN_SETTINGS_JSON_VALUE_PROPERTY, codec = JsonCodec.class) - private JsonNode jsonValue; - - public AdminSettingsEntity() { - super(); - } - - public AdminSettingsEntity(AdminSettings adminSettings) { - if (adminSettings.getId() != null) { - this.id = adminSettings.getId().getId(); - } - this.key = adminSettings.getKey(); - this.jsonValue = adminSettings.getJsonValue(); - } - - public UUID getId() { - return id; - } - - public void setId(UUID id) { - this.id = id; - } - - public String getKey() { - return key; - } - - public void setKey(String key) { - this.key = key; - } - - public JsonNode getJsonValue() { - return jsonValue; - } - - public void setJsonValue(JsonNode jsonValue) { - this.jsonValue = jsonValue; - } - - @Override - public AdminSettings toData() { - AdminSettings adminSettings = new AdminSettings(new AdminSettingsId(id)); - adminSettings.setCreatedTime(UUIDs.unixTimestamp(id)); - adminSettings.setKey(key); - adminSettings.setJsonValue(jsonValue); - return adminSettings; - } - -} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/AlarmEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/AlarmEntity.java deleted file mode 100644 index 807a73fe38..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/AlarmEntity.java +++ /dev/null @@ -1,254 +0,0 @@ -/** - * Copyright © 2016-2019 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.model.nosql; - -import com.datastax.driver.core.utils.UUIDs; -import com.datastax.driver.mapping.annotations.ClusteringColumn; -import com.datastax.driver.mapping.annotations.Column; -import com.datastax.driver.mapping.annotations.PartitionKey; -import com.datastax.driver.mapping.annotations.Table; -import com.fasterxml.jackson.databind.JsonNode; -import lombok.EqualsAndHashCode; -import lombok.ToString; -import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.alarm.Alarm; -import org.thingsboard.server.common.data.alarm.AlarmId; -import org.thingsboard.server.common.data.alarm.AlarmSeverity; -import org.thingsboard.server.common.data.alarm.AlarmStatus; -import org.thingsboard.server.common.data.id.EntityIdFactory; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.dao.model.BaseEntity; -import org.thingsboard.server.dao.model.type.AlarmSeverityCodec; -import org.thingsboard.server.dao.model.type.AlarmStatusCodec; -import org.thingsboard.server.dao.model.type.EntityTypeCodec; -import org.thingsboard.server.dao.model.type.JsonCodec; - -import java.util.UUID; - -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ACK_TS_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_CLEAR_TS_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_DETAILS_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_END_TS_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_TYPE_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_PROPAGATE_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_SEVERITY_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_START_TS_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_STATUS_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_TENANT_ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_TYPE_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY; - -@Table(name = ALARM_COLUMN_FAMILY_NAME) -@EqualsAndHashCode -@ToString -public final class AlarmEntity implements BaseEntity { - - @ClusteringColumn(value = 1) - @Column(name = ID_PROPERTY) - private UUID id; - - @PartitionKey(value = 0) - @Column(name = ALARM_TENANT_ID_PROPERTY) - private UUID tenantId; - - @PartitionKey(value = 1) - @Column(name = ALARM_ORIGINATOR_ID_PROPERTY) - private UUID originatorId; - - @PartitionKey(value = 2) - @Column(name = ALARM_ORIGINATOR_TYPE_PROPERTY, codec = EntityTypeCodec.class) - private EntityType originatorType; - - @ClusteringColumn(value = 0) - @Column(name = ALARM_TYPE_PROPERTY) - private String type; - - @Column(name = ALARM_SEVERITY_PROPERTY, codec = AlarmSeverityCodec.class) - private AlarmSeverity severity; - - @Column(name = ALARM_STATUS_PROPERTY, codec = AlarmStatusCodec.class) - private AlarmStatus status; - - @Column(name = ALARM_START_TS_PROPERTY) - private Long startTs; - - @Column(name = ALARM_END_TS_PROPERTY) - private Long endTs; - - @Column(name = ALARM_ACK_TS_PROPERTY) - private Long ackTs; - - @Column(name = ALARM_CLEAR_TS_PROPERTY) - private Long clearTs; - - @Column(name = ALARM_DETAILS_PROPERTY, codec = JsonCodec.class) - private JsonNode details; - - @Column(name = ALARM_PROPAGATE_PROPERTY) - private Boolean propagate; - - public AlarmEntity() { - super(); - } - - public AlarmEntity(Alarm alarm) { - if (alarm.getId() != null) { - this.id = alarm.getId().getId(); - } - if (alarm.getTenantId() != null) { - this.tenantId = alarm.getTenantId().getId(); - } - this.type = alarm.getType(); - this.originatorId = alarm.getOriginator().getId(); - this.originatorType = alarm.getOriginator().getEntityType(); - this.type = alarm.getType(); - this.severity = alarm.getSeverity(); - this.status = alarm.getStatus(); - this.propagate = alarm.isPropagate(); - this.startTs = alarm.getStartTs(); - this.endTs = alarm.getEndTs(); - this.ackTs = alarm.getAckTs(); - this.clearTs = alarm.getClearTs(); - this.details = alarm.getDetails(); - } - - public UUID getId() { - return id; - } - - public void setId(UUID id) { - this.id = id; - } - - public UUID getTenantId() { - return tenantId; - } - - public void setTenantId(UUID tenantId) { - this.tenantId = tenantId; - } - - public UUID getOriginatorId() { - return originatorId; - } - - public void setOriginatorId(UUID originatorId) { - this.originatorId = originatorId; - } - - public EntityType getOriginatorType() { - return originatorType; - } - - public void setOriginatorType(EntityType originatorType) { - this.originatorType = originatorType; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public AlarmSeverity getSeverity() { - return severity; - } - - public void setSeverity(AlarmSeverity severity) { - this.severity = severity; - } - - public AlarmStatus getStatus() { - return status; - } - - public void setStatus(AlarmStatus status) { - this.status = status; - } - - public Long getStartTs() { - return startTs; - } - - public void setStartTs(Long startTs) { - this.startTs = startTs; - } - - public Long getEndTs() { - return endTs; - } - - public void setEndTs(Long endTs) { - this.endTs = endTs; - } - - public Long getAckTs() { - return ackTs; - } - - public void setAckTs(Long ackTs) { - this.ackTs = ackTs; - } - - public Long getClearTs() { - return clearTs; - } - - public void setClearTs(Long clearTs) { - this.clearTs = clearTs; - } - - public JsonNode getDetails() { - return details; - } - - public void setDetails(JsonNode details) { - this.details = details; - } - - public Boolean getPropagate() { - return propagate; - } - - public void setPropagate(Boolean propagate) { - this.propagate = propagate; - } - - @Override - public Alarm toData() { - Alarm alarm = new Alarm(new AlarmId(id)); - alarm.setCreatedTime(UUIDs.unixTimestamp(id)); - if (tenantId != null) { - alarm.setTenantId(new TenantId(tenantId)); - } - alarm.setOriginator(EntityIdFactory.getByTypeAndUuid(originatorType, originatorId)); - alarm.setType(type); - alarm.setSeverity(severity); - alarm.setStatus(status); - alarm.setPropagate(propagate); - alarm.setStartTs(startTs); - alarm.setEndTs(endTs); - alarm.setAckTs(ackTs); - alarm.setClearTs(clearTs); - alarm.setDetails(details); - return alarm; - } - -} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/AssetEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/AssetEntity.java deleted file mode 100644 index a951b970f3..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/AssetEntity.java +++ /dev/null @@ -1,170 +0,0 @@ -/** - * Copyright © 2016-2019 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.model.nosql; - -import com.datastax.driver.core.utils.UUIDs; -import com.datastax.driver.mapping.annotations.Column; -import com.datastax.driver.mapping.annotations.PartitionKey; -import com.datastax.driver.mapping.annotations.Table; -import com.fasterxml.jackson.databind.JsonNode; -import lombok.EqualsAndHashCode; -import lombok.ToString; -import org.thingsboard.server.common.data.asset.Asset; -import org.thingsboard.server.common.data.id.AssetId; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.dao.model.SearchTextEntity; -import org.thingsboard.server.dao.model.type.JsonCodec; - -import java.util.UUID; - -import static org.thingsboard.server.dao.model.ModelConstants.ASSET_ADDITIONAL_INFO_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ASSET_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.ASSET_CUSTOMER_ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ASSET_NAME_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ASSET_TENANT_ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ASSET_TYPE_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY; - -@Table(name = ASSET_COLUMN_FAMILY_NAME) -@EqualsAndHashCode -@ToString -public final class AssetEntity implements SearchTextEntity { - - @PartitionKey(value = 0) - @Column(name = ID_PROPERTY) - private UUID id; - - @PartitionKey(value = 1) - @Column(name = ASSET_TENANT_ID_PROPERTY) - private UUID tenantId; - - @PartitionKey(value = 2) - @Column(name = ASSET_CUSTOMER_ID_PROPERTY) - private UUID customerId; - - @PartitionKey(value = 3) - @Column(name = ASSET_TYPE_PROPERTY) - private String type; - - @Column(name = ASSET_NAME_PROPERTY) - private String name; - - @Column(name = SEARCH_TEXT_PROPERTY) - private String searchText; - - @Column(name = ASSET_ADDITIONAL_INFO_PROPERTY, codec = JsonCodec.class) - private JsonNode additionalInfo; - - public AssetEntity() { - super(); - } - - public AssetEntity(Asset asset) { - if (asset.getId() != null) { - this.id = asset.getId().getId(); - } - if (asset.getTenantId() != null) { - this.tenantId = asset.getTenantId().getId(); - } - if (asset.getCustomerId() != null) { - this.customerId = asset.getCustomerId().getId(); - } - this.name = asset.getName(); - this.type = asset.getType(); - this.additionalInfo = asset.getAdditionalInfo(); - } - - public UUID getId() { - return id; - } - - public void setId(UUID id) { - this.id = id; - } - - public UUID getTenantId() { - return tenantId; - } - - public void setTenantId(UUID tenantId) { - this.tenantId = tenantId; - } - - public UUID getCustomerId() { - return customerId; - } - - public void setCustomerId(UUID customerId) { - this.customerId = customerId; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public JsonNode getAdditionalInfo() { - return additionalInfo; - } - - public void setAdditionalInfo(JsonNode additionalInfo) { - this.additionalInfo = additionalInfo; - } - - @Override - public String getSearchTextSource() { - return getName(); - } - - @Override - public void setSearchText(String searchText) { - this.searchText = searchText; - } - - public String getSearchText() { - return searchText; - } - - @Override - public Asset toData() { - Asset asset = new Asset(new AssetId(id)); - asset.setCreatedTime(UUIDs.unixTimestamp(id)); - if (tenantId != null) { - asset.setTenantId(new TenantId(tenantId)); - } - if (customerId != null) { - asset.setCustomerId(new CustomerId(customerId)); - } - asset.setName(name); - asset.setType(type); - asset.setAdditionalInfo(additionalInfo); - return asset; - } - -} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/AuditLogEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/AuditLogEntity.java deleted file mode 100644 index e18fd8458c..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/AuditLogEntity.java +++ /dev/null @@ -1,155 +0,0 @@ -/** - * Copyright © 2016-2019 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.model.nosql; - -import com.datastax.driver.core.utils.UUIDs; -import com.datastax.driver.mapping.annotations.Column; -import com.datastax.driver.mapping.annotations.Table; -import com.fasterxml.jackson.databind.JsonNode; -import lombok.Data; -import lombok.NoArgsConstructor; -import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.audit.ActionStatus; -import org.thingsboard.server.common.data.audit.ActionType; -import org.thingsboard.server.common.data.audit.AuditLog; -import org.thingsboard.server.common.data.id.AuditLogId; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.EntityIdFactory; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.dao.model.BaseEntity; -import org.thingsboard.server.dao.model.type.ActionStatusCodec; -import org.thingsboard.server.dao.model.type.ActionTypeCodec; -import org.thingsboard.server.dao.model.type.EntityTypeCodec; -import org.thingsboard.server.dao.model.type.JsonCodec; - -import java.util.UUID; - -import static org.thingsboard.server.dao.model.ModelConstants.AUDIT_LOG_ACTION_DATA_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.AUDIT_LOG_ACTION_FAILURE_DETAILS_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.AUDIT_LOG_ACTION_STATUS_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.AUDIT_LOG_ACTION_TYPE_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.AUDIT_LOG_BY_ENTITY_ID_CF; -import static org.thingsboard.server.dao.model.ModelConstants.AUDIT_LOG_CUSTOMER_ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.AUDIT_LOG_ENTITY_ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.AUDIT_LOG_ENTITY_NAME_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.AUDIT_LOG_ENTITY_TYPE_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.AUDIT_LOG_USER_ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.AUDIT_LOG_USER_NAME_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY; - -@Table(name = AUDIT_LOG_BY_ENTITY_ID_CF) -@Data -@NoArgsConstructor -public class AuditLogEntity implements BaseEntity { - - @Column(name = ID_PROPERTY) - private UUID id; - - @Column(name = AUDIT_LOG_TENANT_ID_PROPERTY) - private UUID tenantId; - - @Column(name = AUDIT_LOG_CUSTOMER_ID_PROPERTY) - private UUID customerId; - - @Column(name = AUDIT_LOG_ENTITY_TYPE_PROPERTY, codec = EntityTypeCodec.class) - private EntityType entityType; - - @Column(name = AUDIT_LOG_ENTITY_ID_PROPERTY) - private UUID entityId; - - @Column(name = AUDIT_LOG_ENTITY_NAME_PROPERTY) - private String entityName; - - @Column(name = AUDIT_LOG_USER_ID_PROPERTY) - private UUID userId; - - @Column(name = AUDIT_LOG_USER_NAME_PROPERTY) - private String userName; - - @Column(name = AUDIT_LOG_ACTION_TYPE_PROPERTY, codec = ActionTypeCodec.class) - private ActionType actionType; - - @Column(name = AUDIT_LOG_ACTION_DATA_PROPERTY, codec = JsonCodec.class) - private JsonNode actionData; - - @Column(name = AUDIT_LOG_ACTION_STATUS_PROPERTY, codec = ActionStatusCodec.class) - private ActionStatus actionStatus; - - @Column(name = AUDIT_LOG_ACTION_FAILURE_DETAILS_PROPERTY) - private String actionFailureDetails; - - @Override - public UUID getId() { - return id; - } - - @Override - public void setId(UUID id) { - this.id = id; - } - - public AuditLogEntity(AuditLog auditLog) { - if (auditLog.getId() != null) { - this.id = auditLog.getId().getId(); - } - if (auditLog.getTenantId() != null) { - this.tenantId = auditLog.getTenantId().getId(); - } - if (auditLog.getEntityId() != null) { - this.entityType = auditLog.getEntityId().getEntityType(); - this.entityId = auditLog.getEntityId().getId(); - } - if (auditLog.getCustomerId() != null) { - this.customerId = auditLog.getCustomerId().getId(); - } - if (auditLog.getUserId() != null) { - this.userId = auditLog.getUserId().getId(); - } - this.entityName = auditLog.getEntityName(); - this.userName = auditLog.getUserName(); - this.actionType = auditLog.getActionType(); - this.actionData = auditLog.getActionData(); - this.actionStatus = auditLog.getActionStatus(); - this.actionFailureDetails = auditLog.getActionFailureDetails(); - } - - @Override - public AuditLog toData() { - AuditLog auditLog = new AuditLog(new AuditLogId(id)); - auditLog.setCreatedTime(UUIDs.unixTimestamp(id)); - if (tenantId != null) { - auditLog.setTenantId(new TenantId(tenantId)); - } - if (entityId != null && entityType != null) { - auditLog.setEntityId(EntityIdFactory.getByTypeAndUuid(entityType, entityId)); - } - if (customerId != null) { - auditLog.setCustomerId(new CustomerId(customerId)); - } - if (userId != null) { - auditLog.setUserId(new UserId(userId)); - } - auditLog.setEntityName(this.entityName); - auditLog.setUserName(this.userName); - auditLog.setActionType(this.actionType); - auditLog.setActionData(this.actionData); - auditLog.setActionStatus(this.actionStatus); - auditLog.setActionFailureDetails(this.actionFailureDetails); - return auditLog; - } -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/ComponentDescriptorEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/ComponentDescriptorEntity.java deleted file mode 100644 index 99a4292f5b..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/ComponentDescriptorEntity.java +++ /dev/null @@ -1,171 +0,0 @@ -/** - * Copyright © 2016-2019 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.model.nosql; - -import com.datastax.driver.mapping.annotations.Column; -import com.datastax.driver.mapping.annotations.PartitionKey; -import com.datastax.driver.mapping.annotations.Table; -import com.fasterxml.jackson.databind.JsonNode; -import org.thingsboard.server.common.data.id.ComponentDescriptorId; -import org.thingsboard.server.common.data.plugin.ComponentDescriptor; -import org.thingsboard.server.common.data.plugin.ComponentScope; -import org.thingsboard.server.common.data.plugin.ComponentType; -import org.thingsboard.server.dao.model.SearchTextEntity; -import org.thingsboard.server.dao.model.type.JsonCodec; - -import java.util.UUID; - -import static org.thingsboard.server.dao.model.ModelConstants.COMPONENT_DESCRIPTOR_ACTIONS_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.COMPONENT_DESCRIPTOR_CLASS_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.COMPONENT_DESCRIPTOR_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.COMPONENT_DESCRIPTOR_CONFIGURATION_DESCRIPTOR_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.COMPONENT_DESCRIPTOR_NAME_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.COMPONENT_DESCRIPTOR_SCOPE_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.COMPONENT_DESCRIPTOR_TYPE_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY; - -/** - * @author Andrew Shvayka - */ -@Table(name = COMPONENT_DESCRIPTOR_COLUMN_FAMILY_NAME) -public class ComponentDescriptorEntity implements SearchTextEntity { - - @PartitionKey - @Column(name = ID_PROPERTY) - private UUID id; - - @Column(name = COMPONENT_DESCRIPTOR_TYPE_PROPERTY) - private ComponentType type; - - @Column(name = COMPONENT_DESCRIPTOR_SCOPE_PROPERTY) - private ComponentScope scope; - - @Column(name = COMPONENT_DESCRIPTOR_NAME_PROPERTY) - private String name; - - @Column(name = COMPONENT_DESCRIPTOR_CLASS_PROPERTY) - private String clazz; - - @Column(name = COMPONENT_DESCRIPTOR_CONFIGURATION_DESCRIPTOR_PROPERTY, codec = JsonCodec.class) - private JsonNode configurationDescriptor; - - @Column(name = COMPONENT_DESCRIPTOR_ACTIONS_PROPERTY) - private String actions; - - @Column(name = SEARCH_TEXT_PROPERTY) - private String searchText; - - public ComponentDescriptorEntity() { - } - - public ComponentDescriptorEntity(ComponentDescriptor component) { - if (component.getId() != null) { - this.id = component.getId().getId(); - } - this.actions = component.getActions(); - this.type = component.getType(); - this.scope = component.getScope(); - this.name = component.getName(); - this.clazz = component.getClazz(); - this.configurationDescriptor = component.getConfigurationDescriptor(); - this.searchText = component.getName(); - } - - @Override - public ComponentDescriptor toData() { - ComponentDescriptor data = new ComponentDescriptor(new ComponentDescriptorId(id)); - data.setType(type); - data.setScope(scope); - data.setName(this.getName()); - data.setClazz(this.getClazz()); - data.setActions(this.getActions()); - data.setConfigurationDescriptor(this.getConfigurationDescriptor()); - return data; - } - - @Override - public UUID getId() { - return id; - } - - @Override - public void setId(UUID id) { - this.id = id; - } - - public String getActions() { - return actions; - } - - public void setActions(String actions) { - this.actions = actions; - } - - public ComponentType getType() { - return type; - } - - public void setType(ComponentType type) { - this.type = type; - } - - public ComponentScope getScope() { - return scope; - } - - public void setScope(ComponentScope scope) { - this.scope = scope; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getClazz() { - return clazz; - } - - public void setClazz(String clazz) { - this.clazz = clazz; - } - - public JsonNode getConfigurationDescriptor() { - return configurationDescriptor; - } - - public void setConfigurationDescriptor(JsonNode configurationDescriptor) { - this.configurationDescriptor = configurationDescriptor; - } - - public String getSearchText() { - return searchText; - } - - @Override - public void setSearchText(String searchText) { - this.searchText = searchText; - } - - @Override - public String getSearchTextSource() { - return getSearchText(); - } -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/CustomerEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/CustomerEntity.java deleted file mode 100644 index 53a3c77d80..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/CustomerEntity.java +++ /dev/null @@ -1,243 +0,0 @@ -/** - * Copyright © 2016-2019 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.model.nosql; - -import com.datastax.driver.core.utils.UUIDs; -import com.datastax.driver.mapping.annotations.Column; -import com.datastax.driver.mapping.annotations.PartitionKey; -import com.datastax.driver.mapping.annotations.Table; -import com.fasterxml.jackson.databind.JsonNode; -import lombok.EqualsAndHashCode; -import lombok.ToString; -import org.thingsboard.server.common.data.Customer; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.dao.model.SearchTextEntity; -import org.thingsboard.server.dao.model.type.JsonCodec; - -import java.util.UUID; - -import static org.thingsboard.server.dao.model.ModelConstants.ADDRESS2_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ADDRESS_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.CITY_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.COUNTRY_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.CUSTOMER_ADDITIONAL_INFO_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.CUSTOMER_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.CUSTOMER_TENANT_ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.CUSTOMER_TITLE_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.EMAIL_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.PHONE_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.STATE_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ZIP_PROPERTY; - -@Table(name = CUSTOMER_COLUMN_FAMILY_NAME) -@EqualsAndHashCode -@ToString -public final class CustomerEntity implements SearchTextEntity { - - @PartitionKey(value = 0) - @Column(name = ID_PROPERTY) - private UUID id; - - @PartitionKey(value = 1) - @Column(name = CUSTOMER_TENANT_ID_PROPERTY) - private UUID tenantId; - - @Column(name = CUSTOMER_TITLE_PROPERTY) - private String title; - - @Column(name = SEARCH_TEXT_PROPERTY) - private String searchText; - - @Column(name = COUNTRY_PROPERTY) - private String country; - - @Column(name = STATE_PROPERTY) - private String state; - - @Column(name = CITY_PROPERTY) - private String city; - - @Column(name = ADDRESS_PROPERTY) - private String address; - - @Column(name = ADDRESS2_PROPERTY) - private String address2; - - @Column(name = ZIP_PROPERTY) - private String zip; - - @Column(name = PHONE_PROPERTY) - private String phone; - - @Column(name = EMAIL_PROPERTY) - private String email; - - @Column(name = CUSTOMER_ADDITIONAL_INFO_PROPERTY, codec = JsonCodec.class) - private JsonNode additionalInfo; - - public CustomerEntity() { - super(); - } - - public CustomerEntity(Customer customer) { - if (customer.getId() != null) { - this.id = customer.getId().getId(); - } - this.tenantId = customer.getTenantId().getId(); - this.title = customer.getTitle(); - this.country = customer.getCountry(); - this.state = customer.getState(); - this.city = customer.getCity(); - this.address = customer.getAddress(); - this.address2 = customer.getAddress2(); - this.zip = customer.getZip(); - this.phone = customer.getPhone(); - this.email = customer.getEmail(); - this.additionalInfo = customer.getAdditionalInfo(); - } - - public UUID getId() { - return id; - } - - public void setId(UUID id) { - this.id = id; - } - - public UUID getTenantId() { - return tenantId; - } - - public void setTenantId(UUID tenantId) { - this.tenantId = tenantId; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getCountry() { - return country; - } - - public void setCountry(String country) { - this.country = country; - } - - public String getState() { - return state; - } - - public void setState(String state) { - this.state = state; - } - - public String getCity() { - return city; - } - - public void setCity(String city) { - this.city = city; - } - - public String getAddress() { - return address; - } - - public void setAddress(String address) { - this.address = address; - } - - public String getAddress2() { - return address2; - } - - public void setAddress2(String address2) { - this.address2 = address2; - } - - public String getZip() { - return zip; - } - - public void setZip(String zip) { - this.zip = zip; - } - - public String getPhone() { - return phone; - } - - public void setPhone(String phone) { - this.phone = phone; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - public JsonNode getAdditionalInfo() { - return additionalInfo; - } - - public void setAdditionalInfo(JsonNode additionalInfo) { - this.additionalInfo = additionalInfo; - } - - @Override - public String getSearchTextSource() { - return getTitle(); - } - - @Override - public void setSearchText(String searchText) { - this.searchText = searchText; - } - - public String getSearchText() { - return searchText; - } - - @Override - public Customer toData() { - Customer customer = new Customer(new CustomerId(id)); - customer.setCreatedTime(UUIDs.unixTimestamp(id)); - customer.setTenantId(new TenantId(tenantId)); - customer.setTitle(title); - customer.setCountry(country); - customer.setState(state); - customer.setCity(city); - customer.setAddress(address); - customer.setAddress2(address2); - customer.setZip(zip); - customer.setPhone(phone); - customer.setEmail(email); - customer.setAdditionalInfo(additionalInfo); - return customer; - } - -} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardEntity.java deleted file mode 100644 index a40fbb7c30..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardEntity.java +++ /dev/null @@ -1,174 +0,0 @@ -/** - * Copyright © 2016-2019 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.model.nosql; - -import com.datastax.driver.core.utils.UUIDs; -import com.datastax.driver.mapping.annotations.Column; -import com.datastax.driver.mapping.annotations.PartitionKey; -import com.datastax.driver.mapping.annotations.Table; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JavaType; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.EqualsAndHashCode; -import lombok.ToString; -import lombok.extern.slf4j.Slf4j; -import org.springframework.util.StringUtils; -import org.thingsboard.server.common.data.Dashboard; -import org.thingsboard.server.common.data.ShortCustomerInfo; -import org.thingsboard.server.common.data.id.DashboardId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.dao.model.SearchTextEntity; -import org.thingsboard.server.dao.model.type.JsonCodec; - -import java.io.IOException; -import java.util.HashSet; -import java.util.UUID; - -import static org.thingsboard.server.dao.model.ModelConstants.DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.DASHBOARD_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.DASHBOARD_CONFIGURATION_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.DASHBOARD_TENANT_ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.DASHBOARD_TITLE_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY; - -@Table(name = DASHBOARD_COLUMN_FAMILY_NAME) -@EqualsAndHashCode -@ToString -@Slf4j -public final class DashboardEntity implements SearchTextEntity { - - private static final ObjectMapper objectMapper = new ObjectMapper(); - private static final JavaType assignedCustomersType = - objectMapper.getTypeFactory().constructCollectionType(HashSet.class, ShortCustomerInfo.class); - - @PartitionKey(value = 0) - @Column(name = ID_PROPERTY) - private UUID id; - - @PartitionKey(value = 1) - @Column(name = DASHBOARD_TENANT_ID_PROPERTY) - private UUID tenantId; - - @Column(name = DASHBOARD_TITLE_PROPERTY) - private String title; - - @Column(name = SEARCH_TEXT_PROPERTY) - private String searchText; - - @Column(name = DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY) - private String assignedCustomers; - - @Column(name = DASHBOARD_CONFIGURATION_PROPERTY, codec = JsonCodec.class) - private JsonNode configuration; - - public DashboardEntity() { - super(); - } - - public DashboardEntity(Dashboard dashboard) { - if (dashboard.getId() != null) { - this.id = dashboard.getId().getId(); - } - if (dashboard.getTenantId() != null) { - this.tenantId = dashboard.getTenantId().getId(); - } - this.title = dashboard.getTitle(); - if (dashboard.getAssignedCustomers() != null) { - try { - this.assignedCustomers = objectMapper.writeValueAsString(dashboard.getAssignedCustomers()); - } catch (JsonProcessingException e) { - log.error("Unable to serialize assigned customers to string!", e); - } - } - this.configuration = dashboard.getConfiguration(); - } - - public UUID getId() { - return id; - } - - public void setId(UUID id) { - this.id = id; - } - - public UUID getTenantId() { - return tenantId; - } - - public void setTenantId(UUID tenantId) { - this.tenantId = tenantId; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getAssignedCustomers() { - return assignedCustomers; - } - - public void setAssignedCustomers(String assignedCustomers) { - this.assignedCustomers = assignedCustomers; - } - - public JsonNode getConfiguration() { - return configuration; - } - - public void setConfiguration(JsonNode configuration) { - this.configuration = configuration; - } - - @Override - public String getSearchTextSource() { - return getTitle(); - } - - @Override - public void setSearchText(String searchText) { - this.searchText = searchText; - } - - public String getSearchText() { - return searchText; - } - - @Override - public Dashboard toData() { - Dashboard dashboard = new Dashboard(new DashboardId(id)); - dashboard.setCreatedTime(UUIDs.unixTimestamp(id)); - if (tenantId != null) { - dashboard.setTenantId(new TenantId(tenantId)); - } - dashboard.setTitle(title); - if (!StringUtils.isEmpty(assignedCustomers)) { - try { - dashboard.setAssignedCustomers(objectMapper.readValue(assignedCustomers, assignedCustomersType)); - } catch (IOException e) { - log.warn("Unable to parse assigned customers!", e); - } - } - dashboard.setConfiguration(configuration); - return dashboard; - } - -} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardInfoEntity.java deleted file mode 100644 index 25bc752b3a..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardInfoEntity.java +++ /dev/null @@ -1,158 +0,0 @@ -/** - * Copyright © 2016-2019 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.model.nosql; - -import com.datastax.driver.core.utils.UUIDs; -import com.datastax.driver.mapping.annotations.Column; -import com.datastax.driver.mapping.annotations.PartitionKey; -import com.datastax.driver.mapping.annotations.Table; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JavaType; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.EqualsAndHashCode; -import lombok.ToString; -import lombok.extern.slf4j.Slf4j; -import org.springframework.util.StringUtils; -import org.thingsboard.server.common.data.DashboardInfo; -import org.thingsboard.server.common.data.ShortCustomerInfo; -import org.thingsboard.server.common.data.id.DashboardId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.dao.model.SearchTextEntity; - -import java.io.IOException; -import java.util.HashSet; -import java.util.UUID; - -import static org.thingsboard.server.dao.model.ModelConstants.DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.DASHBOARD_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.DASHBOARD_TENANT_ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.DASHBOARD_TITLE_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY; - -@Table(name = DASHBOARD_COLUMN_FAMILY_NAME) -@EqualsAndHashCode -@ToString -@Slf4j -public class DashboardInfoEntity implements SearchTextEntity { - - private static final ObjectMapper objectMapper = new ObjectMapper(); - private static final JavaType assignedCustomersType = - objectMapper.getTypeFactory().constructCollectionType(HashSet.class, ShortCustomerInfo.class); - - @PartitionKey(value = 0) - @Column(name = ID_PROPERTY) - private UUID id; - - @PartitionKey(value = 1) - @Column(name = DASHBOARD_TENANT_ID_PROPERTY) - private UUID tenantId; - - @Column(name = DASHBOARD_TITLE_PROPERTY) - private String title; - - @Column(name = SEARCH_TEXT_PROPERTY) - private String searchText; - - @Column(name = DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY) - private String assignedCustomers; - - public DashboardInfoEntity() { - super(); - } - - public DashboardInfoEntity(DashboardInfo dashboardInfo) { - if (dashboardInfo.getId() != null) { - this.id = dashboardInfo.getId().getId(); - } - if (dashboardInfo.getTenantId() != null) { - this.tenantId = dashboardInfo.getTenantId().getId(); - } - this.title = dashboardInfo.getTitle(); - if (dashboardInfo.getAssignedCustomers() != null) { - try { - this.assignedCustomers = objectMapper.writeValueAsString(dashboardInfo.getAssignedCustomers()); - } catch (JsonProcessingException e) { - log.error("Unable to serialize assigned customers to string!", e); - } - } - } - - public UUID getId() { - return id; - } - - public void setId(UUID id) { - this.id = id; - } - - public UUID getTenantId() { - return tenantId; - } - - public void setTenantId(UUID tenantId) { - this.tenantId = tenantId; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getAssignedCustomers() { - return assignedCustomers; - } - - public void setAssignedCustomers(String assignedCustomers) { - this.assignedCustomers = assignedCustomers; - } - - @Override - public String getSearchTextSource() { - return getTitle(); - } - - @Override - public void setSearchText(String searchText) { - this.searchText = searchText; - } - - public String getSearchText() { - return searchText; - } - - @Override - public DashboardInfo toData() { - DashboardInfo dashboardInfo = new DashboardInfo(new DashboardId(id)); - dashboardInfo.setCreatedTime(UUIDs.unixTimestamp(id)); - if (tenantId != null) { - dashboardInfo.setTenantId(new TenantId(tenantId)); - } - dashboardInfo.setTitle(title); - if (!StringUtils.isEmpty(assignedCustomers)) { - try { - dashboardInfo.setAssignedCustomers(objectMapper.readValue(assignedCustomers, assignedCustomersType)); - } catch (IOException e) { - log.warn("Unable to parse assigned customers!", e); - } - } - return dashboardInfo; - } - -} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DeviceCredentialsEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DeviceCredentialsEntity.java deleted file mode 100644 index 783d7a5812..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DeviceCredentialsEntity.java +++ /dev/null @@ -1,130 +0,0 @@ -/** - * Copyright © 2016-2019 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.model.nosql; - -import com.datastax.driver.core.utils.UUIDs; -import com.datastax.driver.mapping.annotations.Column; -import com.datastax.driver.mapping.annotations.PartitionKey; -import com.datastax.driver.mapping.annotations.Table; -import lombok.EqualsAndHashCode; -import lombok.ToString; -import org.thingsboard.server.common.data.id.DeviceCredentialsId; -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.model.BaseEntity; -import org.thingsboard.server.dao.model.type.DeviceCredentialsTypeCodec; - -import java.util.UUID; - -import static org.thingsboard.server.dao.model.ModelConstants.DEVICE_CREDENTIALS_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.DEVICE_CREDENTIALS_CREDENTIALS_ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.DEVICE_CREDENTIALS_CREDENTIALS_TYPE_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.DEVICE_CREDENTIALS_CREDENTIALS_VALUE_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.DEVICE_CREDENTIALS_DEVICE_ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY; - -@Table(name = DEVICE_CREDENTIALS_COLUMN_FAMILY_NAME) -@EqualsAndHashCode -@ToString -public final class DeviceCredentialsEntity implements BaseEntity { - - @PartitionKey(value = 0) - @Column(name = ID_PROPERTY) - private UUID id; - - @Column(name = DEVICE_CREDENTIALS_DEVICE_ID_PROPERTY) - private UUID deviceId; - - @Column(name = DEVICE_CREDENTIALS_CREDENTIALS_TYPE_PROPERTY, codec = DeviceCredentialsTypeCodec.class) - private DeviceCredentialsType credentialsType; - - @Column(name = DEVICE_CREDENTIALS_CREDENTIALS_ID_PROPERTY) - private String credentialsId; - - @Column(name = DEVICE_CREDENTIALS_CREDENTIALS_VALUE_PROPERTY) - private String credentialsValue; - - public DeviceCredentialsEntity() { - super(); - } - - public DeviceCredentialsEntity(DeviceCredentials deviceCredentials) { - if (deviceCredentials.getId() != null) { - this.id = deviceCredentials.getId().getId(); - } - if (deviceCredentials.getDeviceId() != null) { - this.deviceId = deviceCredentials.getDeviceId().getId(); - } - this.credentialsType = deviceCredentials.getCredentialsType(); - this.credentialsId = deviceCredentials.getCredentialsId(); - this.credentialsValue = deviceCredentials.getCredentialsValue(); - } - - public UUID getId() { - return id; - } - - public void setId(UUID id) { - this.id = id; - } - - public UUID getDeviceId() { - return deviceId; - } - - public void setDeviceId(UUID deviceId) { - this.deviceId = deviceId; - } - - public DeviceCredentialsType getCredentialsType() { - return credentialsType; - } - - public void setCredentialsType(DeviceCredentialsType credentialsType) { - this.credentialsType = credentialsType; - } - - public String getCredentialsId() { - return credentialsId; - } - - public void setCredentialsId(String credentialsId) { - this.credentialsId = credentialsId; - } - - public String getCredentialsValue() { - return credentialsValue; - } - - public void setCredentialsValue(String credentialsValue) { - this.credentialsValue = credentialsValue; - } - - @Override - public DeviceCredentials toData() { - DeviceCredentials deviceCredentials = new DeviceCredentials(new DeviceCredentialsId(id)); - deviceCredentials.setCreatedTime(UUIDs.unixTimestamp(id)); - if (deviceId != null) { - deviceCredentials.setDeviceId(new DeviceId(deviceId)); - } - deviceCredentials.setCredentialsType(credentialsType); - deviceCredentials.setCredentialsId(credentialsId); - deviceCredentials.setCredentialsValue(credentialsValue); - return deviceCredentials; - } - -} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DeviceEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DeviceEntity.java deleted file mode 100644 index 2c44cf3d87..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DeviceEntity.java +++ /dev/null @@ -1,168 +0,0 @@ -/** - * Copyright © 2016-2019 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.model.nosql; - -import com.datastax.driver.core.utils.UUIDs; -import com.datastax.driver.mapping.annotations.Column; -import com.datastax.driver.mapping.annotations.PartitionKey; -import com.datastax.driver.mapping.annotations.Table; -import com.fasterxml.jackson.databind.JsonNode; -import lombok.EqualsAndHashCode; -import lombok.ToString; -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.dao.model.SearchTextEntity; -import org.thingsboard.server.dao.model.type.JsonCodec; - -import java.util.UUID; - -import static org.thingsboard.server.dao.model.ModelConstants.*; - -@Table(name = DEVICE_COLUMN_FAMILY_NAME) -@EqualsAndHashCode -@ToString -public final class DeviceEntity implements SearchTextEntity { - - @PartitionKey(value = 0) - @Column(name = ID_PROPERTY) - private UUID id; - - @PartitionKey(value = 1) - @Column(name = DEVICE_TENANT_ID_PROPERTY) - private UUID tenantId; - - @PartitionKey(value = 2) - @Column(name = DEVICE_CUSTOMER_ID_PROPERTY) - private UUID customerId; - - @PartitionKey(value = 3) - @Column(name = DEVICE_TYPE_PROPERTY) - private String type; - - @Column(name = DEVICE_NAME_PROPERTY) - private String name; - - @Column(name = DEVICE_LABEL_PROPERTY) - private String label; - - @Column(name = SEARCH_TEXT_PROPERTY) - private String searchText; - - @Column(name = DEVICE_ADDITIONAL_INFO_PROPERTY, codec = JsonCodec.class) - private JsonNode additionalInfo; - - public DeviceEntity() { - super(); - } - - public DeviceEntity(Device device) { - if (device.getId() != null) { - this.id = device.getId().getId(); - } - if (device.getTenantId() != null) { - this.tenantId = device.getTenantId().getId(); - } - if (device.getCustomerId() != null) { - this.customerId = device.getCustomerId().getId(); - } - this.name = device.getName(); - this.type = device.getType(); - this.label = device.getLabel(); - this.additionalInfo = device.getAdditionalInfo(); - } - - public UUID getId() { - return id; - } - - public void setId(UUID id) { - this.id = id; - } - - public UUID getTenantId() { - return tenantId; - } - - public void setTenantId(UUID tenantId) { - this.tenantId = tenantId; - } - - public UUID getCustomerId() { - return customerId; - } - - public void setCustomerId(UUID customerId) { - this.customerId = customerId; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public JsonNode getAdditionalInfo() { - return additionalInfo; - } - - public void setAdditionalInfo(JsonNode additionalInfo) { - this.additionalInfo = additionalInfo; - } - - @Override - public String getSearchTextSource() { - return getName(); - } - - @Override - public void setSearchText(String searchText) { - this.searchText = searchText; - } - - public String getSearchText() { - return searchText; - } - - @Override - public Device toData() { - Device device = new Device(new DeviceId(id)); - device.setCreatedTime(UUIDs.unixTimestamp(id)); - if (tenantId != null) { - device.setTenantId(new TenantId(tenantId)); - } - if (customerId != null) { - device.setCustomerId(new CustomerId(customerId)); - } - device.setName(name); - device.setType(type); - device.setLabel(label); - device.setAdditionalInfo(additionalInfo); - return device; - } - -} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/EntityViewEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/EntityViewEntity.java deleted file mode 100644 index f5cebe906a..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/EntityViewEntity.java +++ /dev/null @@ -1,164 +0,0 @@ -/** - * Copyright © 2016-2019 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.model.nosql; - -import com.datastax.driver.core.utils.UUIDs; -import com.datastax.driver.mapping.annotations.Column; -import com.datastax.driver.mapping.annotations.PartitionKey; -import com.datastax.driver.mapping.annotations.Table; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; -import lombok.extern.slf4j.Slf4j; -import org.hibernate.annotations.Type; -import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.EntityView; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.EntityIdFactory; -import org.thingsboard.server.common.data.id.EntityViewId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.objects.TelemetryEntityView; -import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.model.SearchTextEntity; - -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import java.io.IOException; -import java.util.UUID; - -import static org.thingsboard.server.dao.model.ModelConstants.DEVICE_TYPE_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_TYPE_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_VIEW_TABLE_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY; - -/** - * Created by Victor Basanets on 8/31/2017. - */ -@Data -@Table(name = ENTITY_VIEW_TABLE_FAMILY_NAME) -@EqualsAndHashCode -@ToString -@Slf4j -public class EntityViewEntity implements SearchTextEntity { - - @PartitionKey(value = 0) - @Column(name = ID_PROPERTY) - private UUID id; - - @PartitionKey(value = 1) - @Column(name = ModelConstants.ENTITY_VIEW_TENANT_ID_PROPERTY) - private UUID tenantId; - - @PartitionKey(value = 2) - @Column(name = ModelConstants.ENTITY_VIEW_CUSTOMER_ID_PROPERTY) - private UUID customerId; - - @PartitionKey(value = 3) - @Column(name = DEVICE_TYPE_PROPERTY) - private String type; - - @Enumerated(EnumType.STRING) - @Column(name = ENTITY_TYPE_PROPERTY) - private EntityType entityType; - - @Column(name = ModelConstants.ENTITY_VIEW_ENTITY_ID_PROPERTY) - private UUID entityId; - - @Column(name = ModelConstants.ENTITY_VIEW_NAME_PROPERTY) - private String name; - - @Column(name = ModelConstants.ENTITY_VIEW_KEYS_PROPERTY) - private String keys; - - @Column(name = ModelConstants.ENTITY_VIEW_START_TS_PROPERTY) - private long startTs; - - @Column(name = ModelConstants.ENTITY_VIEW_END_TS_PROPERTY) - private long endTs; - - @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY) - private String searchText; - - @Type(type = "json") - @Column(name = ModelConstants.ENTITY_VIEW_ADDITIONAL_INFO_PROPERTY) - private JsonNode additionalInfo; - - private static final ObjectMapper mapper = new ObjectMapper(); - - public EntityViewEntity() { - super(); - } - - public EntityViewEntity(EntityView entityView) { - if (entityView.getId() != null) { - this.id = entityView.getId().getId(); - } - if (entityView.getEntityId() != null) { - this.entityId = entityView.getEntityId().getId(); - this.entityType = entityView.getEntityId().getEntityType(); - } - if (entityView.getTenantId() != null) { - this.tenantId = entityView.getTenantId().getId(); - } - if (entityView.getCustomerId() != null) { - this.customerId = entityView.getCustomerId().getId(); - } - this.type = entityView.getType(); - this.name = entityView.getName(); - try { - this.keys = mapper.writeValueAsString(entityView.getKeys()); - } catch (IOException e) { - log.error("Unable to serialize entity view keys!", e); - } - this.startTs = entityView.getStartTimeMs(); - this.endTs = entityView.getEndTimeMs(); - this.searchText = entityView.getSearchText(); - this.additionalInfo = entityView.getAdditionalInfo(); - } - - @Override - public String getSearchTextSource() { - return name; - } - - @Override - public EntityView toData() { - EntityView entityView = new EntityView(new EntityViewId(id)); - entityView.setCreatedTime(UUIDs.unixTimestamp(id)); - if (entityId != null) { - entityView.setEntityId(EntityIdFactory.getByTypeAndId(entityType.name(), entityId.toString())); - } - if (tenantId != null) { - entityView.setTenantId(new TenantId(tenantId)); - } - if (customerId != null) { - entityView.setCustomerId(new CustomerId(customerId)); - } - entityView.setType(type); - entityView.setName(name); - try { - entityView.setKeys(mapper.readValue(keys, TelemetryEntityView.class)); - } catch (IOException e) { - log.error("Unable to read entity view keys!", e); - } - entityView.setStartTimeMs(startTs); - entityView.setEndTimeMs(endTs); - entityView.setAdditionalInfo(additionalInfo); - return entityView; - } -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/EventEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/EventEntity.java deleted file mode 100644 index 1cef88a0d4..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/EventEntity.java +++ /dev/null @@ -1,117 +0,0 @@ -/** - * Copyright © 2016-2019 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.model.nosql; - -import com.datastax.driver.core.utils.UUIDs; -import com.datastax.driver.mapping.annotations.ClusteringColumn; -import com.datastax.driver.mapping.annotations.Column; -import com.datastax.driver.mapping.annotations.PartitionKey; -import com.datastax.driver.mapping.annotations.Table; -import com.fasterxml.jackson.databind.JsonNode; -import lombok.Data; -import lombok.NoArgsConstructor; -import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.Event; -import org.thingsboard.server.common.data.id.EntityIdFactory; -import org.thingsboard.server.common.data.id.EventId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.dao.model.BaseEntity; -import org.thingsboard.server.dao.model.type.EntityTypeCodec; -import org.thingsboard.server.dao.model.type.JsonCodec; - -import java.util.UUID; - -import static org.thingsboard.server.dao.model.ModelConstants.EVENT_BODY_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.EVENT_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.EVENT_ENTITY_ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.EVENT_ENTITY_TYPE_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.EVENT_TENANT_ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.EVENT_TYPE_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.EVENT_UID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY; - -/** - * @author Andrew Shvayka - */ -@Data -@NoArgsConstructor -@Table(name = EVENT_COLUMN_FAMILY_NAME) -public class EventEntity implements BaseEntity { - - @Column(name = ID_PROPERTY) - private UUID id; - - @PartitionKey() - @Column(name = EVENT_TENANT_ID_PROPERTY) - private UUID tenantId; - - @PartitionKey(value = 1) - @Column(name = EVENT_ENTITY_TYPE_PROPERTY, codec = EntityTypeCodec.class) - private EntityType entityType; - - @PartitionKey(value = 2) - @Column(name = EVENT_ENTITY_ID_PROPERTY) - private UUID entityId; - - @ClusteringColumn() - @Column(name = EVENT_TYPE_PROPERTY) - private String eventType; - - @ClusteringColumn(value = 1) - @Column(name = EVENT_UID_PROPERTY) - private String eventUid; - - @Column(name = EVENT_BODY_PROPERTY, codec = JsonCodec.class) - private JsonNode body; - - public EventEntity(Event event) { - if (event.getId() != null) { - this.id = event.getId().getId(); - } - if (event.getTenantId() != null) { - this.tenantId = event.getTenantId().getId(); - } - if (event.getEntityId() != null) { - this.entityType = event.getEntityId().getEntityType(); - this.entityId = event.getEntityId().getId(); - } - this.eventType = event.getType(); - this.eventUid = event.getUid(); - this.body = event.getBody(); - } - - @Override - public UUID getId() { - return id; - } - - @Override - public void setId(UUID id) { - this.id = id; - } - - @Override - public Event toData() { - Event event = new Event(new EventId(id)); - event.setCreatedTime(UUIDs.unixTimestamp(id)); - event.setTenantId(new TenantId(tenantId)); - event.setEntityId(EntityIdFactory.getByTypeAndUuid(entityType, entityId)); - event.setBody(body); - event.setType(eventType); - event.setUid(eventUid); - return event; - } -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/RuleChainEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/RuleChainEntity.java deleted file mode 100644 index c4ead363c2..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/RuleChainEntity.java +++ /dev/null @@ -1,182 +0,0 @@ -/** - * Copyright © 2016-2019 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.model.nosql; - -import com.datastax.driver.core.utils.UUIDs; -import com.datastax.driver.mapping.annotations.ClusteringColumn; -import com.datastax.driver.mapping.annotations.Column; -import com.datastax.driver.mapping.annotations.PartitionKey; -import com.datastax.driver.mapping.annotations.Table; -import com.fasterxml.jackson.databind.JsonNode; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; -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.rule.RuleChain; -import org.thingsboard.server.dao.DaoUtil; -import org.thingsboard.server.dao.model.SearchTextEntity; -import org.thingsboard.server.dao.model.type.JsonCodec; - -import java.util.UUID; - -import static org.thingsboard.server.dao.model.ModelConstants.ADDITIONAL_INFO_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.DEBUG_MODE; -import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.RULE_CHAIN_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.RULE_CHAIN_CONFIGURATION_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.RULE_CHAIN_FIRST_RULE_NODE_ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.RULE_CHAIN_NAME_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.RULE_CHAIN_ROOT_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.RULE_CHAIN_TENANT_ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY; - -@Table(name = RULE_CHAIN_COLUMN_FAMILY_NAME) -@EqualsAndHashCode -@ToString -public class RuleChainEntity implements SearchTextEntity { - - @PartitionKey - @Column(name = ID_PROPERTY) - private UUID id; - @ClusteringColumn - @Column(name = RULE_CHAIN_TENANT_ID_PROPERTY) - private UUID tenantId; - @Column(name = RULE_CHAIN_NAME_PROPERTY) - private String name; - @Column(name = SEARCH_TEXT_PROPERTY) - private String searchText; - @Column(name = RULE_CHAIN_FIRST_RULE_NODE_ID_PROPERTY) - private UUID firstRuleNodeId; - @Column(name = RULE_CHAIN_ROOT_PROPERTY) - private boolean root; - @Getter - @Setter - @Column(name = DEBUG_MODE) - private boolean debugMode; - @Column(name = RULE_CHAIN_CONFIGURATION_PROPERTY, codec = JsonCodec.class) - private JsonNode configuration; - @Column(name = ADDITIONAL_INFO_PROPERTY, codec = JsonCodec.class) - private JsonNode additionalInfo; - - public RuleChainEntity() { - } - - public RuleChainEntity(RuleChain ruleChain) { - if (ruleChain.getId() != null) { - this.id = ruleChain.getUuidId(); - } - this.tenantId = DaoUtil.getId(ruleChain.getTenantId()); - this.name = ruleChain.getName(); - this.searchText = ruleChain.getName(); - this.firstRuleNodeId = DaoUtil.getId(ruleChain.getFirstRuleNodeId()); - this.root = ruleChain.isRoot(); - this.debugMode = ruleChain.isDebugMode(); - this.configuration = ruleChain.getConfiguration(); - this.additionalInfo = ruleChain.getAdditionalInfo(); - } - - @Override - public String getSearchTextSource() { - return getSearchText(); - } - - @Override - public void setSearchText(String searchText) { - this.searchText = searchText; - } - - @Override - public UUID getId() { - return id; - } - - @Override - public void setId(UUID id) { - this.id = id; - } - - public UUID getTenantId() { - return tenantId; - } - - public void setTenantId(UUID tenantId) { - this.tenantId = tenantId; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public UUID getFirstRuleNodeId() { - return firstRuleNodeId; - } - - public void setFirstRuleNodeId(UUID firstRuleNodeId) { - this.firstRuleNodeId = firstRuleNodeId; - } - - public boolean isRoot() { - return root; - } - - public void setRoot(boolean root) { - this.root = root; - } - - public String getSearchText() { - return searchText; - } - - public JsonNode getConfiguration() { - return configuration; - } - - public void setConfiguration(JsonNode configuration) { - this.configuration = configuration; - } - - public JsonNode getAdditionalInfo() { - return additionalInfo; - } - - public void setAdditionalInfo(JsonNode additionalInfo) { - this.additionalInfo = additionalInfo; - } - - @Override - public RuleChain toData() { - RuleChain ruleChain = new RuleChain(new RuleChainId(id)); - ruleChain.setCreatedTime(UUIDs.unixTimestamp(id)); - ruleChain.setTenantId(new TenantId(tenantId)); - ruleChain.setName(name); - if (this.firstRuleNodeId != null) { - ruleChain.setFirstRuleNodeId(new RuleNodeId(this.firstRuleNodeId)); - } - ruleChain.setRoot(this.root); - ruleChain.setDebugMode(this.debugMode); - ruleChain.setConfiguration(this.configuration); - ruleChain.setAdditionalInfo(this.additionalInfo); - return ruleChain; - } - -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/RuleNodeEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/RuleNodeEntity.java deleted file mode 100644 index ae53b0c9e6..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/RuleNodeEntity.java +++ /dev/null @@ -1,167 +0,0 @@ -/** - * Copyright © 2016-2019 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.model.nosql; - -import com.datastax.driver.core.utils.UUIDs; -import com.datastax.driver.mapping.annotations.Column; -import com.datastax.driver.mapping.annotations.PartitionKey; -import com.datastax.driver.mapping.annotations.Table; -import com.fasterxml.jackson.databind.JsonNode; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; -import org.thingsboard.server.common.data.id.RuleChainId; -import org.thingsboard.server.common.data.id.RuleNodeId; -import org.thingsboard.server.common.data.rule.RuleNode; -import org.thingsboard.server.dao.model.SearchTextEntity; -import org.thingsboard.server.dao.model.type.JsonCodec; - -import java.util.UUID; - -import static org.thingsboard.server.dao.model.ModelConstants.ADDITIONAL_INFO_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.DEBUG_MODE; -import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.RULE_NODE_CHAIN_ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.RULE_NODE_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.RULE_NODE_CONFIGURATION_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.RULE_NODE_NAME_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.RULE_NODE_TYPE_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY; - -@Table(name = RULE_NODE_COLUMN_FAMILY_NAME) -@EqualsAndHashCode -@ToString -public class RuleNodeEntity implements SearchTextEntity { - - @PartitionKey - @Column(name = ID_PROPERTY) - private UUID id; - @Column(name = RULE_NODE_CHAIN_ID_PROPERTY) - private UUID ruleChainId; - @Column(name = RULE_NODE_TYPE_PROPERTY) - private String type; - @Column(name = RULE_NODE_NAME_PROPERTY) - private String name; - @Column(name = SEARCH_TEXT_PROPERTY) - private String searchText; - @Column(name = RULE_NODE_CONFIGURATION_PROPERTY, codec = JsonCodec.class) - private JsonNode configuration; - @Column(name = ADDITIONAL_INFO_PROPERTY, codec = JsonCodec.class) - private JsonNode additionalInfo; - @Getter - @Setter - @Column(name = DEBUG_MODE) - private boolean debugMode; - - public RuleNodeEntity() { - } - - public RuleNodeEntity(RuleNode ruleNode) { - if (ruleNode.getId() != null) { - this.id = ruleNode.getUuidId(); - } - if (ruleNode.getRuleChainId() != null) { - this.ruleChainId = ruleNode.getRuleChainId().getId(); - } - this.type = ruleNode.getType(); - this.name = ruleNode.getName(); - this.debugMode = ruleNode.isDebugMode(); - this.searchText = ruleNode.getName(); - this.configuration = ruleNode.getConfiguration(); - this.additionalInfo = ruleNode.getAdditionalInfo(); - } - - @Override - public String getSearchTextSource() { - return getSearchText(); - } - - @Override - public void setSearchText(String searchText) { - this.searchText = searchText; - } - - @Override - public UUID getId() { - return id; - } - - @Override - public void setId(UUID id) { - this.id = id; - } - - public UUID getRuleChainId() { - return ruleChainId; - } - - public void setRuleChainId(UUID ruleChainId) { - this.ruleChainId = ruleChainId; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getSearchText() { - return searchText; - } - - public JsonNode getConfiguration() { - return configuration; - } - - public void setConfiguration(JsonNode configuration) { - this.configuration = configuration; - } - - public JsonNode getAdditionalInfo() { - return additionalInfo; - } - - public void setAdditionalInfo(JsonNode additionalInfo) { - this.additionalInfo = additionalInfo; - } - - @Override - public RuleNode toData() { - RuleNode ruleNode = new RuleNode(new RuleNodeId(id)); - ruleNode.setCreatedTime(UUIDs.unixTimestamp(id)); - if (this.ruleChainId != null) { - ruleNode.setRuleChainId(new RuleChainId(this.ruleChainId)); - } - ruleNode.setType(this.type); - ruleNode.setName(this.name); - ruleNode.setDebugMode(this.debugMode); - ruleNode.setConfiguration(this.configuration); - ruleNode.setAdditionalInfo(this.additionalInfo); - return ruleNode; - } - -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/TenantEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/TenantEntity.java deleted file mode 100644 index f038828255..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/TenantEntity.java +++ /dev/null @@ -1,242 +0,0 @@ -/** - * Copyright © 2016-2019 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.model.nosql; - -import com.datastax.driver.core.utils.UUIDs; -import com.datastax.driver.mapping.annotations.Column; -import com.datastax.driver.mapping.annotations.PartitionKey; -import com.datastax.driver.mapping.annotations.Table; -import com.fasterxml.jackson.databind.JsonNode; -import lombok.EqualsAndHashCode; -import lombok.ToString; -import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.dao.model.SearchTextEntity; -import org.thingsboard.server.dao.model.type.JsonCodec; - -import java.util.UUID; - -import static org.thingsboard.server.dao.model.ModelConstants.ADDRESS2_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ADDRESS_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.CITY_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.COUNTRY_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.EMAIL_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.PHONE_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.STATE_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.TENANT_ADDITIONAL_INFO_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.TENANT_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.TENANT_REGION_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.TENANT_TITLE_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ZIP_PROPERTY; - -@Table(name = TENANT_COLUMN_FAMILY_NAME) -@EqualsAndHashCode -@ToString -public final class TenantEntity implements SearchTextEntity { - - @PartitionKey(value = 0) - @Column(name = ID_PROPERTY) - private UUID id; - - @Column(name = TENANT_TITLE_PROPERTY) - private String title; - - @Column(name = SEARCH_TEXT_PROPERTY) - private String searchText; - - @Column(name = TENANT_REGION_PROPERTY) - private String region; - - @Column(name = COUNTRY_PROPERTY) - private String country; - - @Column(name = STATE_PROPERTY) - private String state; - - @Column(name = CITY_PROPERTY) - private String city; - - @Column(name = ADDRESS_PROPERTY) - private String address; - - @Column(name = ADDRESS2_PROPERTY) - private String address2; - - @Column(name = ZIP_PROPERTY) - private String zip; - - @Column(name = PHONE_PROPERTY) - private String phone; - - @Column(name = EMAIL_PROPERTY) - private String email; - - @Column(name = TENANT_ADDITIONAL_INFO_PROPERTY, codec = JsonCodec.class) - private JsonNode additionalInfo; - - public TenantEntity() { - super(); - } - - public TenantEntity(Tenant tenant) { - if (tenant.getId() != null) { - this.id = tenant.getId().getId(); - } - this.title = tenant.getTitle(); - this.region = tenant.getRegion(); - this.country = tenant.getCountry(); - this.state = tenant.getState(); - this.city = tenant.getCity(); - this.address = tenant.getAddress(); - this.address2 = tenant.getAddress2(); - this.zip = tenant.getZip(); - this.phone = tenant.getPhone(); - this.email = tenant.getEmail(); - this.additionalInfo = tenant.getAdditionalInfo(); - } - - public UUID getId() { - return id; - } - - public void setId(UUID id) { - this.id = id; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getRegion() { - return region; - } - - public void setRegion(String region) { - this.region = region; - } - - public String getCountry() { - return country; - } - - public void setCountry(String country) { - this.country = country; - } - - public String getState() { - return state; - } - - public void setState(String state) { - this.state = state; - } - - public String getCity() { - return city; - } - - public void setCity(String city) { - this.city = city; - } - - public String getAddress() { - return address; - } - - public void setAddress(String address) { - this.address = address; - } - - public String getAddress2() { - return address2; - } - - public void setAddress2(String address2) { - this.address2 = address2; - } - - public String getZip() { - return zip; - } - - public void setZip(String zip) { - this.zip = zip; - } - - public String getPhone() { - return phone; - } - - public void setPhone(String phone) { - this.phone = phone; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - public JsonNode getAdditionalInfo() { - return additionalInfo; - } - - public void setAdditionalInfo(JsonNode additionalInfo) { - this.additionalInfo = additionalInfo; - } - - @Override - public String getSearchTextSource() { - return getTitle(); - } - - @Override - public void setSearchText(String searchText) { - this.searchText = searchText; - } - - public String getSearchText() { - return searchText; - } - - @Override - public Tenant toData() { - Tenant tenant = new Tenant(new TenantId(id)); - tenant.setCreatedTime(UUIDs.unixTimestamp(id)); - tenant.setTitle(title); - tenant.setRegion(region); - tenant.setCountry(country); - tenant.setState(state); - tenant.setCity(city); - tenant.setAddress(address); - tenant.setAddress2(address2); - tenant.setZip(zip); - tenant.setPhone(phone); - tenant.setEmail(email); - tenant.setAdditionalInfo(additionalInfo); - return tenant; - } - - -} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/UserCredentialsEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/UserCredentialsEntity.java deleted file mode 100644 index cbdbf23d6b..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/UserCredentialsEntity.java +++ /dev/null @@ -1,140 +0,0 @@ -/** - * Copyright © 2016-2019 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.model.nosql; - -import com.datastax.driver.core.utils.UUIDs; -import com.datastax.driver.mapping.annotations.Column; -import com.datastax.driver.mapping.annotations.PartitionKey; -import com.datastax.driver.mapping.annotations.Table; -import lombok.EqualsAndHashCode; -import org.thingsboard.server.common.data.id.UserCredentialsId; -import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.common.data.security.UserCredentials; -import org.thingsboard.server.dao.model.BaseEntity; - -import java.util.UUID; - -import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.USER_CREDENTIALS_ACTIVATE_TOKEN_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.USER_CREDENTIALS_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.USER_CREDENTIALS_ENABLED_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.USER_CREDENTIALS_PASSWORD_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.USER_CREDENTIALS_RESET_TOKEN_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.USER_CREDENTIALS_USER_ID_PROPERTY; - -@Table(name = USER_CREDENTIALS_COLUMN_FAMILY_NAME) -@EqualsAndHashCode -public final class UserCredentialsEntity implements BaseEntity { - - @PartitionKey(value = 0) - @Column(name = ID_PROPERTY) - private UUID id; - - @Column(name = USER_CREDENTIALS_USER_ID_PROPERTY) - private UUID userId; - - @Column(name = USER_CREDENTIALS_ENABLED_PROPERTY) - private boolean enabled; - - @Column(name = USER_CREDENTIALS_PASSWORD_PROPERTY) - private String password; - - @Column(name = USER_CREDENTIALS_ACTIVATE_TOKEN_PROPERTY) - private String activateToken; - - @Column(name = USER_CREDENTIALS_RESET_TOKEN_PROPERTY) - private String resetToken; - - public UserCredentialsEntity() { - super(); - } - - public UserCredentialsEntity(UserCredentials userCredentials) { - if (userCredentials.getId() != null) { - this.id = userCredentials.getId().getId(); - } - if (userCredentials.getUserId() != null) { - this.userId = userCredentials.getUserId().getId(); - } - this.enabled = userCredentials.isEnabled(); - this.password = userCredentials.getPassword(); - this.activateToken = userCredentials.getActivateToken(); - this.resetToken = userCredentials.getResetToken(); - } - - public UUID getId() { - return id; - } - - public void setId(UUID id) { - this.id = id; - } - - public UUID getUserId() { - return userId; - } - - public void setUserId(UUID userId) { - this.userId = userId; - } - - public boolean isEnabled() { - return enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - - public String getActivateToken() { - return activateToken; - } - - public void setActivateToken(String activateToken) { - this.activateToken = activateToken; - } - - public String getResetToken() { - return resetToken; - } - - public void setResetToken(String resetToken) { - this.resetToken = resetToken; - } - - @Override - public UserCredentials toData() { - UserCredentials userCredentials = new UserCredentials(new UserCredentialsId(id)); - userCredentials.setCreatedTime(UUIDs.unixTimestamp(id)); - if (userId != null) { - userCredentials.setUserId(new UserId(userId)); - } - userCredentials.setEnabled(enabled); - userCredentials.setPassword(password); - userCredentials.setActivateToken(activateToken); - userCredentials.setResetToken(resetToken); - return userCredentials; - } - -} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/UserEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/UserEntity.java deleted file mode 100644 index 187d9115ac..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/UserEntity.java +++ /dev/null @@ -1,200 +0,0 @@ -/** - * Copyright © 2016-2019 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.model.nosql; - -import com.datastax.driver.core.utils.UUIDs; -import com.datastax.driver.mapping.annotations.Column; -import com.datastax.driver.mapping.annotations.PartitionKey; -import com.datastax.driver.mapping.annotations.Table; -import com.fasterxml.jackson.databind.JsonNode; -import lombok.EqualsAndHashCode; -import lombok.ToString; -import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.common.data.security.Authority; -import org.thingsboard.server.dao.model.SearchTextEntity; -import org.thingsboard.server.dao.model.type.AuthorityCodec; -import org.thingsboard.server.dao.model.type.JsonCodec; - -import java.util.UUID; - -import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.USER_ADDITIONAL_INFO_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.USER_AUTHORITY_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.USER_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.USER_CUSTOMER_ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.USER_EMAIL_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.USER_FIRST_NAME_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.USER_LAST_NAME_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.USER_TENANT_ID_PROPERTY; - -@Table(name = USER_COLUMN_FAMILY_NAME) -@EqualsAndHashCode -@ToString -public final class UserEntity implements SearchTextEntity { - - @PartitionKey(value = 0) - @Column(name = ID_PROPERTY) - private UUID id; - - @PartitionKey(value = 1) - @Column(name = USER_TENANT_ID_PROPERTY) - private UUID tenantId; - - @PartitionKey(value = 2) - @Column(name = USER_CUSTOMER_ID_PROPERTY) - private UUID customerId; - - @PartitionKey(value = 3) - @Column(name = USER_AUTHORITY_PROPERTY, codec = AuthorityCodec.class) - private Authority authority; - - @Column(name = USER_EMAIL_PROPERTY) - private String email; - - @Column(name = SEARCH_TEXT_PROPERTY) - private String searchText; - - @Column(name = USER_FIRST_NAME_PROPERTY) - private String firstName; - - @Column(name = USER_LAST_NAME_PROPERTY) - private String lastName; - - @Column(name = USER_ADDITIONAL_INFO_PROPERTY, codec = JsonCodec.class) - private JsonNode additionalInfo; - - public UserEntity() { - super(); - } - - public UserEntity(User user) { - if (user.getId() != null) { - this.id = user.getId().getId(); - } - this.authority = user.getAuthority(); - if (user.getTenantId() != null) { - this.tenantId = user.getTenantId().getId(); - } - if (user.getCustomerId() != null) { - this.customerId = user.getCustomerId().getId(); - } - this.email = user.getEmail(); - this.firstName = user.getFirstName(); - this.lastName = user.getLastName(); - this.additionalInfo = user.getAdditionalInfo(); - } - - public UUID getId() { - return id; - } - - public void setId(UUID id) { - this.id = id; - } - - public Authority getAuthority() { - return authority; - } - - public void setAuthority(Authority authority) { - this.authority = authority; - } - - public UUID getTenantId() { - return tenantId; - } - - public void setTenantId(UUID tenantId) { - this.tenantId = tenantId; - } - - public UUID getCustomerId() { - return customerId; - } - - public void setCustomerId(UUID customerId) { - this.customerId = customerId; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - public String getFirstName() { - return firstName; - } - - public void setFirstName(String firstName) { - this.firstName = firstName; - } - - public String getLastName() { - return lastName; - } - - public void setLastName(String lastName) { - this.lastName = lastName; - } - - public JsonNode getAdditionalInfo() { - return additionalInfo; - } - - public void setAdditionalInfo(JsonNode additionalInfo) { - this.additionalInfo = additionalInfo; - } - - @Override - public String getSearchTextSource() { - return getEmail(); - } - - @Override - public void setSearchText(String searchText) { - this.searchText = searchText; - } - - public String getSearchText() { - return searchText; - } - - @Override - public User toData() { - User user = new User(new UserId(id)); - user.setCreatedTime(UUIDs.unixTimestamp(id)); - user.setAuthority(authority); - if (tenantId != null) { - user.setTenantId(new TenantId(tenantId)); - } - if (customerId != null) { - user.setCustomerId(new CustomerId(customerId)); - } - user.setEmail(email); - user.setFirstName(firstName); - user.setLastName(lastName); - user.setAdditionalInfo(additionalInfo); - return user; - } - -} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/WidgetTypeEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/WidgetTypeEntity.java deleted file mode 100644 index cd68dc6625..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/WidgetTypeEntity.java +++ /dev/null @@ -1,148 +0,0 @@ -/** - * Copyright © 2016-2019 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.model.nosql; - -import com.datastax.driver.core.utils.UUIDs; -import com.datastax.driver.mapping.annotations.Column; -import com.datastax.driver.mapping.annotations.PartitionKey; -import com.datastax.driver.mapping.annotations.Table; -import com.fasterxml.jackson.databind.JsonNode; -import lombok.EqualsAndHashCode; -import lombok.ToString; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.id.WidgetTypeId; -import org.thingsboard.server.common.data.widget.WidgetType; -import org.thingsboard.server.dao.model.BaseEntity; -import org.thingsboard.server.dao.model.type.JsonCodec; - -import java.util.UUID; - -import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.WIDGET_TYPE_ALIAS_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.WIDGET_TYPE_BUNDLE_ALIAS_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.WIDGET_TYPE_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.WIDGET_TYPE_DESCRIPTOR_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.WIDGET_TYPE_NAME_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.WIDGET_TYPE_TENANT_ID_PROPERTY; - -@Table(name = WIDGET_TYPE_COLUMN_FAMILY_NAME) -@EqualsAndHashCode -@ToString -public final class WidgetTypeEntity implements BaseEntity { - - @PartitionKey(value = 0) - @Column(name = ID_PROPERTY) - private UUID id; - - @PartitionKey(value = 1) - @Column(name = WIDGET_TYPE_TENANT_ID_PROPERTY) - private UUID tenantId; - - @PartitionKey(value = 2) - @Column(name = WIDGET_TYPE_BUNDLE_ALIAS_PROPERTY) - private String bundleAlias; - - @Column(name = WIDGET_TYPE_ALIAS_PROPERTY) - private String alias; - - @Column(name = WIDGET_TYPE_NAME_PROPERTY) - private String name; - - @Column(name = WIDGET_TYPE_DESCRIPTOR_PROPERTY, codec = JsonCodec.class) - private JsonNode descriptor; - - public WidgetTypeEntity() { - super(); - } - - public WidgetTypeEntity(WidgetType widgetType) { - if (widgetType.getId() != null) { - this.id = widgetType.getId().getId(); - } - if (widgetType.getTenantId() != null) { - this.tenantId = widgetType.getTenantId().getId(); - } - this.bundleAlias = widgetType.getBundleAlias(); - this.alias = widgetType.getAlias(); - this.name = widgetType.getName(); - this.descriptor = widgetType.getDescriptor(); - } - - @Override - public UUID getId() { - return id; - } - - @Override - public void setId(UUID id) { - this.id = id; - } - - public UUID getTenantId() { - return tenantId; - } - - public void setTenantId(UUID tenantId) { - this.tenantId = tenantId; - } - - public String getBundleAlias() { - return bundleAlias; - } - - public void setBundleAlias(String bundleAlias) { - this.bundleAlias = bundleAlias; - } - - public String getAlias() { - return alias; - } - - public void setAlias(String alias) { - this.alias = alias; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public JsonNode getDescriptor() { - return descriptor; - } - - public void setDescriptor(JsonNode descriptor) { - this.descriptor = descriptor; - } - - @Override - public WidgetType toData() { - WidgetType widgetType = new WidgetType(new WidgetTypeId(id)); - widgetType.setCreatedTime(UUIDs.unixTimestamp(id)); - if (tenantId != null) { - widgetType.setTenantId(new TenantId(tenantId)); - } - widgetType.setBundleAlias(bundleAlias); - widgetType.setAlias(alias); - widgetType.setName(name); - widgetType.setDescriptor(descriptor); - return widgetType; - } - -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/WidgetsBundleEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/WidgetsBundleEntity.java deleted file mode 100644 index 187ed63fb4..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/WidgetsBundleEntity.java +++ /dev/null @@ -1,156 +0,0 @@ -/** - * Copyright © 2016-2019 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.model.nosql; - - -import com.datastax.driver.core.utils.UUIDs; -import com.datastax.driver.mapping.annotations.Column; -import com.datastax.driver.mapping.annotations.PartitionKey; -import com.datastax.driver.mapping.annotations.Table; -import lombok.EqualsAndHashCode; -import lombok.ToString; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.id.WidgetsBundleId; -import org.thingsboard.server.common.data.widget.WidgetsBundle; -import org.thingsboard.server.dao.model.SearchTextEntity; - -import java.nio.ByteBuffer; -import java.util.UUID; - -import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.WIDGETS_BUNDLE_ALIAS_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.WIDGETS_BUNDLE_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.WIDGETS_BUNDLE_IMAGE_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.WIDGETS_BUNDLE_TENANT_ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.WIDGETS_BUNDLE_TITLE_PROPERTY; - -@Table(name = WIDGETS_BUNDLE_COLUMN_FAMILY_NAME) -@EqualsAndHashCode -@ToString -public final class WidgetsBundleEntity implements SearchTextEntity { - - @PartitionKey(value = 0) - @Column(name = ID_PROPERTY) - private UUID id; - - @PartitionKey(value = 1) - @Column(name = WIDGETS_BUNDLE_TENANT_ID_PROPERTY) - private UUID tenantId; - - @Column(name = WIDGETS_BUNDLE_ALIAS_PROPERTY) - private String alias; - - @Column(name = WIDGETS_BUNDLE_TITLE_PROPERTY) - private String title; - - @Column(name = SEARCH_TEXT_PROPERTY) - private String searchText; - - @Column(name = WIDGETS_BUNDLE_IMAGE_PROPERTY) - private ByteBuffer image; - - public WidgetsBundleEntity() { - super(); - } - - public WidgetsBundleEntity(WidgetsBundle widgetsBundle) { - if (widgetsBundle.getId() != null) { - this.id = widgetsBundle.getId().getId(); - } - if (widgetsBundle.getTenantId() != null) { - this.tenantId = widgetsBundle.getTenantId().getId(); - } - this.alias = widgetsBundle.getAlias(); - this.title = widgetsBundle.getTitle(); - if (widgetsBundle.getImage() != null) { - this.image = ByteBuffer.wrap(widgetsBundle.getImage()); - } - } - - @Override - public UUID getId() { - return id; - } - - @Override - public void setId(UUID id) { - this.id = id; - } - - public UUID getTenantId() { - return tenantId; - } - - public void setTenantId(UUID tenantId) { - this.tenantId = tenantId; - } - - public String getAlias() { - return alias; - } - - public void setAlias(String alias) { - this.alias = alias; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public ByteBuffer getImage() { - return image; - } - - public void setImage(ByteBuffer image) { - this.image = image; - } - - @Override - public String getSearchTextSource() { - return getTitle(); - } - - @Override - public void setSearchText(String searchText) { - this.searchText = searchText; - } - - public String getSearchText() { - return searchText; - } - - @Override - public WidgetsBundle toData() { - WidgetsBundle widgetsBundle = new WidgetsBundle(new WidgetsBundleId(id)); - widgetsBundle.setCreatedTime(UUIDs.unixTimestamp(id)); - if (tenantId != null) { - widgetsBundle.setTenantId(new TenantId(tenantId)); - } - widgetsBundle.setAlias(alias); - widgetsBundle.setTitle(title); - if (image != null) { - byte[] imageByteArray = new byte[image.remaining()]; - image.get(imageByteArray); - widgetsBundle.setImage(imageByteArray); - } - return widgetsBundle; - } -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/type/ActionStatusCodec.java b/dao/src/main/java/org/thingsboard/server/dao/model/type/ActionStatusCodec.java deleted file mode 100644 index 741e799f4a..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/model/type/ActionStatusCodec.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright © 2016-2019 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.model.type; - -import com.datastax.driver.extras.codecs.enums.EnumNameCodec; -import org.thingsboard.server.common.data.audit.ActionStatus; - -public class ActionStatusCodec extends EnumNameCodec { - - public ActionStatusCodec() { - super(ActionStatus.class); - } -} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/type/ActionTypeCodec.java b/dao/src/main/java/org/thingsboard/server/dao/model/type/ActionTypeCodec.java deleted file mode 100644 index 7e75ddabc0..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/model/type/ActionTypeCodec.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright © 2016-2019 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.model.type; - -import com.datastax.driver.extras.codecs.enums.EnumNameCodec; -import org.thingsboard.server.common.data.audit.ActionType; - -public class ActionTypeCodec extends EnumNameCodec { - - public ActionTypeCodec() { - super(ActionType.class); - } -} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/type/AlarmSeverityCodec.java b/dao/src/main/java/org/thingsboard/server/dao/model/type/AlarmSeverityCodec.java deleted file mode 100644 index 307d1af734..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/model/type/AlarmSeverityCodec.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright © 2016-2019 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.model.type; - -import com.datastax.driver.extras.codecs.enums.EnumNameCodec; -import org.thingsboard.server.common.data.alarm.AlarmSeverity; - -public class AlarmSeverityCodec extends EnumNameCodec { - - public AlarmSeverityCodec() { - super(AlarmSeverity.class); - } - -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/type/AlarmStatusCodec.java b/dao/src/main/java/org/thingsboard/server/dao/model/type/AlarmStatusCodec.java deleted file mode 100644 index d395d8644f..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/model/type/AlarmStatusCodec.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright © 2016-2019 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.model.type; - -import com.datastax.driver.extras.codecs.enums.EnumNameCodec; -import org.thingsboard.server.common.data.alarm.AlarmStatus; - -public class AlarmStatusCodec extends EnumNameCodec { - - public AlarmStatusCodec() { - super(AlarmStatus.class); - } - -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/type/AuthorityCodec.java b/dao/src/main/java/org/thingsboard/server/dao/model/type/AuthorityCodec.java deleted file mode 100644 index 42b8e02b04..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/model/type/AuthorityCodec.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright © 2016-2019 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.model.type; - -import com.datastax.driver.extras.codecs.enums.EnumNameCodec; -import org.thingsboard.server.common.data.security.Authority; - -public class AuthorityCodec extends EnumNameCodec { - - public AuthorityCodec() { - super(Authority.class); - } - -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/type/ComponentScopeCodec.java b/dao/src/main/java/org/thingsboard/server/dao/model/type/ComponentScopeCodec.java deleted file mode 100644 index f27aca2a8c..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/model/type/ComponentScopeCodec.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright © 2016-2019 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.model.type; - -import com.datastax.driver.extras.codecs.enums.EnumNameCodec; -import org.thingsboard.server.common.data.plugin.ComponentScope; - -public class ComponentScopeCodec extends EnumNameCodec { - - public ComponentScopeCodec() { - super(ComponentScope.class); - } - -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/type/ComponentTypeCodec.java b/dao/src/main/java/org/thingsboard/server/dao/model/type/ComponentTypeCodec.java deleted file mode 100644 index ab9a30e6ae..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/model/type/ComponentTypeCodec.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright © 2016-2019 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.model.type; - -import com.datastax.driver.extras.codecs.enums.EnumNameCodec; -import org.thingsboard.server.common.data.plugin.ComponentType; - -public class ComponentTypeCodec extends EnumNameCodec { - - public ComponentTypeCodec() { - super(ComponentType.class); - } - -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/type/DeviceCredentialsTypeCodec.java b/dao/src/main/java/org/thingsboard/server/dao/model/type/DeviceCredentialsTypeCodec.java deleted file mode 100644 index fb184daa26..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/model/type/DeviceCredentialsTypeCodec.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright © 2016-2019 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.model.type; - -import com.datastax.driver.extras.codecs.enums.EnumNameCodec; -import org.thingsboard.server.common.data.security.DeviceCredentialsType; - -public class DeviceCredentialsTypeCodec extends EnumNameCodec { - - public DeviceCredentialsTypeCodec() { - super(DeviceCredentialsType.class); - } - -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/type/RelationTypeGroupCodec.java b/dao/src/main/java/org/thingsboard/server/dao/model/type/RelationTypeGroupCodec.java deleted file mode 100644 index 5b54e0eaa0..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/model/type/RelationTypeGroupCodec.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright © 2016-2019 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.model.type; - -import com.datastax.driver.extras.codecs.enums.EnumNameCodec; -import org.thingsboard.server.common.data.relation.RelationTypeGroup; - -public class RelationTypeGroupCodec extends EnumNameCodec { - - public RelationTypeGroupCodec() { - super(RelationTypeGroup.class); - } - -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractDao.java b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractDao.java index 09d4e53928..1a190de724 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractDao.java @@ -15,26 +15,14 @@ */ package org.thingsboard.server.dao.nosql; -import com.datastax.driver.core.BoundStatement; -import com.datastax.driver.core.CodecRegistry; -import com.datastax.driver.core.ConsistencyLevel; -import com.datastax.driver.core.PreparedStatement; -import com.datastax.driver.core.ResultSet; -import com.datastax.driver.core.ResultSetFuture; -import com.datastax.driver.core.Session; -import com.datastax.driver.core.Statement; -import com.datastax.driver.core.TypeCodec; +import com.datastax.driver.core.*; import com.datastax.driver.core.exceptions.CodecNotFoundException; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.cassandra.CassandraCluster; -import org.thingsboard.server.dao.model.type.AuthorityCodec; import org.thingsboard.server.dao.model.type.ComponentLifecycleStateCodec; -import org.thingsboard.server.dao.model.type.ComponentScopeCodec; -import org.thingsboard.server.dao.model.type.ComponentTypeCodec; -import org.thingsboard.server.dao.model.type.DeviceCredentialsTypeCodec; import org.thingsboard.server.dao.model.type.EntityTypeCodec; import org.thingsboard.server.dao.model.type.JsonCodec; @@ -65,11 +53,7 @@ public abstract class CassandraAbstractDao { defaultWriteLevel = cluster.getDefaultWriteConsistencyLevel(); CodecRegistry registry = session.getCluster().getConfiguration().getCodecRegistry(); registerCodecIfNotFound(registry, new JsonCodec()); - registerCodecIfNotFound(registry, new DeviceCredentialsTypeCodec()); - registerCodecIfNotFound(registry, new AuthorityCodec()); registerCodecIfNotFound(registry, new ComponentLifecycleStateCodec()); - registerCodecIfNotFound(registry, new ComponentTypeCodec()); - registerCodecIfNotFound(registry, new ComponentScopeCodec()); registerCodecIfNotFound(registry, new EntityTypeCodec()); } return session; diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationDao.java b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationDao.java deleted file mode 100644 index d3eae63aba..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationDao.java +++ /dev/null @@ -1,381 +0,0 @@ -/** - * Copyright © 2016-2019 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.relation; - -import com.datastax.driver.core.BoundStatement; -import com.datastax.driver.core.PreparedStatement; -import com.datastax.driver.core.ResultSet; -import com.datastax.driver.core.ResultSetFuture; -import com.datastax.driver.core.Row; -import com.datastax.driver.core.querybuilder.QueryBuilder; -import com.datastax.driver.core.querybuilder.Select; -import com.fasterxml.jackson.databind.JsonNode; -import com.google.common.util.concurrent.ListenableFuture; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -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.id.TenantId; -import org.thingsboard.server.common.data.page.TimePageLink; -import org.thingsboard.server.common.data.relation.EntityRelation; -import org.thingsboard.server.common.data.relation.RelationTypeGroup; -import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.model.type.RelationTypeGroupCodec; -import org.thingsboard.server.dao.nosql.CassandraAbstractAsyncDao; -import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTimeDao; -import org.thingsboard.server.dao.util.NoSqlDao; - -import javax.annotation.PostConstruct; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; - -/** - * Created by ashvayka on 25.04.17. - */ -@Component -@Slf4j -@NoSqlDao -public class BaseRelationDao extends CassandraAbstractAsyncDao implements RelationDao { - - private static final String SELECT_COLUMNS = "SELECT " + - ModelConstants.RELATION_FROM_ID_PROPERTY + "," + - ModelConstants.RELATION_FROM_TYPE_PROPERTY + "," + - ModelConstants.RELATION_TO_ID_PROPERTY + "," + - ModelConstants.RELATION_TO_TYPE_PROPERTY + "," + - ModelConstants.RELATION_TYPE_GROUP_PROPERTY + "," + - ModelConstants.RELATION_TYPE_PROPERTY + "," + - ModelConstants.ADDITIONAL_INFO_PROPERTY; - public static final String FROM = " FROM "; - public static final String WHERE = " WHERE "; - public static final String AND = " AND "; - - private static final RelationTypeGroupCodec relationTypeGroupCodec = new RelationTypeGroupCodec(); - public static final String EQUAL_TO_PARAM = " = ? "; - - private PreparedStatement saveStmt; - private PreparedStatement findAllByFromStmt; - private PreparedStatement findAllByFromAndTypeStmt; - private PreparedStatement findAllByToStmt; - private PreparedStatement findAllByToAndTypeStmt; - private PreparedStatement checkRelationStmt; - private PreparedStatement deleteStmt; - private PreparedStatement deleteAllByEntityStmt; - - @PostConstruct - public void init() { - super.startExecutor(); - } - - @Override - public ListenableFuture> findAllByFrom(TenantId tenantId, EntityId from, RelationTypeGroup typeGroup) { - BoundStatement stmt = getFindAllByFromStmt().bind() - .setUUID(0, from.getId()) - .setString(1, from.getEntityType().name()) - .set(2, typeGroup, relationTypeGroupCodec); - return executeAsyncRead(tenantId, from, stmt); - } - - @Override - public ListenableFuture> findAllByFromAndType(TenantId tenantId, EntityId from, String relationType, RelationTypeGroup typeGroup) { - BoundStatement stmt = getFindAllByFromAndTypeStmt().bind() - .setUUID(0, from.getId()) - .setString(1, from.getEntityType().name()) - .set(2, typeGroup, relationTypeGroupCodec) - .setString(3, relationType); - return executeAsyncRead(tenantId, from, stmt); - } - - @Override - public ListenableFuture> findAllByTo(TenantId tenantId, EntityId to, RelationTypeGroup typeGroup) { - BoundStatement stmt = getFindAllByToStmt().bind() - .setUUID(0, to.getId()) - .setString(1, to.getEntityType().name()) - .set(2, typeGroup, relationTypeGroupCodec); - return executeAsyncRead(tenantId, to, stmt); - } - - @Override - public ListenableFuture> findAllByToAndType(TenantId tenantId, EntityId to, String relationType, RelationTypeGroup typeGroup) { - BoundStatement stmt = getFindAllByToAndTypeStmt().bind() - .setUUID(0, to.getId()) - .setString(1, to.getEntityType().name()) - .set(2, typeGroup, relationTypeGroupCodec) - .setString(3, relationType); - return executeAsyncRead(tenantId, to, stmt); - } - - @Override - public ListenableFuture checkRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) { - BoundStatement stmt = getCheckRelationStmt().bind() - .setUUID(0, from.getId()) - .setString(1, from.getEntityType().name()) - .setUUID(2, to.getId()) - .setString(3, to.getEntityType().name()) - .set(4, typeGroup, relationTypeGroupCodec) - .setString(5, relationType); - return getFuture(executeAsyncRead(tenantId, stmt), rs -> rs != null ? rs.one() != null : false); - } - - @Override - public ListenableFuture getRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) { - BoundStatement stmt = getCheckRelationStmt().bind() - .setUUID(0, from.getId()) - .setString(1, from.getEntityType().name()) - .setUUID(2, to.getId()) - .setString(3, to.getEntityType().name()) - .set(4, typeGroup, relationTypeGroupCodec) - .setString(5, relationType); - return getFuture(executeAsyncRead(tenantId, stmt), rs -> rs != null ? getEntityRelation(rs.one()) : null); - } - - @Override - public boolean saveRelation(TenantId tenantId, EntityRelation relation) { - BoundStatement stmt = getSaveRelationStatement(tenantId, relation); - ResultSet rs = executeWrite(tenantId, stmt); - return rs.wasApplied(); - } - - @Override - public ListenableFuture saveRelationAsync(TenantId tenantId, EntityRelation relation) { - BoundStatement stmt = getSaveRelationStatement(tenantId, relation); - ResultSetFuture future = executeAsyncWrite(tenantId, stmt); - return getBooleanListenableFuture(future); - } - - private BoundStatement getSaveRelationStatement(TenantId tenantId, EntityRelation relation) { - BoundStatement stmt = getSaveStmt().bind() - .setUUID(0, relation.getFrom().getId()) - .setString(1, relation.getFrom().getEntityType().name()) - .setUUID(2, relation.getTo().getId()) - .setString(3, relation.getTo().getEntityType().name()) - .set(4, relation.getTypeGroup(), relationTypeGroupCodec) - .setString(5, relation.getType()) - .set(6, relation.getAdditionalInfo(), JsonNode.class); - return stmt; - } - - @Override - public boolean deleteRelation(TenantId tenantId, EntityRelation relation) { - return deleteRelation(tenantId, relation.getFrom(), relation.getTo(), relation.getType(), relation.getTypeGroup()); - } - - @Override - public ListenableFuture deleteRelationAsync(TenantId tenantId, EntityRelation relation) { - return deleteRelationAsync(tenantId, relation.getFrom(), relation.getTo(), relation.getType(), relation.getTypeGroup()); - } - - @Override - public boolean deleteRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) { - BoundStatement stmt = getDeleteRelationStatement(tenantId, from, to, relationType, typeGroup); - ResultSet rs = executeWrite(tenantId, stmt); - return rs.wasApplied(); - } - - @Override - public ListenableFuture deleteRelationAsync(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) { - BoundStatement stmt = getDeleteRelationStatement(tenantId, from, to, relationType, typeGroup); - ResultSetFuture future = executeAsyncWrite(tenantId, stmt); - return getBooleanListenableFuture(future); - } - - private BoundStatement getDeleteRelationStatement(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) { - BoundStatement stmt = getDeleteStmt().bind() - .setUUID(0, from.getId()) - .setString(1, from.getEntityType().name()) - .setUUID(2, to.getId()) - .setString(3, to.getEntityType().name()) - .set(4, typeGroup, relationTypeGroupCodec) - .setString(5, relationType); - return stmt; - } - - @Override - public boolean deleteOutboundRelations(TenantId tenantId, EntityId entity) { - BoundStatement stmt = getDeleteAllByEntityStmt().bind() - .setUUID(0, entity.getId()) - .setString(1, entity.getEntityType().name()); - ResultSet rs = executeWrite(tenantId, stmt); - return rs.wasApplied(); - } - - - @Override - public ListenableFuture deleteOutboundRelationsAsync(TenantId tenantId, EntityId entity) { - BoundStatement stmt = getDeleteAllByEntityStmt().bind() - .setUUID(0, entity.getId()) - .setString(1, entity.getEntityType().name()); - ResultSetFuture future = executeAsyncWrite(tenantId, stmt); - return getBooleanListenableFuture(future); - } - - @Override - public ListenableFuture> findRelations(TenantId tenantId, EntityId from, String relationType, RelationTypeGroup typeGroup, EntityType childType, TimePageLink pageLink) { - Select.Where query = CassandraAbstractSearchTimeDao.buildQuery(ModelConstants.RELATION_BY_TYPE_AND_CHILD_TYPE_VIEW_NAME, - Arrays.asList(eq(ModelConstants.RELATION_FROM_ID_PROPERTY, from.getId()), - eq(ModelConstants.RELATION_FROM_TYPE_PROPERTY, from.getEntityType().name()), - eq(ModelConstants.RELATION_TYPE_GROUP_PROPERTY, typeGroup.name()), - eq(ModelConstants.RELATION_TYPE_PROPERTY, relationType), - eq(ModelConstants.RELATION_TO_TYPE_PROPERTY, childType.name())), - Arrays.asList( - pageLink.isAscOrder() ? QueryBuilder.desc(ModelConstants.RELATION_TYPE_GROUP_PROPERTY) : - QueryBuilder.asc(ModelConstants.RELATION_TYPE_GROUP_PROPERTY), - pageLink.isAscOrder() ? QueryBuilder.desc(ModelConstants.RELATION_TYPE_PROPERTY) : - QueryBuilder.asc(ModelConstants.RELATION_TYPE_PROPERTY), - pageLink.isAscOrder() ? QueryBuilder.desc(ModelConstants.RELATION_TO_TYPE_PROPERTY) : - QueryBuilder.asc(ModelConstants.RELATION_TO_TYPE_PROPERTY) - ), - pageLink, ModelConstants.RELATION_TO_ID_PROPERTY); - return getFuture(executeAsyncRead(tenantId, query), this::getEntityRelations); - } - - private PreparedStatement getSaveStmt() { - if (saveStmt == null) { - saveStmt = prepare("INSERT INTO " + ModelConstants.RELATION_COLUMN_FAMILY_NAME + " " + - "(" + ModelConstants.RELATION_FROM_ID_PROPERTY + - "," + ModelConstants.RELATION_FROM_TYPE_PROPERTY + - "," + ModelConstants.RELATION_TO_ID_PROPERTY + - "," + ModelConstants.RELATION_TO_TYPE_PROPERTY + - "," + ModelConstants.RELATION_TYPE_GROUP_PROPERTY + - "," + ModelConstants.RELATION_TYPE_PROPERTY + - "," + ModelConstants.ADDITIONAL_INFO_PROPERTY + ")" + - " VALUES(?, ?, ?, ?, ?, ?, ?)"); - } - return saveStmt; - } - - private PreparedStatement getDeleteStmt() { - if (deleteStmt == null) { - deleteStmt = prepare("DELETE FROM " + ModelConstants.RELATION_COLUMN_FAMILY_NAME + - WHERE + ModelConstants.RELATION_FROM_ID_PROPERTY + " = ?" + - AND + ModelConstants.RELATION_FROM_TYPE_PROPERTY + " = ?" + - AND + ModelConstants.RELATION_TO_ID_PROPERTY + " = ?" + - AND + ModelConstants.RELATION_TO_TYPE_PROPERTY + " = ?" + - AND + ModelConstants.RELATION_TYPE_GROUP_PROPERTY + " = ?" + - AND + ModelConstants.RELATION_TYPE_PROPERTY + " = ?"); - } - return deleteStmt; - } - - private PreparedStatement getDeleteAllByEntityStmt() { - if (deleteAllByEntityStmt == null) { - deleteAllByEntityStmt = prepare("DELETE FROM " + ModelConstants.RELATION_COLUMN_FAMILY_NAME + - WHERE + ModelConstants.RELATION_FROM_ID_PROPERTY + " = ?" + - AND + ModelConstants.RELATION_FROM_TYPE_PROPERTY + " = ?"); - } - return deleteAllByEntityStmt; - } - - private PreparedStatement getFindAllByFromStmt() { - if (findAllByFromStmt == null) { - findAllByFromStmt = prepare(SELECT_COLUMNS + " " + - FROM + ModelConstants.RELATION_COLUMN_FAMILY_NAME + " " + - WHERE + ModelConstants.RELATION_FROM_ID_PROPERTY + EQUAL_TO_PARAM + - AND + ModelConstants.RELATION_FROM_TYPE_PROPERTY + EQUAL_TO_PARAM + - AND + ModelConstants.RELATION_TYPE_GROUP_PROPERTY + EQUAL_TO_PARAM); - } - return findAllByFromStmt; - } - - private PreparedStatement getFindAllByFromAndTypeStmt() { - if (findAllByFromAndTypeStmt == null) { - findAllByFromAndTypeStmt = prepare(SELECT_COLUMNS + " " + - FROM + ModelConstants.RELATION_COLUMN_FAMILY_NAME + " " + - WHERE + ModelConstants.RELATION_FROM_ID_PROPERTY + EQUAL_TO_PARAM + - AND + ModelConstants.RELATION_FROM_TYPE_PROPERTY + EQUAL_TO_PARAM + - AND + ModelConstants.RELATION_TYPE_GROUP_PROPERTY + EQUAL_TO_PARAM + - AND + ModelConstants.RELATION_TYPE_PROPERTY + EQUAL_TO_PARAM); - } - return findAllByFromAndTypeStmt; - } - - - private PreparedStatement getFindAllByToStmt() { - if (findAllByToStmt == null) { - findAllByToStmt = prepare(SELECT_COLUMNS + " " + - FROM + ModelConstants.RELATION_REVERSE_VIEW_NAME + " " + - WHERE + ModelConstants.RELATION_TO_ID_PROPERTY + EQUAL_TO_PARAM + - AND + ModelConstants.RELATION_TO_TYPE_PROPERTY + EQUAL_TO_PARAM + - AND + ModelConstants.RELATION_TYPE_GROUP_PROPERTY + EQUAL_TO_PARAM); - } - return findAllByToStmt; - } - - private PreparedStatement getFindAllByToAndTypeStmt() { - if (findAllByToAndTypeStmt == null) { - findAllByToAndTypeStmt = prepare(SELECT_COLUMNS + " " + - FROM + ModelConstants.RELATION_REVERSE_VIEW_NAME + " " + - WHERE + ModelConstants.RELATION_TO_ID_PROPERTY + EQUAL_TO_PARAM + - AND + ModelConstants.RELATION_TO_TYPE_PROPERTY + EQUAL_TO_PARAM + - AND + ModelConstants.RELATION_TYPE_GROUP_PROPERTY + EQUAL_TO_PARAM + - AND + ModelConstants.RELATION_TYPE_PROPERTY + EQUAL_TO_PARAM); - } - return findAllByToAndTypeStmt; - } - - - private PreparedStatement getCheckRelationStmt() { - if (checkRelationStmt == null) { - checkRelationStmt = prepare(SELECT_COLUMNS + " " + - FROM + ModelConstants.RELATION_COLUMN_FAMILY_NAME + " " + - WHERE + ModelConstants.RELATION_FROM_ID_PROPERTY + EQUAL_TO_PARAM + - AND + ModelConstants.RELATION_FROM_TYPE_PROPERTY + EQUAL_TO_PARAM + - AND + ModelConstants.RELATION_TO_ID_PROPERTY + EQUAL_TO_PARAM + - AND + ModelConstants.RELATION_TO_TYPE_PROPERTY + EQUAL_TO_PARAM + - AND + ModelConstants.RELATION_TYPE_GROUP_PROPERTY + EQUAL_TO_PARAM + - AND + ModelConstants.RELATION_TYPE_PROPERTY + EQUAL_TO_PARAM); - } - return checkRelationStmt; - } - - private EntityId toEntity(Row row, String uuidColumn, String typeColumn) { - return EntityIdFactory.getByTypeAndUuid(row.getString(typeColumn), row.getUUID(uuidColumn)); - } - - private ListenableFuture> executeAsyncRead(TenantId tenantId, EntityId from, BoundStatement stmt) { - log.debug("Generated query [{}] for entity {}", stmt, from); - return getFuture(executeAsyncRead(tenantId, stmt), rs -> getEntityRelations(rs)); - } - - private ListenableFuture getBooleanListenableFuture(ResultSetFuture rsFuture) { - return getFuture(rsFuture, rs -> rs != null ? rs.wasApplied() : false); - } - - private List getEntityRelations(ResultSet rs) { - List rows = rs.all(); - List entries = new ArrayList<>(rows.size()); - if (!rows.isEmpty()) { - rows.forEach(row -> { - entries.add(getEntityRelation(row)); - }); - } - return entries; - } - - private EntityRelation getEntityRelation(Row row) { - EntityRelation relation = new EntityRelation(); - relation.setTypeGroup(row.get(ModelConstants.RELATION_TYPE_GROUP_PROPERTY, relationTypeGroupCodec)); - relation.setType(row.getString(ModelConstants.RELATION_TYPE_PROPERTY)); - relation.setAdditionalInfo(row.get(ModelConstants.ADDITIONAL_INFO_PROPERTY, JsonNode.class)); - relation.setFrom(toEntity(row, ModelConstants.RELATION_FROM_ID_PROPERTY, ModelConstants.RELATION_FROM_TYPE_PROPERTY)); - relation.setTo(toEntity(row, ModelConstants.RELATION_TO_ID_PROPERTY, ModelConstants.RELATION_TO_TYPE_PROPERTY)); - return relation; - } - -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleChainDao.java b/dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleChainDao.java deleted file mode 100644 index 44c068d9cd..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleChainDao.java +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Copyright © 2016-2019 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.rule; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageLink; -import org.thingsboard.server.common.data.rule.RuleChain; -import org.thingsboard.server.dao.DaoUtil; -import org.thingsboard.server.dao.model.nosql.RuleChainEntity; -import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao; -import org.thingsboard.server.dao.util.NoSqlDao; - -import java.util.Collections; -import java.util.List; -import java.util.UUID; - -import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; -import static org.thingsboard.server.dao.model.ModelConstants.RULE_CHAIN_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.RULE_CHAIN_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.RULE_CHAIN_TENANT_ID_PROPERTY; - -@Component -@Slf4j -@NoSqlDao -public class CassandraRuleChainDao extends CassandraAbstractSearchTextDao implements RuleChainDao { - - @Override - protected Class getColumnFamilyClass() { - return RuleChainEntity.class; - } - - @Override - protected String getColumnFamilyName() { - return RULE_CHAIN_COLUMN_FAMILY_NAME; - } - - @Override - public List findRuleChainsByTenantId(UUID tenantId, TextPageLink pageLink) { - log.debug("Try to find rule chains by tenantId [{}] and pageLink [{}]", tenantId, pageLink); - List ruleChainEntities = findPageWithTextSearch(new TenantId(tenantId), RULE_CHAIN_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME, - Collections.singletonList(eq(RULE_CHAIN_TENANT_ID_PROPERTY, tenantId)), - pageLink); - - log.trace("Found rule chains [{}] by tenantId [{}] and pageLink [{}]", ruleChainEntities, tenantId, pageLink); - return DaoUtil.convertDataList(ruleChainEntities); - } - -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleNodeDao.java b/dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleNodeDao.java deleted file mode 100644 index 9eb42ab856..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/CassandraRuleNodeDao.java +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright © 2016-2019 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.rule; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.rule.RuleNode; -import org.thingsboard.server.dao.model.nosql.RuleNodeEntity; -import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao; -import org.thingsboard.server.dao.util.NoSqlDao; - -import static org.thingsboard.server.dao.model.ModelConstants.RULE_NODE_COLUMN_FAMILY_NAME; - -@Component -@Slf4j -@NoSqlDao -public class CassandraRuleNodeDao extends CassandraAbstractSearchTextDao implements RuleNodeDao { - - @Override - protected Class getColumnFamilyClass() { - return RuleNodeEntity.class; - } - - @Override - protected String getColumnFamilyName() { - return RULE_NODE_COLUMN_FAMILY_NAME; - } - -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/settings/CassandraAdminSettingsDao.java b/dao/src/main/java/org/thingsboard/server/dao/settings/CassandraAdminSettingsDao.java deleted file mode 100644 index fedd015789..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/settings/CassandraAdminSettingsDao.java +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright © 2016-2019 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.settings; - -import com.datastax.driver.core.querybuilder.Select.Where; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.AdminSettings; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.dao.DaoUtil; -import org.thingsboard.server.dao.model.nosql.AdminSettingsEntity; -import org.thingsboard.server.dao.nosql.CassandraAbstractModelDao; -import org.thingsboard.server.dao.util.NoSqlDao; - -import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; -import static com.datastax.driver.core.querybuilder.QueryBuilder.select; -import static org.thingsboard.server.dao.model.ModelConstants.ADMIN_SETTINGS_BY_KEY_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.ADMIN_SETTINGS_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.ADMIN_SETTINGS_KEY_PROPERTY; - -@Component -@Slf4j -@NoSqlDao -public class CassandraAdminSettingsDao extends CassandraAbstractModelDao implements AdminSettingsDao { - - @Override - protected Class getColumnFamilyClass() { - return AdminSettingsEntity.class; - } - - @Override - protected String getColumnFamilyName() { - return ADMIN_SETTINGS_COLUMN_FAMILY_NAME; - } - - @Override - public AdminSettings findByKey(TenantId tenantId, String key) { - log.debug("Try to find admin settings by key [{}] ", key); - Where query = select().from(ADMIN_SETTINGS_BY_KEY_COLUMN_FAMILY_NAME).where(eq(ADMIN_SETTINGS_KEY_PROPERTY, key)); - log.trace("Execute query {}", query); - AdminSettingsEntity adminSettingsEntity = findOneByStatement(tenantId, query); - log.trace("Found admin settings [{}] by key [{}]", adminSettingsEntity, key); - return DaoUtil.getData(adminSettingsEntity); - } - -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/CassandraTenantDao.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/CassandraTenantDao.java deleted file mode 100644 index 88d01ebdae..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/CassandraTenantDao.java +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright © 2016-2019 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.tenant; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageLink; -import org.thingsboard.server.dao.DaoUtil; -import org.thingsboard.server.dao.model.nosql.TenantEntity; -import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao; -import org.thingsboard.server.dao.util.NoSqlDao; - -import java.util.Arrays; -import java.util.List; - -import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; -import static org.thingsboard.server.dao.model.ModelConstants.TENANT_BY_REGION_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.TENANT_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.TENANT_REGION_PROPERTY; - -@Component -@Slf4j -@NoSqlDao -public class CassandraTenantDao extends CassandraAbstractSearchTextDao implements TenantDao { - - @Override - protected Class getColumnFamilyClass() { - return TenantEntity.class; - } - - @Override - protected String getColumnFamilyName() { - return TENANT_COLUMN_FAMILY_NAME; - } - - @Override - public List findTenantsByRegion(TenantId tenantId, String region, TextPageLink pageLink) { - log.debug("Try to find tenants by region [{}] and pageLink [{}]", region, pageLink); - List tenantEntities = findPageWithTextSearch(tenantId, TENANT_BY_REGION_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME, - Arrays.asList(eq(TENANT_REGION_PROPERTY, region)), - pageLink); - log.trace("Found tenants [{}] by region [{}] and pageLink [{}]", tenantEntities, region, pageLink); - return DaoUtil.convertDataList(tenantEntities); - } - -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/CassandraUserCredentialsDao.java b/dao/src/main/java/org/thingsboard/server/dao/user/CassandraUserCredentialsDao.java deleted file mode 100644 index ec90046ce0..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/user/CassandraUserCredentialsDao.java +++ /dev/null @@ -1,83 +0,0 @@ -/** - * Copyright © 2016-2019 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.user; - -import com.datastax.driver.core.querybuilder.Select.Where; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.security.UserCredentials; -import org.thingsboard.server.dao.DaoUtil; -import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.model.nosql.UserCredentialsEntity; -import org.thingsboard.server.dao.nosql.CassandraAbstractModelDao; -import org.thingsboard.server.dao.util.NoSqlDao; - -import java.util.UUID; - -import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; -import static com.datastax.driver.core.querybuilder.QueryBuilder.select; - -@Component -@Slf4j -@NoSqlDao -public class CassandraUserCredentialsDao extends CassandraAbstractModelDao implements UserCredentialsDao { - - public static final String EXECUTE_QUERY = "Execute query {}"; - - @Override - protected Class getColumnFamilyClass() { - return UserCredentialsEntity.class; - } - - @Override - protected String getColumnFamilyName() { - return ModelConstants.USER_CREDENTIALS_COLUMN_FAMILY_NAME; - } - - @Override - public UserCredentials findByUserId(TenantId tenantId, UUID userId) { - log.debug("Try to find user credentials by userId [{}] ", userId); - Where query = select().from(ModelConstants.USER_CREDENTIALS_BY_USER_COLUMN_FAMILY_NAME).where(eq(ModelConstants.USER_CREDENTIALS_USER_ID_PROPERTY, userId)); - log.trace(EXECUTE_QUERY, query); - UserCredentialsEntity userCredentialsEntity = findOneByStatement(tenantId, query); - log.trace("Found user credentials [{}] by userId [{}]", userCredentialsEntity, userId); - return DaoUtil.getData(userCredentialsEntity); - } - - @Override - public UserCredentials findByActivateToken(TenantId tenantId, String activateToken) { - log.debug("Try to find user credentials by activateToken [{}] ", activateToken); - Where query = select().from(ModelConstants.USER_CREDENTIALS_BY_ACTIVATE_TOKEN_COLUMN_FAMILY_NAME) - .where(eq(ModelConstants.USER_CREDENTIALS_ACTIVATE_TOKEN_PROPERTY, activateToken)); - log.trace(EXECUTE_QUERY, query); - UserCredentialsEntity userCredentialsEntity = findOneByStatement(tenantId, query); - log.trace("Found user credentials [{}] by activateToken [{}]", userCredentialsEntity, activateToken); - return DaoUtil.getData(userCredentialsEntity); - } - - @Override - public UserCredentials findByResetToken(TenantId tenantId, String resetToken) { - log.debug("Try to find user credentials by resetToken [{}] ", resetToken); - Where query = select().from(ModelConstants.USER_CREDENTIALS_BY_RESET_TOKEN_COLUMN_FAMILY_NAME) - .where(eq(ModelConstants.USER_CREDENTIALS_RESET_TOKEN_PROPERTY, resetToken)); - log.trace(EXECUTE_QUERY, query); - UserCredentialsEntity userCredentialsEntity = findOneByStatement(tenantId, query); - log.trace("Found user credentials [{}] by resetToken [{}]", userCredentialsEntity, resetToken); - return DaoUtil.getData(userCredentialsEntity); - } - -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/CassandraUserDao.java b/dao/src/main/java/org/thingsboard/server/dao/user/CassandraUserDao.java deleted file mode 100644 index 52a3af2366..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/user/CassandraUserDao.java +++ /dev/null @@ -1,89 +0,0 @@ -/** - * Copyright © 2016-2019 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.user; - -import com.datastax.driver.core.querybuilder.Select.Where; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageLink; -import org.thingsboard.server.common.data.security.Authority; -import org.thingsboard.server.dao.DaoUtil; -import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.model.nosql.UserEntity; -import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao; -import org.thingsboard.server.dao.util.NoSqlDao; - -import java.util.Arrays; -import java.util.List; -import java.util.UUID; - -import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; -import static com.datastax.driver.core.querybuilder.QueryBuilder.select; - -@Component -@Slf4j -@NoSqlDao -public class CassandraUserDao extends CassandraAbstractSearchTextDao implements UserDao { - - @Override - protected Class getColumnFamilyClass() { - return UserEntity.class; - } - - @Override - protected String getColumnFamilyName() { - return ModelConstants.USER_COLUMN_FAMILY_NAME; - } - - @Override - public User findByEmail(TenantId tenantId, String email) { - log.debug("Try to find user by email [{}] ", email); - Where query = select().from(ModelConstants.USER_BY_EMAIL_COLUMN_FAMILY_NAME).where(eq(ModelConstants.USER_EMAIL_PROPERTY, email)); - log.trace("Execute query {}", query); - UserEntity userEntity = findOneByStatement(tenantId, query); - log.trace("Found user [{}] by email [{}]", userEntity, email); - return DaoUtil.getData(userEntity); - } - - @Override - public List findTenantAdmins(UUID tenantId, TextPageLink pageLink) { - log.debug("Try to find tenant admin users by tenantId [{}] and pageLink [{}]", tenantId, pageLink); - List userEntities = findPageWithTextSearch(new TenantId(tenantId), - ModelConstants.USER_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME, - Arrays.asList(eq(ModelConstants.USER_TENANT_ID_PROPERTY, tenantId), - eq(ModelConstants.USER_CUSTOMER_ID_PROPERTY, ModelConstants.NULL_UUID), - eq(ModelConstants.USER_AUTHORITY_PROPERTY, Authority.TENANT_ADMIN.name())), - pageLink); - log.trace("Found tenant admin users [{}] by tenantId [{}] and pageLink [{}]", userEntities, tenantId, pageLink); - return DaoUtil.convertDataList(userEntities); - } - - @Override - public List findCustomerUsers(UUID tenantId, UUID customerId, TextPageLink pageLink) { - log.debug("Try to find customer users by tenantId [{}], customerId [{}] and pageLink [{}]", tenantId, customerId, pageLink); - List userEntities = findPageWithTextSearch(new TenantId(tenantId), - ModelConstants.USER_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME, - Arrays.asList(eq(ModelConstants.USER_TENANT_ID_PROPERTY, tenantId), - eq(ModelConstants.USER_CUSTOMER_ID_PROPERTY, customerId), - eq(ModelConstants.USER_AUTHORITY_PROPERTY, Authority.CUSTOMER_USER.name())), - pageLink); - log.trace("Found customer users [{}] by tenantId [{}], customerId [{}] and pageLink [{}]", userEntities, tenantId, customerId, pageLink); - return DaoUtil.convertDataList(userEntities); - } - -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/widget/CassandraWidgetTypeDao.java b/dao/src/main/java/org/thingsboard/server/dao/widget/CassandraWidgetTypeDao.java deleted file mode 100644 index dde4a5c1c7..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/widget/CassandraWidgetTypeDao.java +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Copyright © 2016-2019 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.widget; - -import com.datastax.driver.core.querybuilder.Select.Where; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.widget.WidgetType; -import org.thingsboard.server.dao.DaoUtil; -import org.thingsboard.server.dao.model.nosql.WidgetTypeEntity; -import org.thingsboard.server.dao.nosql.CassandraAbstractModelDao; -import org.thingsboard.server.dao.util.NoSqlDao; - -import java.util.List; -import java.util.UUID; - -import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; -import static com.datastax.driver.core.querybuilder.QueryBuilder.select; -import static org.thingsboard.server.dao.model.ModelConstants.WIDGET_TYPE_ALIAS_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.WIDGET_TYPE_BUNDLE_ALIAS_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.WIDGET_TYPE_BY_TENANT_AND_ALIASES_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.WIDGET_TYPE_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.WIDGET_TYPE_TENANT_ID_PROPERTY; - -@Component -@Slf4j -@NoSqlDao -public class CassandraWidgetTypeDao extends CassandraAbstractModelDao implements WidgetTypeDao { - - @Override - protected Class getColumnFamilyClass() { - return WidgetTypeEntity.class; - } - - @Override - protected String getColumnFamilyName() { - return WIDGET_TYPE_COLUMN_FAMILY_NAME; - } - - @Override - public List findWidgetTypesByTenantIdAndBundleAlias(UUID tenantId, String bundleAlias) { - log.debug("Try to find widget types by tenantId [{}] and bundleAlias [{}]", tenantId, bundleAlias); - Where query = select().from(WIDGET_TYPE_BY_TENANT_AND_ALIASES_COLUMN_FAMILY_NAME) - .where() - .and(eq(WIDGET_TYPE_TENANT_ID_PROPERTY, tenantId)) - .and(eq(WIDGET_TYPE_BUNDLE_ALIAS_PROPERTY, bundleAlias)); - List widgetTypesEntities = findListByStatement(new TenantId(tenantId), query); - log.trace("Found widget types [{}] by tenantId [{}] and bundleAlias [{}]", widgetTypesEntities, tenantId, bundleAlias); - return DaoUtil.convertDataList(widgetTypesEntities); - } - - @Override - public WidgetType findByTenantIdBundleAliasAndAlias(UUID tenantId, String bundleAlias, String alias) { - log.debug("Try to find widget type by tenantId [{}], bundleAlias [{}] and alias [{}]", tenantId, bundleAlias, alias); - Where query = select().from(WIDGET_TYPE_BY_TENANT_AND_ALIASES_COLUMN_FAMILY_NAME) - .where() - .and(eq(WIDGET_TYPE_TENANT_ID_PROPERTY, tenantId)) - .and(eq(WIDGET_TYPE_BUNDLE_ALIAS_PROPERTY, bundleAlias)) - .and(eq(WIDGET_TYPE_ALIAS_PROPERTY, alias)); - log.trace("Execute query {}", query); - WidgetTypeEntity widgetTypeEntity = findOneByStatement(new TenantId(tenantId), query); - log.trace("Found widget type [{}] by tenantId [{}], bundleAlias [{}] and alias [{}]", - widgetTypeEntity, tenantId, bundleAlias, alias); - return DaoUtil.getData(widgetTypeEntity); - } - -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/widget/CassandraWidgetsBundleDao.java b/dao/src/main/java/org/thingsboard/server/dao/widget/CassandraWidgetsBundleDao.java deleted file mode 100644 index 3e2b40c7a8..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/widget/CassandraWidgetsBundleDao.java +++ /dev/null @@ -1,103 +0,0 @@ -/** - * Copyright © 2016-2019 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.widget; - -import com.datastax.driver.core.querybuilder.Select; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageLink; -import org.thingsboard.server.common.data.widget.WidgetsBundle; -import org.thingsboard.server.dao.DaoUtil; -import org.thingsboard.server.dao.model.nosql.WidgetsBundleEntity; -import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao; -import org.thingsboard.server.dao.util.NoSqlDao; - -import java.util.Arrays; -import java.util.List; -import java.util.UUID; - -import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; -import static com.datastax.driver.core.querybuilder.QueryBuilder.in; -import static com.datastax.driver.core.querybuilder.QueryBuilder.select; -import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; -import static org.thingsboard.server.dao.model.ModelConstants.WIDGETS_BUNDLE_ALIAS_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.WIDGETS_BUNDLE_BY_TENANT_AND_ALIAS_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.WIDGETS_BUNDLE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.WIDGETS_BUNDLE_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.WIDGETS_BUNDLE_TENANT_ID_PROPERTY; - -@Component -@Slf4j -@NoSqlDao -public class CassandraWidgetsBundleDao extends CassandraAbstractSearchTextDao implements WidgetsBundleDao { - - @Override - protected Class getColumnFamilyClass() { - return WidgetsBundleEntity.class; - } - - @Override - protected String getColumnFamilyName() { - return WIDGETS_BUNDLE_COLUMN_FAMILY_NAME; - } - - @Override - public WidgetsBundle findWidgetsBundleByTenantIdAndAlias(UUID tenantId, String alias) { - log.debug("Try to find widgets bundle by tenantId [{}] and alias [{}]", tenantId, alias); - Select.Where query = select().from(WIDGETS_BUNDLE_BY_TENANT_AND_ALIAS_COLUMN_FAMILY_NAME) - .where() - .and(eq(WIDGETS_BUNDLE_TENANT_ID_PROPERTY, tenantId)) - .and(eq(WIDGETS_BUNDLE_ALIAS_PROPERTY, alias)); - log.trace("Execute query {}", query); - WidgetsBundleEntity widgetsBundleEntity = findOneByStatement(new TenantId(tenantId), query); - log.trace("Found widgets bundle [{}] by tenantId [{}] and alias [{}]", - widgetsBundleEntity, tenantId, alias); - return DaoUtil.getData(widgetsBundleEntity); - } - - @Override - public List findSystemWidgetsBundles(TenantId tenantId, TextPageLink pageLink) { - log.debug("Try to find system widgets bundles by pageLink [{}]", pageLink); - List widgetsBundlesEntities = findPageWithTextSearch(tenantId, WIDGETS_BUNDLE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME, - Arrays.asList(eq(WIDGETS_BUNDLE_TENANT_ID_PROPERTY, NULL_UUID)), - pageLink); - log.trace("Found system widgets bundles [{}] by pageLink [{}]", widgetsBundlesEntities, pageLink); - return DaoUtil.convertDataList(widgetsBundlesEntities); - } - - @Override - public List findTenantWidgetsBundlesByTenantId(UUID tenantId, TextPageLink pageLink) { - log.debug("Try to find tenant widgets bundles by tenantId [{}] and pageLink [{}]", tenantId, pageLink); - List widgetsBundlesEntities = findPageWithTextSearch(new TenantId(tenantId), WIDGETS_BUNDLE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME, - Arrays.asList(eq(WIDGETS_BUNDLE_TENANT_ID_PROPERTY, tenantId)), - pageLink); - log.trace("Found tenant widgets bundles [{}] by tenantId [{}] and pageLink [{}]", widgetsBundlesEntities, tenantId, pageLink); - return DaoUtil.convertDataList(widgetsBundlesEntities); - } - - @Override - public List findAllTenantWidgetsBundlesByTenantId(UUID tenantId, TextPageLink pageLink) { - log.debug("Try to find all tenant widgets bundles by tenantId [{}] and pageLink [{}]", tenantId, pageLink); - List widgetsBundlesEntities = findPageWithTextSearch(new TenantId(tenantId), WIDGETS_BUNDLE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME, - Arrays.asList(in(WIDGETS_BUNDLE_TENANT_ID_PROPERTY, Arrays.asList(NULL_UUID, tenantId))), - pageLink); - log.trace("Found all tenant widgets bundles [{}] by tenantId [{}] and pageLink [{}]", widgetsBundlesEntities, tenantId, pageLink); - return DaoUtil.convertDataList(widgetsBundlesEntities); - } - -} diff --git a/dao/src/main/resources/cassandra/schema-entities.cql b/dao/src/main/resources/cassandra/schema-entities.cql deleted file mode 100644 index 611c08d5ef..0000000000 --- a/dao/src/main/resources/cassandra/schema-entities.cql +++ /dev/null @@ -1,714 +0,0 @@ --- --- Copyright © 2016-2019 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. --- - -CREATE KEYSPACE IF NOT EXISTS thingsboard -WITH replication = { - 'class' : 'SimpleStrategy', - 'replication_factor' : 1 -}; - -CREATE TABLE IF NOT EXISTS thingsboard.user ( - id timeuuid, - tenant_id timeuuid, - customer_id timeuuid, - email text, - search_text text, - authority text, - first_name text, - last_name text, - additional_info text, - PRIMARY KEY (id, tenant_id, customer_id, authority) -); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.user_by_email AS - SELECT * - from thingsboard.user - WHERE email IS NOT NULL AND tenant_id IS NOT NULL AND customer_id IS NOT NULL AND id IS NOT NULL AND authority IS NOT - NULL - PRIMARY KEY ( email, tenant_id, customer_id, id, authority ); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.user_by_tenant_and_search_text AS - SELECT * - from thingsboard.user - WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND authority IS NOT NULL AND search_text IS NOT NULL AND id - IS NOT NULL - PRIMARY KEY ( tenant_id, customer_id, authority, search_text, id ) - WITH CLUSTERING ORDER BY ( customer_id DESC, authority DESC, search_text ASC, id DESC ); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.user_by_customer_and_search_text AS - SELECT * - from thingsboard.user - WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND authority IS NOT NULL AND search_text IS NOT NULL AND id - IS NOT NULL - PRIMARY KEY ( customer_id, tenant_id, authority, search_text, id ) - WITH CLUSTERING ORDER BY ( tenant_id DESC, authority DESC, search_text ASC, id DESC ); - -CREATE TABLE IF NOT EXISTS thingsboard.user_credentials ( - id timeuuid PRIMARY KEY, - user_id timeuuid, - enabled boolean, - password text, - activate_token text, - reset_token text -); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.user_credentials_by_user AS - SELECT * - from thingsboard.user_credentials - WHERE user_id IS NOT NULL AND id IS NOT NULL - PRIMARY KEY ( user_id, id ); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.user_credentials_by_activate_token AS - SELECT * - from thingsboard.user_credentials - WHERE activate_token IS NOT NULL AND id IS NOT NULL - PRIMARY KEY ( activate_token, id ); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.user_credentials_by_reset_token AS - SELECT * - from thingsboard.user_credentials - WHERE reset_token IS NOT NULL AND id IS NOT NULL - PRIMARY KEY ( reset_token, id ); - -CREATE TABLE IF NOT EXISTS thingsboard.admin_settings ( - id timeuuid PRIMARY KEY, - key text, - json_value text -); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.admin_settings_by_key AS - SELECT * - from thingsboard.admin_settings - WHERE key IS NOT NULL AND id IS NOT NULL - PRIMARY KEY ( key, id ) - WITH CLUSTERING ORDER BY ( id DESC ); - -CREATE TABLE IF NOT EXISTS thingsboard.tenant ( - id timeuuid, - title text, - search_text text, - region text, - country text, - state text, - city text, - address text, - address2 text, - zip text, - phone text, - email text, - additional_info text, - PRIMARY KEY (id, region) -); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.tenant_by_region_and_search_text AS - SELECT * - from thingsboard.tenant - WHERE region IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL - PRIMARY KEY ( region, search_text, id ) - WITH CLUSTERING ORDER BY ( search_text ASC, id DESC ); - -CREATE TABLE IF NOT EXISTS thingsboard.customer ( - id timeuuid, - tenant_id timeuuid, - title text, - search_text text, - country text, - state text, - city text, - address text, - address2 text, - zip text, - phone text, - email text, - additional_info text, - PRIMARY KEY (id, tenant_id) -); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.customer_by_tenant_and_title AS - SELECT * - from thingsboard.customer - WHERE tenant_id IS NOT NULL AND title IS NOT NULL AND id IS NOT NULL - PRIMARY KEY ( tenant_id, title, id ) - WITH CLUSTERING ORDER BY ( title ASC, id DESC ); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.customer_by_tenant_and_search_text AS - SELECT * - from thingsboard.customer - WHERE tenant_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL - PRIMARY KEY ( tenant_id, search_text, id ) - WITH CLUSTERING ORDER BY ( search_text ASC, id DESC ); - -CREATE TABLE IF NOT EXISTS thingsboard.device ( - id timeuuid, - tenant_id timeuuid, - customer_id timeuuid, - name text, - type text, - label text, - search_text text, - additional_info text, - PRIMARY KEY (id, tenant_id, customer_id, type) -); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_tenant_and_name AS - SELECT * - from thingsboard.device - WHERE tenant_id IS NOT NULL - AND customer_id IS NOT NULL - AND type IS NOT NULL - AND name IS NOT NULL - AND id IS NOT NULL - PRIMARY KEY ( tenant_id, name, id, customer_id, type) - WITH CLUSTERING ORDER BY ( name ASC, id DESC, customer_id DESC); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_tenant_and_search_text AS - SELECT * - from thingsboard.device - WHERE tenant_id IS NOT NULL - AND customer_id IS NOT NULL - AND type IS NOT NULL - AND search_text IS NOT NULL - AND id IS NOT NULL - PRIMARY KEY ( tenant_id, search_text, id, customer_id, type) - WITH CLUSTERING ORDER BY ( search_text ASC, id DESC, customer_id DESC); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_tenant_by_type_and_search_text AS - SELECT * - from thingsboard.device - WHERE tenant_id IS NOT NULL - AND customer_id IS NOT NULL - AND type IS NOT NULL - AND search_text IS NOT NULL - AND id IS NOT NULL - PRIMARY KEY ( tenant_id, type, search_text, id, customer_id) - WITH CLUSTERING ORDER BY ( type ASC, search_text ASC, id DESC, customer_id DESC); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_customer_and_search_text AS - SELECT * - from thingsboard.device - WHERE tenant_id IS NOT NULL - AND customer_id IS NOT NULL - AND type IS NOT NULL - AND search_text IS NOT NULL - AND id IS NOT NULL - PRIMARY KEY ( customer_id, tenant_id, search_text, id, type ) - WITH CLUSTERING ORDER BY ( tenant_id DESC, search_text ASC, id DESC ); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_customer_by_type_and_search_text AS - SELECT * - from thingsboard.device - WHERE tenant_id IS NOT NULL - AND customer_id IS NOT NULL - AND type IS NOT NULL - AND search_text IS NOT NULL - AND id IS NOT NULL - PRIMARY KEY ( customer_id, tenant_id, type, search_text, id ) - WITH CLUSTERING ORDER BY ( tenant_id DESC, type ASC, search_text ASC, id DESC ); - -CREATE TABLE IF NOT EXISTS thingsboard.device_credentials ( - id timeuuid PRIMARY KEY, - device_id timeuuid, - credentials_type text, - credentials_id text, - credentials_value text -); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_credentials_by_device AS - SELECT * - from thingsboard.device_credentials - WHERE device_id IS NOT NULL AND id IS NOT NULL - PRIMARY KEY ( device_id, id ); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_credentials_by_credentials_id AS - SELECT * - from thingsboard.device_credentials - WHERE credentials_id IS NOT NULL AND id IS NOT NULL - PRIMARY KEY ( credentials_id, id ); - -CREATE TABLE IF NOT EXISTS thingsboard.asset ( - id timeuuid, - tenant_id timeuuid, - customer_id timeuuid, - name text, - type text, - search_text text, - additional_info text, - PRIMARY KEY (id, tenant_id, customer_id, type) -); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.asset_by_tenant_and_name AS - SELECT * - from thingsboard.asset - WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND name IS NOT NULL AND id IS NOT NULL - PRIMARY KEY ( tenant_id, name, id, customer_id, type) - WITH CLUSTERING ORDER BY ( name ASC, id DESC, customer_id DESC); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.asset_by_tenant_and_search_text AS - SELECT * - from thingsboard.asset - WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL - PRIMARY KEY ( tenant_id, search_text, id, customer_id, type) - WITH CLUSTERING ORDER BY ( search_text ASC, id DESC, customer_id DESC); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.asset_by_tenant_by_type_and_search_text AS - SELECT * - from thingsboard.asset - WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL - PRIMARY KEY ( tenant_id, type, search_text, id, customer_id) - WITH CLUSTERING ORDER BY ( type ASC, search_text ASC, id DESC, customer_id DESC); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.asset_by_customer_and_search_text AS - SELECT * - from thingsboard.asset - WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL - PRIMARY KEY ( customer_id, tenant_id, search_text, id, type ) - WITH CLUSTERING ORDER BY ( tenant_id DESC, search_text ASC, id DESC ); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.asset_by_customer_by_type_and_search_text AS - SELECT * - from thingsboard.asset - WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL - PRIMARY KEY ( customer_id, tenant_id, type, search_text, id ) - WITH CLUSTERING ORDER BY ( tenant_id DESC, type ASC, search_text ASC, id DESC ); - -CREATE TABLE IF NOT EXISTS thingsboard.entity_subtype ( - tenant_id timeuuid, - entity_type text, // (DEVICE, ASSET) - type text, - PRIMARY KEY (tenant_id, entity_type, type) -); - -CREATE TABLE IF NOT EXISTS thingsboard.alarm ( - id timeuuid, - tenant_id timeuuid, - type text, - originator_id timeuuid, - originator_type text, - severity text, - status text, - start_ts bigint, - end_ts bigint, - ack_ts bigint, - clear_ts bigint, - details text, - propagate boolean, - PRIMARY KEY ((tenant_id, originator_id, originator_type), type, id) -) WITH CLUSTERING ORDER BY ( type ASC, id DESC); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.alarm_by_id AS - SELECT * - from thingsboard.alarm - WHERE tenant_id IS NOT NULL AND originator_id IS NOT NULL AND originator_type IS NOT NULL AND type IS NOT NULL - AND type IS NOT NULL AND id IS NOT NULL - PRIMARY KEY (id, tenant_id, originator_id, originator_type, type) - WITH CLUSTERING ORDER BY ( tenant_id ASC, originator_id ASC, originator_type ASC, type ASC); - -CREATE TABLE IF NOT EXISTS thingsboard.relation ( - from_id timeuuid, - from_type text, - to_id timeuuid, - to_type text, - relation_type_group text, - relation_type text, - additional_info text, - PRIMARY KEY ((from_id, from_type), relation_type_group, relation_type, to_id, to_type) -) WITH CLUSTERING ORDER BY ( relation_type_group ASC, relation_type ASC, to_id ASC, to_type ASC); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.relation_by_type_and_child_type AS - SELECT * - from thingsboard.relation - WHERE from_id IS NOT NULL AND from_type IS NOT NULL AND relation_type_group IS NOT NULL AND relation_type IS NOT NULL AND to_id IS NOT NULL AND to_type IS NOT NULL - PRIMARY KEY ((from_id, from_type), relation_type_group, relation_type, to_type, to_id) - WITH CLUSTERING ORDER BY ( relation_type_group ASC, relation_type ASC, to_type ASC, to_id DESC); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.reverse_relation AS - SELECT * - from thingsboard.relation - WHERE from_id IS NOT NULL AND from_type IS NOT NULL AND relation_type_group IS NOT NULL AND relation_type IS NOT NULL AND to_id IS NOT NULL AND to_type IS NOT NULL - PRIMARY KEY ((to_id, to_type), relation_type_group, relation_type, from_id, from_type) - WITH CLUSTERING ORDER BY ( relation_type_group ASC, relation_type ASC, from_id ASC, from_type ASC); - -CREATE TABLE IF NOT EXISTS thingsboard.widgets_bundle ( - id timeuuid, - tenant_id timeuuid, - alias text, - title text, - search_text text, - image blob, - PRIMARY KEY (id, tenant_id) -); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.widgets_bundle_by_tenant_and_search_text AS - SELECT * - from thingsboard.widgets_bundle - WHERE tenant_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL - PRIMARY KEY ( tenant_id, search_text, id ) - WITH CLUSTERING ORDER BY ( search_text ASC, id DESC ); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.widgets_bundle_by_tenant_and_alias AS - SELECT * - from thingsboard.widgets_bundle - WHERE tenant_id IS NOT NULL AND alias IS NOT NULL AND id IS NOT NULL - PRIMARY KEY ( tenant_id, alias, id ) - WITH CLUSTERING ORDER BY ( alias ASC, id DESC ); - -CREATE TABLE IF NOT EXISTS thingsboard.widget_type ( - id timeuuid, - tenant_id timeuuid, - bundle_alias text, - alias text, - name text, - descriptor text, - PRIMARY KEY (id, tenant_id, bundle_alias) -); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.widget_type_by_tenant_and_aliases AS - SELECT * - from thingsboard.widget_type - WHERE tenant_id IS NOT NULL AND bundle_alias IS NOT NULL AND alias IS NOT NULL AND id IS NOT NULL - PRIMARY KEY ( tenant_id, bundle_alias, alias, id ) - WITH CLUSTERING ORDER BY ( bundle_alias ASC, alias ASC, id DESC ); - -CREATE TABLE IF NOT EXISTS thingsboard.dashboard ( - id timeuuid, - tenant_id timeuuid, - title text, - search_text text, - assigned_customers text, - configuration text, - PRIMARY KEY (id, tenant_id) -); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.dashboard_by_tenant_and_search_text AS - SELECT * - from thingsboard.dashboard - WHERE tenant_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL - PRIMARY KEY ( tenant_id, search_text, id ) - WITH CLUSTERING ORDER BY ( search_text ASC, id DESC ); - -CREATE TABLE IF NOT EXISTS thingsboard.attributes_kv_cf ( - entity_type text, // (DEVICE, CUSTOMER, TENANT) - entity_id timeuuid, - attribute_type text, // (CLIENT_SIDE, SHARED, SERVER_SIDE) - attribute_key text, - bool_v boolean, - str_v text, - long_v bigint, - dbl_v double, - last_update_ts bigint, - PRIMARY KEY ((entity_type, entity_id, attribute_type), attribute_key) -) WITH compaction = { 'class' : 'LeveledCompactionStrategy' }; - -CREATE TABLE IF NOT EXISTS thingsboard.component_descriptor ( - id timeuuid, - type text, - scope text, - name text, - search_text text, - clazz text, - configuration_descriptor text, - actions text, - PRIMARY KEY (clazz, id, type, scope) -); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.component_desc_by_type_search_text AS - SELECT * - from thingsboard.component_descriptor - WHERE type IS NOT NULL AND scope IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL AND clazz IS NOT NULL - PRIMARY KEY ( type, search_text, id, clazz, scope) - WITH CLUSTERING ORDER BY ( search_text DESC); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.component_desc_by_scope_type_search_text AS - SELECT * - from thingsboard.component_descriptor - WHERE type IS NOT NULL AND scope IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL AND clazz IS NOT NULL - PRIMARY KEY ( (scope, type), search_text, id, clazz) - WITH CLUSTERING ORDER BY ( search_text DESC); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.component_desc_by_id AS - SELECT * - from thingsboard.component_descriptor - WHERE type IS NOT NULL AND scope IS NOT NULL AND id IS NOT NULL AND clazz IS NOT NULL - PRIMARY KEY ( id, clazz, scope, type ) - WITH CLUSTERING ORDER BY ( clazz ASC, scope ASC, type DESC); - -CREATE TABLE IF NOT EXISTS thingsboard.event ( - tenant_id timeuuid, // tenant or system - id timeuuid, - event_type text, - event_uid text, - entity_type text, - entity_id timeuuid, - body text, - PRIMARY KEY ((tenant_id, entity_type, entity_id), event_type, event_uid) -); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.event_by_type_and_id AS - SELECT * - FROM thingsboard.event - WHERE tenant_id IS NOT NULL AND entity_type IS NOT NULL AND entity_id IS NOT NULL AND id IS NOT NULL - AND event_type IS NOT NULL AND event_uid IS NOT NULL - PRIMARY KEY ((tenant_id, entity_type, entity_id), event_type, id, event_uid) - WITH CLUSTERING ORDER BY (event_type ASC, id ASC, event_uid ASC); - - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.event_by_id AS - SELECT * - FROM thingsboard.event - WHERE tenant_id IS NOT NULL AND entity_type IS NOT NULL AND entity_id IS NOT NULL AND id IS NOT NULL - AND event_type IS NOT NULL AND event_uid IS NOT NULL - PRIMARY KEY ((tenant_id, entity_type, entity_id), id, event_type, event_uid) - WITH CLUSTERING ORDER BY (id ASC, event_type ASC, event_uid ASC); - -CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_entity_id ( - tenant_id timeuuid, - id timeuuid, - customer_id timeuuid, - entity_id timeuuid, - entity_type text, - entity_name text, - user_id timeuuid, - user_name text, - action_type text, - action_data text, - action_status text, - action_failure_details text, - PRIMARY KEY ((tenant_id, entity_id, entity_type), id) -); - -CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_customer_id ( - tenant_id timeuuid, - id timeuuid, - customer_id timeuuid, - entity_id timeuuid, - entity_type text, - entity_name text, - user_id timeuuid, - user_name text, - action_type text, - action_data text, - action_status text, - action_failure_details text, - PRIMARY KEY ((tenant_id, customer_id), id) -); - -CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_user_id ( - tenant_id timeuuid, - id timeuuid, - customer_id timeuuid, - entity_id timeuuid, - entity_type text, - entity_name text, - user_id timeuuid, - user_name text, - action_type text, - action_data text, - action_status text, - action_failure_details text, - PRIMARY KEY ((tenant_id, user_id), id) -); - -CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_tenant_id ( - tenant_id timeuuid, - id timeuuid, - partition bigint, - customer_id timeuuid, - entity_id timeuuid, - entity_type text, - entity_name text, - user_id timeuuid, - user_name text, - action_type text, - action_data text, - action_status text, - action_failure_details text, - PRIMARY KEY ((tenant_id, partition), id) -); - -CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_tenant_id_partitions ( - tenant_id timeuuid, - partition bigint, - PRIMARY KEY (( tenant_id ), partition) -) WITH CLUSTERING ORDER BY ( partition ASC ) -AND compaction = { 'class' : 'LeveledCompactionStrategy' }; - -CREATE TABLE IF NOT EXISTS thingsboard.msg_queue ( - node_id timeuuid, - cluster_partition bigint, - ts_partition bigint, - ts bigint, - msg blob, - PRIMARY KEY ((node_id, cluster_partition, ts_partition), ts)) -WITH CLUSTERING ORDER BY (ts DESC) -AND compaction = { - 'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', - 'min_threshold': '5', - 'base_time_seconds': '43200', - 'max_window_size_seconds': '43200', - 'tombstone_threshold': '0.9', - 'unchecked_tombstone_compaction': 'true' -}; - -CREATE TABLE IF NOT EXISTS thingsboard.msg_ack_queue ( - node_id timeuuid, - cluster_partition bigint, - ts_partition bigint, - msg_id timeuuid, - PRIMARY KEY ((node_id, cluster_partition, ts_partition), msg_id)) -WITH CLUSTERING ORDER BY (msg_id DESC) -AND compaction = { - 'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', - 'min_threshold': '5', - 'base_time_seconds': '43200', - 'max_window_size_seconds': '43200', - 'tombstone_threshold': '0.9', - 'unchecked_tombstone_compaction': 'true' -}; - -CREATE TABLE IF NOT EXISTS thingsboard.processed_msg_partitions ( - node_id timeuuid, - cluster_partition bigint, - ts_partition bigint, - PRIMARY KEY ((node_id, cluster_partition), ts_partition)) -WITH CLUSTERING ORDER BY (ts_partition DESC) -AND compaction = { - 'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', - 'min_threshold': '5', - 'base_time_seconds': '43200', - 'max_window_size_seconds': '43200', - 'tombstone_threshold': '0.9', - 'unchecked_tombstone_compaction': 'true' -}; - -CREATE TABLE IF NOT EXISTS thingsboard.rule_chain ( - id uuid, - tenant_id uuid, - name text, - search_text text, - first_rule_node_id uuid, - root boolean, - debug_mode boolean, - configuration text, - additional_info text, - PRIMARY KEY (id, tenant_id) -); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.rule_chain_by_tenant_and_search_text AS - SELECT * - from thingsboard.rule_chain - WHERE tenant_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL - PRIMARY KEY ( tenant_id, search_text, id ) - WITH CLUSTERING ORDER BY ( search_text ASC, id DESC ); - -CREATE TABLE IF NOT EXISTS thingsboard.rule_node ( - id uuid, - rule_chain_id uuid, - type text, - name text, - debug_mode boolean, - search_text text, - configuration text, - additional_info text, - PRIMARY KEY (id) -); - -CREATE TABLE IF NOT EXISTS thingsboard.entity_view ( - id timeuuid, - entity_id timeuuid, - entity_type text, - tenant_id timeuuid, - customer_id timeuuid, - name text, - type text, - keys text, - start_ts bigint, - end_ts bigint, - search_text text, - additional_info text, - PRIMARY KEY (id, entity_id, tenant_id, customer_id, type) -); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_name AS - SELECT * - from thingsboard.entity_view - WHERE tenant_id IS NOT NULL - AND entity_id IS NOT NULL - AND customer_id IS NOT NULL - AND type IS NOT NULL - AND name IS NOT NULL - AND id IS NOT NULL - PRIMARY KEY (tenant_id, name, id, customer_id, entity_id, type) - WITH CLUSTERING ORDER BY (name ASC, id DESC, customer_id DESC); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_search_text AS - SELECT * - from thingsboard.entity_view - WHERE tenant_id IS NOT NULL - AND entity_id IS NOT NULL - AND customer_id IS NOT NULL - AND type IS NOT NULL - AND search_text IS NOT NULL - AND id IS NOT NULL - PRIMARY KEY (tenant_id, search_text, id, customer_id, entity_id, type) - WITH CLUSTERING ORDER BY (search_text ASC, id DESC, customer_id DESC); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_by_type_and_search_text AS - SELECT * - from thingsboard.entity_view - WHERE tenant_id IS NOT NULL - AND entity_id IS NOT NULL - AND customer_id IS NOT NULL - AND type IS NOT NULL - AND search_text IS NOT NULL - AND id IS NOT NULL - PRIMARY KEY (tenant_id, type, search_text, id, customer_id, entity_id) - WITH CLUSTERING ORDER BY (type ASC, search_text ASC, id DESC, customer_id DESC); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_customer AS - SELECT * - from thingsboard.entity_view - WHERE tenant_id IS NOT NULL - AND customer_id IS NOT NULL - AND entity_id IS NOT NULL - AND type IS NOT NULL - AND search_text IS NOT NULL - AND id IS NOT NULL - PRIMARY KEY (tenant_id, customer_id, search_text, id, entity_id, type) - WITH CLUSTERING ORDER BY (customer_id DESC, search_text ASC, id DESC); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_customer_and_type AS - SELECT * - from thingsboard.entity_view - WHERE tenant_id IS NOT NULL - AND customer_id IS NOT NULL - AND entity_id IS NOT NULL - AND type IS NOT NULL - AND search_text IS NOT NULL - AND id IS NOT NULL - PRIMARY KEY (tenant_id, type, customer_id, search_text, id, entity_id) - WITH CLUSTERING ORDER BY (type ASC, customer_id DESC, search_text ASC, id DESC); - -CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_entity_id AS - SELECT * - from thingsboard.entity_view - WHERE tenant_id IS NOT NULL - AND customer_id IS NOT NULL - AND entity_id IS NOT NULL - AND type IS NOT NULL - AND search_text IS NOT NULL - AND id IS NOT NULL - PRIMARY KEY (tenant_id, entity_id, customer_id, search_text, id, type) - WITH CLUSTERING ORDER BY (entity_id DESC, customer_id DESC, search_text ASC, id DESC); \ No newline at end of file diff --git a/dao/src/main/resources/cassandra/system-data.cql b/dao/src/main/resources/cassandra/system-data.cql deleted file mode 100644 index 4eb1086708..0000000000 --- a/dao/src/main/resources/cassandra/system-data.cql +++ /dev/null @@ -1,44 +0,0 @@ --- --- Copyright © 2016-2019 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. --- - -/** SYSTEM **/ - -/** System admin **/ -INSERT INTO thingsboard.user ( id, tenant_id, customer_id, email, search_text, authority ) -VALUES ( minTimeuuid ( '2016-11-01 01:01:01+0000' ), minTimeuuid ( 0 ), minTimeuuid ( 0 ), 'sysadmin@thingsboard.org', -'sysadmin@thingsboard.org', 'SYS_ADMIN' ); - -INSERT INTO thingsboard.user_credentials ( id, user_id, enabled, password ) -VALUES ( now ( ), minTimeuuid ( '2016-11-01 01:01:01+0000' ), true, -'$2a$10$5JTB8/hxWc9WAy62nCGSxeefl3KWmipA9nFpVdDa0/xfIseeBB4Bu' ); - -/** System settings **/ -INSERT INTO thingsboard.admin_settings ( id, key, json_value ) -VALUES ( now ( ), 'general', '{ - "baseUrl": "http://localhost:8080" -}' ); - -INSERT INTO thingsboard.admin_settings ( id, key, json_value ) -VALUES ( now ( ), 'mail', '{ - "mailFrom": "Thingsboard ", - "smtpProtocol": "smtp", - "smtpHost": "localhost", - "smtpPort": "25", - "timeout": "10000", - "enableTls": "false", - "username": "", - "password": "" -}' ); \ No newline at end of file diff --git a/dao/src/test/java/org/thingsboard/server/dao/CustomCassandraCQLUnit.java b/dao/src/test/java/org/thingsboard/server/dao/CustomCassandraCQLUnit.java index c74dbd54e5..2fbbe006c0 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/CustomCassandraCQLUnit.java +++ b/dao/src/test/java/org/thingsboard/server/dao/CustomCassandraCQLUnit.java @@ -73,6 +73,7 @@ public class CustomCassandraCQLUnit extends BaseCassandraUnit { CQLDataLoader dataLoader = new CQLDataLoader(session); dataSets.forEach(dataLoader::load); session = dataLoader.getSession(); + System.setSecurityManager(null); } @Override diff --git a/dao/src/test/java/org/thingsboard/server/dao/NoSqlDaoServiceTestSuite.java b/dao/src/test/java/org/thingsboard/server/dao/NoSqlDaoServiceTestSuite.java index dcfd3e65f2..0340e144fa 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/NoSqlDaoServiceTestSuite.java +++ b/dao/src/test/java/org/thingsboard/server/dao/NoSqlDaoServiceTestSuite.java @@ -29,14 +29,19 @@ import java.util.Arrays; }) public class NoSqlDaoServiceTestSuite { + @ClassRule + public static CustomSqlUnit sqlUnit = new CustomSqlUnit( + Arrays.asList("sql/schema-entities.sql", "sql/schema-entities-idx.sql", "sql/system-data.sql", "sql/system-test.sql"), + "sql/drop-all-tables.sql", + "nosql-test.properties" + ); + @ClassRule public static CustomCassandraCQLUnit cassandraUnit = new CustomCassandraCQLUnit( Arrays.asList( - new ClassPathCQLDataSet("cassandra/schema-ts.cql", false, false), - new ClassPathCQLDataSet("cassandra/schema-entities.cql", false, false), - new ClassPathCQLDataSet("cassandra/system-data.cql", false, false), - new ClassPathCQLDataSet("cassandra/system-test.cql", false, false)), + new ClassPathCQLDataSet("cassandra/schema-ts.cql", false, false) + ), "cassandra-test.yaml", 30000L); } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/attributes/nosql/AttributesServiceNoSqlTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/attributes/nosql/AttributesServiceNoSqlTest.java deleted file mode 100644 index 54ddc49c2d..0000000000 --- a/dao/src/test/java/org/thingsboard/server/dao/service/attributes/nosql/AttributesServiceNoSqlTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2019 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.service.attributes.nosql; - -import org.thingsboard.server.dao.service.DaoNoSqlTest; -import org.thingsboard.server.dao.service.attributes.BaseAttributesServiceTest; - -@DaoNoSqlTest -public class AttributesServiceNoSqlTest extends BaseAttributesServiceTest { -} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/event/nosql/EventServiceNoSqlTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/event/nosql/EventServiceNoSqlTest.java deleted file mode 100644 index 9956161b27..0000000000 --- a/dao/src/test/java/org/thingsboard/server/dao/service/event/nosql/EventServiceNoSqlTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2019 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.service.event.nosql; - -import org.thingsboard.server.dao.service.DaoNoSqlTest; -import org.thingsboard.server.dao.service.event.BaseEventServiceTest; - -@DaoNoSqlTest -public class EventServiceNoSqlTest extends BaseEventServiceTest { -} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/nosql/AdminSettingsServiceNoSqlTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/nosql/AdminSettingsServiceNoSqlTest.java deleted file mode 100644 index 46bd70ee0a..0000000000 --- a/dao/src/test/java/org/thingsboard/server/dao/service/nosql/AdminSettingsServiceNoSqlTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2019 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.service.nosql; - -import org.thingsboard.server.dao.service.BaseAdminSettingsServiceTest; -import org.thingsboard.server.dao.service.DaoNoSqlTest; - -@DaoNoSqlTest -public class AdminSettingsServiceNoSqlTest extends BaseAdminSettingsServiceTest { -} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/nosql/AlarmServiceNoSqlTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/nosql/AlarmServiceNoSqlTest.java deleted file mode 100644 index bc84da0cb8..0000000000 --- a/dao/src/test/java/org/thingsboard/server/dao/service/nosql/AlarmServiceNoSqlTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2019 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.service.nosql; - -import org.thingsboard.server.dao.service.BaseAlarmServiceTest; -import org.thingsboard.server.dao.service.DaoNoSqlTest; - -@DaoNoSqlTest -public class AlarmServiceNoSqlTest extends BaseAlarmServiceTest { -} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/nosql/AssetServiceNoSqlTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/nosql/AssetServiceNoSqlTest.java deleted file mode 100644 index 0bb3cce5c1..0000000000 --- a/dao/src/test/java/org/thingsboard/server/dao/service/nosql/AssetServiceNoSqlTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2019 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.service.nosql; - -import org.thingsboard.server.dao.service.BaseAssetServiceTest; -import org.thingsboard.server.dao.service.DaoNoSqlTest; - -@DaoNoSqlTest -public class AssetServiceNoSqlTest extends BaseAssetServiceTest { -} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/nosql/CustomerServiceNoSqlTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/nosql/CustomerServiceNoSqlTest.java deleted file mode 100644 index e2897470b8..0000000000 --- a/dao/src/test/java/org/thingsboard/server/dao/service/nosql/CustomerServiceNoSqlTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2019 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.service.nosql; - -import org.thingsboard.server.dao.service.BaseCustomerServiceTest; -import org.thingsboard.server.dao.service.DaoNoSqlTest; - -@DaoNoSqlTest -public class CustomerServiceNoSqlTest extends BaseCustomerServiceTest { -} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/nosql/DashboardServiceNoSqlTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/nosql/DashboardServiceNoSqlTest.java deleted file mode 100644 index 42b5aff630..0000000000 --- a/dao/src/test/java/org/thingsboard/server/dao/service/nosql/DashboardServiceNoSqlTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2019 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.service.nosql; - -import org.thingsboard.server.dao.service.BaseDashboardServiceTest; -import org.thingsboard.server.dao.service.DaoNoSqlTest; - -@DaoNoSqlTest -public class DashboardServiceNoSqlTest extends BaseDashboardServiceTest { -} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/nosql/DeviceCredentialCacheServiceNoSqlTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/nosql/DeviceCredentialCacheServiceNoSqlTest.java deleted file mode 100644 index 8e12bb2c93..0000000000 --- a/dao/src/test/java/org/thingsboard/server/dao/service/nosql/DeviceCredentialCacheServiceNoSqlTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2019 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.service.nosql; - -import org.thingsboard.server.dao.service.BaseDeviceCredentialsCacheTest; -import org.thingsboard.server.dao.service.DaoNoSqlTest; - -@DaoNoSqlTest -public class DeviceCredentialCacheServiceNoSqlTest extends BaseDeviceCredentialsCacheTest { -} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/nosql/DeviceCredentialServiceNoSqlTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/nosql/DeviceCredentialServiceNoSqlTest.java deleted file mode 100644 index c85baec723..0000000000 --- a/dao/src/test/java/org/thingsboard/server/dao/service/nosql/DeviceCredentialServiceNoSqlTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2019 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.service.nosql; - -import org.thingsboard.server.dao.service.BaseDeviceCredentialsServiceTest; -import org.thingsboard.server.dao.service.DaoNoSqlTest; - -@DaoNoSqlTest -public class DeviceCredentialServiceNoSqlTest extends BaseDeviceCredentialsServiceTest { -} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/nosql/DeviceServiceNoSqlTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/nosql/DeviceServiceNoSqlTest.java deleted file mode 100644 index 835eeaa949..0000000000 --- a/dao/src/test/java/org/thingsboard/server/dao/service/nosql/DeviceServiceNoSqlTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2019 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.service.nosql; - -import org.thingsboard.server.dao.service.BaseDeviceServiceTest; -import org.thingsboard.server.dao.service.DaoNoSqlTest; - -@DaoNoSqlTest -public class DeviceServiceNoSqlTest extends BaseDeviceServiceTest { -} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/nosql/RelationCacheNoSqlTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/nosql/RelationCacheNoSqlTest.java deleted file mode 100644 index ab219cecb4..0000000000 --- a/dao/src/test/java/org/thingsboard/server/dao/service/nosql/RelationCacheNoSqlTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2019 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.service.nosql; - -import org.thingsboard.server.dao.service.BaseRelationCacheTest; -import org.thingsboard.server.dao.service.DaoNoSqlTest; - -@DaoNoSqlTest -public class RelationCacheNoSqlTest extends BaseRelationCacheTest { -} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/nosql/RelationServiceNoSqlTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/nosql/RelationServiceNoSqlTest.java deleted file mode 100644 index f908c5730a..0000000000 --- a/dao/src/test/java/org/thingsboard/server/dao/service/nosql/RelationServiceNoSqlTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2019 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.service.nosql; - -import org.thingsboard.server.dao.service.BaseRelationServiceTest; -import org.thingsboard.server.dao.service.DaoNoSqlTest; - -@DaoNoSqlTest -public class RelationServiceNoSqlTest extends BaseRelationServiceTest { -} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/nosql/RuleChainServiceNoSqlTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/nosql/RuleChainServiceNoSqlTest.java deleted file mode 100644 index 1c8d2bdf3a..0000000000 --- a/dao/src/test/java/org/thingsboard/server/dao/service/nosql/RuleChainServiceNoSqlTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2019 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.service.nosql; - -import org.thingsboard.server.dao.service.BaseRuleChainServiceTest; -import org.thingsboard.server.dao.service.DaoNoSqlTest; - -@DaoNoSqlTest -public class RuleChainServiceNoSqlTest extends BaseRuleChainServiceTest { -} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/nosql/TenantServiceNoSqlTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/nosql/TenantServiceNoSqlTest.java deleted file mode 100644 index d8e538ed60..0000000000 --- a/dao/src/test/java/org/thingsboard/server/dao/service/nosql/TenantServiceNoSqlTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2019 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.service.nosql; - -import org.thingsboard.server.dao.service.BaseTenantServiceTest; -import org.thingsboard.server.dao.service.DaoNoSqlTest; - -@DaoNoSqlTest -public class TenantServiceNoSqlTest extends BaseTenantServiceTest { -} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/nosql/UserServiceNoSqlTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/nosql/UserServiceNoSqlTest.java deleted file mode 100644 index b9e20524e5..0000000000 --- a/dao/src/test/java/org/thingsboard/server/dao/service/nosql/UserServiceNoSqlTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2019 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.service.nosql; - -import org.thingsboard.server.dao.service.BaseUserServiceTest; -import org.thingsboard.server.dao.service.DaoNoSqlTest; - -@DaoNoSqlTest -public class UserServiceNoSqlTest extends BaseUserServiceTest { -} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/nosql/WidgetTypeServiceNoSqlTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/nosql/WidgetTypeServiceNoSqlTest.java deleted file mode 100644 index 0ab6bb8e8c..0000000000 --- a/dao/src/test/java/org/thingsboard/server/dao/service/nosql/WidgetTypeServiceNoSqlTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2019 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.service.nosql; - -import org.thingsboard.server.dao.service.BaseWidgetTypeServiceTest; -import org.thingsboard.server.dao.service.DaoNoSqlTest; - -@DaoNoSqlTest -public class WidgetTypeServiceNoSqlTest extends BaseWidgetTypeServiceTest { -} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/nosql/WidgetsBundleServiceNoSqlTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/nosql/WidgetsBundleServiceNoSqlTest.java deleted file mode 100644 index adceeebb41..0000000000 --- a/dao/src/test/java/org/thingsboard/server/dao/service/nosql/WidgetsBundleServiceNoSqlTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2019 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.service.nosql; - -import org.thingsboard.server.dao.service.BaseWidgetsBundleServiceTest; -import org.thingsboard.server.dao.service.DaoNoSqlTest; - -@DaoNoSqlTest -public class WidgetsBundleServiceNoSqlTest extends BaseWidgetsBundleServiceTest { -} diff --git a/dao/src/test/resources/cassandra/system-test.cql b/dao/src/test/resources/cassandra/system-test.cql deleted file mode 100644 index de4e325745..0000000000 --- a/dao/src/test/resources/cassandra/system-test.cql +++ /dev/null @@ -1,27 +0,0 @@ -TRUNCATE thingsboard.rule_chain; -TRUNCATE thingsboard.rule_node; - --- msg_queue dataset - -INSERT INTO thingsboard.msg_queue (node_id, cluster_partition, ts_partition, ts, msg) - VALUES (055eee50-1883-11e8-b380-65b5d5335ba9, 101, 200, 201, null); -INSERT INTO thingsboard.msg_queue (node_id, cluster_partition, ts_partition, ts, msg) - VALUES (055eee50-1883-11e8-b380-65b5d5335ba9, 101, 200, 202, null); -INSERT INTO thingsboard.msg_queue (node_id, cluster_partition, ts_partition, ts, msg) - VALUES (055eee50-1883-11e8-b380-65b5d5335ba9, 101, 300, 301, null); - --- ack_queue dataset -INSERT INTO thingsboard.msg_ack_queue (node_id, cluster_partition, ts_partition, msg_id) - VALUES (055eee50-1883-11e8-b380-65b5d5335ba9, 101, 300, bebaeb60-1888-11e8-bf21-65b5d5335ba9); -INSERT INTO thingsboard.msg_ack_queue (node_id, cluster_partition, ts_partition, msg_id) - VALUES (055eee50-1883-11e8-b380-65b5d5335ba9, 101, 300, 12baeb60-1888-11e8-bf21-65b5d5335ba9); -INSERT INTO thingsboard.msg_ack_queue (node_id, cluster_partition, ts_partition, msg_id) - VALUES (055eee50-1883-11e8-b380-65b5d5335ba9, 101, 200, 32baeb60-1888-11e8-bf21-65b5d5335ba9); - --- processed partition dataset -INSERT INTO thingsboard.processed_msg_partitions (node_id, cluster_partition, ts_partition) - VALUES (055eee50-1883-11e8-b380-65b5d5335ba9, 101, 100); -INSERT INTO thingsboard.processed_msg_partitions (node_id, cluster_partition, ts_partition) - VALUES (055eee50-1883-11e8-b380-65b5d5335ba9, 101, 777); -INSERT INTO thingsboard.processed_msg_partitions (node_id, cluster_partition, ts_partition) - VALUES (055eee50-1883-11e8-b380-65b5d5335ba9, 202, 200); \ No newline at end of file diff --git a/dao/src/test/resources/nosql-test.properties b/dao/src/test/resources/nosql-test.properties index 7c3ec51e8a..556a024c7d 100644 --- a/dao/src/test/resources/nosql-test.properties +++ b/dao/src/test/resources/nosql-test.properties @@ -1,2 +1,14 @@ -database.entities.type=cassandra database.ts.type=cassandra + +sql.ts_inserts_executor_type=fixed +sql.ts_inserts_fixed_thread_pool_size=10 + +spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true +spring.jpa.show-sql=false +spring.jpa.hibernate.ddl-auto=none +spring.jpa.database-platform=org.hibernate.dialect.HSQLDialect + +spring.datasource.username=sa +spring.datasource.password= +spring.datasource.url=jdbc:hsqldb:file:/tmp/testDb;sql.enforce_size=false +spring.datasource.driverClassName=org.hsqldb.jdbc.JDBCDriver diff --git a/dao/src/test/resources/sql-test.properties b/dao/src/test/resources/sql-test.properties index 745aa9e1e0..121f1b9a7a 100644 --- a/dao/src/test/resources/sql-test.properties +++ b/dao/src/test/resources/sql-test.properties @@ -1,5 +1,4 @@ database.ts.type=sql -database.entities.type=sql sql.ts_inserts_executor_type=fixed sql.ts_inserts_fixed_thread_pool_size=10 diff --git a/dao/src/main/resources/sql/system-data.sql b/dao/src/test/resources/sql/system-data.sql similarity index 100% rename from dao/src/main/resources/sql/system-data.sql rename to dao/src/test/resources/sql/system-data.sql diff --git a/msa/js-executor/package-lock.json b/msa/js-executor/package-lock.json index 6354a502f0..c61ef011a2 100644 --- a/msa/js-executor/package-lock.json +++ b/msa/js-executor/package-lock.json @@ -998,7 +998,7 @@ }, "enabled": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/enabled/-/enabled-1.0.2.tgz", + "resolved": "http://registry.npmjs.org/enabled/-/enabled-1.0.2.tgz", "integrity": "sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M=", "requires": { "env-variable": "0.0.x" @@ -1261,7 +1261,7 @@ }, "fecha": { "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fecha/-/fecha-2.3.3.tgz", + "resolved": "http://registry.npmjs.org/fecha/-/fecha-2.3.3.tgz", "integrity": "sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg==" }, "file-stream-rotator": { @@ -2426,7 +2426,7 @@ }, "json5": { "version": "1.0.1", - "resolved": "http://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", "requires": { "minimist": "^1.2.0" @@ -2947,7 +2947,7 @@ }, "os-homedir": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "resolved": "http://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "optional": true }, @@ -2995,7 +2995,7 @@ }, "path-is-absolute": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, @@ -3401,7 +3401,7 @@ }, "safe-regex": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "dev": true, "requires": { @@ -3753,7 +3753,7 @@ }, "strip-eof": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "resolved": "http://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", "dev": true }, diff --git a/msa/web-ui/package-lock.json b/msa/web-ui/package-lock.json index db80a29195..9298d31f27 100644 --- a/msa/web-ui/package-lock.json +++ b/msa/web-ui/package-lock.json @@ -837,7 +837,7 @@ }, "enabled": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/enabled/-/enabled-1.0.2.tgz", + "resolved": "http://registry.npmjs.org/enabled/-/enabled-1.0.2.tgz", "integrity": "sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M=", "requires": { "env-variable": "0.0.x" @@ -1149,7 +1149,7 @@ }, "fecha": { "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fecha/-/fecha-2.3.3.tgz", + "resolved": "http://registry.npmjs.org/fecha/-/fecha-2.3.3.tgz", "integrity": "sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg==" }, "file-stream-rotator": { @@ -2354,7 +2354,7 @@ }, "json5": { "version": "1.0.1", - "resolved": "http://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", "requires": { "minimist": "^1.2.0" @@ -2486,7 +2486,7 @@ }, "media-typer": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, "merge-descriptors": { @@ -2581,7 +2581,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "requires": { @@ -2590,7 +2590,7 @@ "dependencies": { "minimist": { "version": "0.0.8", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true } @@ -2859,7 +2859,7 @@ }, "path-is-absolute": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, @@ -3233,7 +3233,7 @@ }, "safe-regex": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "dev": true, "requires": { @@ -3585,7 +3585,7 @@ }, "strip-eof": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "resolved": "http://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", "dev": true }, diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbSaveToCustomCassandraTableNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbSaveToCustomCassandraTableNode.java index 7708edce78..58d8c44621 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbSaveToCustomCassandraTableNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbSaveToCustomCassandraTableNode.java @@ -15,15 +15,7 @@ */ package org.thingsboard.rule.engine.action; -import com.datastax.driver.core.BoundStatement; -import com.datastax.driver.core.CodecRegistry; -import com.datastax.driver.core.ConsistencyLevel; -import com.datastax.driver.core.PreparedStatement; -import com.datastax.driver.core.ResultSet; -import com.datastax.driver.core.ResultSetFuture; -import com.datastax.driver.core.Session; -import com.datastax.driver.core.Statement; -import com.datastax.driver.core.TypeCodec; +import com.datastax.driver.core.*; import com.datastax.driver.core.exceptions.CodecNotFoundException; import com.google.common.base.Function; import com.google.common.util.concurrent.Futures; @@ -33,20 +25,12 @@ import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.JsonPrimitive; 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.*; 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.dao.cassandra.CassandraCluster; -import org.thingsboard.server.dao.model.type.AuthorityCodec; import org.thingsboard.server.dao.model.type.ComponentLifecycleStateCodec; -import org.thingsboard.server.dao.model.type.ComponentScopeCodec; -import org.thingsboard.server.dao.model.type.ComponentTypeCodec; -import org.thingsboard.server.dao.model.type.DeviceCredentialsTypeCodec; import org.thingsboard.server.dao.model.type.EntityTypeCodec; import org.thingsboard.server.dao.model.type.JsonCodec; import org.thingsboard.server.dao.nosql.CassandraStatementTask; @@ -60,8 +44,8 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; -import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; import static org.thingsboard.common.util.DonAsynchron.withCallback; +import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; @Slf4j @RuleNode(type = ComponentType.ACTION, @@ -137,11 +121,7 @@ public class TbSaveToCustomCassandraTableNode implements TbNode { defaultWriteLevel = cassandraCluster.getDefaultWriteConsistencyLevel(); CodecRegistry registry = session.getCluster().getConfiguration().getCodecRegistry(); registerCodecIfNotFound(registry, new JsonCodec()); - registerCodecIfNotFound(registry, new DeviceCredentialsTypeCodec()); - registerCodecIfNotFound(registry, new AuthorityCodec()); registerCodecIfNotFound(registry, new ComponentLifecycleStateCodec()); - registerCodecIfNotFound(registry, new ComponentTypeCodec()); - registerCodecIfNotFound(registry, new ComponentScopeCodec()); registerCodecIfNotFound(registry, new EntityTypeCodec()); } return session; diff --git a/ui/package-lock.json b/ui/package-lock.json index a5418e8077..e2fc919489 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -39,7 +39,7 @@ "@babel/code-frame": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", - "integrity": "sha1-BuKrGb21NThVWaq7W6WXKUgoAPg=", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", "dev": true, "requires": { "@babel/highlight": "^7.0.0" @@ -277,7 +277,7 @@ "@babel/helper-function-name": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", - "integrity": "sha1-oM6wFoX3M1XUNgwSR/WCv6/I/1M=", + "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", "dev": true, "requires": { "@babel/helper-get-function-arity": "^7.0.0", @@ -288,7 +288,7 @@ "@babel/helper-get-function-arity": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", - "integrity": "sha1-g1ctQyDipGVyY3NBE8QoaLZOScM=", + "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", "dev": true, "requires": { "@babel/types": "^7.0.0" @@ -669,7 +669,7 @@ "@babel/highlight": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", - "integrity": "sha1-9xDDjI1Fjm3ZogGvtjf8t4HOmeQ=", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", "dev": true, "requires": { "chalk": "^2.0.0", @@ -680,7 +680,7 @@ "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { "color-convert": "^1.9.0" @@ -706,7 +706,7 @@ "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -1661,12 +1661,12 @@ "@flowjs/ng-flow": { "version": "2.7.8", "resolved": "https://registry.npmjs.org/@flowjs/ng-flow/-/ng-flow-2.7.8.tgz", - "integrity": "sha1-HZ+dH4Ks2lNgMowxW6z9YNv9mBk=" + "integrity": "sha512-zO6jNvz41oMOJj9+1N+vLT0ytitbCtuGABJQRzQDOPXyRMmlSXfJ7om5oYOztyUFrr4jDpE4QFPt+r2/RFceCg==" }, "@mrmlnc/readdir-enhanced": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", - "integrity": "sha1-UkryQNGjYFJ7cwR17PoTRKpUDd4=", + "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", "dev": true, "requires": { "call-me-maybe": "^1.0.1", @@ -1912,7 +1912,7 @@ "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha1-+PLIh60Qv2f2NPAFtph/7TF5qsg=" + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, "accepts": { "version": "1.3.7", @@ -2018,7 +2018,7 @@ "angular-carousel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/angular-carousel/-/angular-carousel-1.1.0.tgz", - "integrity": "sha1-PmlA5ovRio85L8Qx2XGSrDSIMdE=" + "integrity": "sha512-UiLMgT7Ueqk4xpliF1gWt4dYKXezdJA1jyZPNsUWkOGO/dwLuKi284h3BgWl4CnaH7kEBw8L2gsBOyqbYaumNQ==" }, "angular-cookies": { "version": "1.5.8", @@ -2039,7 +2039,7 @@ } }, "angular-fullscreen": { - "version": "git://github.com/fabiobiondi/angular-fullscreen.git#8217174565761d3566807bc60a73b5ca015b8cb6", + "version": "git://github.com/fabiobiondi/angular-fullscreen.git#119b7fbac911d154fd56ace38ebe3432475e8a20", "from": "git://github.com/fabiobiondi/angular-fullscreen.git#master" }, "angular-gridster": { @@ -2113,7 +2113,7 @@ "angular-translate": { "version": "2.18.1", "resolved": "https://registry.npmjs.org/angular-translate/-/angular-translate-2.18.1.tgz", - "integrity": "sha1-sp7Q0vm6xEB156rTKEFmxZ4VB5E=", + "integrity": "sha512-Mw0kFBqsv5j8ItL9IhRZunIlVmIRW6iFsiTmRs9wGr2QTt8z4rehYlWyHos8qnXc/kyOYJiW50iH50CSNHGB9A==", "requires": { "angular": ">=1.2.26 <=1.7" } @@ -2121,7 +2121,7 @@ "angular-translate-handler-log": { "version": "2.18.1", "resolved": "https://registry.npmjs.org/angular-translate-handler-log/-/angular-translate-handler-log-2.18.1.tgz", - "integrity": "sha1-icu1mCeALYb4EVJ1+/iNbYiWsNQ=", + "integrity": "sha512-TyKzCW4GubNazwCgLpCVXd2212CWdZOckf+aL5+gLuThPhVpOvlg18RSmz8MNPto3kwCcCw3LzShlZ6RX/MQRA==", "requires": { "angular-translate": "~2.18.1" } @@ -2129,7 +2129,7 @@ "angular-translate-interpolation-messageformat": { "version": "2.18.1", "resolved": "https://registry.npmjs.org/angular-translate-interpolation-messageformat/-/angular-translate-interpolation-messageformat-2.18.1.tgz", - "integrity": "sha1-FsUq4MYcJA8PJBZKBSGUPPi6QI4=", + "integrity": "sha512-SlmyxLB/UUy7FWoGx5QJHrhq8fUu/xzCR0h/ngexOtXZopQjs1vm+TrFZ69d4c/LI7C91sfP4mq4ES29o1xCxA==", "requires": { "angular-translate": "~2.18.1", "messageformat": "~1.0.2" @@ -2138,7 +2138,7 @@ "angular-translate-loader-static-files": { "version": "2.18.1", "resolved": "https://registry.npmjs.org/angular-translate-loader-static-files/-/angular-translate-loader-static-files-2.18.1.tgz", - "integrity": "sha1-rQw8iDsYsIm9uNsCu9Nm2QP4V8w=", + "integrity": "sha512-5MuyzAROfc493kjLjKlLGLBzXiRmZIFbcWZGutDRxW5SRXSpwrH0u0hh0ENNnUyUQbe2vUspHNPIuZqlq8qIhw==", "requires": { "angular-translate": "~2.18.1" } @@ -2146,7 +2146,7 @@ "angular-translate-storage-cookie": { "version": "2.18.1", "resolved": "https://registry.npmjs.org/angular-translate-storage-cookie/-/angular-translate-storage-cookie-2.18.1.tgz", - "integrity": "sha1-j8vaspb6gkkOALQorxp0ahf0QVY=", + "integrity": "sha512-wiMaF/0OGN/3ilaYunfsqdLNpfGZEJK0fj4zT8yjD3XPq7Q9kM88xZ4XJiWKgodZShBljGCRzqgQbKMF7d1MLw==", "requires": { "angular-cookies": ">=1.2.26 <1.8", "angular-translate": "~2.18.1" @@ -2155,7 +2155,7 @@ "angular-translate-storage-local": { "version": "2.18.1", "resolved": "https://registry.npmjs.org/angular-translate-storage-local/-/angular-translate-storage-local-2.18.1.tgz", - "integrity": "sha1-lHQP5NgBq3gpopofBeHDkFTIcwM=", + "integrity": "sha512-zPxcbIJ8tdWXtWNKLtaswynKid0w5le6WPMwiLWhgKPnyzOp/y5WLBW+JEfnZnkGE24yOGhJ6jVPgRNzelLgzg==", "requires": { "angular-translate": "~2.18.1", "angular-translate-storage-cookie": "~2.18.1" @@ -2234,7 +2234,7 @@ "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha1-aALmJk79GMeQobDVF/DyYnvyyUo=", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "dev": true }, "are-we-there-yet": { @@ -2250,7 +2250,7 @@ "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha1-vNZ5HqWuCXJeF+WtmIE0zUCz2RE=", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "requires": { "sprintf-js": "~1.0.2" @@ -2265,7 +2265,7 @@ "arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha1-NgSLv/TntH4TZkQxbJlmnqWukfE=", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", "dev": true }, "arr-union": { @@ -2366,7 +2366,7 @@ }, "util": { "version": "0.10.3", - "resolved": "http://registry.npmjs.org/util/-/util-0.10.3.tgz", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", "dev": true, "requires": { @@ -2426,7 +2426,7 @@ "atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha1-bZUX654DDSQ2ZmZR6GvZ9vE1M8k=", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", "dev": true }, "attr-accept": { @@ -2681,7 +2681,7 @@ "base": { "version": "0.11.2", "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha1-e95c7RRbbVUakNuH+DxVi060io8=", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", "dev": true, "requires": { "cache-base": "^1.0.1", @@ -2705,7 +2705,7 @@ "is-accessor-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -2714,7 +2714,7 @@ "is-data-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -2723,7 +2723,7 @@ "is-descriptor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", @@ -2740,7 +2740,7 @@ "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha1-ARRrNqYhjmTljzqNZt5df8b20FE=", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", "dev": true } } @@ -2768,7 +2768,7 @@ "big.js": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", - "integrity": "sha1-pfwpi4G54Nyi5FiCR4S2XFK6WI4=", + "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", "dev": true }, "binary-extensions": { @@ -2871,7 +2871,7 @@ "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha1-PH/L9SnYcibz0vUrlm/1Jx60Qd0=", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3091,7 +3091,7 @@ "cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha1-Cn9GQWgxyLZi7jb+TnxZ129marI=", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", "dev": true, "requires": { "collection-visit": "^1.0.0", @@ -3130,7 +3130,7 @@ "dependencies": { "callsites": { "version": "2.0.0", - "resolved": "http://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", "dev": true } @@ -3290,13 +3290,13 @@ "circular-json": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", - "integrity": "sha1-gVyZ6oT2gJUp0vRXkb34JxE1LWY=", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", "dev": true }, "class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha1-+TNprouafOAv1B+q0MqDAzGQxGM=", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", "dev": true, "requires": { "arr-union": "^3.1.0", @@ -3437,7 +3437,7 @@ "clone-regexp": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-1.0.1.tgz", - "integrity": "sha1-BRgFzTMXM3XYIRj8CRhgbaOf1g8=", + "integrity": "sha512-Fcij9IwRW27XedRIJnSOEupS7RVcXtObJXbcUOX93UCLqqOdRpkvzKywOOSizmEK/Is3S/RHX9dLdfo6R1Q1mw==", "dev": true, "requires": { "is-regexp": "^1.0.0", @@ -3609,7 +3609,7 @@ "concat-stream": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha1-kEvfGUzTEi/Gdcd/xKw9T/D9GjQ=", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", "dev": true, "requires": { "buffer-from": "^1.0.0", @@ -3672,7 +3672,7 @@ "content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha1-4TjMdeBAxyexlm/l5fjJruJW/js=", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", "dev": true }, "convert-source-map": { @@ -3699,7 +3699,7 @@ "copy-concurrently": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", - "integrity": "sha1-kilzmMrjSTf8r9bsgTnBgFHwteA=", + "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", "dev": true, "requires": { "aproba": "^1.1.1", @@ -3960,7 +3960,7 @@ "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha1-E7BM2z5sXRnfkatph6hpVhmwqnE=", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, "js-yaml": { @@ -4025,7 +4025,7 @@ "create-react-class": { "version": "15.6.3", "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.3.tgz", - "integrity": "sha1-LXMjf7P5cK5uvgEanmb0bbyoADY=", + "integrity": "sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg==", "requires": { "fbjs": "^0.8.9", "loose-envify": "^1.3.1", @@ -4192,7 +4192,7 @@ "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "requires": { "ms": "2.0.0" @@ -4317,7 +4317,7 @@ "define-property": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha1-1Flono1lS6d+AqgX+HENcCyxbp0=", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", "dev": true, "requires": { "is-descriptor": "^1.0.2", @@ -4327,7 +4327,7 @@ "is-accessor-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -4336,7 +4336,7 @@ "is-data-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -4345,7 +4345,7 @@ "is-descriptor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", @@ -4362,7 +4362,7 @@ "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha1-ARRrNqYhjmTljzqNZt5df8b20FE=", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", "dev": true } } @@ -4399,7 +4399,7 @@ "delegate": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", - "integrity": "sha1-tmtxwxWFIuirV0T3INjKDCr1kWY=" + "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==" }, "delegates": { "version": "1.0.0", @@ -4464,7 +4464,7 @@ "path-type": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha1-zvMdyOCho7sNEFwM2Xzzv0f0428=", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", "dev": true, "requires": { "pify": "^3.0.0" @@ -4564,7 +4564,7 @@ "domain-browser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", - "integrity": "sha1-PTH1AZGmdJ3RN1p/Ui6CPULlTto=", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", "dev": true }, "domelementtype": { @@ -4595,7 +4595,7 @@ "dot-prop": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", - "integrity": "sha1-HxngwuGqDjJ5fEl5nyg3rGr2nFc=", + "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", "dev": true, "requires": { "is-obj": "^1.0.0" @@ -4690,7 +4690,7 @@ "end-of-stream": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", - "integrity": "sha1-7SljTRm6ukY7bOa4CjchPqtx7EM=", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", "dev": true, "requires": { "once": "^1.4.0" @@ -4716,7 +4716,7 @@ "errno": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", - "integrity": "sha1-RoTXF3mtOa8Xfj8AeZb3xnyFJhg=", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", "dev": true, "requires": { "prr": "~1.0.1" @@ -5356,7 +5356,7 @@ "esquery": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", - "integrity": "sha1-QGxRZYsfWZGl+bYrHcJbAOPlxwg=", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", "dev": true, "requires": { "estraverse": "^4.0.0" @@ -5365,7 +5365,7 @@ "esrecurse": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha1-AHo7n9vCs7uH5IeeoZyS/b05Qs8=", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", "dev": true, "requires": { "estraverse": "^4.1.0" @@ -5622,7 +5622,7 @@ "external-editor": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", - "integrity": "sha1-BFURz9jRM/OEZnPRBHwVTiFK09U=", + "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", "requires": { "chardet": "^0.4.0", "iconv-lite": "^0.4.17", @@ -6035,7 +6035,7 @@ "fs-readdir-recursive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", - "integrity": "sha1-4y/AMKLM7kSmtTcTCNpUvgs5fSc=", + "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", "dev": true }, "fs-write-stream-atomic": { @@ -6599,7 +6599,7 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha1-pWiZ0+o8m6uHS7l3O3xe3pL0iV0=", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, "functional-red-black-tree": { @@ -6828,7 +6828,7 @@ }, "globby": { "version": "6.1.0", - "resolved": "http://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", "dev": true, "requires": { @@ -6841,7 +6841,7 @@ "dependencies": { "pify": { "version": "2.3.0", - "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true } @@ -7985,7 +7985,7 @@ "ini": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha1-7uJfVtscnsYIXgwid4CD9Zar+Sc=" + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" }, "inline-style-prefixer": { "version": "2.0.5", @@ -8035,7 +8035,7 @@ "invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha1-YQ88ksk1nOHbYW5TgAjSP/NRWOY=", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", "dev": true, "requires": { "loose-envify": "^1.0.0" @@ -8108,7 +8108,7 @@ "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha1-76ouqdqg16suoTqXsritUf776L4=", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, "is-callable": { @@ -8141,7 +8141,7 @@ "is-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha1-Nm2CQN3kh8pRgjsaufB6EKeCUco=", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", "dev": true, "requires": { "is-accessor-descriptor": "^0.1.6", @@ -8152,7 +8152,7 @@ "kind-of": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha1-cpyR4thXt6QZofmqZWhcTDP1hF0=", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", "dev": true } } @@ -8267,7 +8267,7 @@ "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha1-LBY7P6+xtgbZ0Xko8FwqHDjgdnc=", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, "requires": { "isobject": "^3.0.1" @@ -8321,7 +8321,7 @@ "is-supported-regexp-flag": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-supported-regexp-flag/-/is-supported-regexp-flag-1.0.1.tgz", - "integrity": "sha1-Ie4WUY0sHdPt0+mg1X5QIHrDZMo=", + "integrity": "sha512-3vcJecUUrpgCqc/ca0aWeNu64UGgxcvO60K/Fkr1N6RSvfGCTU60UKN68JDmKokgba0rFFJs12EnzOQa14ubKQ==", "dev": true }, "is-symbol": { @@ -8354,7 +8354,7 @@ "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha1-0YUOuXkezRjmGCzhKjDzlmNLsZ0=", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", "dev": true }, "is-word-character": { @@ -8509,7 +8509,7 @@ "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha1-u4Z8+zRQ5pEHwTHRxRS6s9yLyqk=", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", "dev": true }, "json-schema": { @@ -8526,7 +8526,7 @@ "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, "json-stable-stringify-without-jsonify": { @@ -8729,7 +8729,7 @@ "dependencies": { "pify": { "version": "2.3.0", - "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true }, @@ -8835,7 +8835,7 @@ "lodash.merge": { "version": "4.6.1", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.1.tgz", - "integrity": "sha1-rcJdnLmbk5HFliTzefu6YNcRHVQ=" + "integrity": "sha512-AOYza4+Hf5z1/0Hztxpm2/xiPZgi/cjMqdnKTUWTBSKchJlxXXuUSxCCl8rJlf4g6yww/j6mA8nC8Hw/EZWxKQ==" }, "lodash.tail": { "version": "4.1.1", @@ -8851,7 +8851,7 @@ "log-symbols": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha1-V0Dhxdbw39pK2TI7UzIQfva0xAo=", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", "dev": true, "requires": { "chalk": "^2.0.1" @@ -8860,7 +8860,7 @@ "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { "color-convert": "^1.9.0" @@ -8880,7 +8880,7 @@ "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -9180,7 +9180,7 @@ "messageformat-parser": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/messageformat-parser/-/messageformat-parser-1.1.0.tgz", - "integrity": "sha1-E7oiUKdrvejg/KDbs0dflcWUqQo=" + "integrity": "sha512-Hwem6G3MsKDLS1FtBRGIs8T50P1Q00r3srS6QJePCFbad9fq0nYxwf3rnU2BreApRGhmpKMV7oZI06Sy1c9TPA==" }, "methods": { "version": "1.1.2", @@ -9230,7 +9230,7 @@ "mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha1-Ms2eXGRVO9WNGaVor0Uqz/BJgbE=", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "dev": true }, "mime-db": { @@ -9251,7 +9251,7 @@ "mimic-fn": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha1-ggyGo5M0ZA6ZUWkovQP8qIBX0CI=" + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" }, "min-document": { "version": "2.19.0", @@ -9289,7 +9289,7 @@ "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { "brace-expansion": "^1.1.7" } @@ -9302,7 +9302,7 @@ "minimist-options": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz", - "integrity": "sha1-+6TIGRM54T7PTWG+sD8HAQPz2VQ=", + "integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==", "dev": true, "requires": { "arrify": "^1.0.1", @@ -9340,7 +9340,7 @@ "is-extendable": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha1-p0cPnkJnM9gb2B4RVSZOOjUHyrQ=", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { "is-plain-object": "^2.0.4" @@ -9436,7 +9436,7 @@ "nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha1-uHqKpPwN6P5r6IiVs4mD/yZb0Rk=", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", "dev": true, "requires": { "arr-diff": "^4.0.0", @@ -9467,7 +9467,7 @@ "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha1-ARRrNqYhjmTljzqNZt5df8b20FE=", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", "dev": true } } @@ -9591,7 +9591,7 @@ } }, "ngFlowchart": { - "version": "git://github.com/thingsboard/ngFlowchart.git#1343a7478961f68280d81f0ecda4e722a2068e0f", + "version": "git://github.com/thingsboard/ngFlowchart.git#ad172c26bb731f4e4e79d05dfa8cdc3f59cd1690", "from": "git://github.com/thingsboard/ngFlowchart.git#master" }, "ngclipboard": { @@ -9651,7 +9651,7 @@ "no-case": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", - "integrity": "sha1-YLgTOWvjmz8SiKTB7V0efSi0ZKw=", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", "dev": true, "requires": { "lower-case": "^1.1.1" @@ -9670,7 +9670,7 @@ "node-fetch": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", - "integrity": "sha1-mA9vcthSEaU0fGsrwYxbhMPrR+8=", + "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", "requires": { "encoding": "^0.1.11", "is-stream": "^1.0.1" @@ -10097,7 +10097,7 @@ "npmlog": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha1-CKfyqL9zRgR3mp76StXMcXq7lUs=", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "dev": true, "requires": { "are-we-there-yet": "~1.1.2", @@ -10426,7 +10426,7 @@ "osenv": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha1-hc36+uso6Gd/QW4odZK18/SepBA=", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "requires": { "os-homedir": "^1.0.0", "os-tmpdir": "^1.0.0" @@ -10688,7 +10688,7 @@ "dependencies": { "pify": { "version": "2.3.0", - "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true } @@ -10887,7 +10887,7 @@ "postcss-loader": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-3.0.0.tgz", - "integrity": "sha1-a5eUPkfHLYRfqeA/Jzdz1OjdbC0=", + "integrity": "sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA==", "dev": true, "requires": { "loader-utils": "^1.1.0", @@ -10899,7 +10899,7 @@ "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { "color-convert": "^1.9.0" @@ -10973,7 +10973,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, "supports-color": { @@ -11049,7 +11049,7 @@ "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { "color-convert": "^1.9.0" @@ -11080,13 +11080,13 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -11112,7 +11112,7 @@ "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { "color-convert": "^1.9.0" @@ -11143,13 +11143,13 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -11170,7 +11170,7 @@ "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { "color-convert": "^1.9.0" @@ -11201,13 +11201,13 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -11227,7 +11227,7 @@ "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { "color-convert": "^1.9.0" @@ -11258,13 +11258,13 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -11359,7 +11359,7 @@ "private": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha1-I4Hts2ifelPWUxkAYPz4ItLzaP8=", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", "dev": true }, "process": { @@ -11383,7 +11383,7 @@ "promise": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha1-BktyYCsY+Q8pGSuLG8QY/9Hr078=", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", "requires": { "asap": "~2.0.3" } @@ -11463,7 +11463,7 @@ "pumpify": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", - "integrity": "sha1-NlE74karJ1cLGjdKXOJ4v9dDcM4=", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", "dev": true, "requires": { "duplexify": "^3.6.0", @@ -11541,7 +11541,7 @@ "ramda": { "version": "0.25.0", "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.25.0.tgz", - "integrity": "sha1-j99oIxz/qQvC+UYDkKDLdKKbKak=", + "integrity": "sha512-GXpfrYVPwx3K7RQ6aYT8KPS8XViSXUVJT1ONhoKPE9VAleW42YE+U+8VEyGWt41EnEQW7gwecYJriTI0pKoecQ==", "dev": true }, "randomatic": { @@ -11671,7 +11671,7 @@ "rc-menu": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-5.1.4.tgz", - "integrity": "sha1-5d8I/ouDPoFGkTX/E7MKuPIf88Y=", + "integrity": "sha512-ZUkUNda70GtTXcQDiO3rSDdk3sgIwDwzPUm5dVM8nRH/j84qv0BVBkIUwIBu8+s+G3G9lWLurRqh22dCqZPeOA==", "requires": { "babel-runtime": "6.x", "classnames": "2.x", @@ -11702,7 +11702,7 @@ "rc-trigger": { "version": "1.11.5", "resolved": "https://registry.npmjs.org/rc-trigger/-/rc-trigger-1.11.5.tgz", - "integrity": "sha1-+I+fhODnn44O8cjRv4rCIItxViA=", + "integrity": "sha512-MBuUPw1nFzA4K7jQOwb7uvFaZFjXGd00EofUYiZ+l/fgKVq8wnLC0lkv36kwqM7vfKyftRo2sh7cWVpdPuNnnw==", "requires": { "babel-runtime": "6.x", "create-react-class": "15.x", @@ -11866,7 +11866,7 @@ "react-transition-group": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-1.2.1.tgz", - "integrity": "sha1-4R9yslf5IbITIpp3TfRmEjRsfKY=", + "integrity": "sha512-CWaL3laCmgAFdxdKbhhps+c0HRGF4c+hdM4H23+FI1QBNUyx/AMeIJGWorehPNSaKnQNOAxL7PQmqMu78CDj3Q==", "requires": { "chain-function": "^1.0.0", "dom-helpers": "^3.2.0", @@ -11878,7 +11878,7 @@ "reactcss": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz", - "integrity": "sha1-wAATh15Vexzw39mjaKHD2rO1SN0=", + "integrity": "sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==", "requires": { "lodash": "^4.0.1" } @@ -11907,7 +11907,7 @@ "readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -12257,12 +12257,12 @@ "regenerator-runtime": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha1-vgWtf5v30i4Fb5cmzuUBf78Z4uk=" + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" }, "regex-cache": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", - "integrity": "sha1-db3FiioUls7EihKDW8VMjVYjNt0=", + "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", "dev": true, "requires": { "is-equal-shallow": "^0.1.3" @@ -12271,7 +12271,7 @@ "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha1-H07OJ+ALC2XgJHpoEOaoXYOldSw=", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", "dev": true, "requires": { "extend-shallow": "^3.0.2", @@ -12436,7 +12436,7 @@ "require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha1-iaf92TgmEmcxjq/hT5wy5ZjDaQk=", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true }, "require-main-filename": { @@ -12529,7 +12529,7 @@ "ret": { "version": "0.1.15", "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha1-uKSCXVvbH8P29Twrwz+BOIaBx7w=", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", "dev": true }, "retry": { @@ -12622,7 +12622,7 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo=" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sass-graph": { "version": "2.2.4", @@ -12674,7 +12674,7 @@ "schema-utils": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha1-C3mpMgTXtgDUsoUNH2bCo0lRx3A=", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", "dev": true, "requires": { "ajv": "^6.1.0", @@ -12986,7 +12986,7 @@ "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha1-ZJIufFZbDhQgS6GqfWlkJ40lGC0=", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", "dev": true, "requires": { "base": "^0.11.1", @@ -13022,7 +13022,7 @@ "snapdragon-node": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha1-bBdfhv8UvbByRWPo88GwIaKGhTs=", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", "dev": true, "requires": { "define-property": "^1.0.0", @@ -13042,7 +13042,7 @@ "is-accessor-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -13051,7 +13051,7 @@ "is-data-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -13060,7 +13060,7 @@ "is-descriptor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", @@ -13077,7 +13077,7 @@ "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha1-ARRrNqYhjmTljzqNZt5df8b20FE=", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", "dev": true } } @@ -13085,7 +13085,7 @@ "snapdragon-util": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha1-+VZHlIbyrNeXAGk/b3uAXkWrVuI=", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", "dev": true, "requires": { "kind-of": "^3.2.0" @@ -13094,7 +13094,7 @@ "sockjs": { "version": "0.3.19", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz", - "integrity": "sha1-2Xa76ACve9IK4IWY1YI5NQiZPA0=", + "integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==", "dev": true, "requires": { "faye-websocket": "^0.10.0", @@ -13165,7 +13165,7 @@ "source-map-resolve": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", - "integrity": "sha1-cuLMNAlVQ+Q7LGKyxMENSpBU8lk=", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", "dev": true, "requires": { "atob": "^2.1.1", @@ -13218,7 +13218,7 @@ "spdx-expression-parse": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha1-meEZt6XaAOBUkcn6M4t5BII7QdA=", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", "dev": true, "requires": { "spdx-exceptions": "^2.1.0", @@ -13312,7 +13312,7 @@ "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha1-fLCd2jqGWFcFxks5pkZgOGguj+I=", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", "dev": true, "requires": { "extend-shallow": "^3.0.0" @@ -13459,7 +13459,7 @@ "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha1-q5Pyeo3BPSjKyBXEYhQ6bZASrp4=", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "requires": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" @@ -13483,7 +13483,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -13492,7 +13492,7 @@ "stringify-entities": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-1.3.2.tgz", - "integrity": "sha1-qYQX5Ucf0iez5F09sYYcEcr2aPc=", + "integrity": "sha512-nrBAQClJAPN2p+uGCVJRPIPakKeKWZ9GtBCmormE7pWOSlHat7+x5A8gx85M7HM5Dt0BP3pP5RhVW77WdbJJ3A==", "dev": true, "requires": { "character-entities-html4": "^1.0.0", @@ -13614,7 +13614,7 @@ "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { "color-convert": "^1.9.0" @@ -13940,7 +13940,7 @@ "path-type": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha1-zvMdyOCho7sNEFwM2Xzzv0f0428=", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", "dev": true, "requires": { "pify": "^3.0.0" @@ -14014,7 +14014,7 @@ "slice-ansi": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", - "integrity": "sha1-BE8aSdiEL/MHqta1Be0Xi9lQE00=", + "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", "dev": true, "requires": { "is-fullwidth-code-point": "^2.0.0" @@ -14023,7 +14023,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, "strip-indent": { @@ -14035,7 +14035,7 @@ "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -14131,7 +14131,7 @@ "stylelint-webpack-plugin": { "version": "0.10.5", "resolved": "https://registry.npmjs.org/stylelint-webpack-plugin/-/stylelint-webpack-plugin-0.10.5.tgz", - "integrity": "sha1-C24NNz/14DuqgZfr4PJiWYG9Jms=", + "integrity": "sha512-jtYx3aJ2qDMvBMswe5NRPTO7kJgAKafc6GilAkWDp/ewoAmnoxA6TsYMnIPtLECRLwXevaCPvlh2JEUMGZCoUQ==", "dev": true, "requires": { "arrify": "^1.0.1", @@ -14155,7 +14155,7 @@ "braces": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha1-WXn9PxTNUxVl5fot8av/8d+u5yk=", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, "requires": { "arr-flatten": "^1.1.0", @@ -14257,7 +14257,7 @@ "is-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha1-Nm2CQN3kh8pRgjsaufB6EKeCUco=", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", "dev": true, "requires": { "is-accessor-descriptor": "^0.1.6", @@ -14268,7 +14268,7 @@ "kind-of": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha1-cpyR4thXt6QZofmqZWhcTDP1hF0=", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", "dev": true } } @@ -14276,7 +14276,7 @@ "extglob": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha1-rQD+TcYSqSMuhxhxHcXLWrAoVUM=", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", "dev": true, "requires": { "array-unique": "^0.3.2", @@ -14335,7 +14335,7 @@ "is-accessor-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -14344,7 +14344,7 @@ "is-data-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -14353,7 +14353,7 @@ "is-descriptor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", @@ -14390,13 +14390,13 @@ "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha1-ARRrNqYhjmTljzqNZt5df8b20FE=", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", "dev": true }, "micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha1-cIWbyVyYQJUvNZoGij/En57PrCM=", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, "requires": { "arr-diff": "^4.0.0", @@ -14428,7 +14428,7 @@ "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { "color-convert": "^1.9.0" @@ -14459,13 +14459,13 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -14487,7 +14487,7 @@ "symbol-observable": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", - "integrity": "sha1-wiaIrtTqs83C3+rLtWFmBWCgCAQ=" + "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" }, "table": { "version": "5.4.4", @@ -14722,7 +14722,7 @@ "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha1-bTQzWIl2jSGyvNoKonfO07G/rfk=", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "requires": { "os-tmpdir": "~1.0.2" } @@ -14745,7 +14745,7 @@ "to-regex": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha1-E8/dmzNlUvMLUfM6iuG0Knp1mc4=", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", "dev": true, "requires": { "define-property": "^2.0.2", @@ -14951,7 +14951,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true } } @@ -15101,7 +15101,7 @@ "unified": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/unified/-/unified-6.2.0.tgz", - "integrity": "sha1-f71jD3GRJtZ9QMZEt+P2FwNfbbo=", + "integrity": "sha512-1k+KPhlVtqmG99RaTbAv/usu85fcSRu3wY8X+vnsEhIxNP5VbVIDiXnLqyKIG+UMdyTg0ZX9EI6k2AfjJkHPtA==", "dev": true, "requires": { "bail": "^1.0.0", @@ -15175,7 +15175,7 @@ "unist-util-stringify-position": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-1.1.2.tgz", - "integrity": "sha1-Pzf881EnncvKdICrWIm7ioMu4cY=", + "integrity": "sha512-pNCVrk64LZv1kElr0N1wPiHEUoXNVFERp+mlTg/s9R5Lwg87f9bM/3sQB99w+N9D/qnM9ar3+AKDBwo/gm/iQQ==", "dev": true }, "unist-util-visit": { @@ -15263,7 +15263,7 @@ "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha1-lMVA4f93KVbiKZUHwBCupsiDjrA=", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", "dev": true, "requires": { "punycode": "^2.1.0" @@ -15272,7 +15272,7 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha1-tYsBCsQMIsVldhbI0sLALHv0eew=", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true } } @@ -15343,7 +15343,7 @@ "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha1-1QyMrHmhn7wg8pEfVuuXP04QBw8=", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", "dev": true }, "util": { @@ -15455,7 +15455,7 @@ "vfile": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/vfile/-/vfile-2.3.0.tgz", - "integrity": "sha1-5i2OcrIOg8MkvGxnJ47ickiL+Eo=", + "integrity": "sha512-ASt4mBUHcTpMKD/l5Q+WJXNtshlWxOogYyGYYrg4lt/vuRjC1EFQtlAofL5VmtVNIZJzWYFJjzGWZ0Gw8pzW1w==", "dev": true, "requires": { "is-buffer": "^1.1.4", @@ -16544,7 +16544,7 @@ "websocket-extensions": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", - "integrity": "sha1-XS/yKXcAPsaHpLhwc9+7rBRszyk=", + "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==", "dev": true }, "whatwg-fetch": { @@ -16640,7 +16640,7 @@ "ws": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.5.tgz", - "integrity": "sha1-y9nm514J/F0skAFfIfDECHXg3VE=", + "integrity": "sha512-o3KqipXNUdS7wpQzBHSe180lBGO60SoK0yVo3CYJgb2MkobuWuBX6dhkYP5ORCLd55y+SaflMOV5fqAB53ux4w==", "requires": { "options": ">=0.0.5", "ultron": "1.0.x" From cae1f02691e6ef58337f869ad33645915b192f44 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 8 Aug 2019 17:55:11 +0300 Subject: [PATCH 003/133] PageLink refactoring. --- .../rulechain/SystemRuleChainManager.java | 4 +- .../server/controller/AlarmController.java | 11 +- .../server/controller/AssetController.java | 30 +-- .../server/controller/AuditLogController.java | 54 ++--- .../server/controller/BaseController.java | 33 +-- .../server/controller/CustomerController.java | 17 +- .../controller/DashboardController.java | 44 ++-- .../server/controller/DeviceController.java | 30 +-- .../controller/EntityViewController.java | 30 +-- .../server/controller/EventController.java | 26 +-- .../controller/RuleChainController.java | 17 +- .../server/controller/TenantController.java | 17 +- .../server/controller/UserController.java | 30 +-- .../controller/WidgetsBundleController.java | 17 +- .../update/DefaultDataUpdateService.java | 6 +- .../install/update/PaginatedUpdater.java | 12 +- .../state/DefaultDeviceStateService.java | 10 +- .../controller/AbstractControllerTest.java | 35 ++-- .../AbstractRuleEngineControllerTest.java | 6 +- .../controller/BaseAssetControllerTest.java | 108 +++++----- .../BaseAuditLogControllerTest.java | 22 +- .../BaseCustomerControllerTest.java | 34 +-- .../BaseDashboardControllerTest.java | 41 ++-- .../controller/BaseDeviceControllerTest.java | 108 +++++----- .../BaseEntityViewControllerTest.java | 44 ++-- .../controller/BaseTenantControllerTest.java | 40 ++-- .../controller/BaseUserControllerTest.java | 68 +++--- .../BaseWidgetsBundleControllerTest.java | 26 +-- ...AbstractRuleEngineFlowIntegrationTest.java | 7 +- ...actRuleEngineLifecycleIntegrationTest.java | 4 +- .../server/dao/alarm/AlarmService.java | 4 +- .../server/dao/asset/AssetService.java | 12 +- .../server/dao/audit/AuditLogService.java | 10 +- .../component/ComponentDescriptorService.java | 8 +- .../server/dao/customer/CustomerService.java | 6 +- .../dao/dashboard/DashboardService.java | 9 +- .../server/dao/device/DeviceService.java | 12 +- .../dao/entityview/EntityViewService.java | 12 +- .../server/dao/event/EventService.java | 6 +- .../server/dao/rule/RuleChainService.java | 6 +- .../server/dao/tenant/TenantService.java | 6 +- .../server/dao/user/UserService.java | 10 +- .../dao/widget/WidgetsBundleService.java | 10 +- .../page/{TimePageData.java => PageData.java} | 46 ++--- .../common/data/page/PageDataIterable.java | 17 +- .../server/common/data/page/PageLink.java | 60 ++++++ .../{BasePageLink.java => SortOrder.java} | 28 +-- .../server/common/data/page/TextPageData.java | 71 ------- .../server/common/data/page/TextPageLink.java | 80 ------- .../server/common/data/page/TimePageLink.java | 49 +++-- .../org/thingsboard/server/dao/DaoUtil.java | 59 ++++++ .../server/dao/NoSqlDaoConfig.java | 33 --- .../server/dao/alarm/AlarmDao.java | 3 +- .../server/dao/alarm/BaseAlarmService.java | 31 ++- .../server/dao/asset/AssetDao.java | 11 +- .../server/dao/asset/BaseAssetService.java | 36 ++-- .../server/dao/audit/AuditLogDao.java | 17 +- .../server/dao/audit/AuditLogServiceImpl.java | 26 +-- .../dao/audit/DummyAuditLogServiceImpl.java | 18 +- .../BaseComponentDescriptorService.java | 18 +- .../dao/component/ComponentDescriptorDao.java | 7 +- .../server/dao/customer/CustomerDao.java | 5 +- .../dao/customer/CustomerServiceImpl.java | 13 +- .../dao/dashboard/DashboardInfoDao.java | 7 +- .../dao/dashboard/DashboardServiceImpl.java | 32 +-- .../server/dao/device/DeviceDao.java | 11 +- .../server/dao/device/DeviceServiceImpl.java | 36 ++-- .../server/dao/entityview/EntityViewDao.java | 15 +- .../dao/entityview/EntityViewServiceImpl.java | 38 ++-- .../server/dao/event/BaseEventService.java | 12 +- .../server/dao/event/EventDao.java | 5 +- .../dao/nosql/CassandraAbstractModelDao.java | 195 ------------------ .../nosql/CassandraAbstractSearchTextDao.java | 83 -------- .../nosql/CassandraAbstractSearchTimeDao.java | 107 ---------- .../server/dao/relation/RelationDao.java | 3 +- .../server/dao/rule/BaseRuleChainService.java | 13 +- .../server/dao/rule/RuleChainDao.java | 5 +- .../server/dao/service/PaginatedRemover.java | 18 +- .../dao/service/TimePaginatedRemover.java | 14 +- .../server/dao/service/Validator.java | 12 +- .../dao/sql/JpaAbstractSearchTimeDao.java | 52 +---- .../server/dao/sql/alarm/JpaAlarmDao.java | 13 +- .../server/dao/sql/asset/AssetRepository.java | 28 +-- .../server/dao/sql/asset/JpaAssetDao.java | 31 ++- .../server/dao/sql/audit/JpaAuditLogDao.java | 36 +--- .../ComponentDescriptorRepository.java | 16 +- .../JpaBaseComponentDescriptorDao.java | 17 +- .../dao/sql/customer/CustomerRepository.java | 10 +- .../dao/sql/customer/JpaCustomerDao.java | 10 +- .../dashboard/DashboardInfoRepository.java | 10 +- .../sql/dashboard/JpaDashboardInfoDao.java | 22 +- .../dao/sql/device/DeviceRepository.java | 28 +-- .../server/dao/sql/device/JpaDeviceDao.java | 31 ++- .../sql/entityview/EntityViewRepository.java | 28 +-- .../dao/sql/entityview/JpaEntityViewDao.java | 35 ++-- .../server/dao/sql/event/JpaBaseEventDao.java | 10 +- .../dao/sql/relation/JpaRelationDao.java | 13 +- .../server/dao/sql/rule/JpaRuleChainDao.java | 10 +- .../dao/sql/rule/RuleChainRepository.java | 10 +- .../server/dao/sql/tenant/JpaTenantDao.java | 10 +- .../dao/sql/tenant/TenantRepository.java | 10 +- .../server/dao/sql/user/JpaUserDao.java | 17 +- .../server/dao/sql/user/UserRepository.java | 12 +- .../dao/sql/widget/JpaWidgetsBundleDao.java | 24 +-- .../sql/widget/WidgetsBundleRepository.java | 22 +- .../server/dao/tenant/TenantDao.java | 5 +- .../server/dao/tenant/TenantServiceImpl.java | 13 +- .../thingsboard/server/dao/user/UserDao.java | 7 +- .../server/dao/user/UserServiceImpl.java | 22 +- .../server/dao/widget/WidgetsBundleDao.java | 9 +- .../dao/widget/WidgetsBundleServiceImpl.java | 36 ++-- .../dao/service/BaseAlarmServiceTest.java | 40 ++-- .../dao/service/BaseAssetServiceTest.java | 76 +++---- .../dao/service/BaseCustomerServiceTest.java | 26 +-- .../dao/service/BaseDashboardServiceTest.java | 31 ++- .../dao/service/BaseDeviceServiceTest.java | 76 +++---- .../dao/service/BaseRuleChainServiceTest.java | 26 +-- .../dao/service/BaseTenantServiceTest.java | 28 +-- .../dao/service/BaseUserServiceTest.java | 48 ++--- .../service/BaseWidgetsBundleServiceTest.java | 32 +-- .../service/event/BaseEventServiceTest.java | 23 ++- .../server/dao/sql/asset/JpaAssetDaoTest.java | 39 ++-- .../JpaBaseComponentDescriptorDaoTest.java | 31 +-- .../dao/sql/customer/JpaCustomerDaoTest.java | 15 +- .../dashboard/JpaDashboardInfoDaoTest.java | 14 +- .../dao/sql/device/JpaDeviceDaoTest.java | 16 +- .../dao/sql/event/JpaBaseEventDaoTest.java | 67 +++--- .../dao/sql/tenant/JpaTenantDaoTest.java | 25 ++- .../server/dao/sql/user/JpaUserDaoTest.java | 41 ++-- .../sql/widget/JpaWidgetsBundleDaoTest.java | 68 +++--- .../msa/connectivity/MqttClientTest.java | 8 +- .../action/TbAbstractRelationActionNode.java | 6 +- 132 files changed, 1546 insertions(+), 2063 deletions(-) rename common/data/src/main/java/org/thingsboard/server/common/data/page/{TimePageData.java => PageData.java} (50%) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/page/PageLink.java rename common/data/src/main/java/org/thingsboard/server/common/data/page/{BasePageLink.java => SortOrder.java} (59%) delete mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/page/TextPageData.java delete mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/page/TextPageLink.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/NoSqlDaoConfig.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractModelDao.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractSearchTextDao.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractSearchTimeDao.java diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/SystemRuleChainManager.java b/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/SystemRuleChainManager.java index 4a6ec45ea4..74fce99e6e 100644 --- a/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/SystemRuleChainManager.java +++ b/application/src/main/java/org/thingsboard/server/actors/shared/rulechain/SystemRuleChainManager.java @@ -18,8 +18,8 @@ package org.thingsboard.server.actors.shared.rulechain; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.service.DefaultActorService; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction; -import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.dao.model.ModelConstants; @@ -33,7 +33,7 @@ public class SystemRuleChainManager extends RuleChainManager { @Override protected FetchFunction getFetchEntitiesFunction() { - return link -> new TextPageData<>(Collections.emptyList(), link); + return link -> new PageData<>(); } @Override diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java index 5f9585209d..620e7dc104 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java @@ -39,7 +39,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; -import org.thingsboard.server.common.data.page.TimePageData; +import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; @@ -143,16 +143,16 @@ public class AlarmController extends BaseController { @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/alarm/{entityType}/{entityId}", method = RequestMethod.GET) @ResponseBody - public TimePageData getAlarms( + public PageData getAlarms( @PathVariable("entityType") String strEntityType, @PathVariable("entityId") String strEntityId, @RequestParam(required = false) String searchStatus, @RequestParam(required = false) String status, - @RequestParam int limit, + @RequestParam int pageSize, + @RequestParam int page, @RequestParam(required = false) Long startTime, @RequestParam(required = false) Long endTime, @RequestParam(required = false, defaultValue = "false") boolean ascOrder, - @RequestParam(required = false) String offset, @RequestParam(required = false) Boolean fetchOriginator ) throws ThingsboardException { checkParameter("EntityId", strEntityId); @@ -166,7 +166,8 @@ public class AlarmController extends BaseController { } checkEntityId(entityId, Operation.READ); try { - TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset); + TimePageLink pageLink = createTimePageLink(pageSize, page, "", + "id", ascOrder ? "asc" : "desc", startTime, endTime); return checkNotNull(alarmService.findAlarms(getCurrentUser().getTenantId(), new AlarmQuery(entityId, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator)).get()); } catch (Exception e) { throw handleException(e); diff --git a/application/src/main/java/org/thingsboard/server/controller/AssetController.java b/application/src/main/java/org/thingsboard/server/controller/AssetController.java index cb4a989d60..060ce10cb7 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AssetController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AssetController.java @@ -37,8 +37,8 @@ import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +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.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.model.ModelConstants; @@ -207,17 +207,18 @@ public class AssetController extends BaseController { } @PreAuthorize("hasAuthority('TENANT_ADMIN')") - @RequestMapping(value = "/tenant/assets", params = {"limit"}, method = RequestMethod.GET) + @RequestMapping(value = "/tenant/assets", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody - public TextPageData getTenantAssets( - @RequestParam int limit, + public PageData getTenantAssets( + @RequestParam int pageSize, + @RequestParam int page, @RequestParam(required = false) String type, @RequestParam(required = false) String textSearch, - @RequestParam(required = false) String idOffset, - @RequestParam(required = false) String textOffset) throws ThingsboardException { + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder) throws ThingsboardException { try { TenantId tenantId = getCurrentUser().getTenantId(); - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); if (type != null && type.trim().length()>0) { return checkNotNull(assetService.findAssetsByTenantIdAndType(tenantId, type, pageLink)); } else { @@ -242,21 +243,22 @@ public class AssetController extends BaseController { } @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") - @RequestMapping(value = "/customer/{customerId}/assets", params = {"limit"}, method = RequestMethod.GET) + @RequestMapping(value = "/customer/{customerId}/assets", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody - public TextPageData getCustomerAssets( + public PageData getCustomerAssets( @PathVariable("customerId") String strCustomerId, - @RequestParam int limit, + @RequestParam int pageSize, + @RequestParam int page, @RequestParam(required = false) String type, @RequestParam(required = false) String textSearch, - @RequestParam(required = false) String idOffset, - @RequestParam(required = false) String textOffset) throws ThingsboardException { + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder) throws ThingsboardException { checkParameter("customerId", strCustomerId); try { TenantId tenantId = getCurrentUser().getTenantId(); CustomerId customerId = new CustomerId(toUUID(strCustomerId)); checkCustomerId(customerId, Operation.READ); - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); if (type != null && type.trim().length()>0) { return checkNotNull(assetService.findAssetsByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink)); } else { diff --git a/application/src/main/java/org/thingsboard/server/controller/AuditLogController.java b/application/src/main/java/org/thingsboard/server/controller/AuditLogController.java index 61fe7a384e..c3ef7f5f17 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AuditLogController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AuditLogController.java @@ -28,7 +28,7 @@ import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.common.data.page.TimePageData; +import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.TimePageLink; import java.util.UUID; @@ -38,19 +38,20 @@ import java.util.UUID; public class AuditLogController extends BaseController { @PreAuthorize("hasAuthority('TENANT_ADMIN')") - @RequestMapping(value = "/audit/logs/customer/{customerId}", params = {"limit"}, method = RequestMethod.GET) + @RequestMapping(value = "/audit/logs/customer/{customerId}", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody - public TimePageData getAuditLogsByCustomerId( + public PageData getAuditLogsByCustomerId( @PathVariable("customerId") String strCustomerId, - @RequestParam int limit, + @RequestParam int pageSize, + @RequestParam int page, @RequestParam(required = false) Long startTime, @RequestParam(required = false) Long endTime, - @RequestParam(required = false, defaultValue = "false") boolean ascOrder, - @RequestParam(required = false) String offset) throws ThingsboardException { + @RequestParam(required = false, defaultValue = "false") boolean ascOrder) throws ThingsboardException { try { checkParameter("CustomerId", strCustomerId); TenantId tenantId = getCurrentUser().getTenantId(); - TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset); + TimePageLink pageLink = createTimePageLink(pageSize, page, "", + "createdTime", ascOrder ? "asc" : "desc", startTime, endTime); return checkNotNull(auditLogService.findAuditLogsByTenantIdAndCustomerId(tenantId, new CustomerId(UUID.fromString(strCustomerId)), pageLink)); } catch (Exception e) { throw handleException(e); @@ -58,19 +59,20 @@ public class AuditLogController extends BaseController { } @PreAuthorize("hasAuthority('TENANT_ADMIN')") - @RequestMapping(value = "/audit/logs/user/{userId}", params = {"limit"}, method = RequestMethod.GET) + @RequestMapping(value = "/audit/logs/user/{userId}", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody - public TimePageData getAuditLogsByUserId( + public PageData getAuditLogsByUserId( @PathVariable("userId") String strUserId, - @RequestParam int limit, + @RequestParam int pageSize, + @RequestParam int page, @RequestParam(required = false) Long startTime, @RequestParam(required = false) Long endTime, - @RequestParam(required = false, defaultValue = "false") boolean ascOrder, - @RequestParam(required = false) String offset) throws ThingsboardException { + @RequestParam(required = false, defaultValue = "false") boolean ascOrder) throws ThingsboardException { try { checkParameter("UserId", strUserId); TenantId tenantId = getCurrentUser().getTenantId(); - TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset); + TimePageLink pageLink = createTimePageLink(pageSize, page, "", + "createdTime", ascOrder ? "asc" : "desc", startTime, endTime); return checkNotNull(auditLogService.findAuditLogsByTenantIdAndUserId(tenantId, new UserId(UUID.fromString(strUserId)), pageLink)); } catch (Exception e) { throw handleException(e); @@ -78,21 +80,22 @@ public class AuditLogController extends BaseController { } @PreAuthorize("hasAuthority('TENANT_ADMIN')") - @RequestMapping(value = "/audit/logs/entity/{entityType}/{entityId}", params = {"limit"}, method = RequestMethod.GET) + @RequestMapping(value = "/audit/logs/entity/{entityType}/{entityId}", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody - public TimePageData getAuditLogsByEntityId( + public PageData getAuditLogsByEntityId( @PathVariable("entityType") String strEntityType, @PathVariable("entityId") String strEntityId, - @RequestParam int limit, + @RequestParam int pageSize, + @RequestParam int page, @RequestParam(required = false) Long startTime, @RequestParam(required = false) Long endTime, - @RequestParam(required = false, defaultValue = "false") boolean ascOrder, - @RequestParam(required = false) String offset) throws ThingsboardException { + @RequestParam(required = false, defaultValue = "false") boolean ascOrder) throws ThingsboardException { try { checkParameter("EntityId", strEntityId); checkParameter("EntityType", strEntityType); TenantId tenantId = getCurrentUser().getTenantId(); - TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset); + TimePageLink pageLink = createTimePageLink(pageSize, page, "", + "createdTime", ascOrder ? "asc" : "desc", startTime, endTime); return checkNotNull(auditLogService.findAuditLogsByTenantIdAndEntityId(tenantId, EntityIdFactory.getByTypeAndId(strEntityType, strEntityId), pageLink)); } catch (Exception e) { throw handleException(e); @@ -100,17 +103,18 @@ public class AuditLogController extends BaseController { } @PreAuthorize("hasAuthority('TENANT_ADMIN')") - @RequestMapping(value = "/audit/logs", params = {"limit"}, method = RequestMethod.GET) + @RequestMapping(value = "/audit/logs", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody - public TimePageData getAuditLogs( - @RequestParam int limit, + public PageData getAuditLogs( + @RequestParam int pageSize, + @RequestParam int page, @RequestParam(required = false) Long startTime, @RequestParam(required = false) Long endTime, - @RequestParam(required = false, defaultValue = "false") boolean ascOrder, - @RequestParam(required = false) String offset) throws ThingsboardException { + @RequestParam(required = false, defaultValue = "false") boolean ascOrder) throws ThingsboardException { try { TenantId tenantId = getCurrentUser().getTenantId(); - TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset); + TimePageLink pageLink = createTimePageLink(pageSize, page, "", + "createdTime", ascOrder ? "asc" : "desc", startTime, endTime); return checkNotNull(auditLogService.findAuditLogsByTenantId(tenantId, pageLink)); } catch (Exception e) { throw handleException(e); 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 3fc809c424..ae447c1a6f 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -60,7 +60,8 @@ import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.id.WidgetsBundleId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.DataType; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.page.SortOrder; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.plugin.ComponentDescriptor; import org.thingsboard.server.common.data.plugin.ComponentType; @@ -256,21 +257,27 @@ public abstract class BaseController { return UUID.fromString(id); } - TimePageLink createPageLink(int limit, Long startTime, Long endTime, boolean ascOrder, String idOffset) { - UUID idOffsetUuid = null; - if (StringUtils.isNotEmpty(idOffset)) { - idOffsetUuid = toUUID(idOffset); + PageLink createPageLink(int pageSize, int page, String textSearch, String sortProperty, String sortOrder) throws ThingsboardException { + if (!StringUtils.isEmpty(sortProperty)) { + SortOrder.Direction direction = SortOrder.Direction.ASC; + if (!StringUtils.isEmpty(sortOrder)) { + try { + direction = SortOrder.Direction.valueOf(sortOrder.toUpperCase()); + } catch (IllegalArgumentException e) { + throw new ThingsboardException("Unsupported sort order '" + sortOrder + "'! Only 'ASC' or 'DESC' types are allowed.", ThingsboardErrorCode.BAD_REQUEST_PARAMS); + } + } + SortOrder sort = new SortOrder(sortProperty, direction); + return new PageLink(pageSize, page, textSearch, sort); + } else { + return new PageLink(pageSize, page, textSearch); } - return new TimePageLink(limit, startTime, endTime, ascOrder, idOffsetUuid); } - - TextPageLink createPageLink(int limit, String textSearch, String idOffset, String textOffset) { - UUID idOffsetUuid = null; - if (StringUtils.isNotEmpty(idOffset)) { - idOffsetUuid = toUUID(idOffset); - } - return new TextPageLink(limit, textSearch, idOffsetUuid, textOffset); + TimePageLink createTimePageLink(int pageSize, int page, String textSearch, + String sortProperty, String sortOrder, Long startTime, Long endTime) throws ThingsboardException { + PageLink pageLink = this.createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + return new TimePageLink(pageLink, startTime, endTime); } protected SecurityUser getCurrentUser() throws ThingsboardException { diff --git a/application/src/main/java/org/thingsboard/server/controller/CustomerController.java b/application/src/main/java/org/thingsboard/server/controller/CustomerController.java index 7b7cfeee87..6e453f3442 100644 --- a/application/src/main/java/org/thingsboard/server/controller/CustomerController.java +++ b/application/src/main/java/org/thingsboard/server/controller/CustomerController.java @@ -34,8 +34,8 @@ import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; @@ -143,14 +143,15 @@ public class CustomerController extends BaseController { } @PreAuthorize("hasAuthority('TENANT_ADMIN')") - @RequestMapping(value = "/customers", params = {"limit"}, method = RequestMethod.GET) + @RequestMapping(value = "/customers", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody - public TextPageData getCustomers(@RequestParam int limit, - @RequestParam(required = false) String textSearch, - @RequestParam(required = false) String idOffset, - @RequestParam(required = false) String textOffset) throws ThingsboardException { + public PageData getCustomers(@RequestParam int pageSize, + @RequestParam int page, + @RequestParam(required = false) String textSearch, + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder) throws ThingsboardException { try { - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); TenantId tenantId = getCurrentUser().getTenantId(); return checkNotNull(customerService.findCustomersByTenantId(tenantId, pageLink)); } catch (Exception e) { diff --git a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java index e47e2d1a06..a7c427b35e 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java @@ -37,9 +37,8 @@ import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; -import org.thingsboard.server.common.data.page.TimePageData; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; @@ -417,18 +416,19 @@ public class DashboardController extends BaseController { } @PreAuthorize("hasAuthority('SYS_ADMIN')") - @RequestMapping(value = "/tenant/{tenantId}/dashboards", params = { "limit" }, method = RequestMethod.GET) + @RequestMapping(value = "/tenant/{tenantId}/dashboards", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody - public TextPageData getTenantDashboards( + public PageData getTenantDashboards( @PathVariable("tenantId") String strTenantId, - @RequestParam int limit, + @RequestParam int pageSize, + @RequestParam int page, @RequestParam(required = false) String textSearch, - @RequestParam(required = false) String idOffset, - @RequestParam(required = false) String textOffset) throws ThingsboardException { + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder) throws ThingsboardException { try { TenantId tenantId = new TenantId(toUUID(strTenantId)); checkTenantId(tenantId, Operation.READ); - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); return checkNotNull(dashboardService.findDashboardsByTenantId(tenantId, pageLink)); } catch (Exception e) { throw handleException(e); @@ -436,16 +436,17 @@ public class DashboardController extends BaseController { } @PreAuthorize("hasAuthority('TENANT_ADMIN')") - @RequestMapping(value = "/tenant/dashboards", params = { "limit" }, method = RequestMethod.GET) + @RequestMapping(value = "/tenant/dashboards", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody - public TextPageData getTenantDashboards( - @RequestParam int limit, + public PageData getTenantDashboards( + @RequestParam int pageSize, + @RequestParam int page, @RequestParam(required = false) String textSearch, - @RequestParam(required = false) String idOffset, - @RequestParam(required = false) String textOffset) throws ThingsboardException { + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder) throws ThingsboardException { try { TenantId tenantId = getCurrentUser().getTenantId(); - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); return checkNotNull(dashboardService.findDashboardsByTenantId(tenantId, pageLink)); } catch (Exception e) { throw handleException(e); @@ -453,21 +454,22 @@ public class DashboardController extends BaseController { } @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") - @RequestMapping(value = "/customer/{customerId}/dashboards", params = { "limit" }, method = RequestMethod.GET) + @RequestMapping(value = "/customer/{customerId}/dashboards", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody - public TimePageData getCustomerDashboards( + public PageData getCustomerDashboards( @PathVariable("customerId") String strCustomerId, - @RequestParam int limit, + @RequestParam int pageSize, + @RequestParam int page, @RequestParam(required = false) Long startTime, @RequestParam(required = false) Long endTime, - @RequestParam(required = false, defaultValue = "false") boolean ascOrder, - @RequestParam(required = false) String offset) throws ThingsboardException { + @RequestParam(required = false, defaultValue = "false") boolean ascOrder) throws ThingsboardException { checkParameter("customerId", strCustomerId); try { TenantId tenantId = getCurrentUser().getTenantId(); CustomerId customerId = new CustomerId(toUUID(strCustomerId)); checkCustomerId(customerId, Operation.READ); - TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset); + TimePageLink pageLink = createTimePageLink(pageSize, page, "", + "createdTime", ascOrder ? "asc" : "desc", startTime, endTime); return checkNotNull(dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink).get()); } catch (Exception e) { throw handleException(e); 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 39d67ecb9c..c4396dbc7e 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -41,8 +41,8 @@ import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.controller.claim.data.ClaimRequest; import org.thingsboard.server.dao.device.claim.ClaimResponse; @@ -265,17 +265,18 @@ public class DeviceController extends BaseController { } @PreAuthorize("hasAuthority('TENANT_ADMIN')") - @RequestMapping(value = "/tenant/devices", params = {"limit"}, method = RequestMethod.GET) + @RequestMapping(value = "/tenant/devices", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody - public TextPageData getTenantDevices( - @RequestParam int limit, + public PageData getTenantDevices( + @RequestParam int pageSize, + @RequestParam int page, @RequestParam(required = false) String type, @RequestParam(required = false) String textSearch, - @RequestParam(required = false) String idOffset, - @RequestParam(required = false) String textOffset) throws ThingsboardException { + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder) throws ThingsboardException { try { TenantId tenantId = getCurrentUser().getTenantId(); - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); if (type != null && type.trim().length() > 0) { return checkNotNull(deviceService.findDevicesByTenantIdAndType(tenantId, type, pageLink)); } else { @@ -300,21 +301,22 @@ public class DeviceController extends BaseController { } @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") - @RequestMapping(value = "/customer/{customerId}/devices", params = {"limit"}, method = RequestMethod.GET) + @RequestMapping(value = "/customer/{customerId}/devices", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody - public TextPageData getCustomerDevices( + public PageData getCustomerDevices( @PathVariable("customerId") String strCustomerId, - @RequestParam int limit, + @RequestParam int pageSize, + @RequestParam int page, @RequestParam(required = false) String type, @RequestParam(required = false) String textSearch, - @RequestParam(required = false) String idOffset, - @RequestParam(required = false) String textOffset) throws ThingsboardException { + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder) throws ThingsboardException { checkParameter("customerId", strCustomerId); try { TenantId tenantId = getCurrentUser().getTenantId(); CustomerId customerId = new CustomerId(toUUID(strCustomerId)); checkCustomerId(customerId, Operation.READ); - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); if (type != null && type.trim().length() > 0) { return checkNotNull(deviceService.findDevicesByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink)); } else { diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java b/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java index b0d9896e4b..e8438e0703 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java @@ -43,8 +43,8 @@ import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UUIDBased; import org.thingsboard.server.common.data.kv.AttributeKvEntry; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.service.security.model.SecurityUser; @@ -256,21 +256,22 @@ public class EntityViewController extends BaseController { } @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") - @RequestMapping(value = "/customer/{customerId}/entityViews", params = {"limit"}, method = RequestMethod.GET) + @RequestMapping(value = "/customer/{customerId}/entityViews", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody - public TextPageData getCustomerEntityViews( + public PageData getCustomerEntityViews( @PathVariable("customerId") String strCustomerId, - @RequestParam int limit, + @RequestParam int pageSize, + @RequestParam int page, @RequestParam(required = false) String type, @RequestParam(required = false) String textSearch, - @RequestParam(required = false) String idOffset, - @RequestParam(required = false) String textOffset) throws ThingsboardException { + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder) throws ThingsboardException { checkParameter("customerId", strCustomerId); try { TenantId tenantId = getCurrentUser().getTenantId(); CustomerId customerId = new CustomerId(toUUID(strCustomerId)); checkCustomerId(customerId, Operation.READ); - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); if (type != null && type.trim().length() > 0) { return checkNotNull(entityViewService.findEntityViewsByTenantIdAndCustomerIdAndType(tenantId, customerId, pageLink, type)); } else { @@ -282,17 +283,18 @@ public class EntityViewController extends BaseController { } @PreAuthorize("hasAuthority('TENANT_ADMIN')") - @RequestMapping(value = "/tenant/entityViews", params = {"limit"}, method = RequestMethod.GET) + @RequestMapping(value = "/tenant/entityViews", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody - public TextPageData getTenantEntityViews( - @RequestParam int limit, + public PageData getTenantEntityViews( + @RequestParam int pageSize, + @RequestParam int page, @RequestParam(required = false) String type, @RequestParam(required = false) String textSearch, - @RequestParam(required = false) String idOffset, - @RequestParam(required = false) String textOffset) throws ThingsboardException { + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder) throws ThingsboardException { try { TenantId tenantId = getCurrentUser().getTenantId(); - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); if (type != null && type.trim().length() > 0) { return checkNotNull(entityViewService.findEntityViewByTenantIdAndType(tenantId, pageLink, type)); diff --git a/application/src/main/java/org/thingsboard/server/controller/EventController.java b/application/src/main/java/org/thingsboard/server/controller/EventController.java index 8d08179409..0754b97a5c 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EventController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EventController.java @@ -29,7 +29,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TimePageData; +import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.dao.event.EventService; import org.thingsboard.server.dao.model.ModelConstants; @@ -46,16 +46,16 @@ public class EventController extends BaseController { @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/events/{entityType}/{entityId}/{eventType}", method = RequestMethod.GET) @ResponseBody - public TimePageData getEvents( + public PageData getEvents( @PathVariable("entityType") String strEntityType, @PathVariable("entityId") String strEntityId, @PathVariable("eventType") String eventType, @RequestParam("tenantId") String strTenantId, - @RequestParam int limit, + @RequestParam int pageSize, + @RequestParam int page, @RequestParam(required = false) Long startTime, @RequestParam(required = false) Long endTime, - @RequestParam(required = false, defaultValue = "false") boolean ascOrder, - @RequestParam(required = false) String offset + @RequestParam(required = false, defaultValue = "false") boolean ascOrder ) throws ThingsboardException { checkParameter("EntityId", strEntityId); checkParameter("EntityType", strEntityType); @@ -64,8 +64,8 @@ public class EventController extends BaseController { EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId); checkEntityId(entityId, Operation.READ); - - TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset); + TimePageLink pageLink = createTimePageLink(pageSize, page, "", + "createdTime", ascOrder ? "asc" : "desc", startTime, endTime); return checkNotNull(eventService.findEvents(tenantId, entityId, eventType, pageLink)); } catch (Exception e) { throw handleException(e); @@ -75,15 +75,15 @@ public class EventController extends BaseController { @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/events/{entityType}/{entityId}", method = RequestMethod.GET) @ResponseBody - public TimePageData getEvents( + public PageData getEvents( @PathVariable("entityType") String strEntityType, @PathVariable("entityId") String strEntityId, @RequestParam("tenantId") String strTenantId, - @RequestParam int limit, + @RequestParam int pageSize, + @RequestParam int page, @RequestParam(required = false) Long startTime, @RequestParam(required = false) Long endTime, - @RequestParam(required = false, defaultValue = "false") boolean ascOrder, - @RequestParam(required = false) String offset + @RequestParam(required = false, defaultValue = "false") boolean ascOrder ) throws ThingsboardException { checkParameter("EntityId", strEntityId); checkParameter("EntityType", strEntityType); @@ -93,7 +93,9 @@ public class EventController extends BaseController { EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId); checkEntityId(entityId, Operation.READ); - TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset); + TimePageLink pageLink = createTimePageLink(pageSize, page, "", + "createdTime", ascOrder ? "asc" : "desc", startTime, endTime); + return checkNotNull(eventService.findEvents(tenantId, entityId, pageLink)); } catch (Exception e) { throw handleException(e); diff --git a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java index 14fffc7486..e38c4fd725 100644 --- a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java +++ b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java @@ -45,8 +45,8 @@ import org.thingsboard.server.common.data.exception.ThingsboardException; 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.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; @@ -220,16 +220,17 @@ public class RuleChainController extends BaseController { } @PreAuthorize("hasAuthority('TENANT_ADMIN')") - @RequestMapping(value = "/ruleChains", params = {"limit"}, method = RequestMethod.GET) + @RequestMapping(value = "/ruleChains", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody - public TextPageData getRuleChains( - @RequestParam int limit, + public PageData getRuleChains( + @RequestParam int pageSize, + @RequestParam int page, @RequestParam(required = false) String textSearch, - @RequestParam(required = false) String idOffset, - @RequestParam(required = false) String textOffset) throws ThingsboardException { + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder) throws ThingsboardException { try { TenantId tenantId = getCurrentUser().getTenantId(); - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); return checkNotNull(ruleChainService.findTenantRuleChains(tenantId, pageLink)); } catch (Exception e) { throw handleException(e); diff --git a/application/src/main/java/org/thingsboard/server/controller/TenantController.java b/application/src/main/java/org/thingsboard/server/controller/TenantController.java index e7e13e18bb..b77e158b50 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TenantController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TenantController.java @@ -30,8 +30,8 @@ import org.springframework.web.bind.annotation.RestController; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.service.install.InstallScripts; @@ -102,14 +102,15 @@ public class TenantController extends BaseController { } @PreAuthorize("hasAuthority('SYS_ADMIN')") - @RequestMapping(value = "/tenants", params = {"limit"}, method = RequestMethod.GET) + @RequestMapping(value = "/tenants", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody - public TextPageData getTenants(@RequestParam int limit, - @RequestParam(required = false) String textSearch, - @RequestParam(required = false) String idOffset, - @RequestParam(required = false) String textOffset) throws ThingsboardException { + public PageData getTenants(@RequestParam int pageSize, + @RequestParam int page, + @RequestParam(required = false) String textSearch, + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder) throws ThingsboardException { try { - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); return checkNotNull(tenantService.findTenants(pageLink)); } catch (Exception e) { throw handleException(e); diff --git a/application/src/main/java/org/thingsboard/server/controller/UserController.java b/application/src/main/java/org/thingsboard/server/controller/UserController.java index b6a8d9da06..684ab195ad 100644 --- a/application/src/main/java/org/thingsboard/server/controller/UserController.java +++ b/application/src/main/java/org/thingsboard/server/controller/UserController.java @@ -40,8 +40,8 @@ import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +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.security.UserCredentials; import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository; @@ -247,18 +247,19 @@ public class UserController extends BaseController { } @PreAuthorize("hasAuthority('SYS_ADMIN')") - @RequestMapping(value = "/tenant/{tenantId}/users", params = { "limit" }, method = RequestMethod.GET) + @RequestMapping(value = "/tenant/{tenantId}/users", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody - public TextPageData getTenantAdmins( + public PageData getTenantAdmins( @PathVariable("tenantId") String strTenantId, - @RequestParam int limit, + @RequestParam int pageSize, + @RequestParam int page, @RequestParam(required = false) String textSearch, - @RequestParam(required = false) String idOffset, - @RequestParam(required = false) String textOffset) throws ThingsboardException { + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder) throws ThingsboardException { checkParameter("tenantId", strTenantId); try { TenantId tenantId = new TenantId(toUUID(strTenantId)); - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); return checkNotNull(userService.findTenantAdmins(tenantId, pageLink)); } catch (Exception e) { throw handleException(e); @@ -266,19 +267,20 @@ public class UserController extends BaseController { } @PreAuthorize("hasAuthority('TENANT_ADMIN')") - @RequestMapping(value = "/customer/{customerId}/users", params = { "limit" }, method = RequestMethod.GET) + @RequestMapping(value = "/customer/{customerId}/users", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody - public TextPageData getCustomerUsers( + public PageData getCustomerUsers( @PathVariable("customerId") String strCustomerId, - @RequestParam int limit, + @RequestParam int pageSize, + @RequestParam int page, @RequestParam(required = false) String textSearch, - @RequestParam(required = false) String idOffset, - @RequestParam(required = false) String textOffset) throws ThingsboardException { + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder) throws ThingsboardException { checkParameter("customerId", strCustomerId); try { CustomerId customerId = new CustomerId(toUUID(strCustomerId)); checkCustomerId(customerId, Operation.READ); - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); TenantId tenantId = getCurrentUser().getTenantId(); return checkNotNull(userService.findCustomerUsers(tenantId, customerId, pageLink)); } catch (Exception e) { diff --git a/application/src/main/java/org/thingsboard/server/controller/WidgetsBundleController.java b/application/src/main/java/org/thingsboard/server/controller/WidgetsBundleController.java index 3c8265e7a7..b991f2ce2d 100644 --- a/application/src/main/java/org/thingsboard/server/controller/WidgetsBundleController.java +++ b/application/src/main/java/org/thingsboard/server/controller/WidgetsBundleController.java @@ -28,8 +28,8 @@ import org.springframework.web.bind.annotation.RestController; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.WidgetsBundleId; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +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.WidgetsBundle; import org.thingsboard.server.dao.model.ModelConstants; @@ -92,15 +92,16 @@ public class WidgetsBundleController extends BaseController { } @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") - @RequestMapping(value = "/widgetsBundles", params = { "limit" }, method = RequestMethod.GET) + @RequestMapping(value = "/widgetsBundles", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody - public TextPageData getWidgetsBundles( - @RequestParam int limit, + public PageData getWidgetsBundles( + @RequestParam int pageSize, + @RequestParam int page, @RequestParam(required = false) String textSearch, - @RequestParam(required = false) String idOffset, - @RequestParam(required = false) String textOffset) throws ThingsboardException { + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder) throws ThingsboardException { try { - TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); if (getCurrentUser().getAuthority() == Authority.SYS_ADMIN) { return checkNotNull(widgetsBundleService.findSystemWidgetsBundlesByPageLink(getTenantId(), pageLink)); } else { 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 9396ae7c3d..faf81d8286 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 @@ -22,8 +22,8 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.SearchTextBased; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.UUIDBased; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.tenant.TenantService; @@ -59,7 +59,7 @@ public class DefaultDataUpdateService implements DataUpdateService { new PaginatedUpdater() { @Override - protected TextPageData findEntities(String region, TextPageLink pageLink) { + protected PageData findEntities(String region, PageLink pageLink) { return tenantService.findTenants(pageLink); } diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java b/application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java index b57d340d1b..432b8226d0 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java @@ -17,29 +17,29 @@ package org.thingsboard.server.service.install.update; import org.thingsboard.server.common.data.SearchTextBased; import org.thingsboard.server.common.data.id.UUIDBased; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; public abstract class PaginatedUpdater> { private static final int DEFAULT_LIMIT = 100; public void updateEntities(I id) { - TextPageLink pageLink = new TextPageLink(DEFAULT_LIMIT); + PageLink pageLink = new PageLink(DEFAULT_LIMIT); boolean hasNext = true; while (hasNext) { - TextPageData entities = findEntities(id, pageLink); + PageData entities = findEntities(id, pageLink); for (D entity : entities.getData()) { updateEntity(entity); } hasNext = entities.hasNext(); if (hasNext) { - pageLink = entities.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } } - protected abstract TextPageData findEntities(I id, TextPageLink pageLink); + protected abstract PageData findEntities(I id, PageLink pageLink); protected abstract void updateEntity(D entity); 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 37d6473d53..a0c2bda6b8 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 @@ -38,7 +38,7 @@ 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.kv.AttributeKvEntry; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; @@ -219,10 +219,10 @@ public class DefaultDeviceStateService implements DeviceStateService { } private void onClusterUpdateSync() { - List tenants = tenantService.findTenants(new TextPageLink(Integer.MAX_VALUE)).getData(); + List tenants = tenantService.findTenants(new PageLink(Integer.MAX_VALUE)).getData(); for (Tenant tenant : tenants) { List> fetchFutures = new ArrayList<>(); - List devices = deviceService.findDevicesByTenantId(tenant.getId(), new TextPageLink(Integer.MAX_VALUE)).getData(); + List devices = deviceService.findDevicesByTenantId(tenant.getId(), new PageLink(Integer.MAX_VALUE)).getData(); for (Device device : devices) { if (!routingService.resolveById(device.getId()).isPresent()) { if (!deviceStates.containsKey(device.getId())) { @@ -245,10 +245,10 @@ public class DefaultDeviceStateService implements DeviceStateService { } private void initStateFromDB() { - List tenants = tenantService.findTenants(new TextPageLink(Integer.MAX_VALUE)).getData(); + List tenants = tenantService.findTenants(new PageLink(Integer.MAX_VALUE)).getData(); for (Tenant tenant : tenants) { List> fetchFutures = new ArrayList<>(); - List devices = deviceService.findDevicesByTenantId(tenant.getId(), new TextPageLink(Integer.MAX_VALUE)).getData(); + List devices = deviceService.findDevicesByTenantId(tenant.getId(), new PageLink(Integer.MAX_VALUE)).getData(); for (Device device : devices) { if (!routingService.resolveById(device.getId()).isPresent()) { fetchFutures.add(fetchDeviceState(device)); diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java index 9850181a5f..d25655ccdf 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java @@ -65,7 +65,8 @@ import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UUIDBased; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.page.SortOrder; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.config.ThingsboardSecurityConfiguration; @@ -314,22 +315,20 @@ public abstract class AbstractControllerTest { } protected T doGetTypedWithPageLink(String urlTemplate, TypeReference responseType, - TextPageLink pageLink, + PageLink pageLink, Object... urlVariables) throws Exception { List pageLinkVariables = new ArrayList<>(); - urlTemplate += "limit={limit}"; - pageLinkVariables.add(pageLink.getLimit()); + urlTemplate += "pageSize={pageSize}&page={page}"; + pageLinkVariables.add(pageLink.getPageSize()); + pageLinkVariables.add(pageLink.getPage()); if (StringUtils.isNotEmpty(pageLink.getTextSearch())) { urlTemplate += "&textSearch={textSearch}"; pageLinkVariables.add(pageLink.getTextSearch()); } - if (pageLink.getIdOffset() != null) { - urlTemplate += "&idOffset={idOffset}"; - pageLinkVariables.add(pageLink.getIdOffset().toString()); - } - if (StringUtils.isNotEmpty(pageLink.getTextOffset())) { - urlTemplate += "&textOffset={textOffset}"; - pageLinkVariables.add(pageLink.getTextOffset()); + if (pageLink.getSortOrder() != null) { + urlTemplate += "&sortProperty={sortProperty}&sortOrder={sortOrder}"; + pageLinkVariables.add(pageLink.getSortOrder().getProperty()); + pageLinkVariables.add(pageLink.getSortOrder().getDirection().name()); } Object[] vars = new Object[urlVariables.length + pageLinkVariables.size()]; @@ -343,8 +342,10 @@ public abstract class AbstractControllerTest { TimePageLink pageLink, Object... urlVariables) throws Exception { List pageLinkVariables = new ArrayList<>(); - urlTemplate += "limit={limit}"; - pageLinkVariables.add(pageLink.getLimit()); + urlTemplate += "pageSize={pageSize}&page={page}"; + pageLinkVariables.add(pageLink.getPageSize()); + pageLinkVariables.add(pageLink.getPage()); + if (pageLink.getStartTime() != null) { urlTemplate += "&startTime={startTime}"; pageLinkVariables.add(pageLink.getStartTime()); @@ -353,13 +354,9 @@ public abstract class AbstractControllerTest { urlTemplate += "&endTime={endTime}"; pageLinkVariables.add(pageLink.getEndTime()); } - if (pageLink.getIdOffset() != null) { - urlTemplate += "&offset={offset}"; - pageLinkVariables.add(pageLink.getIdOffset().toString()); - } - if (pageLink.isAscOrder()) { + if (pageLink.getSortOrder() != null) { urlTemplate += "&ascOrder={ascOrder}"; - pageLinkVariables.add(pageLink.isAscOrder()); + pageLinkVariables.add(pageLink.getSortOrder().getDirection() == SortOrder.Direction.ASC); } Object[] vars = new Object[urlVariables.length + pageLinkVariables.size()]; System.arraycopy(urlVariables, 0, vars, 0, urlVariables.length); diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java index 2a03c9e783..c9036c5995 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java @@ -23,7 +23,7 @@ import org.thingsboard.server.common.data.Event; 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.page.TimePageData; +import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; @@ -56,10 +56,10 @@ public class AbstractRuleEngineControllerTest extends AbstractControllerTest { return doGet("/api/ruleChain/metadata/" + ruleChainId.getId().toString(), RuleChainMetaData.class); } - protected TimePageData getDebugEvents(TenantId tenantId, EntityId entityId, int limit) throws Exception { + protected PageData getDebugEvents(TenantId tenantId, EntityId entityId, int limit) throws Exception { TimePageLink pageLink = new TimePageLink(limit); return doGetTypedWithTimePageLink("/api/events/{entityType}/{entityId}/{eventType}?tenantId={tenantId}&", - new TypeReference>() { + new TypeReference>() { }, pageLink, entityId.getEntityType(), entityId.getId(), DataConstants.DEBUG_RULE_NODE, tenantId.getId()); } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseAssetControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseAssetControllerTest.java index ff607df1d7..7467436c64 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseAssetControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseAssetControllerTest.java @@ -28,8 +28,8 @@ import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +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.dao.model.ModelConstants; @@ -258,14 +258,14 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { assets.add(doPost("/api/asset", asset, Asset.class)); } List loadedAssets = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(23); - TextPageData pageData = null; + PageLink pageLink = new PageLink(23); + PageData pageData = null; do { pageData = doGetTypedWithPageLink("/api/tenant/assets?", - new TypeReference>(){}, pageLink); + new TypeReference>(){}, pageLink); loadedAssets.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -301,14 +301,14 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { } List loadedAssetsTitle1 = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(15, title1); - TextPageData pageData = null; + PageLink pageLink = new PageLink(15, 0, title1); + PageData pageData = null; do { pageData = doGetTypedWithPageLink("/api/tenant/assets?", - new TypeReference>(){}, pageLink); + new TypeReference>(){}, pageLink); loadedAssetsTitle1.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -318,13 +318,13 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { Assert.assertEquals(assetsTitle1, loadedAssetsTitle1); List loadedAssetsTitle2 = new ArrayList<>(); - pageLink = new TextPageLink(4, title2); + pageLink = new PageLink(4, 0, title2); do { pageData = doGetTypedWithPageLink("/api/tenant/assets?", - new TypeReference>(){}, pageLink); + new TypeReference>(){}, pageLink); loadedAssetsTitle2.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -338,9 +338,9 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { .andExpect(status().isOk()); } - pageLink = new TextPageLink(4, title1); + pageLink = new PageLink(4, 0, title1); pageData = doGetTypedWithPageLink("/api/tenant/assets?", - new TypeReference>(){}, pageLink); + new TypeReference>(){}, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -349,9 +349,9 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { .andExpect(status().isOk()); } - pageLink = new TextPageLink(4, title2); + pageLink = new PageLink(4, 0, title2); pageData = doGetTypedWithPageLink("/api/tenant/assets?", - new TypeReference>(){}, pageLink); + new TypeReference>(){}, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); } @@ -384,14 +384,14 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { } List loadedAssetsType1 = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(15); - TextPageData pageData = null; + PageLink pageLink = new PageLink(15); + PageData pageData = null; do { pageData = doGetTypedWithPageLink("/api/tenant/assets?type={type}&", - new TypeReference>(){}, pageLink, type1); + new TypeReference>(){}, pageLink, type1); loadedAssetsType1.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -401,13 +401,13 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { Assert.assertEquals(assetsType1, loadedAssetsType1); List loadedAssetsType2 = new ArrayList<>(); - pageLink = new TextPageLink(4); + pageLink = new PageLink(4); do { pageData = doGetTypedWithPageLink("/api/tenant/assets?type={type}&", - new TypeReference>(){}, pageLink, type2); + new TypeReference>(){}, pageLink, type2); loadedAssetsType2.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -421,9 +421,9 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { .andExpect(status().isOk()); } - pageLink = new TextPageLink(4); + pageLink = new PageLink(4); pageData = doGetTypedWithPageLink("/api/tenant/assets?type={type}&", - new TypeReference>(){}, pageLink, type1); + new TypeReference>(){}, pageLink, type1); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -432,9 +432,9 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { .andExpect(status().isOk()); } - pageLink = new TextPageLink(4); + pageLink = new PageLink(4); pageData = doGetTypedWithPageLink("/api/tenant/assets?type={type}&", - new TypeReference>(){}, pageLink, type2); + new TypeReference>(){}, pageLink, type2); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); } @@ -457,14 +457,14 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { } List loadedAssets = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(23); - TextPageData pageData = null; + PageLink pageLink = new PageLink(23); + PageData pageData = null; do { pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?", - new TypeReference>(){}, pageLink); + new TypeReference>(){}, pageLink); loadedAssets.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -509,14 +509,14 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { } List loadedAssetsTitle1 = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(15, title1); - TextPageData pageData = null; + PageLink pageLink = new PageLink(15, 0, title1); + PageData pageData = null; do { pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?", - new TypeReference>(){}, pageLink); + new TypeReference>(){}, pageLink); loadedAssetsTitle1.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -526,13 +526,13 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { Assert.assertEquals(assetsTitle1, loadedAssetsTitle1); List loadedAssetsTitle2 = new ArrayList<>(); - pageLink = new TextPageLink(4, title2); + pageLink = new PageLink(4, 0, title2); do { pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?", - new TypeReference>(){}, pageLink); + new TypeReference>(){}, pageLink); loadedAssetsTitle2.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -546,9 +546,9 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { .andExpect(status().isOk()); } - pageLink = new TextPageLink(4, title1); + pageLink = new PageLink(4, 0, title1); pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?", - new TypeReference>(){}, pageLink); + new TypeReference>(){}, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -557,9 +557,9 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { .andExpect(status().isOk()); } - pageLink = new TextPageLink(4, title2); + pageLink = new PageLink(4, 0, title2); pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?", - new TypeReference>(){}, pageLink); + new TypeReference>(){}, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); } @@ -601,14 +601,14 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { } List loadedAssetsType1 = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(15); - TextPageData pageData = null; + PageLink pageLink = new PageLink(15); + PageData pageData = null; do { pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?type={type}&", - new TypeReference>(){}, pageLink, type1); + new TypeReference>(){}, pageLink, type1); loadedAssetsType1.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -618,13 +618,13 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { Assert.assertEquals(assetsType1, loadedAssetsType1); List loadedAssetsType2 = new ArrayList<>(); - pageLink = new TextPageLink(4); + pageLink = new PageLink(4); do { pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?type={type}&", - new TypeReference>(){}, pageLink, type2); + new TypeReference>(){}, pageLink, type2); loadedAssetsType2.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -638,9 +638,9 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { .andExpect(status().isOk()); } - pageLink = new TextPageLink(4); + pageLink = new PageLink(4); pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?type={type}&", - new TypeReference>(){}, pageLink, type1); + new TypeReference>(){}, pageLink, type1); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -649,9 +649,9 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { .andExpect(status().isOk()); } - pageLink = new TextPageLink(4); + pageLink = new PageLink(4); pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?type={type}&", - new TypeReference>(){}, pageLink, type2); + new TypeReference>(){}, pageLink, type2); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseAuditLogControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseAuditLogControllerTest.java index 4bba508901..8503375228 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseAuditLogControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseAuditLogControllerTest.java @@ -24,7 +24,7 @@ import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.AuditLog; -import org.thingsboard.server.common.data.page.TimePageData; +import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.dao.model.ModelConstants; @@ -77,14 +77,14 @@ public abstract class BaseAuditLogControllerTest extends AbstractControllerTest List loadedAuditLogs = new ArrayList<>(); TimePageLink pageLink = new TimePageLink(23); - TimePageData pageData; + PageData pageData; do { pageData = doGetTypedWithTimePageLink("/api/audit/logs?", - new TypeReference>() { + new TypeReference>() { }, pageLink); loadedAuditLogs.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -94,11 +94,11 @@ public abstract class BaseAuditLogControllerTest extends AbstractControllerTest pageLink = new TimePageLink(23); do { pageData = doGetTypedWithTimePageLink("/api/audit/logs/customer/" + ModelConstants.NULL_UUID + "?", - new TypeReference>() { + new TypeReference>() { }, pageLink); loadedAuditLogs.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -108,11 +108,11 @@ public abstract class BaseAuditLogControllerTest extends AbstractControllerTest pageLink = new TimePageLink(23); do { pageData = doGetTypedWithTimePageLink("/api/audit/logs/user/" + tenantAdmin.getId().getId().toString() + "?", - new TypeReference>() { + new TypeReference>() { }, pageLink); loadedAuditLogs.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -132,14 +132,14 @@ public abstract class BaseAuditLogControllerTest extends AbstractControllerTest List loadedAuditLogs = new ArrayList<>(); TimePageLink pageLink = new TimePageLink(23); - TimePageData pageData; + PageData pageData; do { pageData = doGetTypedWithTimePageLink("/api/audit/logs/entity/DEVICE/" + savedDevice.getId().getId() + "?", - new TypeReference>() { + new TypeReference>() { }, pageLink); loadedAuditLogs.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseCustomerControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseCustomerControllerTest.java index 07e71ad2b8..6114e98a27 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseCustomerControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseCustomerControllerTest.java @@ -27,8 +27,8 @@ import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +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.junit.Assert; import org.junit.Test; @@ -241,13 +241,13 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest } List loadedCustomers = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(23); - TextPageData pageData = null; + PageLink pageLink = new PageLink(23); + PageData pageData = null; do { - pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference>(){}, pageLink); + pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference>(){}, pageLink); loadedCustomers.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -307,13 +307,13 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest } List loadedCustomersTitle1 = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(15, title1); - TextPageData pageData = null; + PageLink pageLink = new PageLink(15, 0, title1); + PageData pageData = null; do { - pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference>(){}, pageLink); + pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference>(){}, pageLink); loadedCustomersTitle1.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -323,12 +323,12 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest Assert.assertEquals(customersTitle1, loadedCustomersTitle1); List loadedCustomersTitle2 = new ArrayList<>(); - pageLink = new TextPageLink(4, title2); + pageLink = new PageLink(4, 0, title2); do { - pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference>(){}, pageLink); + pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference>(){}, pageLink); loadedCustomersTitle2.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -342,8 +342,8 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest .andExpect(status().isOk()); } - pageLink = new TextPageLink(4, title1); - pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference>(){}, pageLink); + pageLink = new PageLink(4, 0, title1); + pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference>(){}, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -352,8 +352,8 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest .andExpect(status().isOk()); } - pageLink = new TextPageLink(4, title2); - pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference>(){}, pageLink); + pageLink = new PageLink(4, 0, title2); + pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference>(){}, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java index 2c7ce3ee65..2997b3d7e5 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java @@ -28,9 +28,8 @@ import com.datastax.driver.core.utils.UUIDs; import org.apache.commons.lang3.RandomStringUtils; import org.thingsboard.server.common.data.*; import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; -import org.thingsboard.server.common.data.page.TimePageData; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.dao.model.ModelConstants; @@ -211,14 +210,14 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest dashboards.add(new DashboardInfo(doPost("/api/dashboard", dashboard, Dashboard.class))); } List loadedDashboards = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(24); - TextPageData pageData = null; + PageLink pageLink = new PageLink(24); + PageData pageData = null; do { pageData = doGetTypedWithPageLink("/api/tenant/dashboards?", - new TypeReference>(){}, pageLink); + new TypeReference>(){}, pageLink); loadedDashboards.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -252,14 +251,14 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest } List loadedDashboardsTitle1 = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(15, title1); - TextPageData pageData = null; + PageLink pageLink = new PageLink(15, 0, title1); + PageData pageData = null; do { pageData = doGetTypedWithPageLink("/api/tenant/dashboards?", - new TypeReference>(){}, pageLink); + new TypeReference>(){}, pageLink); loadedDashboardsTitle1.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -269,13 +268,13 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest Assert.assertEquals(dashboardsTitle1, loadedDashboardsTitle1); List loadedDashboardsTitle2 = new ArrayList<>(); - pageLink = new TextPageLink(4, title2); + pageLink = new PageLink(4, 0, title2); do { pageData = doGetTypedWithPageLink("/api/tenant/dashboards?", - new TypeReference>(){}, pageLink); + new TypeReference>(){}, pageLink); loadedDashboardsTitle2.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -289,9 +288,9 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest .andExpect(status().isOk()); } - pageLink = new TextPageLink(4, title1); + pageLink = new PageLink(4, 0, title1); pageData = doGetTypedWithPageLink("/api/tenant/dashboards?", - new TypeReference>(){}, pageLink); + new TypeReference>(){}, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -300,9 +299,9 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest .andExpect(status().isOk()); } - pageLink = new TextPageLink(4, title2); + pageLink = new PageLink(4, 0, title2); pageData = doGetTypedWithPageLink("/api/tenant/dashboards?", - new TypeReference>(){}, pageLink); + new TypeReference>(){}, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); } @@ -325,13 +324,13 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest List loadedDashboards = new ArrayList<>(); TimePageLink pageLink = new TimePageLink(21); - TimePageData pageData = null; + PageData pageData = null; do { pageData = doGetTypedWithTimePageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?", - new TypeReference>(){}, pageLink); + new TypeReference>(){}, pageLink); loadedDashboards.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java index 20ebed1b9b..3ddc689279 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java @@ -29,8 +29,8 @@ import org.thingsboard.server.common.data.*; 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.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +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.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; @@ -366,14 +366,14 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { devices.add(doPost("/api/device", device, Device.class)); } List loadedDevices = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(23); - TextPageData pageData = null; + PageLink pageLink = new PageLink(23); + PageData pageData = null; do { pageData = doGetTypedWithPageLink("/api/tenant/devices?", - new TypeReference>(){}, pageLink); + new TypeReference>(){}, pageLink); loadedDevices.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -409,14 +409,14 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { } List loadedDevicesTitle1 = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(15, title1); - TextPageData pageData = null; + PageLink pageLink = new PageLink(15, 0, title1); + PageData pageData = null; do { pageData = doGetTypedWithPageLink("/api/tenant/devices?", - new TypeReference>(){}, pageLink); + new TypeReference>(){}, pageLink); loadedDevicesTitle1.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -426,13 +426,13 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { Assert.assertEquals(devicesTitle1, loadedDevicesTitle1); List loadedDevicesTitle2 = new ArrayList<>(); - pageLink = new TextPageLink(4, title2); + pageLink = new PageLink(4, 0, title2); do { pageData = doGetTypedWithPageLink("/api/tenant/devices?", - new TypeReference>(){}, pageLink); + new TypeReference>(){}, pageLink); loadedDevicesTitle2.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -446,9 +446,9 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { .andExpect(status().isOk()); } - pageLink = new TextPageLink(4, title1); + pageLink = new PageLink(4, 0, title1); pageData = doGetTypedWithPageLink("/api/tenant/devices?", - new TypeReference>(){}, pageLink); + new TypeReference>(){}, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -457,9 +457,9 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { .andExpect(status().isOk()); } - pageLink = new TextPageLink(4, title2); + pageLink = new PageLink(4, 0, title2); pageData = doGetTypedWithPageLink("/api/tenant/devices?", - new TypeReference>(){}, pageLink); + new TypeReference>(){}, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); } @@ -492,14 +492,14 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { } List loadedDevicesType1 = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(15); - TextPageData pageData = null; + PageLink pageLink = new PageLink(15); + PageData pageData = null; do { pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&", - new TypeReference>(){}, pageLink, type1); + new TypeReference>(){}, pageLink, type1); loadedDevicesType1.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -509,13 +509,13 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { Assert.assertEquals(devicesType1, loadedDevicesType1); List loadedDevicesType2 = new ArrayList<>(); - pageLink = new TextPageLink(4); + pageLink = new PageLink(4); do { pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&", - new TypeReference>(){}, pageLink, type2); + new TypeReference>(){}, pageLink, type2); loadedDevicesType2.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -529,9 +529,9 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { .andExpect(status().isOk()); } - pageLink = new TextPageLink(4); + pageLink = new PageLink(4); pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&", - new TypeReference>(){}, pageLink, type1); + new TypeReference>(){}, pageLink, type1); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -540,9 +540,9 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { .andExpect(status().isOk()); } - pageLink = new TextPageLink(4); + pageLink = new PageLink(4); pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&", - new TypeReference>(){}, pageLink, type2); + new TypeReference>(){}, pageLink, type2); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); } @@ -565,14 +565,14 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { } List loadedDevices = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(23); - TextPageData pageData = null; + PageLink pageLink = new PageLink(23); + PageData pageData = null; do { pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?", - new TypeReference>(){}, pageLink); + new TypeReference>(){}, pageLink); loadedDevices.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -617,14 +617,14 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { } List loadedDevicesTitle1 = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(15, title1); - TextPageData pageData = null; + PageLink pageLink = new PageLink(15, 0, title1); + PageData pageData = null; do { pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?", - new TypeReference>(){}, pageLink); + new TypeReference>(){}, pageLink); loadedDevicesTitle1.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -634,13 +634,13 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { Assert.assertEquals(devicesTitle1, loadedDevicesTitle1); List loadedDevicesTitle2 = new ArrayList<>(); - pageLink = new TextPageLink(4, title2); + pageLink = new PageLink(4, 0, title2); do { pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?", - new TypeReference>(){}, pageLink); + new TypeReference>(){}, pageLink); loadedDevicesTitle2.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -654,9 +654,9 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { .andExpect(status().isOk()); } - pageLink = new TextPageLink(4, title1); + pageLink = new PageLink(4, 0, title1); pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?", - new TypeReference>(){}, pageLink); + new TypeReference>(){}, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -665,9 +665,9 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { .andExpect(status().isOk()); } - pageLink = new TextPageLink(4, title2); + pageLink = new PageLink(4, 0, title2); pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?", - new TypeReference>(){}, pageLink); + new TypeReference>(){}, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); } @@ -709,14 +709,14 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { } List loadedDevicesType1 = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(15); - TextPageData pageData = null; + PageLink pageLink = new PageLink(15); + PageData pageData = null; do { pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&", - new TypeReference>(){}, pageLink, type1); + new TypeReference>(){}, pageLink, type1); loadedDevicesType1.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -726,13 +726,13 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { Assert.assertEquals(devicesType1, loadedDevicesType1); List loadedDevicesType2 = new ArrayList<>(); - pageLink = new TextPageLink(4); + pageLink = new PageLink(4); do { pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&", - new TypeReference>(){}, pageLink, type2); + new TypeReference>(){}, pageLink, type2); loadedDevicesType2.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -746,9 +746,9 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { .andExpect(status().isOk()); } - pageLink = new TextPageLink(4); + pageLink = new PageLink(4); pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&", - new TypeReference>(){}, pageLink, type1); + new TypeReference>(){}, pageLink, type1); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -757,9 +757,9 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { .andExpect(status().isOk()); } - pageLink = new TextPageLink(4); + pageLink = new PageLink(4); pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&", - new TypeReference>(){}, pageLink, type2); + new TypeReference>(){}, pageLink, type2); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java index c1f4fb4ebd..04f315bfc9 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java @@ -33,8 +33,8 @@ import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.objects.AttributesEntityView; import org.thingsboard.server.common.data.objects.TelemetryEntityView; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +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.security.DeviceCredentials; import org.thingsboard.server.dao.model.ModelConstants; @@ -223,7 +223,7 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes + getNewSavedEntityView("Test entity view " + i).getId().getId().toString(), EntityView.class)); } - List loadedViews = loadListOf(new TextPageLink(23), urlTemplate); + List loadedViews = loadListOf(new PageLink(23), urlTemplate); Collections.sort(views, idComparator); Collections.sort(loadedViews, idComparator); @@ -239,7 +239,7 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes String name1 = "Entity view name1"; List namesOfView1 = fillListOf(125, name1, "/api/customer/" + customerId.getId().toString() + "/entityView/"); - List loadedNamesOfView1 = loadListOf(new TextPageLink(15, name1), urlTemplate); + List loadedNamesOfView1 = loadListOf(new PageLink(15, 0, name1), urlTemplate); Collections.sort(namesOfView1, idComparator); Collections.sort(loadedNamesOfView1, idComparator); assertEquals(namesOfView1, loadedNamesOfView1); @@ -247,7 +247,7 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes String name2 = "Entity view name2"; List NamesOfView2 = fillListOf(143, name2, "/api/customer/" + customerId.getId().toString() + "/entityView/"); - List loadedNamesOfView2 = loadListOf(new TextPageLink(4, name2), urlTemplate); + List loadedNamesOfView2 = loadListOf(new PageLink(4, 0, name2), urlTemplate); Collections.sort(NamesOfView2, idComparator); Collections.sort(loadedNamesOfView2, idComparator); assertEquals(NamesOfView2, loadedNamesOfView2); @@ -255,18 +255,18 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes for (EntityView view : loadedNamesOfView1) { doDelete("/api/customer/entityView/" + view.getId().getId().toString()).andExpect(status().isOk()); } - TextPageData pageData = doGetTypedWithPageLink(urlTemplate, - new TypeReference>() { - }, new TextPageLink(4, name1)); + PageData pageData = doGetTypedWithPageLink(urlTemplate, + new TypeReference>() { + }, new PageLink(4, 0, name1)); Assert.assertFalse(pageData.hasNext()); assertEquals(0, pageData.getData().size()); for (EntityView view : loadedNamesOfView2) { doDelete("/api/customer/entityView/" + view.getId().getId().toString()).andExpect(status().isOk()); } - pageData = doGetTypedWithPageLink(urlTemplate, new TypeReference>() { + pageData = doGetTypedWithPageLink(urlTemplate, new TypeReference>() { }, - new TextPageLink(4, name2)); + new PageLink(4, 0, name2)); Assert.assertFalse(pageData.hasNext()); assertEquals(0, pageData.getData().size()); } @@ -278,7 +278,7 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes for (int i = 0; i < 178; i++) { views.add(getNewSavedEntityView("Test entity view" + i)); } - List loadedViews = loadListOf(new TextPageLink(23), "/api/tenant/entityViews?"); + List loadedViews = loadListOf(new PageLink(23), "/api/tenant/entityViews?"); Collections.sort(views, idComparator); Collections.sort(loadedViews, idComparator); @@ -290,14 +290,14 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes public void testGetTenantEntityViewsByName() throws Exception { String name1 = "Entity view name1"; List namesOfView1 = fillListOf(143, name1); - List loadedNamesOfView1 = loadListOf(new TextPageLink(15, name1), "/api/tenant/entityViews?"); + List loadedNamesOfView1 = loadListOf(new PageLink(15, 0, name1), "/api/tenant/entityViews?"); Collections.sort(namesOfView1, idComparator); Collections.sort(loadedNamesOfView1, idComparator); assertEquals(namesOfView1, loadedNamesOfView1); String name2 = "Entity view name2"; List NamesOfView2 = fillListOf(75, name2); - List loadedNamesOfView2 = loadListOf(new TextPageLink(4, name2), "/api/tenant/entityViews?"); + List loadedNamesOfView2 = loadListOf(new PageLink(4, 0, name2), "/api/tenant/entityViews?"); Collections.sort(NamesOfView2, idComparator); Collections.sort(loadedNamesOfView2, idComparator); assertEquals(NamesOfView2, loadedNamesOfView2); @@ -305,18 +305,18 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes for (EntityView view : loadedNamesOfView1) { doDelete("/api/entityView/" + view.getId().getId().toString()).andExpect(status().isOk()); } - TextPageData pageData = doGetTypedWithPageLink("/api/tenant/entityViews?", - new TypeReference>() { - }, new TextPageLink(4, name1)); + PageData pageData = doGetTypedWithPageLink("/api/tenant/entityViews?", + new TypeReference>() { + }, new PageLink(4, 0, name1)); Assert.assertFalse(pageData.hasNext()); assertEquals(0, pageData.getData().size()); for (EntityView view : loadedNamesOfView2) { doDelete("/api/entityView/" + view.getId().getId().toString()).andExpect(status().isOk()); } - pageData = doGetTypedWithPageLink("/api/tenant/entityViews?", new TypeReference>() { + pageData = doGetTypedWithPageLink("/api/tenant/entityViews?", new TypeReference>() { }, - new TextPageLink(4, name2)); + new PageLink(4, 0, name2)); Assert.assertFalse(pageData.hasNext()); assertEquals(0, pageData.getData().size()); } @@ -516,15 +516,15 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes return viewNames; } - private List loadListOf(TextPageLink pageLink, String urlTemplate) throws Exception { + private List loadListOf(PageLink pageLink, String urlTemplate) throws Exception { List loadedItems = new ArrayList<>(); - TextPageData pageData; + PageData pageData; do { - pageData = doGetTypedWithPageLink(urlTemplate, new TypeReference>() { + pageData = doGetTypedWithPageLink(urlTemplate, new TypeReference>() { }, pageLink); loadedItems.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseTenantControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseTenantControllerTest.java index d3a0026992..469cd7edfb 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseTenantControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseTenantControllerTest.java @@ -24,8 +24,8 @@ import java.util.List; import org.apache.commons.lang3.RandomStringUtils; import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.junit.Assert; import org.junit.Test; @@ -102,8 +102,8 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest { public void testFindTenants() throws Exception { loginSysAdmin(); List tenants = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(17); - TextPageData pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference>(){}, pageLink); + PageLink pageLink = new PageLink(17); + PageData pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference>(){}, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(1, pageData.getData().size()); tenants.addAll(pageData.getData()); @@ -115,12 +115,12 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest { } List loadedTenants = new ArrayList<>(); - pageLink = new TextPageLink(17); + pageLink = new PageLink(17); do { - pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference>(){}, pageLink); + pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference>(){}, pageLink); loadedTenants.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -136,8 +136,8 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest { } } - pageLink = new TextPageLink(17); - pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference>(){}, pageLink); + pageLink = new PageLink(17); + pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference>(){}, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(1, pageData.getData().size()); } @@ -167,13 +167,13 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest { } List loadedTenantsTitle1 = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(15, title1); - TextPageData pageData = null; + PageLink pageLink = new PageLink(15, 0, title1); + PageData pageData = null; do { - pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference>(){}, pageLink); + pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference>(){}, pageLink); loadedTenantsTitle1.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -183,12 +183,12 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest { Assert.assertEquals(tenantsTitle1, loadedTenantsTitle1); List loadedTenantsTitle2 = new ArrayList<>(); - pageLink = new TextPageLink(4, title2); + pageLink = new PageLink(4, 0, title2); do { - pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference>(){}, pageLink); + pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference>(){}, pageLink); loadedTenantsTitle2.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -202,8 +202,8 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest { .andExpect(status().isOk()); } - pageLink = new TextPageLink(4, title1); - pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference>(){}, pageLink); + pageLink = new PageLink(4, 0, title1); + pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference>(){}, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -212,8 +212,8 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest { .andExpect(status().isOk()); } - pageLink = new TextPageLink(4, title2); - pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference>(){}, pageLink); + pageLink = new PageLink(4, 0, title2); + pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference>(){}, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java index 04e69021a9..7daf0f49c4 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java @@ -27,8 +27,8 @@ import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +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.service.mail.TestMailService; @@ -326,14 +326,14 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { } List loadedTenantAdmins = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(33); - TextPageData pageData = null; + PageLink pageLink = new PageLink(33); + PageData pageData = null; do { pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?", - new TypeReference>(){}, pageLink); + new TypeReference>(){}, pageLink); loadedTenantAdmins.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -345,9 +345,9 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) .andExpect(status().isOk()); - pageLink = new TextPageLink(33); + pageLink = new PageLink(33); pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?", - new TypeReference>(){}, pageLink); + new TypeReference>(){}, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertTrue(pageData.getData().isEmpty()); } @@ -393,14 +393,14 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { } List loadedTenantAdminsEmail1 = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(33, email1); - TextPageData pageData = null; + PageLink pageLink = new PageLink(33, 0, email1); + PageData pageData = null; do { pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?", - new TypeReference>(){}, pageLink); + new TypeReference>(){}, pageLink); loadedTenantAdminsEmail1.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -410,13 +410,13 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { Assert.assertEquals(tenantAdminsEmail1, loadedTenantAdminsEmail1); List loadedTenantAdminsEmail2 = new ArrayList<>(); - pageLink = new TextPageLink(16, email2); + pageLink = new PageLink(16, 0, email2); do { pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?", - new TypeReference>(){}, pageLink); + new TypeReference>(){}, pageLink); loadedTenantAdminsEmail2.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -430,9 +430,9 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { .andExpect(status().isOk()); } - pageLink = new TextPageLink(4, email1); + pageLink = new PageLink(4, 0, email1); pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?", - new TypeReference>(){}, pageLink); + new TypeReference>(){}, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -441,9 +441,9 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { .andExpect(status().isOk()); } - pageLink = new TextPageLink(4, email2); + pageLink = new PageLink(4, 0, email2); pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?", - new TypeReference>(){}, pageLink); + new TypeReference>(){}, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -486,14 +486,14 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { } List loadedCustomerUsers = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(33); - TextPageData pageData = null; + PageLink pageLink = new PageLink(33); + PageData pageData = null; do { pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?", - new TypeReference>(){}, pageLink); + new TypeReference>(){}, pageLink); loadedCustomerUsers.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -565,14 +565,14 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { } List loadedCustomerUsersEmail1 = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(33, email1); - TextPageData pageData = null; + PageLink pageLink = new PageLink(33, 0, email1); + PageData pageData = null; do { pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?", - new TypeReference>(){}, pageLink); + new TypeReference>(){}, pageLink); loadedCustomerUsersEmail1.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -582,13 +582,13 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { Assert.assertEquals(customerUsersEmail1, loadedCustomerUsersEmail1); List loadedCustomerUsersEmail2 = new ArrayList<>(); - pageLink = new TextPageLink(16, email2); + pageLink = new PageLink(16, 0, email2); do { pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?", - new TypeReference>(){}, pageLink); + new TypeReference>(){}, pageLink); loadedCustomerUsersEmail2.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -602,9 +602,9 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { .andExpect(status().isOk()); } - pageLink = new TextPageLink(4, email1); + pageLink = new PageLink(4, 0, email1); pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?", - new TypeReference>(){}, pageLink); + new TypeReference>(){}, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -613,9 +613,9 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { .andExpect(status().isOk()); } - pageLink = new TextPageLink(4, email2); + pageLink = new PageLink(4, 0, email2); pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?", - new TypeReference>(){}, pageLink); + new TypeReference>(){}, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseWidgetsBundleControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseWidgetsBundleControllerTest.java index 23428bb976..c6b92068b0 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseWidgetsBundleControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseWidgetsBundleControllerTest.java @@ -22,8 +22,8 @@ import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +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.WidgetsBundle; @@ -150,14 +150,14 @@ public abstract class BaseWidgetsBundleControllerTest extends AbstractController widgetsBundles.addAll(sysWidgetsBundles); List loadedWidgetsBundles = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(14); - TextPageData pageData; + PageLink pageLink = new PageLink(14); + PageData pageData; do { pageData = doGetTypedWithPageLink("/api/widgetsBundles?", - new TypeReference>(){}, pageLink); + new TypeReference>(){}, pageLink); loadedWidgetsBundles.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -186,14 +186,14 @@ public abstract class BaseWidgetsBundleControllerTest extends AbstractController widgetsBundles.addAll(sysWidgetsBundles); List loadedWidgetsBundles = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(14); - TextPageData pageData; + PageLink pageLink = new PageLink(14); + PageData pageData; do { pageData = doGetTypedWithPageLink("/api/widgetsBundles?", - new TypeReference>(){}, pageLink); + new TypeReference>(){}, pageLink); loadedWidgetsBundles.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -207,14 +207,14 @@ public abstract class BaseWidgetsBundleControllerTest extends AbstractController .andExpect(status().isOk()); } - pageLink = new TextPageLink(17); + pageLink = new PageLink(17); loadedWidgetsBundles.clear(); do { pageData = doGetTypedWithPageLink("/api/widgetsBundles?", - new TypeReference>(){}, pageLink); + new TypeReference>(){}, pageLink); loadedWidgetsBundles.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); 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 3fcd3898b4..d12fbe4db1 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 @@ -30,8 +30,7 @@ import org.thingsboard.server.actors.service.ActorService; import org.thingsboard.server.common.data.*; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; -import org.thingsboard.server.common.data.page.TextPageLink; -import org.thingsboard.server.common.data.page.TimePageData; +import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleNode; @@ -160,7 +159,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule Thread.sleep(3000); - TimePageData eventsPage = getDebugEvents(savedTenant.getId(), ruleChain.getFirstRuleNodeId(), 1000); + PageData eventsPage = getDebugEvents(savedTenant.getId(), ruleChain.getFirstRuleNodeId(), 1000); List events = eventsPage.getData().stream().filter(filterByCustomEvent()).collect(Collectors.toList()); Assert.assertEquals(2, events.size()); @@ -275,7 +274,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule Thread.sleep(3000); - TimePageData eventsPage = getDebugEvents(savedTenant.getId(), rootRuleChain.getFirstRuleNodeId(), 1000); + PageData eventsPage = getDebugEvents(savedTenant.getId(), rootRuleChain.getFirstRuleNodeId(), 1000); List events = eventsPage.getData().stream().filter(filterByCustomEvent()).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 10bb3a03c0..08d6143864 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 @@ -32,7 +32,7 @@ import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; -import org.thingsboard.server.common.data.page.TimePageData; +import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleNode; @@ -147,7 +147,7 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac Thread.sleep(3000); - TimePageData eventsPage = getDebugEvents(savedTenant.getId(), ruleChain.getFirstRuleNodeId(), 1000); + PageData eventsPage = getDebugEvents(savedTenant.getId(), ruleChain.getFirstRuleNodeId(), 1000); List events = eventsPage.getData().stream().filter(filterByCustomEvent()).collect(Collectors.toList()); Assert.assertEquals(2, events.size()); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java index 44227f095c..1ca839e3bd 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java @@ -26,7 +26,7 @@ import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TimePageData; +import org.thingsboard.server.common.data.page.PageData; /** * Created by ashvayka on 11.05.17. @@ -45,7 +45,7 @@ public interface AlarmService { ListenableFuture findAlarmInfoByIdAsync(TenantId tenantId, AlarmId alarmId); - ListenableFuture> findAlarms(TenantId tenantId, AlarmQuery query); + ListenableFuture> findAlarms(TenantId tenantId, AlarmQuery query); AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, AlarmStatus alarmStatus); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetService.java index 97933f7ab7..64803654fd 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetService.java @@ -22,8 +22,8 @@ import org.thingsboard.server.common.data.asset.AssetSearchQuery; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import java.util.List; import java.util.Optional; @@ -44,17 +44,17 @@ public interface AssetService { void deleteAsset(TenantId tenantId, AssetId assetId); - TextPageData findAssetsByTenantId(TenantId tenantId, TextPageLink pageLink); + PageData findAssetsByTenantId(TenantId tenantId, PageLink pageLink); - TextPageData findAssetsByTenantIdAndType(TenantId tenantId, String type, TextPageLink pageLink); + PageData findAssetsByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink); ListenableFuture> findAssetsByTenantIdAndIdsAsync(TenantId tenantId, List assetIds); void deleteAssetsByTenantId(TenantId tenantId); - TextPageData findAssetsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink); + PageData findAssetsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink); - TextPageData findAssetsByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, TextPageLink pageLink); + PageData findAssetsByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, PageLink pageLink); ListenableFuture> findAssetsByTenantIdCustomerIdAndIdsAsync(TenantId tenantId, CustomerId customerId, List assetIds); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/audit/AuditLogService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/audit/AuditLogService.java index 90557dbabc..7946472506 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/audit/AuditLogService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/audit/AuditLogService.java @@ -25,20 +25,20 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UUIDBased; import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.common.data.page.TimePageData; +import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.TimePageLink; import java.util.List; public interface AuditLogService { - TimePageData findAuditLogsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink); + PageData findAuditLogsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink); - TimePageData findAuditLogsByTenantIdAndUserId(TenantId tenantId, UserId userId, TimePageLink pageLink); + PageData findAuditLogsByTenantIdAndUserId(TenantId tenantId, UserId userId, TimePageLink pageLink); - TimePageData findAuditLogsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId, TimePageLink pageLink); + PageData findAuditLogsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId, TimePageLink pageLink); - TimePageData findAuditLogsByTenantId(TenantId tenantId, TimePageLink pageLink); + PageData findAuditLogsByTenantId(TenantId tenantId, TimePageLink pageLink); ListenableFuture> logEntityAction( TenantId tenantId, diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/component/ComponentDescriptorService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/component/ComponentDescriptorService.java index 1afe523a7b..a417f2d539 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/component/ComponentDescriptorService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/component/ComponentDescriptorService.java @@ -18,8 +18,8 @@ package org.thingsboard.server.dao.component; import com.fasterxml.jackson.databind.JsonNode; import org.thingsboard.server.common.data.id.ComponentDescriptorId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.plugin.ComponentDescriptor; import org.thingsboard.server.common.data.plugin.ComponentScope; import org.thingsboard.server.common.data.plugin.ComponentType; @@ -35,9 +35,9 @@ public interface ComponentDescriptorService { ComponentDescriptor findByClazz(TenantId tenantId, String clazz); - TextPageData findByTypeAndPageLink(TenantId tenantId, ComponentType type, TextPageLink pageLink); + PageData findByTypeAndPageLink(TenantId tenantId, ComponentType type, PageLink pageLink); - TextPageData findByScopeAndTypeAndPageLink(TenantId tenantId, ComponentScope scope, ComponentType type, TextPageLink pageLink); + PageData findByScopeAndTypeAndPageLink(TenantId tenantId, ComponentScope scope, ComponentType type, PageLink pageLink); boolean validate(TenantId tenantId, ComponentDescriptor component, JsonNode configuration); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/customer/CustomerService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/customer/CustomerService.java index b5eee20b59..2e4d2262bf 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/customer/CustomerService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/customer/CustomerService.java @@ -19,8 +19,8 @@ import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import java.util.Optional; @@ -38,7 +38,7 @@ public interface CustomerService { Customer findOrCreatePublicCustomer(TenantId tenantId); - TextPageData findCustomersByTenantId(TenantId tenantId, TextPageLink pageLink); + PageData findCustomersByTenantId(TenantId tenantId, PageLink pageLink); void deleteCustomersByTenantId(TenantId tenantId); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java index 2d9461cc66..bb49599aa4 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java @@ -21,9 +21,8 @@ import org.thingsboard.server.common.data.DashboardInfo; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; -import org.thingsboard.server.common.data.page.TimePageData; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.TimePageLink; public interface DashboardService { @@ -44,11 +43,11 @@ public interface DashboardService { void deleteDashboard(TenantId tenantId, DashboardId dashboardId); - TextPageData findDashboardsByTenantId(TenantId tenantId, TextPageLink pageLink); + PageData findDashboardsByTenantId(TenantId tenantId, PageLink pageLink); void deleteDashboardsByTenantId(TenantId tenantId); - ListenableFuture> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink); + ListenableFuture> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink); void unassignCustomerDashboards(TenantId tenantId, CustomerId customerId); 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 793bb9829b..f7e34fbc5b 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 @@ -22,8 +22,8 @@ import org.thingsboard.server.common.data.device.DeviceSearchQuery; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import java.util.List; @@ -43,17 +43,17 @@ public interface DeviceService { void deleteDevice(TenantId tenantId, DeviceId deviceId); - TextPageData findDevicesByTenantId(TenantId tenantId, TextPageLink pageLink); + PageData findDevicesByTenantId(TenantId tenantId, PageLink pageLink); - TextPageData findDevicesByTenantIdAndType(TenantId tenantId, String type, TextPageLink pageLink); + PageData findDevicesByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink); ListenableFuture> findDevicesByTenantIdAndIdsAsync(TenantId tenantId, List deviceIds); void deleteDevicesByTenantId(TenantId tenantId); - TextPageData findDevicesByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink); + PageData findDevicesByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink); - TextPageData findDevicesByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, TextPageLink pageLink); + PageData findDevicesByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, PageLink pageLink); ListenableFuture> findDevicesByTenantIdCustomerIdAndIdsAsync(TenantId tenantId, CustomerId customerId, List deviceIds); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java index 454a36446b..310f302725 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java @@ -24,8 +24,8 @@ import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import java.util.List; @@ -46,13 +46,13 @@ public interface EntityViewService { EntityView findEntityViewByTenantIdAndName(TenantId tenantId, String name); - TextPageData findEntityViewByTenantId(TenantId tenantId, TextPageLink pageLink); + PageData findEntityViewByTenantId(TenantId tenantId, PageLink pageLink); - TextPageData findEntityViewByTenantIdAndType(TenantId tenantId, TextPageLink pageLink, String type); + PageData findEntityViewByTenantIdAndType(TenantId tenantId, PageLink pageLink, String type); - TextPageData findEntityViewsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink); + PageData findEntityViewsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink); - TextPageData findEntityViewsByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, TextPageLink pageLink, String type); + PageData findEntityViewsByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, PageLink pageLink, String type); ListenableFuture> findEntityViewsByQuery(TenantId tenantId, EntityViewSearchQuery query); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java index 5192789d49..ef41dfb7ce 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java @@ -19,7 +19,7 @@ import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.Event; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TimePageData; +import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.TimePageLink; import java.util.List; @@ -35,9 +35,9 @@ public interface EventService { Optional findEvent(TenantId tenantId, EntityId entityId, String eventType, String eventUid); - TimePageData findEvents(TenantId tenantId, EntityId entityId, TimePageLink pageLink); + PageData findEvents(TenantId tenantId, EntityId entityId, TimePageLink pageLink); - TimePageData findEvents(TenantId tenantId, EntityId entityId, String eventType, TimePageLink pageLink); + PageData findEvents(TenantId tenantId, EntityId entityId, String eventType, TimePageLink pageLink); List findLatestEvents(TenantId tenantId, EntityId entityId, String eventType, int limit); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java index 5a1949e699..cc6fad476b 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java @@ -19,8 +19,8 @@ import com.google.common.util.concurrent.ListenableFuture; 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.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +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.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; @@ -57,7 +57,7 @@ public interface RuleChainService { List getRuleNodeRelations(TenantId tenantId, RuleNodeId ruleNodeId); - TextPageData findTenantRuleChains(TenantId tenantId, TextPageLink pageLink); + PageData findTenantRuleChains(TenantId tenantId, PageLink pageLink); void deleteRuleChainById(TenantId tenantId, RuleChainId ruleChainId); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/tenant/TenantService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/tenant/TenantService.java index 74099b2a9c..ba85e68380 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/tenant/TenantService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/tenant/TenantService.java @@ -18,8 +18,8 @@ package org.thingsboard.server.dao.tenant; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; public interface TenantService { @@ -31,7 +31,7 @@ public interface TenantService { void deleteTenant(TenantId tenantId); - TextPageData findTenants(TextPageLink pageLink); + PageData findTenants(PageLink pageLink); void deleteTenants(); } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java index 2121b6c483..4e1dfc77cd 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java @@ -21,8 +21,8 @@ import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserCredentialsId; import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.UserCredentials; public interface UserService { @@ -53,11 +53,11 @@ public interface UserService { void deleteUser(TenantId tenantId, UserId userId); - TextPageData findTenantAdmins(TenantId tenantId, TextPageLink pageLink); + PageData findTenantAdmins(TenantId tenantId, PageLink pageLink); void deleteTenantAdmins(TenantId tenantId); - - TextPageData findCustomerUsers(TenantId tenantId, CustomerId customerId, TextPageLink pageLink); + + PageData findCustomerUsers(TenantId tenantId, CustomerId customerId, PageLink pageLink); void deleteCustomerUsers(TenantId tenantId, CustomerId customerId); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleService.java index 9c8594d079..ea752cef26 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleService.java @@ -17,8 +17,8 @@ package org.thingsboard.server.dao.widget; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.WidgetsBundleId; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.widget.WidgetsBundle; import java.util.List; @@ -33,13 +33,13 @@ public interface WidgetsBundleService { WidgetsBundle findWidgetsBundleByTenantIdAndAlias(TenantId tenantId, String alias); - TextPageData findSystemWidgetsBundlesByPageLink(TenantId tenantId, TextPageLink pageLink); + PageData findSystemWidgetsBundlesByPageLink(TenantId tenantId, PageLink pageLink); List findSystemWidgetsBundles(TenantId tenantId); - TextPageData findTenantWidgetsBundlesByTenantId(TenantId tenantId, TextPageLink pageLink); + PageData findTenantWidgetsBundlesByTenantId(TenantId tenantId, PageLink pageLink); - TextPageData findAllTenantWidgetsBundlesByTenantIdAndPageLink(TenantId tenantId, TextPageLink pageLink); + PageData findAllTenantWidgetsBundlesByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink); List findAllTenantWidgetsBundlesByTenantId(TenantId tenantId); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/page/TimePageData.java b/common/data/src/main/java/org/thingsboard/server/common/data/page/PageData.java similarity index 50% rename from common/data/src/main/java/org/thingsboard/server/common/data/page/TimePageData.java rename to common/data/src/main/java/org/thingsboard/server/common/data/page/PageData.java index f530f7a88e..0c647def6d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/page/TimePageData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/page/PageData.java @@ -18,39 +18,29 @@ package org.thingsboard.server.common.data.page; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import org.thingsboard.server.common.data.BaseData; -import org.thingsboard.server.common.data.SearchTextBased; -import org.thingsboard.server.common.data.id.UUIDBased; +import java.util.Collections; import java.util.List; -import java.util.UUID; -public class TimePageData> { +public class PageData { private final List data; - private final TimePageLink nextPageLink; + private final int totalPages; + private final long totalElements; private final boolean hasNext; - public TimePageData(List data, TimePageLink pageLink) { - super(); - this.data = data; - int limit = pageLink.getLimit(); - if (data != null && data.size() == limit) { - int index = data.size() - 1; - UUID idOffset = data.get(index).getId().getId(); - nextPageLink = new TimePageLink(limit, pageLink.getStartTime(), pageLink.getEndTime(), pageLink.isAscOrder(), idOffset); - hasNext = true; - } else { - nextPageLink = null; - hasNext = false; - } + public PageData() { + this(Collections.emptyList(), 0, 0, false); } @JsonCreator - public TimePageData(@JsonProperty("data") List data, - @JsonProperty("nextPageLink") TimePageLink nextPageLink, - @JsonProperty("hasNext") boolean hasNext) { + public PageData(@JsonProperty("data") List data, + @JsonProperty("totalPages") int totalPages, + @JsonProperty("totalElements") long totalElements, + @JsonProperty("hasNext") boolean hasNext) { this.data = data; - this.nextPageLink = nextPageLink; + this.totalPages = totalPages; + this.totalElements = totalElements; this.hasNext = hasNext; } @@ -58,13 +48,17 @@ public class TimePageData> { return data; } + public int getTotalPages() { + return totalPages; + } + + public long getTotalElements() { + return totalElements; + } + @JsonProperty("hasNext") public boolean hasNext() { return hasNext; } - public TimePageLink getNextPageLink() { - return nextPageLink; - } - } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/page/PageDataIterable.java b/common/data/src/main/java/org/thingsboard/server/common/data/page/PageDataIterable.java index 2971b9e927..99d5e01683 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/page/PageDataIterable.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/page/PageDataIterable.java @@ -19,11 +19,12 @@ import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; +import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.SearchTextBased; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.UUIDBased; -public class PageDataIterable> implements Iterable, Iterator { +public class PageDataIterable implements Iterable, Iterator { private final FetchFunction function; private final int fetchSize; @@ -31,7 +32,7 @@ public class PageDataIterable> im private List currentItems; private int currentIdx; private boolean hasNextPack; - private TextPageLink nextPackLink; + private PageLink nextPackLink; private boolean initialized; public PageDataIterable(FetchFunction function, int fetchSize) { @@ -48,7 +49,7 @@ public class PageDataIterable> im @Override public boolean hasNext() { if(!initialized){ - fetch(new TextPageLink(fetchSize)); + fetch(new PageLink(fetchSize)); initialized = true; } if(currentIdx == currentItems.size()){ @@ -59,12 +60,12 @@ public class PageDataIterable> im return currentIdx < currentItems.size(); } - private void fetch(TextPageLink link) { - TextPageData pageData = function.fetch(link); + private void fetch(PageLink link) { + PageData pageData = function.fetch(link); currentIdx = 0; currentItems = pageData.getData(); hasNextPack = pageData.hasNext(); - nextPackLink = pageData.getNextPageLink(); + nextPackLink = link.nextPageLink(); } @Override @@ -75,9 +76,9 @@ public class PageDataIterable> im return currentItems.get(currentIdx++); } - public static interface FetchFunction> { + public static interface FetchFunction { - TextPageData fetch(TextPageLink link); + PageData fetch(PageLink link); } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/page/PageLink.java b/common/data/src/main/java/org/thingsboard/server/common/data/page/PageLink.java new file mode 100644 index 0000000000..242cb39933 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/page/PageLink.java @@ -0,0 +1,60 @@ +/** + * Copyright © 2016-2019 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.page; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Data; + +@Data +public class PageLink { + + private final String textSearch; + private final int pageSize; + private final int page; + private final SortOrder sortOrder; + + public PageLink(PageLink pageLink) { + this.pageSize = pageLink.getPageSize(); + this.page = pageLink.getPage(); + this.textSearch = pageLink.getTextSearch(); + this.sortOrder = pageLink.getSortOrder(); + } + + public PageLink(int pageSize) { + this(pageSize, 0); + } + + public PageLink(int pageSize, int page) { + this(pageSize, page, null, null); + } + + public PageLink(int pageSize, int page, String textSearch) { + this(pageSize, page, textSearch, null); + } + + public PageLink(int pageSize, int page, String textSearch, SortOrder sortOrder) { + this.pageSize = pageSize; + this.page = page; + this.textSearch = textSearch; + this.sortOrder = sortOrder; + } + + @JsonIgnore + public PageLink nextPageLink() { + return new PageLink(this.pageSize, this.page+1, this.textSearch, this.sortOrder); + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/page/BasePageLink.java b/common/data/src/main/java/org/thingsboard/server/common/data/page/SortOrder.java similarity index 59% rename from common/data/src/main/java/org/thingsboard/server/common/data/page/BasePageLink.java rename to common/data/src/main/java/org/thingsboard/server/common/data/page/SortOrder.java index 21e3199bcc..405cfd0f52 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/page/BasePageLink.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/page/SortOrder.java @@ -15,23 +15,25 @@ */ package org.thingsboard.server.common.data.page; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.Setter; +import lombok.Data; -import java.io.Serializable; -import java.util.Arrays; -import java.util.UUID; +@Data +public class SortOrder { -@RequiredArgsConstructor -@AllArgsConstructor -public abstract class BasePageLink implements Serializable { + private final String property; + private final Direction direction; - private static final long serialVersionUID = -4189954843653250481L; + public SortOrder(String property) { + this(property, Direction.ASC); + } - @Getter protected final int limit; + public SortOrder(String property, Direction direction) { + this.property = property; + this.direction = direction; + } - @Getter @Setter protected UUID idOffset; + public static enum Direction { + ASC, DESC + } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/page/TextPageData.java b/common/data/src/main/java/org/thingsboard/server/common/data/page/TextPageData.java deleted file mode 100644 index e5af470e0e..0000000000 --- a/common/data/src/main/java/org/thingsboard/server/common/data/page/TextPageData.java +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Copyright © 2016-2019 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.page; - -import java.util.List; -import java.util.UUID; - -import org.thingsboard.server.common.data.SearchTextBased; -import org.thingsboard.server.common.data.id.UUIDBased; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; - -public class TextPageData> { - - private final List data; - private final TextPageLink nextPageLink; - private final boolean hasNext; - - public TextPageData(List data, TextPageLink pageLink) { - super(); - this.data = data; - int limit = pageLink.getLimit(); - if (data != null && data.size() == limit) { - int index = data.size()-1; - UUID idOffset = data.get(index).getId().getId(); - String textOffset = data.get(index).getSearchText(); - nextPageLink = new TextPageLink(limit, pageLink.getTextSearch(), idOffset, textOffset); - hasNext = true; - } else { - nextPageLink = null; - hasNext = false; - } - } - - @JsonCreator - public TextPageData(@JsonProperty("data") List data, - @JsonProperty("nextPageLink") TextPageLink nextPageLink, - @JsonProperty("hasNext") boolean hasNext) { - this.data = data; - this.nextPageLink = nextPageLink; - this.hasNext = hasNext; - } - - public List getData() { - return data; - } - - @JsonProperty("hasNext") - public boolean hasNext() { - return hasNext; - } - - public TextPageLink getNextPageLink() { - return nextPageLink; - } - -} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/page/TextPageLink.java b/common/data/src/main/java/org/thingsboard/server/common/data/page/TextPageLink.java deleted file mode 100644 index 1cb3ecdecd..0000000000 --- a/common/data/src/main/java/org/thingsboard/server/common/data/page/TextPageLink.java +++ /dev/null @@ -1,80 +0,0 @@ -/** - * Copyright © 2016-2019 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.page; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Getter; -import lombok.ToString; - -import java.io.Serializable; -import java.util.Arrays; -import java.util.UUID; - -@ToString -public class TextPageLink extends BasePageLink implements Serializable { - - private static final long serialVersionUID = -4189954843653250480L; - - @Getter private final String textSearch; - @Getter private final String textSearchBound; - @Getter private final String textOffset; - - public TextPageLink(int limit) { - this(limit, null, null, null); - } - - public TextPageLink(int limit, String textSearch) { - this(limit, textSearch, null, null); - } - - public TextPageLink(int limit, String textSearch, UUID idOffset, String textOffset) { - super(limit, idOffset); - this.textSearch = textSearch != null ? textSearch.toLowerCase() : null; - this.textSearchBound = nextSequence(this.textSearch); - this.textOffset = textOffset != null ? textOffset.toLowerCase() : null; - } - - @JsonCreator - public TextPageLink(@JsonProperty("limit") int limit, - @JsonProperty("textSearch") String textSearch, - @JsonProperty("textSearchBound") String textSearchBound, - @JsonProperty("textOffset") String textOffset, - @JsonProperty("idOffset") UUID idOffset) { - super(limit, idOffset); - this.textSearch = textSearch; - this.textSearchBound = textSearchBound; - this.textOffset = textOffset; - this.idOffset = idOffset; - } - - private static String nextSequence(String input) { - if (input != null && input.length() > 0) { - char[] chars = input.toCharArray(); - int i = chars.length - 1; - while (i >= 0 && ++chars[i--] == Character.MIN_VALUE) ; - if (i == -1 && (chars.length == 0 || chars[0] == Character.MIN_VALUE)) { - char buf[] = Arrays.copyOf(input.toCharArray(), input.length() + 1); - buf[buf.length - 1] = Character.MIN_VALUE; - return new String(buf); - } - return new String(chars); - } else { - return null; - } - } - -} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/page/TimePageLink.java b/common/data/src/main/java/org/thingsboard/server/common/data/page/TimePageLink.java index 2d9f6923a4..1d3081885c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/page/TimePageLink.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/page/TimePageLink.java @@ -16,7 +16,9 @@ package org.thingsboard.server.common.data.page; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; import lombok.Getter; import lombok.ToString; @@ -24,40 +26,43 @@ import java.io.Serializable; import java.util.Arrays; import java.util.UUID; -@ToString -public class TimePageLink extends BasePageLink implements Serializable { +@Data +public class TimePageLink extends PageLink { - private static final long serialVersionUID = -4189954843653250480L; + private final Long startTime; + private final Long endTime; - @Getter private final Long startTime; - @Getter private final Long endTime; - @Getter private final boolean ascOrder; + public TimePageLink(PageLink pageLink, Long startTime, Long endTime) { + super(pageLink); + this.startTime = startTime; + this.endTime = endTime; + } - public TimePageLink(int limit) { - this(limit, null, null, false, null); + public TimePageLink(int pageSize) { + this(pageSize, 0); } - public TimePageLink(int limit, Long startTime) { - this(limit, startTime, null, false, null); + public TimePageLink(int pageSize, int page) { + this(pageSize, page, null); } - public TimePageLink(int limit, Long startTime, Long endTime) { - this(limit, startTime, endTime, false, null); + public TimePageLink(int pageSize, int page, String textSearch) { + this(pageSize, page, textSearch, null, null, null); } - public TimePageLink(int limit, Long startTime, Long endTime, boolean ascOrder) { - this(limit, startTime, endTime, ascOrder, null); + public TimePageLink(int pageSize, int page, String textSearch, SortOrder sortOrder) { + this(pageSize, page, textSearch, sortOrder, null, null); } - @JsonCreator - public TimePageLink(@JsonProperty("limit") int limit, - @JsonProperty("startTime") Long startTime, - @JsonProperty("endTime") Long endTime, - @JsonProperty("ascOrder") boolean ascOrder, - @JsonProperty("idOffset") UUID idOffset) { - super(limit, idOffset); + public TimePageLink(int pageSize, int page, String textSearch, SortOrder sortOrder, Long startTime, Long endTime) { + super(pageSize, page, textSearch, sortOrder); this.startTime = startTime; this.endTime = endTime; - this.ascOrder = ascOrder; + } + + @JsonIgnore + public TimePageLink nextPageLink() { + return new TimePageLink(this.getPageSize(), this.getPage()+1, this.getTextSearch(), this.getSortOrder(), + this.startTime, this.endTime); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java b/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java index d1395f43db..667f0d2d56 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java +++ b/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java @@ -15,7 +15,16 @@ */ package org.thingsboard.server.dao; +import com.datastax.driver.core.utils.UUIDs; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.thingsboard.server.common.data.UUIDConverter; import org.thingsboard.server.common.data.id.UUIDBased; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.page.SortOrder; import org.thingsboard.server.dao.model.ToData; import java.util.*; @@ -25,6 +34,56 @@ public abstract class DaoUtil { private DaoUtil() { } + public static PageData toPageData(Page> page) { + List data = convertDataList(page.getContent()); + return new PageData(data, page.getTotalPages(), page.getTotalElements(), page.hasNext()); + } + + public static Pageable toPageable(PageLink pageLink) { + return toPageable(pageLink, Collections.emptyMap()); + } + + public static Pageable toPageable(PageLink pageLink, Map columnMap) { + return PageRequest.of(pageLink.getPage(), pageLink.getPageSize(), toSort(pageLink.getSortOrder(), columnMap)); + } + + public static String startTimeToId(Long startTime) { + if (startTime != null) { + UUID startOf = UUIDs.startOf(startTime); + return UUIDConverter.fromTimeUUID(startOf); + } else { + return null; + } + } + + public static String endTimeToId(Long endTime) { + if (endTime != null) { + UUID endOf = UUIDs.endOf(endTime); + return UUIDConverter.fromTimeUUID(endOf); + } else { + return null; + } + } + + public static Sort toSort(SortOrder sortOrder) { + return toSort(sortOrder, Collections.emptyMap()); + } + + public static Sort toSort(SortOrder sortOrder, Map columnMap) { + if (sortOrder == null) { + return Sort.unsorted(); + } else { + String property = sortOrder.getProperty(); + if (columnMap.containsKey(property)) { + property = columnMap.get(property); + } + if (property.equals("createdTime")) { + property = "id"; + } + return Sort.by(Sort.Direction.fromString(sortOrder.getDirection().name()), property); + } + } + public static List convertDataList(Collection> toDataList) { List list = Collections.emptyList(); if (toDataList != null && !toDataList.isEmpty()) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/NoSqlDaoConfig.java b/dao/src/main/java/org/thingsboard/server/dao/NoSqlDaoConfig.java deleted file mode 100644 index 81a69ca603..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/NoSqlDaoConfig.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright © 2016-2019 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; - -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration; -import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; -import org.springframework.context.annotation.Configuration; -import org.thingsboard.server.dao.util.NoSqlDao; - -@Configuration -@EnableAutoConfiguration( - exclude = { - DataSourceAutoConfiguration.class, - DataSourceTransactionManagerAutoConfiguration.class, - HibernateJpaAutoConfiguration.class}) -@NoSqlDao -public class NoSqlDaoConfig { -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java index f1de0f0904..c9b996cba7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java @@ -21,6 +21,7 @@ import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmQuery; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.dao.Dao; import java.util.List; @@ -39,5 +40,5 @@ public interface AlarmDao extends Dao { Alarm save(TenantId tenantId, Alarm alarm); - ListenableFuture> findAlarms(TenantId tenantId, AlarmQuery query); + ListenableFuture> findAlarms(TenantId tenantId, AlarmQuery query); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java index 3d81dafb5c..83ef00c6af 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java @@ -35,7 +35,7 @@ import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TimePageData; +import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelationsQuery; @@ -260,12 +260,12 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ } @Override - public ListenableFuture> findAlarms(TenantId tenantId, AlarmQuery query) { - ListenableFuture> alarms = alarmDao.findAlarms(tenantId, query); + public ListenableFuture> findAlarms(TenantId tenantId, AlarmQuery query) { + ListenableFuture> alarms = alarmDao.findAlarms(tenantId, query); if (query.getFetchOriginator() != null && query.getFetchOriginator().booleanValue()) { alarms = Futures.transformAsync(alarms, input -> { - List> alarmFutures = new ArrayList<>(input.size()); - for (AlarmInfo alarmInfo : input) { + List> alarmFutures = new ArrayList<>(input.getData().size()); + for (AlarmInfo alarmInfo : input.getData()) { alarmFutures.add(Futures.transform( entityService.fetchEntityNameAsync(tenantId, alarmInfo.getOriginator()), originatorName -> { if (originatorName == null) { @@ -276,16 +276,12 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ } )); } - return Futures.successfulAsList(alarmFutures); + return Futures.transform(Futures.successfulAsList(alarmFutures), alarmInfos -> { + return new PageData(alarmInfos, input.getTotalPages(), input.getTotalElements(), input.hasNext()); + }); }); } - return Futures.transform(alarms, new Function, TimePageData>() { - @Nullable - @Override - public TimePageData apply(@Nullable List alarms) { - return new TimePageData<>(alarms, query.getPageLink()); - } - }); + return alarms; } @Override @@ -297,7 +293,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ AlarmQuery query; while (hasNext && AlarmSeverity.CRITICAL != highestSeverity) { query = new AlarmQuery(entityId, nextPageLink, alarmSearchStatus, alarmStatus, false); - List alarms; + PageData alarms; try { alarms = alarmDao.findAlarms(tenantId, query).get(); } catch (ExecutionException | InterruptedException e) { @@ -305,11 +301,10 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ entityId, alarmSearchStatus, alarmStatus); throw new RuntimeException(e); } - hasNext = alarms.size() == nextPageLink.getLimit(); - if (hasNext) { - nextPageLink = new TimePageData<>(alarms, nextPageLink).getNextPageLink(); + if (alarms.hasNext()) { + nextPageLink = nextPageLink.nextPageLink(); } - AlarmSeverity severity = detectHighestSeverity(alarms); + AlarmSeverity severity = detectHighestSeverity(alarms.getData()); if (severity == null) { continue; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java index 1c67fd40eb..4caffc43d5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java @@ -19,7 +19,8 @@ import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.Dao; import java.util.List; @@ -47,7 +48,7 @@ public interface AssetDao extends Dao { * @param pageLink the page link * @return the list of asset objects */ - List findAssetsByTenantId(UUID tenantId, TextPageLink pageLink); + PageData findAssetsByTenantId(UUID tenantId, PageLink pageLink); /** * Find assets by tenantId, type and page link. @@ -57,7 +58,7 @@ public interface AssetDao extends Dao { * @param pageLink the page link * @return the list of asset objects */ - List findAssetsByTenantIdAndType(UUID tenantId, String type, TextPageLink pageLink); + PageData findAssetsByTenantIdAndType(UUID tenantId, String type, PageLink pageLink); /** * Find assets by tenantId and assets Ids. @@ -76,7 +77,7 @@ public interface AssetDao extends Dao { * @param pageLink the page link * @return the list of asset objects */ - List findAssetsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink); + PageData findAssetsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, PageLink pageLink); /** * Find assets by tenantId, customerId, type and page link. @@ -87,7 +88,7 @@ public interface AssetDao extends Dao { * @param pageLink the page link * @return the list of asset objects */ - List findAssetsByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, TextPageLink pageLink); + PageData findAssetsByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, PageLink pageLink); /** * Find assets by tenantId, customerId and assets Ids. diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java b/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java index cf679a103f..c57bd8d937 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java @@ -38,8 +38,8 @@ 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.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +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.EntitySearchDirection; import org.thingsboard.server.dao.customer.CustomerDao; @@ -157,22 +157,20 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ } @Override - public TextPageData findAssetsByTenantId(TenantId tenantId, TextPageLink pageLink) { + public PageData findAssetsByTenantId(TenantId tenantId, PageLink pageLink) { log.trace("Executing findAssetsByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - validatePageLink(pageLink, INCORRECT_PAGE_LINK + pageLink); - List assets = assetDao.findAssetsByTenantId(tenantId.getId(), pageLink); - return new TextPageData<>(assets, pageLink); + validatePageLink(pageLink); + return assetDao.findAssetsByTenantId(tenantId.getId(), pageLink); } @Override - public TextPageData findAssetsByTenantIdAndType(TenantId tenantId, String type, TextPageLink pageLink) { + public PageData findAssetsByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink) { log.trace("Executing findAssetsByTenantIdAndType, tenantId [{}], type [{}], pageLink [{}]", tenantId, type, pageLink); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); validateString(type, "Incorrect type " + type); - validatePageLink(pageLink, INCORRECT_PAGE_LINK + pageLink); - List assets = assetDao.findAssetsByTenantIdAndType(tenantId.getId(), type, pageLink); - return new TextPageData<>(assets, pageLink); + validatePageLink(pageLink); + return assetDao.findAssetsByTenantIdAndType(tenantId.getId(), type, pageLink); } @Override @@ -191,24 +189,22 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ } @Override - public TextPageData findAssetsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink) { + public PageData findAssetsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink) { log.trace("Executing findAssetsByTenantIdAndCustomerId, tenantId [{}], customerId [{}], pageLink [{}]", tenantId, customerId, pageLink); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); validateId(customerId, INCORRECT_CUSTOMER_ID + customerId); - validatePageLink(pageLink, INCORRECT_PAGE_LINK + pageLink); - List assets = assetDao.findAssetsByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), pageLink); - return new TextPageData<>(assets, pageLink); + validatePageLink(pageLink); + return assetDao.findAssetsByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), pageLink); } @Override - public TextPageData findAssetsByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, TextPageLink pageLink) { + public PageData findAssetsByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, PageLink pageLink) { log.trace("Executing findAssetsByTenantIdAndCustomerIdAndType, tenantId [{}], customerId [{}], type [{}], pageLink [{}]", tenantId, customerId, type, pageLink); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); validateId(customerId, INCORRECT_CUSTOMER_ID + customerId); validateString(type, "Incorrect type " + type); - validatePageLink(pageLink, INCORRECT_PAGE_LINK + pageLink); - List assets = assetDao.findAssetsByTenantIdAndCustomerIdAndType(tenantId.getId(), customerId.getId(), type, pageLink); - return new TextPageData<>(assets, pageLink); + validatePageLink(pageLink); + return assetDao.findAssetsByTenantIdAndCustomerIdAndType(tenantId.getId(), customerId.getId(), type, pageLink); } @Override @@ -317,7 +313,7 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ new PaginatedRemover() { @Override - protected List findEntities(TenantId tenantId, TenantId id, TextPageLink pageLink) { + protected PageData findEntities(TenantId tenantId, TenantId id, PageLink pageLink) { return assetDao.findAssetsByTenantId(id.getId(), pageLink); } @@ -330,7 +326,7 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ private PaginatedRemover customerAssetsUnasigner = new PaginatedRemover() { @Override - protected List findEntities(TenantId tenantId, CustomerId id, TextPageLink pageLink) { + protected PageData findEntities(TenantId tenantId, CustomerId id, PageLink pageLink) { return assetDao.findAssetsByTenantIdAndCustomerId(tenantId.getId(), id.getId(), pageLink); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogDao.java b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogDao.java index 2d8db71ab7..c0536deac0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogDao.java @@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.audit.AuditLog; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.TimePageLink; import java.util.List; @@ -29,19 +30,11 @@ public interface AuditLogDao { ListenableFuture saveByTenantId(AuditLog auditLog); - ListenableFuture saveByTenantIdAndEntityId(AuditLog auditLog); + PageData findAuditLogsByTenantIdAndEntityId(UUID tenantId, EntityId entityId, TimePageLink pageLink); - ListenableFuture saveByTenantIdAndCustomerId(AuditLog auditLog); + PageData findAuditLogsByTenantIdAndCustomerId(UUID tenantId, CustomerId customerId, TimePageLink pageLink); - ListenableFuture saveByTenantIdAndUserId(AuditLog auditLog); + PageData findAuditLogsByTenantIdAndUserId(UUID tenantId, UserId userId, TimePageLink pageLink); - ListenableFuture savePartitionsByTenantId(AuditLog auditLog); - - List findAuditLogsByTenantIdAndEntityId(UUID tenantId, EntityId entityId, TimePageLink pageLink); - - List findAuditLogsByTenantIdAndCustomerId(UUID tenantId, CustomerId customerId, TimePageLink pageLink); - - List findAuditLogsByTenantIdAndUserId(UUID tenantId, UserId userId, TimePageLink pageLink); - - List findAuditLogsByTenantId(UUID tenantId, TimePageLink pageLink); + PageData findAuditLogsByTenantId(UUID tenantId, TimePageLink pageLink); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java index 3af67454d6..139bb0826b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java @@ -41,7 +41,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UUIDBased; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; -import org.thingsboard.server.common.data.page.TimePageData; +import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.rule.RuleChainMetaData; @@ -81,38 +81,34 @@ public class AuditLogServiceImpl implements AuditLogService { private AuditLogSink auditLogSink; @Override - public TimePageData findAuditLogsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink) { + public PageData findAuditLogsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink) { log.trace("Executing findAuditLogsByTenantIdAndCustomerId [{}], [{}], [{}]", tenantId, customerId, pageLink); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); validateId(customerId, "Incorrect customerId " + customerId); - List auditLogs = auditLogDao.findAuditLogsByTenantIdAndCustomerId(tenantId.getId(), customerId, pageLink); - return new TimePageData<>(auditLogs, pageLink); + return auditLogDao.findAuditLogsByTenantIdAndCustomerId(tenantId.getId(), customerId, pageLink); } @Override - public TimePageData findAuditLogsByTenantIdAndUserId(TenantId tenantId, UserId userId, TimePageLink pageLink) { + public PageData findAuditLogsByTenantIdAndUserId(TenantId tenantId, UserId userId, TimePageLink pageLink) { log.trace("Executing findAuditLogsByTenantIdAndUserId [{}], [{}], [{}]", tenantId, userId, pageLink); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); validateId(userId, "Incorrect userId" + userId); - List auditLogs = auditLogDao.findAuditLogsByTenantIdAndUserId(tenantId.getId(), userId, pageLink); - return new TimePageData<>(auditLogs, pageLink); + return auditLogDao.findAuditLogsByTenantIdAndUserId(tenantId.getId(), userId, pageLink); } @Override - public TimePageData findAuditLogsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId, TimePageLink pageLink) { + public PageData findAuditLogsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId, TimePageLink pageLink) { log.trace("Executing findAuditLogsByTenantIdAndEntityId [{}], [{}], [{}]", tenantId, entityId, pageLink); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); validateEntityId(entityId, INCORRECT_TENANT_ID + entityId); - List auditLogs = auditLogDao.findAuditLogsByTenantIdAndEntityId(tenantId.getId(), entityId, pageLink); - return new TimePageData<>(auditLogs, pageLink); + return auditLogDao.findAuditLogsByTenantIdAndEntityId(tenantId.getId(), entityId, pageLink); } @Override - public TimePageData findAuditLogsByTenantId(TenantId tenantId, TimePageLink pageLink) { + public PageData findAuditLogsByTenantId(TenantId tenantId, TimePageLink pageLink) { log.trace("Executing findAuditLogs [{}]", pageLink); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - List auditLogs = auditLogDao.findAuditLogsByTenantId(tenantId.getId(), pageLink); - return new TimePageData<>(auditLogs, pageLink); + return auditLogDao.findAuditLogsByTenantId(tenantId.getId(), pageLink); } @Override @@ -328,11 +324,7 @@ public class AuditLogServiceImpl implements AuditLogService { log.trace("Executing logAction [{}]", auditLogEntry); auditLogValidator.validate(auditLogEntry, AuditLog::getTenantId); List> futures = Lists.newArrayListWithExpectedSize(INSERTS_PER_ENTRY); - futures.add(auditLogDao.savePartitionsByTenantId(auditLogEntry)); futures.add(auditLogDao.saveByTenantId(auditLogEntry)); - futures.add(auditLogDao.saveByTenantIdAndEntityId(auditLogEntry)); - futures.add(auditLogDao.saveByTenantIdAndCustomerId(auditLogEntry)); - futures.add(auditLogDao.saveByTenantIdAndUserId(auditLogEntry)); auditLogSink.logAction(auditLogEntry); diff --git a/dao/src/main/java/org/thingsboard/server/dao/audit/DummyAuditLogServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/audit/DummyAuditLogServiceImpl.java index b8fabd5f23..35e9d8255c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/audit/DummyAuditLogServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/audit/DummyAuditLogServiceImpl.java @@ -27,7 +27,7 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UUIDBased; import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.common.data.page.TimePageData; +import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.TimePageLink; import java.util.List; @@ -37,23 +37,23 @@ import java.util.List; public class DummyAuditLogServiceImpl implements AuditLogService { @Override - public TimePageData findAuditLogsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink) { - return new TimePageData<>(null, pageLink); + public PageData findAuditLogsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink) { + return new PageData(); } @Override - public TimePageData findAuditLogsByTenantIdAndUserId(TenantId tenantId, UserId userId, TimePageLink pageLink) { - return new TimePageData<>(null, pageLink); + public PageData findAuditLogsByTenantIdAndUserId(TenantId tenantId, UserId userId, TimePageLink pageLink) { + return new PageData(); } @Override - public TimePageData findAuditLogsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId, TimePageLink pageLink) { - return new TimePageData<>(null, pageLink); + public PageData findAuditLogsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId, TimePageLink pageLink) { + return new PageData(); } @Override - public TimePageData findAuditLogsByTenantId(TenantId tenantId, TimePageLink pageLink) { - return new TimePageData<>(null, pageLink); + public PageData findAuditLogsByTenantId(TenantId tenantId, TimePageLink pageLink) { + return new PageData(); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/component/BaseComponentDescriptorService.java b/dao/src/main/java/org/thingsboard/server/dao/component/BaseComponentDescriptorService.java index b12e114d93..28ac2bd4a3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/component/BaseComponentDescriptorService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/component/BaseComponentDescriptorService.java @@ -27,8 +27,8 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.id.ComponentDescriptorId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.plugin.ComponentDescriptor; import org.thingsboard.server.common.data.plugin.ComponentScope; import org.thingsboard.server.common.data.plugin.ComponentType; @@ -74,17 +74,15 @@ public class BaseComponentDescriptorService implements ComponentDescriptorServic } @Override - public TextPageData findByTypeAndPageLink(TenantId tenantId, ComponentType type, TextPageLink pageLink) { - Validator.validatePageLink(pageLink, "Incorrect PageLink object for search plugin components request."); - List components = componentDescriptorDao.findByTypeAndPageLink(tenantId, type, pageLink); - return new TextPageData<>(components, pageLink); + public PageData findByTypeAndPageLink(TenantId tenantId, ComponentType type, PageLink pageLink) { + Validator.validatePageLink(pageLink); + return componentDescriptorDao.findByTypeAndPageLink(tenantId, type, pageLink); } @Override - public TextPageData findByScopeAndTypeAndPageLink(TenantId tenantId, ComponentScope scope, ComponentType type, TextPageLink pageLink) { - Validator.validatePageLink(pageLink, "Incorrect PageLink object for search plugin components request."); - List components = componentDescriptorDao.findByScopeAndTypeAndPageLink(tenantId, scope, type, pageLink); - return new TextPageData<>(components, pageLink); + public PageData findByScopeAndTypeAndPageLink(TenantId tenantId, ComponentScope scope, ComponentType type, PageLink pageLink) { + Validator.validatePageLink(pageLink); + return componentDescriptorDao.findByScopeAndTypeAndPageLink(tenantId, scope, type, pageLink); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/component/ComponentDescriptorDao.java b/dao/src/main/java/org/thingsboard/server/dao/component/ComponentDescriptorDao.java index 95ce126a1e..b69535e162 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/component/ComponentDescriptorDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/component/ComponentDescriptorDao.java @@ -17,7 +17,8 @@ package org.thingsboard.server.dao.component; import org.thingsboard.server.common.data.id.ComponentDescriptorId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.plugin.ComponentDescriptor; import org.thingsboard.server.common.data.plugin.ComponentScope; import org.thingsboard.server.common.data.plugin.ComponentType; @@ -37,9 +38,9 @@ public interface ComponentDescriptorDao extends Dao { ComponentDescriptor findByClazz(TenantId tenantId, String clazz); - List findByTypeAndPageLink(TenantId tenantId, ComponentType type, TextPageLink pageLink); + PageData findByTypeAndPageLink(TenantId tenantId, ComponentType type, PageLink pageLink); - List findByScopeAndTypeAndPageLink(TenantId tenantId, ComponentScope scope, ComponentType type, TextPageLink pageLink); + PageData findByScopeAndTypeAndPageLink(TenantId tenantId, ComponentScope scope, ComponentType type, PageLink pageLink); void deleteById(TenantId tenantId, ComponentDescriptorId componentId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerDao.java b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerDao.java index bccd919c32..aaa2b9c8d2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerDao.java @@ -17,7 +17,8 @@ package org.thingsboard.server.dao.customer; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.Dao; import java.util.List; @@ -44,7 +45,7 @@ public interface CustomerDao extends Dao { * @param pageLink the page link * @return the list of customer objects */ - List findCustomersByTenantId(UUID tenantId, TextPageLink pageLink); + PageData findCustomersByTenantId(UUID tenantId, PageLink pageLink); /** * Find customers by tenantId and customer title. diff --git a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java index 511e410447..0c847d2854 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java @@ -26,8 +26,8 @@ import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.device.DeviceService; @@ -144,12 +144,11 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom } @Override - public TextPageData findCustomersByTenantId(TenantId tenantId, TextPageLink pageLink) { + public PageData findCustomersByTenantId(TenantId tenantId, PageLink pageLink) { log.trace("Executing findCustomersByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink); Validator.validateId(tenantId, "Incorrect tenantId " + tenantId); - Validator.validatePageLink(pageLink, "Incorrect page link " + pageLink); - List customers = customerDao.findCustomersByTenantId(tenantId.getId(), pageLink); - return new TextPageData<>(customers, pageLink); + Validator.validatePageLink(pageLink); + return customerDao.findCustomersByTenantId(tenantId.getId(), pageLink); } @Override @@ -208,7 +207,7 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom new PaginatedRemover() { @Override - protected List findEntities(TenantId tenantId, TenantId id, TextPageLink pageLink) { + protected PageData findEntities(TenantId tenantId, TenantId id, PageLink pageLink) { return customerDao.findCustomersByTenantId(id.getId(), pageLink); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardInfoDao.java index a90ba4b464..002a07045c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardInfoDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardInfoDao.java @@ -17,7 +17,8 @@ package org.thingsboard.server.dao.dashboard; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.DashboardInfo; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.dao.Dao; @@ -36,7 +37,7 @@ public interface DashboardInfoDao extends Dao { * @param pageLink the page link * @return the list of dashboard objects */ - List findDashboardsByTenantId(UUID tenantId, TextPageLink pageLink); + PageData findDashboardsByTenantId(UUID tenantId, PageLink pageLink); /** * Find dashboards by tenantId, customerId and page link. @@ -46,6 +47,6 @@ public interface DashboardInfoDao extends Dao { * @param pageLink the page link * @return the list of dashboard objects */ - ListenableFuture> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TimePageLink pageLink); + ListenableFuture> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TimePageLink pageLink); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java index 83d4c55068..63ac5e93ca 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java @@ -29,9 +29,8 @@ import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; -import org.thingsboard.server.common.data.page.TimePageData; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.RelationTypeGroup; @@ -174,12 +173,11 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb } @Override - public TextPageData findDashboardsByTenantId(TenantId tenantId, TextPageLink pageLink) { + public PageData findDashboardsByTenantId(TenantId tenantId, PageLink pageLink) { log.trace("Executing findDashboardsByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink); Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - Validator.validatePageLink(pageLink, "Incorrect page link " + pageLink); - List dashboards = dashboardInfoDao.findDashboardsByTenantId(tenantId.getId(), pageLink); - return new TextPageData<>(dashboards, pageLink); + Validator.validatePageLink(pageLink); + return dashboardInfoDao.findDashboardsByTenantId(tenantId.getId(), pageLink); } @Override @@ -190,20 +188,12 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb } @Override - public ListenableFuture> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink) { + public ListenableFuture> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink) { log.trace("Executing findDashboardsByTenantIdAndCustomerId, tenantId [{}], customerId [{}], pageLink [{}]", tenantId, customerId, pageLink); Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId); Validator.validateId(customerId, "Incorrect customerId " + customerId); - Validator.validatePageLink(pageLink, "Incorrect page link " + pageLink); - ListenableFuture> dashboards = dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), pageLink); - - return Futures.transform(dashboards, new Function, TimePageData>() { - @Nullable - @Override - public TimePageData apply(@Nullable List dashboards) { - return new TimePageData<>(dashboards, pageLink); - } - }); + Validator.validatePageLink(pageLink); + return dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), pageLink); } @Override @@ -250,7 +240,7 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb new PaginatedRemover() { @Override - protected List findEntities(TenantId tenantId, TenantId id, TextPageLink pageLink) { + protected PageData findEntities(TenantId tenantId, TenantId id, PageLink pageLink) { return dashboardInfoDao.findDashboardsByTenantId(id.getId(), pageLink); } @@ -269,7 +259,7 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb } @Override - protected List findEntities(TenantId tenantId, Customer customer, TimePageLink pageLink) { + protected PageData findEntities(TenantId tenantId, Customer customer, TimePageLink pageLink) { try { return dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(customer.getTenantId().getId(), customer.getId().getId(), pageLink).get(); } catch (InterruptedException | ExecutionException e) { @@ -294,7 +284,7 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb } @Override - protected List findEntities(TenantId tenantId, Customer customer, TimePageLink pageLink) { + protected PageData findEntities(TenantId tenantId, Customer customer, TimePageLink pageLink) { try { return dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(customer.getTenantId().getId(), customer.getId().getId(), pageLink).get(); } catch (InterruptedException | ExecutionException e) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java index 83533f1e36..64ac69ac59 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java @@ -19,7 +19,8 @@ import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.Dao; import java.util.List; @@ -47,7 +48,7 @@ public interface DeviceDao extends Dao { * @param pageLink the page link * @return the list of device objects */ - List findDevicesByTenantId(UUID tenantId, TextPageLink pageLink); + PageData findDevicesByTenantId(UUID tenantId, PageLink pageLink); /** * Find devices by tenantId, type and page link. @@ -57,7 +58,7 @@ public interface DeviceDao extends Dao { * @param pageLink the page link * @return the list of device objects */ - List findDevicesByTenantIdAndType(UUID tenantId, String type, TextPageLink pageLink); + PageData findDevicesByTenantIdAndType(UUID tenantId, String type, PageLink pageLink); /** * Find devices by tenantId and devices Ids. @@ -76,7 +77,7 @@ public interface DeviceDao extends Dao { * @param pageLink the page link * @return the list of device objects */ - List findDevicesByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink); + PageData findDevicesByTenantIdAndCustomerId(UUID tenantId, UUID customerId, PageLink pageLink); /** * Find devices by tenantId, customerId, type and page link. @@ -87,7 +88,7 @@ public interface DeviceDao extends Dao { * @param pageLink the page link * @return the list of device objects */ - List findDevicesByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, TextPageLink pageLink); + PageData findDevicesByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, PageLink pageLink); /** 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 872e03a387..ec6d85e0d2 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,8 +38,8 @@ 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.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +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.EntitySearchDirection; import org.thingsboard.server.common.data.security.DeviceCredentials; @@ -180,22 +180,20 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe } @Override - public TextPageData findDevicesByTenantId(TenantId tenantId, TextPageLink pageLink) { + public PageData findDevicesByTenantId(TenantId tenantId, PageLink pageLink) { log.trace("Executing findDevicesByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - validatePageLink(pageLink, INCORRECT_PAGE_LINK + pageLink); - List devices = deviceDao.findDevicesByTenantId(tenantId.getId(), pageLink); - return new TextPageData<>(devices, pageLink); + validatePageLink(pageLink); + return deviceDao.findDevicesByTenantId(tenantId.getId(), pageLink); } @Override - public TextPageData findDevicesByTenantIdAndType(TenantId tenantId, String type, TextPageLink pageLink) { + public PageData findDevicesByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink) { log.trace("Executing findDevicesByTenantIdAndType, tenantId [{}], type [{}], pageLink [{}]", tenantId, type, pageLink); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); validateString(type, "Incorrect type " + type); - validatePageLink(pageLink, INCORRECT_PAGE_LINK + pageLink); - List devices = deviceDao.findDevicesByTenantIdAndType(tenantId.getId(), type, pageLink); - return new TextPageData<>(devices, pageLink); + validatePageLink(pageLink); + return deviceDao.findDevicesByTenantIdAndType(tenantId.getId(), type, pageLink); } @Override @@ -215,24 +213,22 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe } @Override - public TextPageData findDevicesByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink) { + public PageData findDevicesByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink) { log.trace("Executing findDevicesByTenantIdAndCustomerId, tenantId [{}], customerId [{}], pageLink [{}]", tenantId, customerId, pageLink); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); validateId(customerId, INCORRECT_CUSTOMER_ID + customerId); - validatePageLink(pageLink, INCORRECT_PAGE_LINK + pageLink); - List devices = deviceDao.findDevicesByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), pageLink); - return new TextPageData<>(devices, pageLink); + validatePageLink(pageLink); + return deviceDao.findDevicesByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), pageLink); } @Override - public TextPageData findDevicesByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, TextPageLink pageLink) { + public PageData findDevicesByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, PageLink pageLink) { log.trace("Executing findDevicesByTenantIdAndCustomerIdAndType, tenantId [{}], customerId [{}], type [{}], pageLink [{}]", tenantId, customerId, type, pageLink); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); validateId(customerId, INCORRECT_CUSTOMER_ID + customerId); validateString(type, "Incorrect type " + type); - validatePageLink(pageLink, INCORRECT_PAGE_LINK + pageLink); - List devices = deviceDao.findDevicesByTenantIdAndCustomerIdAndType(tenantId.getId(), customerId.getId(), type, pageLink); - return new TextPageData<>(devices, pageLink); + validatePageLink(pageLink); + return deviceDao.findDevicesByTenantIdAndCustomerIdAndType(tenantId.getId(), customerId.getId(), type, pageLink); } @Override @@ -348,7 +344,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe new PaginatedRemover() { @Override - protected List findEntities(TenantId tenantId, TenantId id, TextPageLink pageLink) { + protected PageData findEntities(TenantId tenantId, TenantId id, PageLink pageLink) { return deviceDao.findDevicesByTenantId(id.getId(), pageLink); } @@ -361,7 +357,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe private PaginatedRemover customerDeviceUnasigner = new PaginatedRemover() { @Override - protected List findEntities(TenantId tenantId, CustomerId id, TextPageLink pageLink) { + protected PageData findEntities(TenantId tenantId, CustomerId id, PageLink pageLink) { return deviceDao.findDevicesByTenantIdAndCustomerId(tenantId.getId(), id.getId(), pageLink); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewDao.java b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewDao.java index b839cac825..ebc101861f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewDao.java @@ -20,7 +20,8 @@ import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.Dao; import java.util.List; @@ -47,7 +48,7 @@ public interface EntityViewDao extends Dao { * @param pageLink the page link * @return the list of entity view objects */ - List findEntityViewsByTenantId(UUID tenantId, TextPageLink pageLink); + PageData findEntityViewsByTenantId(UUID tenantId, PageLink pageLink); /** * Find entity views by tenantId, type and page link. @@ -57,7 +58,7 @@ public interface EntityViewDao extends Dao { * @param pageLink the page link * @return the list of entity view objects */ - List findEntityViewsByTenantIdAndType(UUID tenantId, String type, TextPageLink pageLink); + PageData findEntityViewsByTenantIdAndType(UUID tenantId, String type, PageLink pageLink); /** * Find entity views by tenantId and entity view name. @@ -76,9 +77,9 @@ public interface EntityViewDao extends Dao { * @param pageLink the page link * @return the list of entity view objects */ - List findEntityViewsByTenantIdAndCustomerId(UUID tenantId, + PageData findEntityViewsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, - TextPageLink pageLink); + PageLink pageLink); /** * Find entity views by tenantId, customerId, type and page link. @@ -89,10 +90,10 @@ public interface EntityViewDao extends Dao { * @param pageLink the page link * @return the list of entity view objects */ - List findEntityViewsByTenantIdAndCustomerIdAndType(UUID tenantId, + PageData findEntityViewsByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, - TextPageLink pageLink); + PageLink pageLink); ListenableFuture> findEntityViewsByTenantIdAndEntityIdAsync(UUID tenantId, UUID entityId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java index e3142e216e..b0f323b2a6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java @@ -38,8 +38,8 @@ import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +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.EntitySearchDirection; import org.thingsboard.server.dao.customer.CustomerDao; @@ -142,48 +142,44 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti } @Override - public TextPageData findEntityViewByTenantId(TenantId tenantId, TextPageLink pageLink) { + public PageData findEntityViewByTenantId(TenantId tenantId, PageLink pageLink) { log.trace("Executing findEntityViewsByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - validatePageLink(pageLink, INCORRECT_PAGE_LINK + pageLink); - List entityViews = entityViewDao.findEntityViewsByTenantId(tenantId.getId(), pageLink); - return new TextPageData<>(entityViews, pageLink); + validatePageLink(pageLink); + return entityViewDao.findEntityViewsByTenantId(tenantId.getId(), pageLink); } @Override - public TextPageData findEntityViewByTenantIdAndType(TenantId tenantId, TextPageLink pageLink, String type) { + public PageData findEntityViewByTenantIdAndType(TenantId tenantId, PageLink pageLink, String type) { log.trace("Executing findEntityViewByTenantIdAndType, tenantId [{}], pageLink [{}], type [{}]", tenantId, pageLink, type); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - validatePageLink(pageLink, INCORRECT_PAGE_LINK + pageLink); + validatePageLink(pageLink); validateString(type, "Incorrect type " + type); - List entityViews = entityViewDao.findEntityViewsByTenantIdAndType(tenantId.getId(), type, pageLink); - return new TextPageData<>(entityViews, pageLink); + return entityViewDao.findEntityViewsByTenantIdAndType(tenantId.getId(), type, pageLink); } @Override - public TextPageData findEntityViewsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, - TextPageLink pageLink) { + public PageData findEntityViewsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, + PageLink pageLink) { log.trace("Executing findEntityViewByTenantIdAndCustomerId, tenantId [{}], customerId [{}]," + " pageLink [{}]", tenantId, customerId, pageLink); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); validateId(customerId, INCORRECT_CUSTOMER_ID + customerId); - validatePageLink(pageLink, INCORRECT_PAGE_LINK + pageLink); - List entityViews = entityViewDao.findEntityViewsByTenantIdAndCustomerId(tenantId.getId(), + validatePageLink(pageLink); + return entityViewDao.findEntityViewsByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), pageLink); - return new TextPageData<>(entityViews, pageLink); } @Override - public TextPageData findEntityViewsByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, TextPageLink pageLink, String type) { + public PageData findEntityViewsByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, PageLink pageLink, String type) { log.trace("Executing findEntityViewsByTenantIdAndCustomerIdAndType, tenantId [{}], customerId [{}]," + " pageLink [{}], type [{}]", tenantId, customerId, pageLink, type); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); validateId(customerId, INCORRECT_CUSTOMER_ID + customerId); - validatePageLink(pageLink, INCORRECT_PAGE_LINK + pageLink); + validatePageLink(pageLink); validateString(type, "Incorrect type " + type); - List entityViews = entityViewDao.findEntityViewsByTenantIdAndCustomerIdAndType(tenantId.getId(), + return entityViewDao.findEntityViewsByTenantIdAndCustomerIdAndType(tenantId.getId(), customerId.getId(), type, pageLink); - return new TextPageData<>(entityViews, pageLink); } @Override @@ -335,7 +331,7 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti private PaginatedRemover tenantEntityViewRemover = new PaginatedRemover() { @Override - protected List findEntities(TenantId tenantId, TenantId id, TextPageLink pageLink) { + protected PageData findEntities(TenantId tenantId, TenantId id, PageLink pageLink) { return entityViewDao.findEntityViewsByTenantId(id.getId(), pageLink); } @@ -347,7 +343,7 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti private PaginatedRemover customerEntityViewsUnAssigner = new PaginatedRemover() { @Override - protected List findEntities(TenantId tenantId, CustomerId id, TextPageLink pageLink) { + protected PageData findEntities(TenantId tenantId, CustomerId id, PageLink pageLink) { return entityViewDao.findEntityViewsByTenantIdAndCustomerId(tenantId.getId(), id.getId(), pageLink); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java b/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java index 7691484d68..e3975c98b1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java @@ -23,7 +23,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.Event; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TimePageData; +import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; @@ -78,15 +78,13 @@ public class BaseEventService implements EventService { } @Override - public TimePageData findEvents(TenantId tenantId, EntityId entityId, TimePageLink pageLink) { - List events = eventDao.findEvents(tenantId.getId(), entityId, pageLink); - return new TimePageData<>(events, pageLink); + public PageData findEvents(TenantId tenantId, EntityId entityId, TimePageLink pageLink) { + return eventDao.findEvents(tenantId.getId(), entityId, pageLink); } @Override - public TimePageData findEvents(TenantId tenantId, EntityId entityId, String eventType, TimePageLink pageLink) { - List events = eventDao.findEvents(tenantId.getId(), entityId, eventType, pageLink); - return new TimePageData<>(events, pageLink); + public PageData findEvents(TenantId tenantId, EntityId entityId, String eventType, TimePageLink pageLink) { + return eventDao.findEvents(tenantId.getId(), entityId, eventType, pageLink); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/event/EventDao.java b/dao/src/main/java/org/thingsboard/server/dao/event/EventDao.java index bf1c56e92e..e6fc135a5c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/event/EventDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/event/EventDao.java @@ -19,6 +19,7 @@ import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.Event; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.dao.Dao; @@ -74,7 +75,7 @@ public interface EventDao extends Dao { * @param pageLink the pageLink * @return the event list */ - List findEvents(UUID tenantId, EntityId entityId, TimePageLink pageLink); + PageData findEvents(UUID tenantId, EntityId entityId, TimePageLink pageLink); /** * Find events by tenantId, entityId, eventType and pageLink. @@ -85,7 +86,7 @@ public interface EventDao extends Dao { * @param pageLink the pageLink * @return the event list */ - List findEvents(UUID tenantId, EntityId entityId, String eventType, TimePageLink pageLink); + PageData findEvents(UUID tenantId, EntityId entityId, String eventType, TimePageLink pageLink); /** * Find latest events by tenantId, entityId and eventType. diff --git a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractModelDao.java b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractModelDao.java deleted file mode 100644 index a5580a6327..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractModelDao.java +++ /dev/null @@ -1,195 +0,0 @@ -/** - * Copyright © 2016-2019 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.nosql; - -import com.datastax.driver.core.ResultSet; -import com.datastax.driver.core.ResultSetFuture; -import com.datastax.driver.core.Statement; -import com.datastax.driver.core.querybuilder.QueryBuilder; -import com.datastax.driver.core.querybuilder.Select; -import com.datastax.driver.core.utils.UUIDs; -import com.datastax.driver.mapping.Mapper; -import com.datastax.driver.mapping.Result; -import com.google.common.base.Function; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import lombok.extern.slf4j.Slf4j; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.dao.Dao; -import org.thingsboard.server.dao.DaoUtil; -import org.thingsboard.server.dao.model.BaseEntity; -import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.model.wrapper.EntityResultSet; - -import javax.annotation.Nullable; -import java.util.Collections; -import java.util.List; -import java.util.UUID; - -import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; -import static com.datastax.driver.core.querybuilder.QueryBuilder.select; - -@Slf4j -public abstract class CassandraAbstractModelDao, D> extends CassandraAbstractDao implements Dao { - - protected abstract Class getColumnFamilyClass(); - - protected abstract String getColumnFamilyName(); - - protected E updateSearchTextIfPresent(E entity) { - return entity; - } - - protected Mapper getMapper() { - return cluster.getMapper(getColumnFamilyClass()); - } - - protected List findListByStatement(TenantId tenantId, Statement statement) { - List list = Collections.emptyList(); - if (statement != null) { - statement.setConsistencyLevel(cluster.getDefaultReadConsistencyLevel()); - ResultSet resultSet = executeRead(tenantId, statement); - Result result = getMapper().map(resultSet); - if (result != null) { - list = result.all(); - } - } - return list; - } - - protected ListenableFuture> findListByStatementAsync(TenantId tenantId, Statement statement) { - if (statement != null) { - statement.setConsistencyLevel(cluster.getDefaultReadConsistencyLevel()); - ResultSetFuture resultSetFuture = executeAsyncRead(tenantId, statement); - return Futures.transform(resultSetFuture, new Function>() { - @Nullable - @Override - public List apply(@Nullable ResultSet resultSet) { - Result result = getMapper().map(resultSet); - if (result != null) { - List entities = result.all(); - return DaoUtil.convertDataList(entities); - } else { - return Collections.emptyList(); - } - } - }); - } - return Futures.immediateFuture(Collections.emptyList()); - } - - protected E findOneByStatement(TenantId tenantId, Statement statement) { - E object = null; - if (statement != null) { - statement.setConsistencyLevel(cluster.getDefaultReadConsistencyLevel()); - ResultSet resultSet = executeRead(tenantId, statement); - Result result = getMapper().map(resultSet); - if (result != null) { - object = result.one(); - } - } - return object; - } - - protected ListenableFuture findOneByStatementAsync(TenantId tenantId, Statement statement) { - if (statement != null) { - statement.setConsistencyLevel(cluster.getDefaultReadConsistencyLevel()); - ResultSetFuture resultSetFuture = executeAsyncRead(tenantId, statement); - return Futures.transform(resultSetFuture, new Function() { - @Nullable - @Override - public D apply(@Nullable ResultSet resultSet) { - Result result = getMapper().map(resultSet); - if (result != null) { - E entity = result.one(); - return DaoUtil.getData(entity); - } else { - return null; - } - } - }); - } - return Futures.immediateFuture(null); - } - - protected Statement getSaveQuery(E dto) { - return getMapper().saveQuery(dto); - } - - protected EntityResultSet saveWithResult(TenantId tenantId, E entity) { - log.debug("Save entity {}", entity); - if (entity.getId() == null) { - entity.setId(UUIDs.timeBased()); - } else if (isDeleteOnSave()) { - removeById(tenantId, entity.getId()); - } - Statement saveStatement = getSaveQuery(entity); - saveStatement.setConsistencyLevel(cluster.getDefaultWriteConsistencyLevel()); - ResultSet resultSet = executeWrite(tenantId, saveStatement); - return new EntityResultSet<>(resultSet, entity); - } - - protected boolean isDeleteOnSave() { - return true; - } - - @Override - public D save(TenantId tenantId, D domain) { - E entity; - try { - entity = getColumnFamilyClass().getConstructor(domain.getClass()).newInstance(domain); - } catch (Exception e) { - log.error("Can't create entity for domain object {}", domain, e); - throw new IllegalArgumentException("Can't create entity for domain object {" + domain + "}", e); - } - entity = updateSearchTextIfPresent(entity); - log.debug("Saving entity {}", entity); - entity = saveWithResult(tenantId, entity).getEntity(); - return DaoUtil.getData(entity); - } - - @Override - public D findById(TenantId tenantId, UUID key) { - log.debug("Get entity by key {}", key); - Select.Where query = select().from(getColumnFamilyName()).where(eq(ModelConstants.ID_PROPERTY, key)); - log.trace("Execute query {}", query); - E entity = findOneByStatement(tenantId, query); - return DaoUtil.getData(entity); - } - - @Override - public ListenableFuture findByIdAsync(TenantId tenantId, UUID key) { - log.debug("Get entity by key {}", key); - Select.Where query = select().from(getColumnFamilyName()).where(eq(ModelConstants.ID_PROPERTY, key)); - log.trace("Execute query {}", query); - return findOneByStatementAsync(tenantId, query); - } - - @Override - public boolean removeById(TenantId tenantId, UUID key) { - Statement delete = QueryBuilder.delete().all().from(getColumnFamilyName()).where(eq(ModelConstants.ID_PROPERTY, key)); - log.debug("Remove request: {}", delete.toString()); - return executeWrite(tenantId, delete).wasApplied(); - } - - @Override - public List find(TenantId tenantId) { - log.debug("Get all entities from column family {}", getColumnFamilyName()); - List entities = findListByStatement(tenantId, QueryBuilder.select().all().from(getColumnFamilyName()).setConsistencyLevel(cluster.getDefaultReadConsistencyLevel())); - return DaoUtil.convertDataList(entities); - } - -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractSearchTextDao.java b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractSearchTextDao.java deleted file mode 100644 index a48bbfffff..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractSearchTextDao.java +++ /dev/null @@ -1,83 +0,0 @@ -/** - * Copyright © 2016-2019 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.nosql; - -import com.datastax.driver.core.querybuilder.Clause; -import com.datastax.driver.core.querybuilder.QueryBuilder; -import com.datastax.driver.core.querybuilder.Select; -import com.datastax.driver.core.querybuilder.Select.Where; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageLink; -import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.model.SearchTextEntity; - -import java.util.List; - -import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; -import static com.datastax.driver.core.querybuilder.QueryBuilder.select; - -@Slf4j -public abstract class CassandraAbstractSearchTextDao, D> extends CassandraAbstractModelDao { - - @Override - protected E updateSearchTextIfPresent(E entity) { - if (entity.getSearchTextSource() != null) { - entity.setSearchText(entity.getSearchTextSource().toLowerCase()); - } else { - log.trace("Entity [{}] has null SearchTextSource", entity); - } - return entity; - } - - protected List findPageWithTextSearch(TenantId tenantId, String searchView, List clauses, TextPageLink pageLink) { - Select select = select().from(searchView); - Where query = select.where(); - for (Clause clause : clauses) { - query.and(clause); - } - query.limit(pageLink.getLimit()); - if (!StringUtils.isEmpty(pageLink.getTextOffset())) { - query.and(eq(ModelConstants.SEARCH_TEXT_PROPERTY, pageLink.getTextOffset())); - query.and(QueryBuilder.lt(ModelConstants.ID_PROPERTY, pageLink.getIdOffset())); - List result = findListByStatement(tenantId, query); - if (result.size() < pageLink.getLimit()) { - select = select().from(searchView); - query = select.where(); - for (Clause clause : clauses) { - query.and(clause); - } - query.and(QueryBuilder.gt(ModelConstants.SEARCH_TEXT_PROPERTY, pageLink.getTextOffset())); - if (!StringUtils.isEmpty(pageLink.getTextSearch())) { - query.and(QueryBuilder.lt(ModelConstants.SEARCH_TEXT_PROPERTY, pageLink.getTextSearchBound())); - } - int limit = pageLink.getLimit() - result.size(); - query.limit(limit); - result.addAll(findListByStatement(tenantId, query)); - } - return result; - } else if (!StringUtils.isEmpty(pageLink.getTextSearch())) { - query.and(QueryBuilder.gte(ModelConstants.SEARCH_TEXT_PROPERTY, pageLink.getTextSearch())); - query.and(QueryBuilder.lt(ModelConstants.SEARCH_TEXT_PROPERTY, pageLink.getTextSearchBound())); - return findListByStatement(tenantId, query); - } else { - return findListByStatement(tenantId, query); - } - } - - -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractSearchTimeDao.java b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractSearchTimeDao.java deleted file mode 100644 index 5489588fea..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractSearchTimeDao.java +++ /dev/null @@ -1,107 +0,0 @@ -/** - * Copyright © 2016-2019 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.nosql; - -import com.datastax.driver.core.querybuilder.Clause; -import com.datastax.driver.core.querybuilder.Ordering; -import com.datastax.driver.core.querybuilder.QueryBuilder; -import com.datastax.driver.core.querybuilder.Select; -import com.datastax.driver.core.querybuilder.Select.Where; -import com.datastax.driver.core.utils.UUIDs; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TimePageLink; -import org.thingsboard.server.dao.model.BaseEntity; -import org.thingsboard.server.dao.model.ModelConstants; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.UUID; - -import static com.datastax.driver.core.querybuilder.QueryBuilder.select; - -public abstract class CassandraAbstractSearchTimeDao, D> extends CassandraAbstractModelDao { - - - protected List findPageWithTimeSearch(TenantId tenantId, String searchView, List clauses, TimePageLink pageLink) { - return findPageWithTimeSearch(tenantId, searchView, clauses, Collections.emptyList(), pageLink); - } - - protected List findPageWithTimeSearch(TenantId tenantId, String searchView, List clauses, Ordering ordering, TimePageLink pageLink) { - return findPageWithTimeSearch(tenantId, searchView, clauses, Collections.singletonList(ordering), pageLink); - } - - protected List findPageWithTimeSearch(TenantId tenantId, String searchView, List clauses, List topLevelOrderings, TimePageLink pageLink) { - return findPageWithTimeSearch(tenantId, searchView, clauses, topLevelOrderings, pageLink, ModelConstants.ID_PROPERTY); - } - - protected List findPageWithTimeSearch(TenantId tenantId, String searchView, List clauses, TimePageLink pageLink, String idColumn) { - return findPageWithTimeSearch(tenantId, searchView, clauses, Collections.emptyList(), pageLink, idColumn); - } - - protected List findPageWithTimeSearch(TenantId tenantId, String searchView, List clauses, List topLevelOrderings, TimePageLink pageLink, String idColumn) { - return findListByStatement(tenantId, buildQuery(searchView, clauses, topLevelOrderings, pageLink, idColumn)); - } - - public static Where buildQuery(String searchView, List clauses, TimePageLink pageLink, String idColumn) { - return buildQuery(searchView, clauses, Collections.emptyList(), pageLink, idColumn); - } - - public static Where buildQuery(String searchView, List clauses, Ordering order, TimePageLink pageLink, String idColumn) { - return buildQuery(searchView, clauses, Collections.singletonList(order), pageLink, idColumn); - } - - public static Where buildQuery(String searchView, List clauses, List topLevelOrderings, TimePageLink pageLink, String idColumn) { - Select select = select().from(searchView); - Where query = select.where(); - for (Clause clause : clauses) { - query.and(clause); - } - query.limit(pageLink.getLimit()); - if (pageLink.isAscOrder()) { - if (pageLink.getIdOffset() != null) { - query.and(QueryBuilder.gt(idColumn, pageLink.getIdOffset())); - } else if (pageLink.getStartTime() != null) { - final UUID startOf = UUIDs.startOf(pageLink.getStartTime()); - query.and(QueryBuilder.gte(idColumn, startOf)); - } - if (pageLink.getEndTime() != null) { - final UUID endOf = UUIDs.endOf(pageLink.getEndTime()); - query.and(QueryBuilder.lte(idColumn, endOf)); - } - } else { - if (pageLink.getIdOffset() != null) { - query.and(QueryBuilder.lt(idColumn, pageLink.getIdOffset())); - } else if (pageLink.getEndTime() != null) { - final UUID endOf = UUIDs.endOf(pageLink.getEndTime()); - query.and(QueryBuilder.lte(idColumn, endOf)); - } - if (pageLink.getStartTime() != null) { - final UUID startOf = UUIDs.startOf(pageLink.getStartTime()); - query.and(QueryBuilder.gte(idColumn, startOf)); - } - } - List orderings = new ArrayList<>(topLevelOrderings); - if (pageLink.isAscOrder()) { - orderings.add(QueryBuilder.asc(idColumn)); - } else { - orderings.add(QueryBuilder.desc(idColumn)); - } - query.orderBy(orderings.toArray(new Ordering[orderings.size()])); - return query; - } - -} 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 5e31cf79c0..f07ffc9986 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 @@ -19,6 +19,7 @@ import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.RelationTypeGroup; @@ -58,6 +59,6 @@ public interface RelationDao { ListenableFuture deleteOutboundRelationsAsync(TenantId tenantId, EntityId entity); - ListenableFuture> findRelations(TenantId tenantId, EntityId from, String relationType, RelationTypeGroup typeGroup, EntityType toType, TimePageLink pageLink); + ListenableFuture> findRelations(TenantId tenantId, EntityId from, String relationType, RelationTypeGroup typeGroup, EntityType toType, TimePageLink pageLink); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java index 3852ec175e..2e3d8f8a8d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java @@ -27,8 +27,8 @@ 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.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +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.rule.NodeConnectionInfo; @@ -329,11 +329,10 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC } @Override - public TextPageData findTenantRuleChains(TenantId tenantId, TextPageLink pageLink) { + public PageData findTenantRuleChains(TenantId tenantId, PageLink pageLink) { Validator.validateId(tenantId, "Incorrect tenant id for search rule chain request."); - Validator.validatePageLink(pageLink, "Incorrect PageLink object for search rule chain request."); - List ruleChains = ruleChainDao.findRuleChainsByTenantId(tenantId.getId(), pageLink); - return new TextPageData<>(ruleChains, pageLink); + Validator.validatePageLink(pageLink); + return ruleChainDao.findRuleChainsByTenantId(tenantId.getId(), pageLink); } @Override @@ -411,7 +410,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC new PaginatedRemover() { @Override - protected List findEntities(TenantId tenantId, TenantId id, TextPageLink pageLink) { + protected PageData findEntities(TenantId tenantId, TenantId id, PageLink pageLink) { return ruleChainDao.findRuleChainsByTenantId(id.getId(), pageLink); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java index 0b1bcd633d..b711950250 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java @@ -15,7 +15,8 @@ */ package org.thingsboard.server.dao.rule; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.dao.Dao; @@ -34,6 +35,6 @@ public interface RuleChainDao extends Dao { * @param pageLink the page link * @return the list of rule chain objects */ - List findRuleChainsByTenantId(UUID tenantId, TextPageLink pageLink); + PageData findRuleChainsByTenantId(UUID tenantId, PageLink pageLink); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/PaginatedRemover.java b/dao/src/main/java/org/thingsboard/server/dao/service/PaginatedRemover.java index e87ee504c5..b806a48c88 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/PaginatedRemover.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/PaginatedRemover.java @@ -17,7 +17,8 @@ package org.thingsboard.server.dao.service; import org.thingsboard.server.common.data.id.IdBased; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import java.util.List; import java.util.UUID; @@ -27,23 +28,18 @@ public abstract class PaginatedRemover> { private static final int DEFAULT_LIMIT = 100; public void removeEntities(TenantId tenantId, I id) { - TextPageLink pageLink = new TextPageLink(DEFAULT_LIMIT); + PageLink pageLink = new PageLink(DEFAULT_LIMIT); boolean hasNext = true; while (hasNext) { - List entities = findEntities(tenantId, id, pageLink); - for (D entity : entities) { + PageData entities = findEntities(tenantId, id, pageLink); + for (D entity : entities.getData()) { removeEntity(tenantId, entity); } - hasNext = entities.size() == pageLink.getLimit(); - if (hasNext) { - int index = entities.size() - 1; - UUID idOffset = entities.get(index).getUuidId(); - pageLink.setIdOffset(idOffset); - } + hasNext = entities.hasNext(); } } - protected abstract List findEntities(TenantId tenantId,I id, TextPageLink pageLink); + protected abstract PageData findEntities(TenantId tenantId, I id, PageLink pageLink); protected abstract void removeEntity(TenantId tenantId, D entity); diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/TimePaginatedRemover.java b/dao/src/main/java/org/thingsboard/server/dao/service/TimePaginatedRemover.java index 8d0a7f87e9..cd922f2653 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/TimePaginatedRemover.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/TimePaginatedRemover.java @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.service; import org.thingsboard.server.common.data.id.IdBased; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.TimePageLink; import java.util.List; @@ -30,20 +31,15 @@ public abstract class TimePaginatedRemover> { TimePageLink pageLink = new TimePageLink(DEFAULT_LIMIT); boolean hasNext = true; while (hasNext) { - List entities = findEntities(tenantId, id, pageLink); - for (D entity : entities) { + PageData entities = findEntities(tenantId, id, pageLink); + for (D entity : entities.getData()) { removeEntity(tenantId, entity); } - hasNext = entities.size() == pageLink.getLimit(); - if (hasNext) { - int index = entities.size() - 1; - UUID idOffset = entities.get(index).getUuidId(); - pageLink.setIdOffset(idOffset); - } + hasNext = entities.hasNext(); } } - protected abstract List findEntities(TenantId tenantId, I id, TimePageLink pageLink); + protected abstract PageData findEntities(TenantId tenantId, I id, TimePageLink pageLink); protected abstract void removeEntity(TenantId tenantId, D entity); diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/Validator.java b/dao/src/main/java/org/thingsboard/server/dao/service/Validator.java index ac3ec54eed..528edb8747 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/Validator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/Validator.java @@ -17,7 +17,7 @@ package org.thingsboard.server.dao.service; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.UUIDBased; -import org.thingsboard.server.common.data.page.BasePageLink; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.exception.IncorrectParameterException; import java.util.List; @@ -116,9 +116,13 @@ public class Validator { * @param pageLink the page link * @param errorMessage the error message for exception */ - public static void validatePageLink(BasePageLink pageLink, String errorMessage) { - if (pageLink == null || pageLink.getLimit() < 1 || (pageLink.getIdOffset() != null && pageLink.getIdOffset().version() != 1)) { - throw new IncorrectParameterException(errorMessage); + public static void validatePageLink(PageLink pageLink) { + if (pageLink == null) { + throw new IncorrectParameterException("Page link must be specified."); + } else if (pageLink.getPageSize() < 1) { + throw new IncorrectParameterException("Incorrect page link page size '"+pageLink.getPageSize()+"'. Page size must be greater than zero."); + } else if (pageLink.getPage() < 0) { + throw new IncorrectParameterException("Incorrect page link page '"+pageLink.getPage()+"'. Page must be positive integer."); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractSearchTimeDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractSearchTimeDao.java index 0066696de1..a73f73f615 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractSearchTimeDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractSearchTimeDao.java @@ -38,51 +38,19 @@ public abstract class JpaAbstractSearchTimeDao, D> exten return new Specification() { @Override public Predicate toPredicate(Root root, CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder) { - List predicates; - if (pageLink.isAscOrder()) { - predicates = createAscPredicates(pageLink, idColumn, root, criteriaBuilder); - } else { - predicates = createDescPredicates(pageLink, idColumn, root, criteriaBuilder); + List predicates = new ArrayList<>(); + if (pageLink.getStartTime() != null) { + UUID startOf = UUIDs.startOf(pageLink.getStartTime()); + Predicate lowerBound = criteriaBuilder.greaterThanOrEqualTo(root.get(idColumn), UUIDConverter.fromTimeUUID(startOf)); + predicates.add(lowerBound); + } + if (pageLink.getEndTime() != null) { + UUID endOf = UUIDs.endOf(pageLink.getEndTime()); + Predicate upperBound = criteriaBuilder.lessThanOrEqualTo(root.get(idColumn), UUIDConverter.fromTimeUUID(endOf)); + predicates.add(upperBound); } return criteriaBuilder.and(predicates.toArray(new Predicate[0])); } }; } - - private static List createAscPredicates(TimePageLink pageLink, String idColumn, Root root, CriteriaBuilder criteriaBuilder) { - List predicates = new ArrayList<>(); - if (pageLink.getIdOffset() != null) { - Predicate lowerBound = criteriaBuilder.greaterThan(root.get(idColumn), UUIDConverter.fromTimeUUID(pageLink.getIdOffset())); - predicates.add(lowerBound); - } else if (pageLink.getStartTime() != null) { - UUID startOf = UUIDs.startOf(pageLink.getStartTime()); - Predicate lowerBound = criteriaBuilder.greaterThanOrEqualTo(root.get(idColumn), UUIDConverter.fromTimeUUID(startOf)); - predicates.add(lowerBound); - } - if (pageLink.getEndTime() != null) { - UUID endOf = UUIDs.endOf(pageLink.getEndTime()); - Predicate upperBound = criteriaBuilder.lessThanOrEqualTo(root.get(idColumn), UUIDConverter.fromTimeUUID(endOf)); - predicates.add(upperBound); - } - return predicates; - } - - private static List createDescPredicates(TimePageLink pageLink, String idColumn, Root root, CriteriaBuilder criteriaBuilder) { - List predicates = new ArrayList<>(); - if (pageLink.getIdOffset() != null) { - Predicate lowerBound = criteriaBuilder.lessThan(root.get(idColumn), UUIDConverter.fromTimeUUID(pageLink.getIdOffset())); - predicates.add(lowerBound); - } else if (pageLink.getEndTime() != null) { - UUID endOf = UUIDs.endOf(pageLink.getEndTime()); - Predicate lowerBound = criteriaBuilder.lessThanOrEqualTo(root.get(idColumn), UUIDConverter.fromTimeUUID(endOf)); - predicates.add(lowerBound); - } - if (pageLink.getStartTime() != null) { - UUID startOf = UUIDs.startOf(pageLink.getStartTime()); - Predicate upperBound = criteriaBuilder.greaterThanOrEqualTo(root.get(idColumn), UUIDConverter.fromTimeUUID(startOf)); - predicates.add(upperBound); - } - return predicates; - } - } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java index 4762c5c4ec..5f4259dd8f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java @@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.alarm.AlarmQuery; import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.dao.DaoUtil; @@ -92,7 +93,7 @@ public class JpaAlarmDao extends JpaAbstractDao implements A } @Override - public ListenableFuture> findAlarms(TenantId tenantId, AlarmQuery query) { + public ListenableFuture> findAlarms(TenantId tenantId, AlarmQuery query) { log.trace("Try to find alarms by entity [{}], status [{}] and pageLink [{}]", query.getAffectedEntityId(), query.getStatus(), query.getPageLink()); EntityId affectedEntity = query.getAffectedEntityId(); String searchStatusName; @@ -104,15 +105,17 @@ public class JpaAlarmDao extends JpaAbstractDao implements A searchStatusName = query.getStatus().name(); } String relationType = BaseAlarmService.ALARM_RELATION_PREFIX + searchStatusName; - ListenableFuture> relations = relationDao.findRelations(tenantId, affectedEntity, relationType, RelationTypeGroup.ALARM, EntityType.ALARM, query.getPageLink()); + ListenableFuture> relations = relationDao.findRelations(tenantId, affectedEntity, relationType, RelationTypeGroup.ALARM, EntityType.ALARM, query.getPageLink()); return Futures.transformAsync(relations, input -> { - List> alarmFutures = new ArrayList<>(input.size()); - for (EntityRelation relation : input) { + List> alarmFutures = new ArrayList<>(input.getData().size()); + for (EntityRelation relation : input.getData()) { alarmFutures.add(Futures.transform( findAlarmByIdAsync(tenantId, relation.getTo().getId()), AlarmInfo::new)); } - return Futures.successfulAsList(alarmFutures); + return Futures.transform(Futures.successfulAsList(alarmFutures), alarmInfos -> { + return new PageData(alarmInfos, input.getTotalPages(), input.getTotalElements(), input.hasNext()); + }); }); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java index f5e98253b7..de21137286 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java @@ -15,9 +15,11 @@ */ package org.thingsboard.server.dao.sql.asset; +import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; import org.thingsboard.server.dao.model.sql.AssetEntity; import org.thingsboard.server.dao.util.SqlDao; @@ -28,24 +30,20 @@ import java.util.List; * Created by Valerii Sosliuk on 5/21/2017. */ @SqlDao -public interface AssetRepository extends CrudRepository { +public interface AssetRepository extends PagingAndSortingRepository { @Query("SELECT a FROM AssetEntity a WHERE a.tenantId = :tenantId " + - "AND LOWER(a.searchText) LIKE LOWER(CONCAT(:textSearch, '%')) " + - "AND a.id > :idOffset ORDER BY a.id") - List findByTenantId(@Param("tenantId") String tenantId, + "AND LOWER(a.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))") + Page findByTenantId(@Param("tenantId") String tenantId, @Param("textSearch") String textSearch, - @Param("idOffset") String idOffset, Pageable pageable); @Query("SELECT a FROM AssetEntity a WHERE a.tenantId = :tenantId " + "AND a.customerId = :customerId " + - "AND LOWER(a.searchText) LIKE LOWER(CONCAT(:textSearch, '%')) " + - "AND a.id > :idOffset ORDER BY a.id") - List findByTenantIdAndCustomerId(@Param("tenantId") String tenantId, + "AND LOWER(a.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))") + Page findByTenantIdAndCustomerId(@Param("tenantId") String tenantId, @Param("customerId") String customerId, @Param("textSearch") String textSearch, - @Param("idOffset") String idOffset, Pageable pageable); List findByTenantIdAndIdIn(String tenantId, List assetIds); @@ -56,23 +54,19 @@ public interface AssetRepository extends CrudRepository { @Query("SELECT a FROM AssetEntity a WHERE a.tenantId = :tenantId " + "AND a.type = :type " + - "AND LOWER(a.searchText) LIKE LOWER(CONCAT(:textSearch, '%')) " + - "AND a.id > :idOffset ORDER BY a.id") - List findByTenantIdAndType(@Param("tenantId") String tenantId, + "AND LOWER(a.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))") + Page findByTenantIdAndType(@Param("tenantId") String tenantId, @Param("type") String type, @Param("textSearch") String textSearch, - @Param("idOffset") String idOffset, Pageable pageable); @Query("SELECT a FROM AssetEntity a WHERE a.tenantId = :tenantId " + "AND a.customerId = :customerId AND a.type = :type " + - "AND LOWER(a.searchText) LIKE LOWER(CONCAT(:textSearch, '%')) " + - "AND a.id > :idOffset ORDER BY a.id") - List findByTenantIdAndCustomerIdAndType(@Param("tenantId") String tenantId, + "AND LOWER(a.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))") + Page findByTenantIdAndCustomerIdAndType(@Param("tenantId") String tenantId, @Param("customerId") String customerId, @Param("type") String type, @Param("textSearch") String textSearch, - @Param("idOffset") String idOffset, Pageable pageable); @Query("SELECT DISTINCT a.type FROM AssetEntity a WHERE a.tenantId = :tenantId") diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java index c600efcdf3..576df99566 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java @@ -24,7 +24,8 @@ import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.asset.AssetDao; import org.thingsboard.server.dao.model.sql.AssetEntity; @@ -63,13 +64,12 @@ public class JpaAssetDao extends JpaAbstractSearchTextDao im } @Override - public List findAssetsByTenantId(UUID tenantId, TextPageLink pageLink) { - return DaoUtil.convertDataList(assetRepository + public PageData findAssetsByTenantId(UUID tenantId, PageLink pageLink) { + return DaoUtil.toPageData(assetRepository .findByTenantId( fromTimeUUID(tenantId), Objects.toString(pageLink.getTextSearch(), ""), - pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()), - new PageRequest(0, pageLink.getLimit()))); + DaoUtil.toPageable(pageLink))); } @Override @@ -79,14 +79,13 @@ public class JpaAssetDao extends JpaAbstractSearchTextDao im } @Override - public List findAssetsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink) { - return DaoUtil.convertDataList(assetRepository + public PageData findAssetsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, PageLink pageLink) { + return DaoUtil.toPageData(assetRepository .findByTenantIdAndCustomerId( fromTimeUUID(tenantId), fromTimeUUID(customerId), Objects.toString(pageLink.getTextSearch(), ""), - pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()), - new PageRequest(0, pageLink.getLimit()))); + DaoUtil.toPageable(pageLink))); } @Override @@ -102,26 +101,24 @@ public class JpaAssetDao extends JpaAbstractSearchTextDao im } @Override - public List findAssetsByTenantIdAndType(UUID tenantId, String type, TextPageLink pageLink) { - return DaoUtil.convertDataList(assetRepository + public PageData findAssetsByTenantIdAndType(UUID tenantId, String type, PageLink pageLink) { + return DaoUtil.toPageData(assetRepository .findByTenantIdAndType( fromTimeUUID(tenantId), type, Objects.toString(pageLink.getTextSearch(), ""), - pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()), - new PageRequest(0, pageLink.getLimit()))); + DaoUtil.toPageable(pageLink))); } @Override - public List findAssetsByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, TextPageLink pageLink) { - return DaoUtil.convertDataList(assetRepository + public PageData findAssetsByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, PageLink pageLink) { + return DaoUtil.toPageData(assetRepository .findByTenantIdAndCustomerIdAndType( fromTimeUUID(tenantId), fromTimeUUID(customerId), type, Objects.toString(pageLink.getTextSearch(), ""), - pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()), - new PageRequest(0, pageLink.getLimit()))); + DaoUtil.toPageable(pageLink))); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDao.java index d6f5498acc..91b0a16dd7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDao.java @@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.audit.AuditLog; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.audit.AuditLogDao; @@ -81,51 +82,30 @@ public class JpaAuditLogDao extends JpaAbstractDao imp } @Override - public ListenableFuture saveByTenantIdAndEntityId(AuditLog auditLog) { - return insertService.submit(() -> null); - } - - @Override - public ListenableFuture saveByTenantIdAndCustomerId(AuditLog auditLog) { - return insertService.submit(() -> null); - } - - @Override - public ListenableFuture saveByTenantIdAndUserId(AuditLog auditLog) { - return insertService.submit(() -> null); - } - - @Override - public ListenableFuture savePartitionsByTenantId(AuditLog auditLog) { - return insertService.submit(() -> null); - } - - @Override - public List findAuditLogsByTenantIdAndEntityId(UUID tenantId, EntityId entityId, TimePageLink pageLink) { + public PageData findAuditLogsByTenantIdAndEntityId(UUID tenantId, EntityId entityId, TimePageLink pageLink) { return findAuditLogs(tenantId, entityId, null, null, pageLink); } @Override - public List findAuditLogsByTenantIdAndCustomerId(UUID tenantId, CustomerId customerId, TimePageLink pageLink) { + public PageData findAuditLogsByTenantIdAndCustomerId(UUID tenantId, CustomerId customerId, TimePageLink pageLink) { return findAuditLogs(tenantId, null, customerId, null, pageLink); } @Override - public List findAuditLogsByTenantIdAndUserId(UUID tenantId, UserId userId, TimePageLink pageLink) { + public PageData findAuditLogsByTenantIdAndUserId(UUID tenantId, UserId userId, TimePageLink pageLink) { return findAuditLogs(tenantId, null, null, userId, pageLink); } @Override - public List findAuditLogsByTenantId(UUID tenantId, TimePageLink pageLink) { + public PageData findAuditLogsByTenantId(UUID tenantId, TimePageLink pageLink) { return findAuditLogs(tenantId, null, null, null, pageLink); } - private List findAuditLogs(UUID tenantId, EntityId entityId, CustomerId customerId, UserId userId, TimePageLink pageLink) { + private PageData findAuditLogs(UUID tenantId, EntityId entityId, CustomerId customerId, UserId userId, TimePageLink pageLink) { Specification timeSearchSpec = JpaAbstractSearchTimeDao.getTimeSearchPageSpec(pageLink, "id"); Specification fieldsSpec = getEntityFieldsSpec(tenantId, entityId, customerId, userId); - Sort.Direction sortDirection = pageLink.isAscOrder() ? Sort.Direction.ASC : Sort.Direction.DESC; - Pageable pageable = new PageRequest(0, pageLink.getLimit(), sortDirection, ID_PROPERTY); - return DaoUtil.convertDataList(auditLogRepository.findAll(where(timeSearchSpec).and(fieldsSpec), pageable).getContent()); + Pageable pageable = DaoUtil.toPageable(pageLink); + return DaoUtil.toPageData(auditLogRepository.findAll(where(timeSearchSpec).and(fieldsSpec), pageable)); } private Specification getEntityFieldsSpec(UUID tenantId, EntityId entityId, CustomerId customerId, UserId userId) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/component/ComponentDescriptorRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/component/ComponentDescriptorRepository.java index 997c458606..23e51976c0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/component/ComponentDescriptorRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/component/ComponentDescriptorRepository.java @@ -15,9 +15,11 @@ */ package org.thingsboard.server.dao.sql.component; +import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; import org.thingsboard.server.common.data.plugin.ComponentScope; import org.thingsboard.server.common.data.plugin.ComponentType; @@ -30,25 +32,21 @@ import java.util.List; * Created by Valerii Sosliuk on 5/6/2017. */ @SqlDao -public interface ComponentDescriptorRepository extends CrudRepository { +public interface ComponentDescriptorRepository extends PagingAndSortingRepository { ComponentDescriptorEntity findByClazz(String clazz); @Query("SELECT cd FROM ComponentDescriptorEntity cd WHERE cd.type = :type " + - "AND LOWER(cd.searchText) LIKE LOWER(CONCAT(:textSearch, '%')) " + - "AND cd.id > :idOffset ORDER BY cd.id") - List findByType(@Param("type") ComponentType type, + "AND LOWER(cd.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))") + Page findByType(@Param("type") ComponentType type, @Param("textSearch") String textSearch, - @Param("idOffset") String idOffset, Pageable pageable); @Query("SELECT cd FROM ComponentDescriptorEntity cd WHERE cd.type = :type " + - "AND cd.scope = :scope AND LOWER(cd.searchText) LIKE LOWER(CONCAT(:textSearch, '%')) " + - "AND cd.id > :idOffset ORDER BY cd.id") - List findByScopeAndType(@Param("type") ComponentType type, + "AND cd.scope = :scope AND LOWER(cd.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))") + Page findByScopeAndType(@Param("type") ComponentType type, @Param("scope") ComponentScope scope, @Param("textSearch") String textSearch, - @Param("idOffset") String idOffset, Pageable pageable); void deleteByClazz(String clazz); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/component/JpaBaseComponentDescriptorDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/component/JpaBaseComponentDescriptorDao.java index c66ea9c5cc..fa47712d14 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/component/JpaBaseComponentDescriptorDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/component/JpaBaseComponentDescriptorDao.java @@ -24,7 +24,8 @@ import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.common.data.UUIDConverter; import org.thingsboard.server.common.data.id.ComponentDescriptorId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.plugin.ComponentDescriptor; import org.thingsboard.server.common.data.plugin.ComponentScope; import org.thingsboard.server.common.data.plugin.ComponentType; @@ -83,24 +84,22 @@ public class JpaBaseComponentDescriptorDao extends JpaAbstractSearchTextDao findByTypeAndPageLink(TenantId tenantId, ComponentType type, TextPageLink pageLink) { - return DaoUtil.convertDataList(componentDescriptorRepository + public PageData findByTypeAndPageLink(TenantId tenantId, ComponentType type, PageLink pageLink) { + return DaoUtil.toPageData(componentDescriptorRepository .findByType( type, Objects.toString(pageLink.getTextSearch(), ""), - pageLink.getIdOffset() == null ? NULL_UUID_STR : UUIDConverter.fromTimeUUID(pageLink.getIdOffset()), - new PageRequest(0, pageLink.getLimit()))); + DaoUtil.toPageable(pageLink))); } @Override - public List findByScopeAndTypeAndPageLink(TenantId tenantId, ComponentScope scope, ComponentType type, TextPageLink pageLink) { - return DaoUtil.convertDataList(componentDescriptorRepository + public PageData findByScopeAndTypeAndPageLink(TenantId tenantId, ComponentScope scope, ComponentType type, PageLink pageLink) { + return DaoUtil.toPageData(componentDescriptorRepository .findByScopeAndType( type, scope, Objects.toString(pageLink.getTextSearch(), ""), - pageLink.getIdOffset() == null ? NULL_UUID_STR : UUIDConverter.fromTimeUUID(pageLink.getIdOffset()), - new PageRequest(0, pageLink.getLimit()))); + DaoUtil.toPageable(pageLink))); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/customer/CustomerRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/customer/CustomerRepository.java index c21611686e..92e82a1189 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/customer/CustomerRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/customer/CustomerRepository.java @@ -15,9 +15,11 @@ */ package org.thingsboard.server.dao.sql.customer; +import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; import org.thingsboard.server.dao.model.sql.CustomerEntity; import org.thingsboard.server.dao.util.SqlDao; @@ -28,14 +30,12 @@ import java.util.List; * Created by Valerii Sosliuk on 5/6/2017. */ @SqlDao -public interface CustomerRepository extends CrudRepository { +public interface CustomerRepository extends PagingAndSortingRepository { @Query("SELECT c FROM CustomerEntity c WHERE c.tenantId = :tenantId " + - "AND LOWER(c.searchText) LIKE LOWER(CONCAT(:textSearch, '%')) " + - "AND c.id > :idOffset ORDER BY c.id") - List findByTenantId(@Param("tenantId") String tenantId, + "AND LOWER(c.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))") + Page findByTenantId(@Param("tenantId") String tenantId, @Param("textSearch") String textSearch, - @Param("idOffset") String idOffset, Pageable pageable); CustomerEntity findByTenantIdAndTitle(String tenantId, String title); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/customer/JpaCustomerDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/customer/JpaCustomerDao.java index 33b4e34479..aa3b491a01 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/customer/JpaCustomerDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/customer/JpaCustomerDao.java @@ -21,7 +21,8 @@ import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.UUIDConverter; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.customer.CustomerDao; import org.thingsboard.server.dao.model.sql.CustomerEntity; @@ -56,12 +57,11 @@ public class JpaCustomerDao extends JpaAbstractSearchTextDao findCustomersByTenantId(UUID tenantId, TextPageLink pageLink) { - return DaoUtil.convertDataList(customerRepository.findByTenantId( + public PageData findCustomersByTenantId(UUID tenantId, PageLink pageLink) { + return DaoUtil.toPageData(customerRepository.findByTenantId( UUIDConverter.fromTimeUUID(tenantId), Objects.toString(pageLink.getTextSearch(), ""), - pageLink.getIdOffset() == null ? NULL_UUID_STR : UUIDConverter.fromTimeUUID(pageLink.getIdOffset()), - new PageRequest(0, pageLink.getLimit()))); + DaoUtil.toPageable(pageLink))); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java index a6f883e097..34b2937150 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java @@ -15,9 +15,11 @@ */ package org.thingsboard.server.dao.sql.dashboard; +import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; import org.thingsboard.server.dao.model.sql.DashboardInfoEntity; import org.thingsboard.server.dao.util.SqlDao; @@ -28,14 +30,12 @@ import java.util.List; * Created by Valerii Sosliuk on 5/6/2017. */ @SqlDao -public interface DashboardInfoRepository extends CrudRepository { +public interface DashboardInfoRepository extends PagingAndSortingRepository { @Query("SELECT di FROM DashboardInfoEntity di WHERE di.tenantId = :tenantId " + - "AND LOWER(di.searchText) LIKE LOWER(CONCAT(:searchText, '%')) " + - "AND di.id > :idOffset ORDER BY di.id") - List findByTenantId(@Param("tenantId") String tenantId, + "AND LOWER(di.searchText) LIKE LOWER(CONCAT(:searchText, '%'))") + Page findByTenantId(@Param("tenantId") String tenantId, @Param("searchText") String searchText, - @Param("idOffset") String idOffset, Pageable pageable); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java index 8e3e0e0c1e..2b2dbf5820 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java @@ -27,7 +27,8 @@ import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.UUIDConverter; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.RelationTypeGroup; @@ -70,27 +71,28 @@ public class JpaDashboardInfoDao extends JpaAbstractSearchTextDao findDashboardsByTenantId(UUID tenantId, TextPageLink pageLink) { - return DaoUtil.convertDataList(dashboardInfoRepository + public PageData findDashboardsByTenantId(UUID tenantId, PageLink pageLink) { + return DaoUtil.toPageData(dashboardInfoRepository .findByTenantId( UUIDConverter.fromTimeUUID(tenantId), Objects.toString(pageLink.getTextSearch(), ""), - pageLink.getIdOffset() == null ? NULL_UUID_STR : UUIDConverter.fromTimeUUID(pageLink.getIdOffset()), - new PageRequest(0, pageLink.getLimit()))); + DaoUtil.toPageable(pageLink))); } @Override - public ListenableFuture> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TimePageLink pageLink) { + public ListenableFuture> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TimePageLink pageLink) { log.debug("Try to find dashboards by tenantId [{}], customerId[{}] and pageLink [{}]", tenantId, customerId, pageLink); - ListenableFuture> relations = relationDao.findRelations(new TenantId(tenantId), new CustomerId(customerId), EntityRelation.CONTAINS_TYPE, RelationTypeGroup.DASHBOARD, EntityType.DASHBOARD, pageLink); + ListenableFuture> relations = relationDao.findRelations(new TenantId(tenantId), new CustomerId(customerId), EntityRelation.CONTAINS_TYPE, RelationTypeGroup.DASHBOARD, EntityType.DASHBOARD, pageLink); return Futures.transformAsync(relations, input -> { - List> dashboardFutures = new ArrayList<>(input.size()); - for (EntityRelation relation : input) { + List> dashboardFutures = new ArrayList<>(input.getData().size()); + for (EntityRelation relation : input.getData()) { dashboardFutures.add(findByIdAsync(new TenantId(tenantId), relation.getTo().getId())); } - return Futures.successfulAsList(dashboardFutures); + return Futures.transform(Futures.successfulAsList(dashboardFutures), dashboards -> { + return new PageData(dashboards, input.getTotalPages(), input.getTotalElements(), input.hasNext()); + }); }); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java index 1c61b33f82..4021881b88 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java @@ -15,9 +15,11 @@ */ package org.thingsboard.server.dao.sql.device; +import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; import org.thingsboard.server.dao.model.sql.DeviceEntity; import org.thingsboard.server.dao.util.SqlDao; @@ -28,47 +30,39 @@ import java.util.List; * Created by Valerii Sosliuk on 5/6/2017. */ @SqlDao -public interface DeviceRepository extends CrudRepository { +public interface DeviceRepository extends PagingAndSortingRepository { @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + "AND d.customerId = :customerId " + - "AND LOWER(d.searchText) LIKE LOWER(CONCAT(:searchText, '%')) " + - "AND d.id > :idOffset ORDER BY d.id") - List findByTenantIdAndCustomerId(@Param("tenantId") String tenantId, + "AND LOWER(d.searchText) LIKE LOWER(CONCAT(:searchText, '%'))") + Page findByTenantIdAndCustomerId(@Param("tenantId") String tenantId, @Param("customerId") String customerId, @Param("searchText") String searchText, - @Param("idOffset") String idOffset, Pageable pageable); @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + - "AND LOWER(d.searchText) LIKE LOWER(CONCAT(:textSearch, '%')) " + - "AND d.id > :idOffset ORDER BY d.id") - List findByTenantId(@Param("tenantId") String tenantId, + "AND LOWER(d.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))") + Page findByTenantId(@Param("tenantId") String tenantId, @Param("textSearch") String textSearch, - @Param("idOffset") String idOffset, Pageable pageable); @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + "AND d.type = :type " + - "AND LOWER(d.searchText) LIKE LOWER(CONCAT(:textSearch, '%')) " + - "AND d.id > :idOffset ORDER BY d.id") - List findByTenantIdAndType(@Param("tenantId") String tenantId, + "AND LOWER(d.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))") + Page findByTenantIdAndType(@Param("tenantId") String tenantId, @Param("type") String type, @Param("textSearch") String textSearch, - @Param("idOffset") String idOffset, Pageable pageable); @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + "AND d.customerId = :customerId " + "AND d.type = :type " + - "AND LOWER(d.searchText) LIKE LOWER(CONCAT(:textSearch, '%')) " + - "AND d.id > :idOffset ORDER BY d.id") - List findByTenantIdAndCustomerIdAndType(@Param("tenantId") String tenantId, + "AND LOWER(d.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))") + Page findByTenantIdAndCustomerIdAndType(@Param("tenantId") String tenantId, @Param("customerId") String customerId, @Param("type") String type, @Param("textSearch") String textSearch, - @Param("idOffset") String idOffset, Pageable pageable); @Query("SELECT DISTINCT d.type FROM DeviceEntity d WHERE d.tenantId = :tenantId") diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java index be0b4b89df..cc195eb3d2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java @@ -25,7 +25,8 @@ import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.UUIDConverter; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.device.DeviceDao; import org.thingsboard.server.dao.model.sql.DeviceEntity; @@ -64,13 +65,12 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao } @Override - public List findDevicesByTenantId(UUID tenantId, TextPageLink pageLink) { - return DaoUtil.convertDataList( + public PageData findDevicesByTenantId(UUID tenantId, PageLink pageLink) { + return DaoUtil.toPageData( deviceRepository.findByTenantId( fromTimeUUID(tenantId), Objects.toString(pageLink.getTextSearch(), ""), - pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()), - new PageRequest(0, pageLink.getLimit()))); + DaoUtil.toPageable(pageLink))); } @Override @@ -79,14 +79,13 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao } @Override - public List findDevicesByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink) { - return DaoUtil.convertDataList( + public PageData findDevicesByTenantIdAndCustomerId(UUID tenantId, UUID customerId, PageLink pageLink) { + return DaoUtil.toPageData( deviceRepository.findByTenantIdAndCustomerId( fromTimeUUID(tenantId), fromTimeUUID(customerId), Objects.toString(pageLink.getTextSearch(), ""), - pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()), - new PageRequest(0, pageLink.getLimit()))); + DaoUtil.toPageable(pageLink))); } @Override @@ -102,26 +101,24 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao } @Override - public List findDevicesByTenantIdAndType(UUID tenantId, String type, TextPageLink pageLink) { - return DaoUtil.convertDataList( + public PageData findDevicesByTenantIdAndType(UUID tenantId, String type, PageLink pageLink) { + return DaoUtil.toPageData( deviceRepository.findByTenantIdAndType( fromTimeUUID(tenantId), type, Objects.toString(pageLink.getTextSearch(), ""), - pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()), - new PageRequest(0, pageLink.getLimit()))); + DaoUtil.toPageable(pageLink))); } @Override - public List findDevicesByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, TextPageLink pageLink) { - return DaoUtil.convertDataList( + public PageData findDevicesByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, PageLink pageLink) { + return DaoUtil.toPageData( deviceRepository.findByTenantIdAndCustomerIdAndType( fromTimeUUID(tenantId), fromTimeUUID(customerId), type, Objects.toString(pageLink.getTextSearch(), ""), - pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()), - new PageRequest(0, pageLink.getLimit()))); + DaoUtil.toPageable(pageLink))); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/EntityViewRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/EntityViewRepository.java index f152e1f665..7d9d224f2b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/EntityViewRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/EntityViewRepository.java @@ -15,9 +15,11 @@ */ package org.thingsboard.server.dao.sql.entityview; +import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; import org.thingsboard.server.dao.model.sql.EntityViewEntity; import org.thingsboard.server.dao.util.SqlDao; @@ -28,46 +30,38 @@ import java.util.List; * Created by Victor Basanets on 8/31/2017. */ @SqlDao -public interface EntityViewRepository extends CrudRepository { +public interface EntityViewRepository extends PagingAndSortingRepository { @Query("SELECT e FROM EntityViewEntity e WHERE e.tenantId = :tenantId " + - "AND LOWER(e.searchText) LIKE LOWER(CONCAT(:textSearch, '%')) " + - "AND e.id > :idOffset ORDER BY e.id") - List findByTenantId(@Param("tenantId") String tenantId, + "AND LOWER(e.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))") + Page findByTenantId(@Param("tenantId") String tenantId, @Param("textSearch") String textSearch, - @Param("idOffset") String idOffset, Pageable pageable); @Query("SELECT e FROM EntityViewEntity e WHERE e.tenantId = :tenantId " + "AND e.type = :type " + - "AND LOWER(e.searchText) LIKE LOWER(CONCAT(:textSearch, '%')) " + - "AND e.id > :idOffset ORDER BY e.id") - List findByTenantIdAndType(@Param("tenantId") String tenantId, + "AND LOWER(e.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))") + Page findByTenantIdAndType(@Param("tenantId") String tenantId, @Param("type") String type, @Param("textSearch") String textSearch, - @Param("idOffset") String idOffset, Pageable pageable); @Query("SELECT e FROM EntityViewEntity e WHERE e.tenantId = :tenantId " + "AND e.customerId = :customerId " + - "AND LOWER(e.searchText) LIKE LOWER(CONCAT(:searchText, '%')) " + - "AND e.id > :idOffset ORDER BY e.id") - List findByTenantIdAndCustomerId(@Param("tenantId") String tenantId, + "AND LOWER(e.searchText) LIKE LOWER(CONCAT(:searchText, '%'))") + Page findByTenantIdAndCustomerId(@Param("tenantId") String tenantId, @Param("customerId") String customerId, @Param("searchText") String searchText, - @Param("idOffset") String idOffset, Pageable pageable); @Query("SELECT e FROM EntityViewEntity e WHERE e.tenantId = :tenantId " + "AND e.customerId = :customerId " + "AND e.type = :type " + - "AND LOWER(e.searchText) LIKE LOWER(CONCAT(:searchText, '%')) " + - "AND e.id > :idOffset ORDER BY e.id") - List findByTenantIdAndCustomerIdAndType(@Param("tenantId") String tenantId, + "AND LOWER(e.searchText) LIKE LOWER(CONCAT(:searchText, '%'))") + Page findByTenantIdAndCustomerIdAndType(@Param("tenantId") String tenantId, @Param("customerId") String customerId, @Param("type") String type, @Param("searchText") String searchText, - @Param("idOffset") String idOffset, Pageable pageable); EntityViewEntity findByTenantIdAndName(String tenantId, String name); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/JpaEntityViewDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/JpaEntityViewDao.java index 981b239e62..a668cbda60 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/JpaEntityViewDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/JpaEntityViewDao.java @@ -25,7 +25,8 @@ import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.UUIDConverter; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.entityview.EntityViewDao; import org.thingsboard.server.dao.model.sql.EntityViewEntity; @@ -64,24 +65,22 @@ public class JpaEntityViewDao extends JpaAbstractSearchTextDao findEntityViewsByTenantId(UUID tenantId, TextPageLink pageLink) { - return DaoUtil.convertDataList( + public PageData findEntityViewsByTenantId(UUID tenantId, PageLink pageLink) { + return DaoUtil.toPageData( entityViewRepository.findByTenantId( fromTimeUUID(tenantId), Objects.toString(pageLink.getTextSearch(), ""), - pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()), - new PageRequest(0, pageLink.getLimit()))); + DaoUtil.toPageable(pageLink))); } @Override - public List findEntityViewsByTenantIdAndType(UUID tenantId, String type, TextPageLink pageLink) { - return DaoUtil.convertDataList( + public PageData findEntityViewsByTenantIdAndType(UUID tenantId, String type, PageLink pageLink) { + return DaoUtil.toPageData( entityViewRepository.findByTenantIdAndType( fromTimeUUID(tenantId), type, Objects.toString(pageLink.getTextSearch(), ""), - pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()), - new PageRequest(0, pageLink.getLimit()))); + DaoUtil.toPageable(pageLink))); } @Override @@ -91,29 +90,27 @@ public class JpaEntityViewDao extends JpaAbstractSearchTextDao findEntityViewsByTenantIdAndCustomerId(UUID tenantId, - UUID customerId, - TextPageLink pageLink) { - return DaoUtil.convertDataList( + public PageData findEntityViewsByTenantIdAndCustomerId(UUID tenantId, + UUID customerId, + PageLink pageLink) { + return DaoUtil.toPageData( entityViewRepository.findByTenantIdAndCustomerId( fromTimeUUID(tenantId), fromTimeUUID(customerId), Objects.toString(pageLink.getTextSearch(), ""), - pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()), - new PageRequest(0, pageLink.getLimit()) + DaoUtil.toPageable(pageLink) )); } @Override - public List findEntityViewsByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, TextPageLink pageLink) { - return DaoUtil.convertDataList( + public PageData findEntityViewsByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, PageLink pageLink) { + return DaoUtil.toPageData( entityViewRepository.findByTenantIdAndCustomerIdAndType( fromTimeUUID(tenantId), fromTimeUUID(customerId), type, Objects.toString(pageLink.getTextSearch(), ""), - pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()), - new PageRequest(0, pageLink.getLimit()) + DaoUtil.toPageable(pageLink) )); } 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 c33cb6bf34..9c7e94243d 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.UUIDConverter; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EventId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.event.EventDao; @@ -107,17 +108,16 @@ public class JpaBaseEventDao extends JpaAbstractSearchTimeDao findEvents(UUID tenantId, EntityId entityId, TimePageLink pageLink) { + public PageData findEvents(UUID tenantId, EntityId entityId, TimePageLink pageLink) { return findEvents(tenantId, entityId, null, pageLink); } @Override - public List findEvents(UUID tenantId, EntityId entityId, String eventType, TimePageLink pageLink) { + public PageData findEvents(UUID tenantId, EntityId entityId, String eventType, TimePageLink pageLink) { Specification timeSearchSpec = JpaAbstractSearchTimeDao.getTimeSearchPageSpec(pageLink, "id"); Specification fieldsSpec = getEntityFieldsSpec(tenantId, entityId, eventType); - Sort.Direction sortDirection = pageLink.isAscOrder() ? Sort.Direction.ASC : Sort.Direction.DESC; - Pageable pageable = new PageRequest(0, pageLink.getLimit(), sortDirection, ID_PROPERTY); - return DaoUtil.convertDataList(eventRepository.findAll(where(timeSearchSpec).and(fieldsSpec), pageable).getContent()); + Pageable pageable = DaoUtil.toPageable(pageLink); + return DaoUtil.toPageData(eventRepository.findAll(where(timeSearchSpec).and(fieldsSpec), pageable)); } @Override 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 9bce2d3dea..67f5621d83 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 @@ -27,6 +27,8 @@ import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.UUIDConverter; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.SortOrder; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.RelationTypeGroup; @@ -185,13 +187,16 @@ public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService imple } @Override - public ListenableFuture> findRelations(TenantId tenantId, EntityId from, String relationType, RelationTypeGroup typeGroup, EntityType childType, TimePageLink pageLink) { + public ListenableFuture> findRelations(TenantId tenantId, EntityId from, String relationType, RelationTypeGroup typeGroup, EntityType childType, TimePageLink pageLink) { Specification timeSearchSpec = JpaAbstractSearchTimeDao.getTimeSearchPageSpec(pageLink, "toId"); Specification fieldsSpec = getEntityFieldsSpec(from, relationType, typeGroup, childType); - Sort.Direction sortDirection = pageLink.isAscOrder() ? Sort.Direction.ASC : Sort.Direction.DESC; - Pageable pageable = new PageRequest(0, pageLink.getLimit(), sortDirection, "toId"); + Sort.Direction sortDirection = Sort.Direction.DESC; + if (pageLink.getSortOrder() != null) { + sortDirection = pageLink.getSortOrder().getDirection() == SortOrder.Direction.ASC ? Sort.Direction.ASC : Sort.Direction.DESC; + } + Pageable pageable = new PageRequest(pageLink.getPage(), pageLink.getPageSize(), sortDirection, "toId"); return service.submit(() -> - DaoUtil.convertDataList(relationRepository.findAll(where(timeSearchSpec).and(fieldsSpec), pageable).getContent())); + DaoUtil.toPageData(relationRepository.findAll(where(timeSearchSpec).and(fieldsSpec), pageable))); } private Specification getEntityFieldsSpec(EntityId from, String relationType, RelationTypeGroup typeGroup, EntityType childType) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java index 3c26af35a0..dd1da8ef6e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java @@ -21,7 +21,8 @@ import org.springframework.data.domain.PageRequest; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.UUIDConverter; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.model.sql.RuleChainEntity; @@ -54,13 +55,12 @@ public class JpaRuleChainDao extends JpaAbstractSearchTextDao findRuleChainsByTenantId(UUID tenantId, TextPageLink pageLink) { - return DaoUtil.convertDataList(ruleChainRepository + public PageData findRuleChainsByTenantId(UUID tenantId, PageLink pageLink) { + return DaoUtil.toPageData(ruleChainRepository .findByTenantId( UUIDConverter.fromTimeUUID(tenantId), Objects.toString(pageLink.getTextSearch(), ""), - pageLink.getIdOffset() == null ? NULL_UUID_STR : UUIDConverter.fromTimeUUID(pageLink.getIdOffset()), - new PageRequest(0, pageLink.getLimit()))); + DaoUtil.toPageable(pageLink))); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java index b1e8200132..506b4c073f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java @@ -15,9 +15,11 @@ */ package org.thingsboard.server.dao.sql.rule; +import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; import org.thingsboard.server.dao.model.sql.RuleChainEntity; import org.thingsboard.server.dao.util.SqlDao; @@ -25,14 +27,12 @@ import org.thingsboard.server.dao.util.SqlDao; import java.util.List; @SqlDao -public interface RuleChainRepository extends CrudRepository { +public interface RuleChainRepository extends PagingAndSortingRepository { @Query("SELECT rc FROM RuleChainEntity rc WHERE rc.tenantId = :tenantId " + - "AND LOWER(rc.searchText) LIKE LOWER(CONCAT(:searchText, '%')) " + - "AND rc.id > :idOffset ORDER BY rc.id") - List findByTenantId(@Param("tenantId") String tenantId, + "AND LOWER(rc.searchText) LIKE LOWER(CONCAT(:searchText, '%'))") + Page findByTenantId(@Param("tenantId") String tenantId, @Param("searchText") String searchText, - @Param("idOffset") String idOffset, Pageable pageable); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/tenant/JpaTenantDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/tenant/JpaTenantDao.java index 4b42e36fef..f77030c76a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/tenant/JpaTenantDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/tenant/JpaTenantDao.java @@ -22,7 +22,8 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.UUIDConverter; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.model.sql.TenantEntity; import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; @@ -55,12 +56,11 @@ public class JpaTenantDao extends JpaAbstractSearchTextDao } @Override - public List findTenantsByRegion(TenantId tenantId, String region, TextPageLink pageLink) { - return DaoUtil.convertDataList(tenantRepository + public PageData findTenantsByRegion(TenantId tenantId, String region, PageLink pageLink) { + return DaoUtil.toPageData(tenantRepository .findByRegionNextPage( region, Objects.toString(pageLink.getTextSearch(), ""), - pageLink.getIdOffset() == null ? NULL_UUID_STR : UUIDConverter.fromTimeUUID(pageLink.getIdOffset()), - new PageRequest(0, pageLink.getLimit()))); + DaoUtil.toPageable(pageLink))); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/tenant/TenantRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/tenant/TenantRepository.java index 0aec76b112..cf6699cad2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/tenant/TenantRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/tenant/TenantRepository.java @@ -15,9 +15,11 @@ */ package org.thingsboard.server.dao.sql.tenant; +import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; import org.thingsboard.server.dao.model.sql.TenantEntity; import org.thingsboard.server.dao.util.SqlDao; @@ -28,13 +30,11 @@ import java.util.List; * Created by Valerii Sosliuk on 4/30/2017. */ @SqlDao -public interface TenantRepository extends CrudRepository { +public interface TenantRepository extends PagingAndSortingRepository { @Query("SELECT t FROM TenantEntity t WHERE t.region = :region " + - "AND LOWER(t.searchText) LIKE LOWER(CONCAT(:textSearch, '%')) " + - "AND t.id > :idOffset ORDER BY t.id") - List findByRegionNextPage(@Param("region") String region, + "AND LOWER(t.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))") + Page findByRegionNextPage(@Param("region") String region, @Param("textSearch") String textSearch, - @Param("idOffset") String idOffset, Pageable pageable); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserDao.java index 7468c47910..5cec2d4b80 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserDao.java @@ -21,7 +21,8 @@ import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageLink; +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.dao.DaoUtil; import org.thingsboard.server.dao.model.sql.UserEntity; @@ -62,29 +63,27 @@ public class JpaUserDao extends JpaAbstractSearchTextDao imple } @Override - public List findTenantAdmins(UUID tenantId, TextPageLink pageLink) { - return DaoUtil.convertDataList( + public PageData findTenantAdmins(UUID tenantId, PageLink pageLink) { + return DaoUtil.toPageData( userRepository .findUsersByAuthority( fromTimeUUID(tenantId), NULL_UUID_STR, - pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()), Objects.toString(pageLink.getTextSearch(), ""), Authority.TENANT_ADMIN, - new PageRequest(0, pageLink.getLimit()))); + DaoUtil.toPageable(pageLink))); } @Override - public List findCustomerUsers(UUID tenantId, UUID customerId, TextPageLink pageLink) { - return DaoUtil.convertDataList( + public PageData findCustomerUsers(UUID tenantId, UUID customerId, PageLink pageLink) { + return DaoUtil.toPageData( userRepository .findUsersByAuthority( fromTimeUUID(tenantId), fromTimeUUID(customerId), - pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()), Objects.toString(pageLink.getTextSearch(), ""), Authority.CUSTOMER_USER, - new PageRequest(0, pageLink.getLimit()))); + DaoUtil.toPageable(pageLink))); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserRepository.java index de088ecbcb..f1b4692d3d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserRepository.java @@ -15,9 +15,11 @@ */ package org.thingsboard.server.dao.sql.user; +import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.dao.model.sql.UserEntity; @@ -29,19 +31,17 @@ import java.util.List; * @author Valerii Sosliuk */ @SqlDao -public interface UserRepository extends CrudRepository { +public interface UserRepository extends PagingAndSortingRepository { UserEntity findByEmail(String email); @Query("SELECT u FROM UserEntity u WHERE u.tenantId = :tenantId " + "AND u.customerId = :customerId AND u.authority = :authority " + - "AND LOWER(u.searchText) LIKE LOWER(CONCAT(:searchText, '%'))" + - "AND u.id > :idOffset ORDER BY u.id") - List findUsersByAuthority(@Param("tenantId") String tenantId, + "AND LOWER(u.searchText) LIKE LOWER(CONCAT(:searchText, '%'))") + Page findUsersByAuthority(@Param("tenantId") String tenantId, @Param("customerId") String customerId, - @Param("idOffset") String idOffset, @Param("searchText") String searchText, @Param("authority") Authority authority, Pageable pageable); -} \ No newline at end of file +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetsBundleDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetsBundleDao.java index f03105f9b0..9bb1300a3d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetsBundleDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetsBundleDao.java @@ -21,7 +21,8 @@ import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.UUIDConverter; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.model.sql.WidgetsBundleEntity; @@ -61,36 +62,33 @@ public class JpaWidgetsBundleDao extends JpaAbstractSearchTextDao findSystemWidgetsBundles(TenantId tenantId, TextPageLink pageLink) { - return DaoUtil.convertDataList( + public PageData findSystemWidgetsBundles(TenantId tenantId, PageLink pageLink) { + return DaoUtil.toPageData( widgetsBundleRepository .findSystemWidgetsBundles( NULL_UUID_STR, Objects.toString(pageLink.getTextSearch(), ""), - pageLink.getIdOffset() == null ? NULL_UUID_STR : UUIDConverter.fromTimeUUID(pageLink.getIdOffset()), - new PageRequest(0, pageLink.getLimit()))); + DaoUtil.toPageable(pageLink))); } @Override - public List findTenantWidgetsBundlesByTenantId(UUID tenantId, TextPageLink pageLink) { - return DaoUtil.convertDataList( + public PageData findTenantWidgetsBundlesByTenantId(UUID tenantId, PageLink pageLink) { + return DaoUtil.toPageData( widgetsBundleRepository .findTenantWidgetsBundlesByTenantId( UUIDConverter.fromTimeUUID(tenantId), Objects.toString(pageLink.getTextSearch(), ""), - pageLink.getIdOffset() == null ? NULL_UUID_STR : UUIDConverter.fromTimeUUID(pageLink.getIdOffset()), - new PageRequest(0, pageLink.getLimit()))); + DaoUtil.toPageable(pageLink))); } @Override - public List findAllTenantWidgetsBundlesByTenantId(UUID tenantId, TextPageLink pageLink) { - return DaoUtil.convertDataList( + public PageData findAllTenantWidgetsBundlesByTenantId(UUID tenantId, PageLink pageLink) { + return DaoUtil.toPageData( widgetsBundleRepository .findAllTenantWidgetsBundlesByTenantId( UUIDConverter.fromTimeUUID(tenantId), NULL_UUID_STR, Objects.toString(pageLink.getTextSearch(), ""), - pageLink.getIdOffset() == null ? NULL_UUID_STR : UUIDConverter.fromTimeUUID(pageLink.getIdOffset()), - new PageRequest(0, pageLink.getLimit()))); + DaoUtil.toPageable(pageLink))); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetsBundleRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetsBundleRepository.java index da923ec49b..e69bc6c2c9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetsBundleRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetsBundleRepository.java @@ -15,9 +15,11 @@ */ package org.thingsboard.server.dao.sql.widget; +import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; import org.thingsboard.server.dao.model.sql.WidgetsBundleEntity; import org.thingsboard.server.dao.util.SqlDao; @@ -28,32 +30,26 @@ import java.util.List; * Created by Valerii Sosliuk on 4/23/2017. */ @SqlDao -public interface WidgetsBundleRepository extends CrudRepository { +public interface WidgetsBundleRepository extends PagingAndSortingRepository { WidgetsBundleEntity findWidgetsBundleByTenantIdAndAlias(String tenantId, String alias); @Query("SELECT wb FROM WidgetsBundleEntity wb WHERE wb.tenantId = :systemTenantId " + - "AND LOWER(wb.searchText) LIKE LOWER(CONCAT(:searchText, '%')) " + - "AND wb.id > :idOffset ORDER BY wb.id") - List findSystemWidgetsBundles(@Param("systemTenantId") String systemTenantId, + "AND LOWER(wb.searchText) LIKE LOWER(CONCAT(:searchText, '%'))") + Page findSystemWidgetsBundles(@Param("systemTenantId") String systemTenantId, @Param("searchText") String searchText, - @Param("idOffset") String idOffset, Pageable pageable); @Query("SELECT wb FROM WidgetsBundleEntity wb WHERE wb.tenantId = :tenantId " + - "AND LOWER(wb.searchText) LIKE LOWER(CONCAT(:textSearch, '%')) " + - "AND wb.id > :idOffset ORDER BY wb.id") - List findTenantWidgetsBundlesByTenantId(@Param("tenantId") String tenantId, + "AND LOWER(wb.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))") + Page findTenantWidgetsBundlesByTenantId(@Param("tenantId") String tenantId, @Param("textSearch") String textSearch, - @Param("idOffset") String idOffset, Pageable pageable); @Query("SELECT wb FROM WidgetsBundleEntity wb WHERE wb.tenantId IN (:tenantId, :nullTenantId) " + - "AND LOWER(wb.searchText) LIKE LOWER(CONCAT(:textSearch, '%')) " + - "AND wb.id > :idOffset ORDER BY wb.id") - List findAllTenantWidgetsBundlesByTenantId(@Param("tenantId") String tenantId, + "AND LOWER(wb.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))") + Page findAllTenantWidgetsBundlesByTenantId(@Param("tenantId") String tenantId, @Param("nullTenantId") String nullTenantId, @Param("textSearch") String textSearch, - @Param("idOffset") String idOffset, Pageable pageable); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantDao.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantDao.java index cb0a6be5ce..d8cae38ad8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantDao.java @@ -17,7 +17,8 @@ package org.thingsboard.server.dao.tenant; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.Dao; import java.util.List; @@ -39,6 +40,6 @@ public interface TenantDao extends Dao { * @param pageLink the page link * @return the list of tenant objects */ - List findTenantsByRegion(TenantId tenantId, String region, TextPageLink pageLink); + PageData findTenantsByRegion(TenantId tenantId, String region, PageLink pageLink); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java index e06e6b0b2c..5a8a59be44 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java @@ -23,8 +23,8 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.dashboard.DashboardService; @@ -116,11 +116,10 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe } @Override - public TextPageData findTenants(TextPageLink pageLink) { + public PageData findTenants(PageLink pageLink) { log.trace("Executing findTenants pageLink [{}]", pageLink); - Validator.validatePageLink(pageLink, "Incorrect page link " + pageLink); - List tenants = tenantDao.findTenantsByRegion(new TenantId(EntityId.NULL_UUID), DEFAULT_TENANT_REGION, pageLink); - return new TextPageData<>(tenants, pageLink); + Validator.validatePageLink(pageLink); + return tenantDao.findTenantsByRegion(new TenantId(EntityId.NULL_UUID), DEFAULT_TENANT_REGION, pageLink); } @Override @@ -146,7 +145,7 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe new PaginatedRemover() { @Override - protected List findEntities(TenantId tenantId, String region, TextPageLink pageLink) { + protected PageData findEntities(TenantId tenantId, String region, PageLink pageLink) { return tenantDao.findTenantsByRegion(tenantId, region, pageLink); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserDao.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserDao.java index a83a422824..fec181d81b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserDao.java @@ -17,7 +17,8 @@ package org.thingsboard.server.dao.user; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.Dao; import java.util.List; @@ -48,7 +49,7 @@ public interface UserDao extends Dao { * @param pageLink the page link * @return the list of user entities */ - List findTenantAdmins(UUID tenantId, TextPageLink pageLink); + PageData findTenantAdmins(UUID tenantId, PageLink pageLink); /** * Find customer users by tenantId, customerId and page link. @@ -58,6 +59,6 @@ public interface UserDao extends Dao { * @param pageLink the page link * @return the list of user entities */ - List findCustomerUsers(UUID tenantId, UUID customerId, TextPageLink pageLink); + PageData findCustomerUsers(UUID tenantId, UUID customerId, PageLink pageLink); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java index 9908fd17f6..bed05bdd79 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java @@ -29,8 +29,8 @@ import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserCredentialsId; import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +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.security.UserCredentials; import org.thingsboard.server.dao.customer.CustomerDao; @@ -207,12 +207,11 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic } @Override - public TextPageData findTenantAdmins(TenantId tenantId, TextPageLink pageLink) { + public PageData findTenantAdmins(TenantId tenantId, PageLink pageLink) { log.trace("Executing findTenantAdmins, tenantId [{}], pageLink [{}]", tenantId, pageLink); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - validatePageLink(pageLink, "Incorrect page link " + pageLink); - List users = userDao.findTenantAdmins(tenantId.getId(), pageLink); - return new TextPageData<>(users, pageLink); + validatePageLink(pageLink); + return userDao.findTenantAdmins(tenantId.getId(), pageLink); } @Override @@ -223,13 +222,12 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic } @Override - public TextPageData findCustomerUsers(TenantId tenantId, CustomerId customerId, TextPageLink pageLink) { + public PageData findCustomerUsers(TenantId tenantId, CustomerId customerId, PageLink pageLink) { log.trace("Executing findCustomerUsers, tenantId [{}], customerId [{}], pageLink [{}]", tenantId, customerId, pageLink); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); validateId(customerId, "Incorrect customerId " + customerId); - validatePageLink(pageLink, "Incorrect page link " + pageLink); - List users = userDao.findCustomerUsers(tenantId.getId(), customerId.getId(), pageLink); - return new TextPageData<>(users, pageLink); + validatePageLink(pageLink); + return userDao.findCustomerUsers(tenantId.getId(), customerId.getId(), pageLink); } @Override @@ -345,7 +343,7 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic private PaginatedRemover tenantAdminsRemover = new PaginatedRemover() { @Override - protected List findEntities(TenantId tenantId, TenantId id, TextPageLink pageLink) { + protected PageData findEntities(TenantId tenantId, TenantId id, PageLink pageLink) { return userDao.findTenantAdmins(id.getId(), pageLink); } @@ -357,7 +355,7 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic private PaginatedRemover customerUsersRemover = new PaginatedRemover() { @Override - protected List findEntities(TenantId tenantId, CustomerId id, TextPageLink pageLink) { + protected PageData findEntities(TenantId tenantId, CustomerId id, PageLink pageLink) { return userDao.findCustomerUsers(tenantId.getId(), id.getId(), pageLink); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleDao.java b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleDao.java index 2f0e9808b9..e729855620 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleDao.java @@ -16,7 +16,8 @@ package org.thingsboard.server.dao.widget; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.dao.Dao; @@ -52,7 +53,7 @@ public interface WidgetsBundleDao extends Dao { * @param pageLink the page link * @return the list of widgets bundles objects */ - List findSystemWidgetsBundles(TenantId tenantId, TextPageLink pageLink); + PageData findSystemWidgetsBundles(TenantId tenantId, PageLink pageLink); /** * Find tenant widgets bundles by tenantId and page link. @@ -61,7 +62,7 @@ public interface WidgetsBundleDao extends Dao { * @param pageLink the page link * @return the list of widgets bundles objects */ - List findTenantWidgetsBundlesByTenantId(UUID tenantId, TextPageLink pageLink); + PageData findTenantWidgetsBundlesByTenantId(UUID tenantId, PageLink pageLink); /** * Find all tenant widgets bundles (including system) by tenantId and page link. @@ -70,7 +71,7 @@ public interface WidgetsBundleDao extends Dao { * @param pageLink the page link * @return the list of widgets bundles objects */ - List findAllTenantWidgetsBundlesByTenantId(UUID tenantId, TextPageLink pageLink); + PageData findAllTenantWidgetsBundlesByTenantId(UUID tenantId, PageLink pageLink); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java index 1a6ac92a31..28102cdbf7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java @@ -22,8 +22,8 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.WidgetsBundleId; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.IncorrectParameterException; @@ -88,42 +88,42 @@ public class WidgetsBundleServiceImpl implements WidgetsBundleService { } @Override - public TextPageData findSystemWidgetsBundlesByPageLink(TenantId tenantId, TextPageLink pageLink) { + public PageData findSystemWidgetsBundlesByPageLink(TenantId tenantId, PageLink pageLink) { log.trace("Executing findSystemWidgetsBundles, pageLink [{}]", pageLink); - Validator.validatePageLink(pageLink, INCORRECT_PAGE_LINK + pageLink); - return new TextPageData<>(widgetsBundleDao.findSystemWidgetsBundles(tenantId, pageLink), pageLink); + Validator.validatePageLink(pageLink); + return widgetsBundleDao.findSystemWidgetsBundles(tenantId, pageLink); } @Override public List findSystemWidgetsBundles(TenantId tenantId) { log.trace("Executing findSystemWidgetsBundles"); List widgetsBundles = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(DEFAULT_WIDGETS_BUNDLE_LIMIT); - TextPageData pageData; + PageLink pageLink = new PageLink(DEFAULT_WIDGETS_BUNDLE_LIMIT); + PageData pageData; do { pageData = findSystemWidgetsBundlesByPageLink(tenantId, pageLink); widgetsBundles.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); return widgetsBundles; } @Override - public TextPageData findTenantWidgetsBundlesByTenantId(TenantId tenantId, TextPageLink pageLink) { + public PageData findTenantWidgetsBundlesByTenantId(TenantId tenantId, PageLink pageLink) { log.trace("Executing findTenantWidgetsBundlesByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink); Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - Validator.validatePageLink(pageLink, INCORRECT_PAGE_LINK + pageLink); - return new TextPageData<>(widgetsBundleDao.findTenantWidgetsBundlesByTenantId(tenantId.getId(), pageLink), pageLink); + Validator.validatePageLink(pageLink); + return widgetsBundleDao.findTenantWidgetsBundlesByTenantId(tenantId.getId(), pageLink); } @Override - public TextPageData findAllTenantWidgetsBundlesByTenantIdAndPageLink(TenantId tenantId, TextPageLink pageLink) { + public PageData findAllTenantWidgetsBundlesByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink) { log.trace("Executing findAllTenantWidgetsBundlesByTenantIdAndPageLink, tenantId [{}], pageLink [{}]", tenantId, pageLink); Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - Validator.validatePageLink(pageLink, INCORRECT_PAGE_LINK + pageLink); - return new TextPageData<>(widgetsBundleDao.findAllTenantWidgetsBundlesByTenantId(tenantId.getId(), pageLink), pageLink); + Validator.validatePageLink(pageLink); + return widgetsBundleDao.findAllTenantWidgetsBundlesByTenantId(tenantId.getId(), pageLink); } @Override @@ -131,13 +131,13 @@ public class WidgetsBundleServiceImpl implements WidgetsBundleService { log.trace("Executing findAllTenantWidgetsBundlesByTenantId, tenantId [{}]", tenantId); Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId); List widgetsBundles = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(DEFAULT_WIDGETS_BUNDLE_LIMIT); - TextPageData pageData; + PageLink pageLink = new PageLink(DEFAULT_WIDGETS_BUNDLE_LIMIT); + PageData pageData; do { pageData = findAllTenantWidgetsBundlesByTenantIdAndPageLink(tenantId, pageLink); widgetsBundles.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); return widgetsBundles; @@ -204,7 +204,7 @@ public class WidgetsBundleServiceImpl implements WidgetsBundleService { new PaginatedRemover() { @Override - protected List findEntities(TenantId tenantId, TenantId id, TextPageLink pageLink) { + protected PageData findEntities(TenantId tenantId, TenantId id, PageLink pageLink) { return widgetsBundleDao.findTenantWidgetsBundlesByTenantId(id.getId(), pageLink); } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java index 6e995611c1..2d263f67d1 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java @@ -28,7 +28,8 @@ import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TimePageData; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.SortOrder; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.RelationTypeGroup; @@ -112,10 +113,11 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { Alarm created = alarmService.createOrUpdateAlarm(alarm); // Check child relation - TimePageData alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder() + PageData alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder() .affectedEntityId(childId) .status(AlarmStatus.ACTIVE_UNACK).pageLink( - new TimePageLink(1, 0L, System.currentTimeMillis(), false) + new TimePageLink(1, 0, "", + new SortOrder("createdTime", SortOrder.Direction.DESC), 0L, System.currentTimeMillis()) ).build()).get(); Assert.assertNotNull(alarms.getData()); Assert.assertEquals(1, alarms.getData().size()); @@ -125,7 +127,8 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder() .affectedEntityId(parentId) .status(AlarmStatus.ACTIVE_UNACK).pageLink( - new TimePageLink(1, 0L, System.currentTimeMillis(), false) + new TimePageLink(1, 0, "", + new SortOrder("createdTime", SortOrder.Direction.DESC), 0L, System.currentTimeMillis()) ).build()).get(); Assert.assertNotNull(alarms.getData()); Assert.assertEquals(0, alarms.getData().size()); @@ -137,7 +140,8 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder() .affectedEntityId(childId) .status(AlarmStatus.ACTIVE_UNACK).pageLink( - new TimePageLink(1, 0L, System.currentTimeMillis(), false) + new TimePageLink(1, 0, "", + new SortOrder("createdTime", SortOrder.Direction.DESC), 0L, System.currentTimeMillis()) ).build()).get(); Assert.assertNotNull(alarms.getData()); Assert.assertEquals(1, alarms.getData().size()); @@ -147,7 +151,8 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder() .affectedEntityId(parentId) .status(AlarmStatus.ACTIVE_UNACK).pageLink( - new TimePageLink(1, 0L, System.currentTimeMillis(), false) + new TimePageLink(1, 0, "", + new SortOrder("createdTime", SortOrder.Direction.DESC), 0L, System.currentTimeMillis()) ).build()).get(); Assert.assertNotNull(alarms.getData()); Assert.assertEquals(1, alarms.getData().size()); @@ -159,7 +164,8 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder() .affectedEntityId(childId) .status(AlarmStatus.ACTIVE_ACK).pageLink( - new TimePageLink(1, 0L, System.currentTimeMillis(), false) + new TimePageLink(1, 0, "", + new SortOrder("createdTime", SortOrder.Direction.DESC), 0L, System.currentTimeMillis()) ).build()).get(); Assert.assertNotNull(alarms.getData()); Assert.assertEquals(1, alarms.getData().size()); @@ -169,7 +175,8 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder() .affectedEntityId(childId) .status(AlarmStatus.ACTIVE_UNACK).pageLink( - new TimePageLink(1, 0L, System.currentTimeMillis(), false) + new TimePageLink(1, 0, "", + new SortOrder("createdTime", SortOrder.Direction.DESC), 0L, System.currentTimeMillis()) ).build()).get(); Assert.assertNotNull(alarms.getData()); Assert.assertEquals(0, alarms.getData().size()); @@ -180,7 +187,8 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder() .affectedEntityId(childId) .status(AlarmStatus.CLEARED_ACK).pageLink( - new TimePageLink(1, 0L, System.currentTimeMillis(), false) + new TimePageLink(1, 0, "", + new SortOrder("createdTime", SortOrder.Direction.DESC), 0L, System.currentTimeMillis()) ).build()).get(); Assert.assertNotNull(alarms.getData()); Assert.assertEquals(1, alarms.getData().size()); @@ -205,10 +213,11 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { Alarm created = alarmService.createOrUpdateAlarm(alarm); - TimePageData alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder() + PageData alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder() .affectedEntityId(childId) .status(AlarmStatus.ACTIVE_UNACK).pageLink( - new TimePageLink(1, 0L, System.currentTimeMillis(), false) + new TimePageLink(1, 0, "", + new SortOrder("createdTime", SortOrder.Direction.DESC), 0L, System.currentTimeMillis()) ).build()).get(); Assert.assertNotNull(alarms.getData()); Assert.assertEquals(1, alarms.getData().size()); @@ -218,7 +227,8 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder() .affectedEntityId(parentId) .status(AlarmStatus.ACTIVE_UNACK).pageLink( - new TimePageLink(1, 0L, System.currentTimeMillis(), false) + new TimePageLink(1, 0, "", + new SortOrder("createdTime", SortOrder.Direction.DESC), 0L, System.currentTimeMillis()) ).build()).get(); Assert.assertNotNull(alarms.getData()); Assert.assertEquals(1, alarms.getData().size()); @@ -243,7 +253,8 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder() .affectedEntityId(childId) .status(AlarmStatus.ACTIVE_UNACK).pageLink( - new TimePageLink(1, 0L, System.currentTimeMillis(), false) + new TimePageLink(1, 0, "", + new SortOrder("createdTime", SortOrder.Direction.DESC), 0L, System.currentTimeMillis()) ).build()).get(); Assert.assertNotNull(alarms.getData()); Assert.assertEquals(0, alarms.getData().size()); @@ -252,7 +263,8 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder() .affectedEntityId(parentId) .status(AlarmStatus.ACTIVE_UNACK).pageLink( - new TimePageLink(1, 0L, System.currentTimeMillis(), false) + new TimePageLink(1, 0, "", + new SortOrder("createdTime", SortOrder.Direction.DESC), 0L, System.currentTimeMillis()) ).build()).get(); Assert.assertNotNull(alarms.getData()); Assert.assertEquals(0, alarms.getData().size()); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseAssetServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseAssetServiceTest.java index d34865452a..8fdeeb1e11 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseAssetServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseAssetServiceTest.java @@ -27,8 +27,8 @@ import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.exception.DataValidationException; import java.util.ArrayList; @@ -224,13 +224,13 @@ public abstract class BaseAssetServiceTest extends AbstractServiceTest { } List loadedAssets = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(23); - TextPageData pageData = null; + PageLink pageLink = new PageLink(23); + PageData pageData = null; do { pageData = assetService.findAssetsByTenantId(tenantId, pageLink); loadedAssets.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -241,7 +241,7 @@ public abstract class BaseAssetServiceTest extends AbstractServiceTest { assetService.deleteAssetsByTenantId(tenantId); - pageLink = new TextPageLink(33); + pageLink = new PageLink(33); pageData = assetService.findAssetsByTenantId(tenantId, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertTrue(pageData.getData().isEmpty()); @@ -277,13 +277,13 @@ public abstract class BaseAssetServiceTest extends AbstractServiceTest { } List loadedAssetsTitle1 = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(15, title1); - TextPageData pageData = null; + PageLink pageLink = new PageLink(15, 0, title1); + PageData pageData = null; do { pageData = assetService.findAssetsByTenantId(tenantId, pageLink); loadedAssetsTitle1.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -293,12 +293,12 @@ public abstract class BaseAssetServiceTest extends AbstractServiceTest { Assert.assertEquals(assetsTitle1, loadedAssetsTitle1); List loadedAssetsTitle2 = new ArrayList<>(); - pageLink = new TextPageLink(4, title2); + pageLink = new PageLink(4, 0, title2); do { pageData = assetService.findAssetsByTenantId(tenantId, pageLink); loadedAssetsTitle2.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -311,7 +311,7 @@ public abstract class BaseAssetServiceTest extends AbstractServiceTest { assetService.deleteAsset(tenantId, asset.getId()); } - pageLink = new TextPageLink(4, title1); + pageLink = new PageLink(4, 0, title1); pageData = assetService.findAssetsByTenantId(tenantId, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -320,7 +320,7 @@ public abstract class BaseAssetServiceTest extends AbstractServiceTest { assetService.deleteAsset(tenantId, asset.getId()); } - pageLink = new TextPageLink(4, title2); + pageLink = new PageLink(4, 0, title2); pageData = assetService.findAssetsByTenantId(tenantId, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -356,13 +356,13 @@ public abstract class BaseAssetServiceTest extends AbstractServiceTest { } List loadedAssetsType1 = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(15); - TextPageData pageData = null; + PageLink pageLink = new PageLink(15); + PageData pageData = null; do { pageData = assetService.findAssetsByTenantIdAndType(tenantId, type1, pageLink); loadedAssetsType1.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -372,12 +372,12 @@ public abstract class BaseAssetServiceTest extends AbstractServiceTest { Assert.assertEquals(assetsType1, loadedAssetsType1); List loadedAssetsType2 = new ArrayList<>(); - pageLink = new TextPageLink(4); + pageLink = new PageLink(4); do { pageData = assetService.findAssetsByTenantIdAndType(tenantId, type2, pageLink); loadedAssetsType2.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -390,7 +390,7 @@ public abstract class BaseAssetServiceTest extends AbstractServiceTest { assetService.deleteAsset(tenantId, asset.getId()); } - pageLink = new TextPageLink(4); + pageLink = new PageLink(4); pageData = assetService.findAssetsByTenantIdAndType(tenantId, type1, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -399,7 +399,7 @@ public abstract class BaseAssetServiceTest extends AbstractServiceTest { assetService.deleteAsset(tenantId, asset.getId()); } - pageLink = new TextPageLink(4); + pageLink = new PageLink(4); pageData = assetService.findAssetsByTenantIdAndType(tenantId, type2, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -430,13 +430,13 @@ public abstract class BaseAssetServiceTest extends AbstractServiceTest { } List loadedAssets = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(23); - TextPageData pageData = null; + PageLink pageLink = new PageLink(23); + PageData pageData = null; do { pageData = assetService.findAssetsByTenantIdAndCustomerId(tenantId, customerId, pageLink); loadedAssets.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -447,7 +447,7 @@ public abstract class BaseAssetServiceTest extends AbstractServiceTest { assetService.unassignCustomerAssets(tenantId, customerId); - pageLink = new TextPageLink(33); + pageLink = new PageLink(33); pageData = assetService.findAssetsByTenantIdAndCustomerId(tenantId, customerId, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertTrue(pageData.getData().isEmpty()); @@ -492,13 +492,13 @@ public abstract class BaseAssetServiceTest extends AbstractServiceTest { } List loadedAssetsTitle1 = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(15, title1); - TextPageData pageData = null; + PageLink pageLink = new PageLink(15, 0, title1); + PageData pageData = null; do { pageData = assetService.findAssetsByTenantIdAndCustomerId(tenantId, customerId, pageLink); loadedAssetsTitle1.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -508,12 +508,12 @@ public abstract class BaseAssetServiceTest extends AbstractServiceTest { Assert.assertEquals(assetsTitle1, loadedAssetsTitle1); List loadedAssetsTitle2 = new ArrayList<>(); - pageLink = new TextPageLink(4, title2); + pageLink = new PageLink(4, 0, title2); do { pageData = assetService.findAssetsByTenantIdAndCustomerId(tenantId, customerId, pageLink); loadedAssetsTitle2.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -526,7 +526,7 @@ public abstract class BaseAssetServiceTest extends AbstractServiceTest { assetService.deleteAsset(tenantId, asset.getId()); } - pageLink = new TextPageLink(4, title1); + pageLink = new PageLink(4, 0, title1); pageData = assetService.findAssetsByTenantIdAndCustomerId(tenantId, customerId, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -535,7 +535,7 @@ public abstract class BaseAssetServiceTest extends AbstractServiceTest { assetService.deleteAsset(tenantId, asset.getId()); } - pageLink = new TextPageLink(4, title2); + pageLink = new PageLink(4, 0, title2); pageData = assetService.findAssetsByTenantIdAndCustomerId(tenantId, customerId, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -581,13 +581,13 @@ public abstract class BaseAssetServiceTest extends AbstractServiceTest { } List loadedAssetsType1 = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(15); - TextPageData pageData = null; + PageLink pageLink = new PageLink(15); + PageData pageData = null; do { pageData = assetService.findAssetsByTenantIdAndCustomerIdAndType(tenantId, customerId, type1, pageLink); loadedAssetsType1.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -597,12 +597,12 @@ public abstract class BaseAssetServiceTest extends AbstractServiceTest { Assert.assertEquals(assetsType1, loadedAssetsType1); List loadedAssetsType2 = new ArrayList<>(); - pageLink = new TextPageLink(4); + pageLink = new PageLink(4); do { pageData = assetService.findAssetsByTenantIdAndCustomerIdAndType(tenantId, customerId, type2, pageLink); loadedAssetsType2.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -615,7 +615,7 @@ public abstract class BaseAssetServiceTest extends AbstractServiceTest { assetService.deleteAsset(tenantId, asset.getId()); } - pageLink = new TextPageLink(4); + pageLink = new PageLink(4); pageData = assetService.findAssetsByTenantIdAndCustomerIdAndType(tenantId, customerId, type1, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -624,7 +624,7 @@ public abstract class BaseAssetServiceTest extends AbstractServiceTest { assetService.deleteAsset(tenantId, asset.getId()); } - pageLink = new TextPageLink(4); + pageLink = new PageLink(4); pageData = assetService.findAssetsByTenantIdAndCustomerIdAndType(tenantId, customerId, type2, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseCustomerServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseCustomerServiceTest.java index 51d9150eb6..91d05782f6 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseCustomerServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseCustomerServiceTest.java @@ -24,8 +24,8 @@ import org.junit.Test; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.exception.DataValidationException; import java.util.ArrayList; @@ -146,13 +146,13 @@ public abstract class BaseCustomerServiceTest extends AbstractServiceTest { } List loadedCustomers = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(23); - TextPageData pageData = null; + PageLink pageLink = new PageLink(23); + PageData pageData = null; do { pageData = customerService.findCustomersByTenantId(tenantId, pageLink); loadedCustomers.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -163,7 +163,7 @@ public abstract class BaseCustomerServiceTest extends AbstractServiceTest { customerService.deleteCustomersByTenantId(tenantId); - pageLink = new TextPageLink(33); + pageLink = new PageLink(33); pageData = customerService.findCustomersByTenantId(tenantId, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertTrue(pageData.getData().isEmpty()); @@ -197,13 +197,13 @@ public abstract class BaseCustomerServiceTest extends AbstractServiceTest { } List loadedCustomersTitle1 = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(15, title1); - TextPageData pageData = null; + PageLink pageLink = new PageLink(15, 0, title1); + PageData pageData = null; do { pageData = customerService.findCustomersByTenantId(tenantId, pageLink); loadedCustomersTitle1.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -213,12 +213,12 @@ public abstract class BaseCustomerServiceTest extends AbstractServiceTest { Assert.assertEquals(customersTitle1, loadedCustomersTitle1); List loadedCustomersTitle2 = new ArrayList<>(); - pageLink = new TextPageLink(4, title2); + pageLink = new PageLink(4, 0, title2); do { pageData = customerService.findCustomersByTenantId(tenantId, pageLink); loadedCustomersTitle2.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -231,7 +231,7 @@ public abstract class BaseCustomerServiceTest extends AbstractServiceTest { customerService.deleteCustomer(tenantId, customer.getId()); } - pageLink = new TextPageLink(4, title1); + pageLink = new PageLink(4, 0, title1); pageData = customerService.findCustomersByTenantId(tenantId, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -240,7 +240,7 @@ public abstract class BaseCustomerServiceTest extends AbstractServiceTest { customerService.deleteCustomer(tenantId, customer.getId()); } - pageLink = new TextPageLink(4, title2); + pageLink = new PageLink(4, 0, title2); pageData = customerService.findCustomersByTenantId(tenantId, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java index d8fecd04f0..ee4bfe02cb 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java @@ -27,9 +27,8 @@ import org.thingsboard.server.common.data.DashboardInfo; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; -import org.thingsboard.server.common.data.page.TimePageData; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.dao.exception.DataValidationException; @@ -179,13 +178,13 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest { } List loadedDashboards = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(16); - TextPageData pageData = null; + PageLink pageLink = new PageLink(16); + PageData pageData = null; do { pageData = dashboardService.findDashboardsByTenantId(tenantId, pageLink); loadedDashboards.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -196,7 +195,7 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest { dashboardService.deleteDashboardsByTenantId(tenantId); - pageLink = new TextPageLink(31); + pageLink = new PageLink(31); pageData = dashboardService.findDashboardsByTenantId(tenantId, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertTrue(pageData.getData().isEmpty()); @@ -230,13 +229,13 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest { } List loadedDashboardsTitle1 = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(19, title1); - TextPageData pageData = null; + PageLink pageLink = new PageLink(19, 0, title1); + PageData pageData = null; do { pageData = dashboardService.findDashboardsByTenantId(tenantId, pageLink); loadedDashboardsTitle1.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -246,12 +245,12 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest { Assert.assertEquals(dashboardsTitle1, loadedDashboardsTitle1); List loadedDashboardsTitle2 = new ArrayList<>(); - pageLink = new TextPageLink(4, title2); + pageLink = new PageLink(4, 0, title2); do { pageData = dashboardService.findDashboardsByTenantId(tenantId, pageLink); loadedDashboardsTitle2.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -264,7 +263,7 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest { dashboardService.deleteDashboard(tenantId, dashboard.getId()); } - pageLink = new TextPageLink(4, title1); + pageLink = new PageLink(4, 0, title1); pageData = dashboardService.findDashboardsByTenantId(tenantId, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -273,7 +272,7 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest { dashboardService.deleteDashboard(tenantId, dashboard.getId()); } - pageLink = new TextPageLink(4, title2); + pageLink = new PageLink(4, 0, title2); pageData = dashboardService.findDashboardsByTenantId(tenantId, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -304,12 +303,12 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest { List loadedDashboards = new ArrayList<>(); TimePageLink pageLink = new TimePageLink(23); - TimePageData pageData = null; + PageData pageData = null; do { pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink).get(); loadedDashboards.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java index 7a2935ce3b..f4f910d01e 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java @@ -27,8 +27,8 @@ import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.dao.exception.DataValidationException; @@ -236,13 +236,13 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { } List loadedDevices = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(23); - TextPageData pageData = null; + PageLink pageLink = new PageLink(23); + PageData pageData = null; do { pageData = deviceService.findDevicesByTenantId(tenantId, pageLink); loadedDevices.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -253,7 +253,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { deviceService.deleteDevicesByTenantId(tenantId); - pageLink = new TextPageLink(33); + pageLink = new PageLink(33); pageData = deviceService.findDevicesByTenantId(tenantId, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertTrue(pageData.getData().isEmpty()); @@ -289,13 +289,13 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { } List loadedDevicesTitle1 = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(15, title1); - TextPageData pageData = null; + PageLink pageLink = new PageLink(15, 0, title1); + PageData pageData = null; do { pageData = deviceService.findDevicesByTenantId(tenantId, pageLink); loadedDevicesTitle1.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -305,12 +305,12 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { Assert.assertEquals(devicesTitle1, loadedDevicesTitle1); List loadedDevicesTitle2 = new ArrayList<>(); - pageLink = new TextPageLink(4, title2); + pageLink = new PageLink(4, 0, title2); do { pageData = deviceService.findDevicesByTenantId(tenantId, pageLink); loadedDevicesTitle2.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -323,7 +323,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { deviceService.deleteDevice(tenantId, device.getId()); } - pageLink = new TextPageLink(4, title1); + pageLink = new PageLink(4, 0, title1); pageData = deviceService.findDevicesByTenantId(tenantId, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -332,7 +332,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { deviceService.deleteDevice(tenantId, device.getId()); } - pageLink = new TextPageLink(4, title2); + pageLink = new PageLink(4, 0, title2); pageData = deviceService.findDevicesByTenantId(tenantId, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -368,13 +368,13 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { } List loadedDevicesType1 = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(15); - TextPageData pageData = null; + PageLink pageLink = new PageLink(15); + PageData pageData = null; do { pageData = deviceService.findDevicesByTenantIdAndType(tenantId, type1, pageLink); loadedDevicesType1.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -384,12 +384,12 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { Assert.assertEquals(devicesType1, loadedDevicesType1); List loadedDevicesType2 = new ArrayList<>(); - pageLink = new TextPageLink(4); + pageLink = new PageLink(4); do { pageData = deviceService.findDevicesByTenantIdAndType(tenantId, type2, pageLink); loadedDevicesType2.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -402,7 +402,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { deviceService.deleteDevice(tenantId, device.getId()); } - pageLink = new TextPageLink(4); + pageLink = new PageLink(4); pageData = deviceService.findDevicesByTenantIdAndType(tenantId, type1, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -411,7 +411,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { deviceService.deleteDevice(tenantId, device.getId()); } - pageLink = new TextPageLink(4); + pageLink = new PageLink(4); pageData = deviceService.findDevicesByTenantIdAndType(tenantId, type2, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -442,13 +442,13 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { } List loadedDevices = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(23); - TextPageData pageData = null; + PageLink pageLink = new PageLink(23); + PageData pageData = null; do { pageData = deviceService.findDevicesByTenantIdAndCustomerId(tenantId, customerId, pageLink); loadedDevices.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -459,7 +459,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { deviceService.unassignCustomerDevices(tenantId, customerId); - pageLink = new TextPageLink(33); + pageLink = new PageLink(33); pageData = deviceService.findDevicesByTenantIdAndCustomerId(tenantId, customerId, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertTrue(pageData.getData().isEmpty()); @@ -504,13 +504,13 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { } List loadedDevicesTitle1 = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(15, title1); - TextPageData pageData = null; + PageLink pageLink = new PageLink(15, 0, title1); + PageData pageData = null; do { pageData = deviceService.findDevicesByTenantIdAndCustomerId(tenantId, customerId, pageLink); loadedDevicesTitle1.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -520,12 +520,12 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { Assert.assertEquals(devicesTitle1, loadedDevicesTitle1); List loadedDevicesTitle2 = new ArrayList<>(); - pageLink = new TextPageLink(4, title2); + pageLink = new PageLink(4, 0, title2); do { pageData = deviceService.findDevicesByTenantIdAndCustomerId(tenantId, customerId, pageLink); loadedDevicesTitle2.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -538,7 +538,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { deviceService.deleteDevice(tenantId, device.getId()); } - pageLink = new TextPageLink(4, title1); + pageLink = new PageLink(4, 0, title1); pageData = deviceService.findDevicesByTenantIdAndCustomerId(tenantId, customerId, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -547,7 +547,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { deviceService.deleteDevice(tenantId, device.getId()); } - pageLink = new TextPageLink(4, title2); + pageLink = new PageLink(4, 0, title2); pageData = deviceService.findDevicesByTenantIdAndCustomerId(tenantId, customerId, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -593,13 +593,13 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { } List loadedDevicesType1 = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(15); - TextPageData pageData = null; + PageLink pageLink = new PageLink(15); + PageData pageData = null; do { pageData = deviceService.findDevicesByTenantIdAndCustomerIdAndType(tenantId, customerId, type1, pageLink); loadedDevicesType1.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -609,12 +609,12 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { Assert.assertEquals(devicesType1, loadedDevicesType1); List loadedDevicesType2 = new ArrayList<>(); - pageLink = new TextPageLink(4); + pageLink = new PageLink(4); do { pageData = deviceService.findDevicesByTenantIdAndCustomerIdAndType(tenantId, customerId, type2, pageLink); loadedDevicesType2.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -627,7 +627,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { deviceService.deleteDevice(tenantId, device.getId()); } - pageLink = new TextPageLink(4); + pageLink = new PageLink(4); pageData = deviceService.findDevicesByTenantIdAndCustomerIdAndType(tenantId, customerId, type1, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -636,7 +636,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { deviceService.deleteDevice(tenantId, device.getId()); } - pageLink = new TextPageLink(4); + pageLink = new PageLink(4); pageData = deviceService.findDevicesByTenantIdAndCustomerIdAndType(tenantId, customerId, type2, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseRuleChainServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseRuleChainServiceTest.java index 79c2a12bcc..e955c129b6 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseRuleChainServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseRuleChainServiceTest.java @@ -24,8 +24,8 @@ import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +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.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; @@ -140,13 +140,13 @@ public abstract class BaseRuleChainServiceTest extends AbstractServiceTest { } List loadedRuleChains = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(16); - TextPageData pageData = null; + PageLink pageLink = new PageLink(16); + PageData pageData = null; do { pageData = ruleChainService.findTenantRuleChains(tenantId, pageLink); loadedRuleChains.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -157,7 +157,7 @@ public abstract class BaseRuleChainServiceTest extends AbstractServiceTest { ruleChainService.deleteRuleChainsByTenantId(tenantId); - pageLink = new TextPageLink(31); + pageLink = new PageLink(31); pageData = ruleChainService.findTenantRuleChains(tenantId, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertTrue(pageData.getData().isEmpty()); @@ -191,13 +191,13 @@ public abstract class BaseRuleChainServiceTest extends AbstractServiceTest { } List loadedRuleChainsName1 = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(19, name1); - TextPageData pageData = null; + PageLink pageLink = new PageLink(19, 0, name1); + PageData pageData = null; do { pageData = ruleChainService.findTenantRuleChains(tenantId, pageLink); loadedRuleChainsName1.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -207,12 +207,12 @@ public abstract class BaseRuleChainServiceTest extends AbstractServiceTest { Assert.assertEquals(ruleChainsName1, loadedRuleChainsName1); List loadedRuleChainsName2 = new ArrayList<>(); - pageLink = new TextPageLink(4, name2); + pageLink = new PageLink(4, 0, name2); do { pageData = ruleChainService.findTenantRuleChains(tenantId, pageLink); loadedRuleChainsName2.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -225,7 +225,7 @@ public abstract class BaseRuleChainServiceTest extends AbstractServiceTest { ruleChainService.deleteRuleChainById(tenantId, ruleChain.getId()); } - pageLink = new TextPageLink(4, name1); + pageLink = new PageLink(4, 0, name1); pageData = ruleChainService.findTenantRuleChains(tenantId, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -234,7 +234,7 @@ public abstract class BaseRuleChainServiceTest extends AbstractServiceTest { ruleChainService.deleteRuleChainById(tenantId, ruleChain.getId()); } - pageLink = new TextPageLink(4, name2); + pageLink = new PageLink(4, 0, name2); pageData = ruleChainService.findTenantRuleChains(tenantId, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantServiceTest.java index 314629bfcf..d18444422a 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantServiceTest.java @@ -19,8 +19,8 @@ import org.apache.commons.lang3.RandomStringUtils; import org.junit.Assert; import org.junit.Test; import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.exception.DataValidationException; import java.util.ArrayList; @@ -88,8 +88,8 @@ public abstract class BaseTenantServiceTest extends AbstractServiceTest { public void testFindTenants() { List tenants = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(17); - TextPageData pageData = tenantService.findTenants(pageLink); + PageLink pageLink = new PageLink(17); + PageData pageData = tenantService.findTenants(pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertTrue(pageData.getData().isEmpty()); tenants.addAll(pageData.getData()); @@ -101,12 +101,12 @@ public abstract class BaseTenantServiceTest extends AbstractServiceTest { } List loadedTenants = new ArrayList<>(); - pageLink = new TextPageLink(17); + pageLink = new PageLink(17); do { pageData = tenantService.findTenants(pageLink); loadedTenants.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -121,7 +121,7 @@ public abstract class BaseTenantServiceTest extends AbstractServiceTest { } } - pageLink = new TextPageLink(17); + pageLink = new PageLink(17); pageData = tenantService.findTenants(pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertTrue(pageData.getData().isEmpty()); @@ -152,13 +152,13 @@ public abstract class BaseTenantServiceTest extends AbstractServiceTest { } List loadedTenantsTitle1 = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(15, title1); - TextPageData pageData = null; + PageLink pageLink = new PageLink(15, 0, title1); + PageData pageData = null; do { pageData = tenantService.findTenants(pageLink); loadedTenantsTitle1.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -168,12 +168,12 @@ public abstract class BaseTenantServiceTest extends AbstractServiceTest { Assert.assertEquals(tenantsTitle1, loadedTenantsTitle1); List loadedTenantsTitle2 = new ArrayList<>(); - pageLink = new TextPageLink(4, title2); + pageLink = new PageLink(4, 0, title2); do { pageData = tenantService.findTenants(pageLink); loadedTenantsTitle2.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -186,7 +186,7 @@ public abstract class BaseTenantServiceTest extends AbstractServiceTest { tenantService.deleteTenant(tenant.getId()); } - pageLink = new TextPageLink(4, title1); + pageLink = new PageLink(4, 0, title1); pageData = tenantService.findTenants(pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -195,7 +195,7 @@ public abstract class BaseTenantServiceTest extends AbstractServiceTest { tenantService.deleteTenant(tenant.getId()); } - pageLink = new TextPageLink(4, title2); + pageLink = new PageLink(4, 0, title2); pageData = tenantService.findTenants(pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseUserServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseUserServiceTest.java index e1b77fa3c5..39ce336d37 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseUserServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseUserServiceTest.java @@ -25,8 +25,8 @@ import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +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.security.UserCredentials; import org.thingsboard.server.dao.exception.DataValidationException; @@ -188,7 +188,7 @@ public abstract class BaseUserServiceTest extends AbstractServiceTest { @Test public void testFindTenantAdmins() { User tenantAdminUser = userService.findUserByEmail(tenantId, "tenant@thingsboard.org"); - TextPageData pageData = userService.findTenantAdmins(tenantAdminUser.getTenantId(), new TextPageLink(10)); + PageData pageData = userService.findTenantAdmins(tenantAdminUser.getTenantId(), new PageLink(10)); Assert.assertFalse(pageData.hasNext()); List users = pageData.getData(); Assert.assertEquals(1, users.size()); @@ -210,12 +210,12 @@ public abstract class BaseUserServiceTest extends AbstractServiceTest { } List loadedTenantAdmins = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(33); + PageLink pageLink = new PageLink(33); do { pageData = userService.findTenantAdmins(tenantId, pageLink); loadedTenantAdmins.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -226,7 +226,7 @@ public abstract class BaseUserServiceTest extends AbstractServiceTest { tenantService.deleteTenant(tenantId); - pageLink = new TextPageLink(33); + pageLink = new PageLink(33); pageData = userService.findTenantAdmins(tenantId, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertTrue(pageData.getData().isEmpty()); @@ -270,13 +270,13 @@ public abstract class BaseUserServiceTest extends AbstractServiceTest { } List loadedTenantAdminsEmail1 = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(33, email1); - TextPageData pageData = null; + PageLink pageLink = new PageLink(33, 0, email1); + PageData pageData = null; do { pageData = userService.findTenantAdmins(tenantId, pageLink); loadedTenantAdminsEmail1.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -286,12 +286,12 @@ public abstract class BaseUserServiceTest extends AbstractServiceTest { Assert.assertEquals(tenantAdminsEmail1, loadedTenantAdminsEmail1); List loadedTenantAdminsEmail2 = new ArrayList<>(); - pageLink = new TextPageLink(16, email2); + pageLink = new PageLink(16, 0, email2); do { pageData = userService.findTenantAdmins(tenantId, pageLink); loadedTenantAdminsEmail2.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -304,7 +304,7 @@ public abstract class BaseUserServiceTest extends AbstractServiceTest { userService.deleteUser(tenantId, user.getId()); } - pageLink = new TextPageLink(4, email1); + pageLink = new PageLink(4, 0, email1); pageData = userService.findTenantAdmins(tenantId, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -313,7 +313,7 @@ public abstract class BaseUserServiceTest extends AbstractServiceTest { userService.deleteUser(tenantId, user.getId()); } - pageLink = new TextPageLink(4, email2); + pageLink = new PageLink(4, 0, email2); pageData = userService.findTenantAdmins(tenantId, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -324,8 +324,8 @@ public abstract class BaseUserServiceTest extends AbstractServiceTest { @Test public void testFindCustomerUsers() { User customerUser = userService.findUserByEmail(tenantId, "customer@thingsboard.org"); - TextPageData pageData = userService.findCustomerUsers(customerUser.getTenantId(), - customerUser.getCustomerId(), new TextPageLink(10)); + PageData pageData = userService.findCustomerUsers(customerUser.getTenantId(), + customerUser.getCustomerId(), new PageLink(10)); Assert.assertFalse(pageData.hasNext()); List users = pageData.getData(); Assert.assertEquals(1, users.size()); @@ -355,12 +355,12 @@ public abstract class BaseUserServiceTest extends AbstractServiceTest { } List loadedCustomerUsers = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(33); + PageLink pageLink = new PageLink(33); do { pageData = userService.findCustomerUsers(tenantId, customerId, pageLink); loadedCustomerUsers.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -423,13 +423,13 @@ public abstract class BaseUserServiceTest extends AbstractServiceTest { } List loadedCustomerUsersEmail1 = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(33, email1); - TextPageData pageData = null; + PageLink pageLink = new PageLink(33, 0, email1); + PageData pageData = null; do { pageData = userService.findCustomerUsers(tenantId, customerId, pageLink); loadedCustomerUsersEmail1.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -439,12 +439,12 @@ public abstract class BaseUserServiceTest extends AbstractServiceTest { Assert.assertEquals(customerUsersEmail1, loadedCustomerUsersEmail1); List loadedCustomerUsersEmail2 = new ArrayList<>(); - pageLink = new TextPageLink(16, email2); + pageLink = new PageLink(16, 0, email2); do { pageData = userService.findCustomerUsers(tenantId, customerId, pageLink); loadedCustomerUsersEmail2.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -457,7 +457,7 @@ public abstract class BaseUserServiceTest extends AbstractServiceTest { userService.deleteUser(tenantId, user.getId()); } - pageLink = new TextPageLink(4, email1); + pageLink = new PageLink(4, 0, email1); pageData = userService.findCustomerUsers(tenantId, customerId, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -466,7 +466,7 @@ public abstract class BaseUserServiceTest extends AbstractServiceTest { userService.deleteUser(tenantId, user.getId()); } - pageLink = new TextPageLink(4, email2); + pageLink = new PageLink(4, 0, email2); pageData = userService.findCustomerUsers(tenantId, customerId, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseWidgetsBundleServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseWidgetsBundleServiceTest.java index 2a4858f20c..c63c9b8c05 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseWidgetsBundleServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseWidgetsBundleServiceTest.java @@ -22,8 +22,8 @@ import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.model.ModelConstants; @@ -175,13 +175,13 @@ public abstract class BaseWidgetsBundleServiceTest extends AbstractServiceTest { widgetsBundles.addAll(systemWidgetsBundles); List loadedWidgetsBundles = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(19); - TextPageData pageData = null; + PageLink pageLink = new PageLink(19); + PageData pageData = null; do { pageData = widgetsBundleService.findSystemWidgetsBundlesByPageLink(tenantId, pageLink); loadedWidgetsBundles.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -255,13 +255,13 @@ public abstract class BaseWidgetsBundleServiceTest extends AbstractServiceTest { } List loadedWidgetsBundles = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(11); - TextPageData pageData = null; + PageLink pageLink = new PageLink(11); + PageData pageData = null; do { pageData = widgetsBundleService.findTenantWidgetsBundlesByTenantId(tenantId, pageLink); loadedWidgetsBundles.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -272,7 +272,7 @@ public abstract class BaseWidgetsBundleServiceTest extends AbstractServiceTest { widgetsBundleService.deleteWidgetsBundlesByTenantId(tenantId); - pageLink = new TextPageLink(15); + pageLink = new PageLink(15); pageData = widgetsBundleService.findTenantWidgetsBundlesByTenantId(tenantId, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertTrue(pageData.getData().isEmpty()); @@ -309,13 +309,13 @@ public abstract class BaseWidgetsBundleServiceTest extends AbstractServiceTest { widgetsBundles.addAll(systemWidgetsBundles); List loadedWidgetsBundles = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(17); - TextPageData pageData = null; + PageLink pageLink = new PageLink(17); + PageData pageData = null; do { pageData = widgetsBundleService.findAllTenantWidgetsBundlesByTenantIdAndPageLink(tenantId, pageLink); loadedWidgetsBundles.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -327,12 +327,12 @@ public abstract class BaseWidgetsBundleServiceTest extends AbstractServiceTest { widgetsBundleService.deleteWidgetsBundlesByTenantId(tenantId); loadedWidgetsBundles.clear(); - pageLink = new TextPageLink(14); + pageLink = new PageLink(14); do { pageData = widgetsBundleService.findAllTenantWidgetsBundlesByTenantIdAndPageLink(tenantId, pageLink); loadedWidgetsBundles.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); @@ -349,12 +349,12 @@ public abstract class BaseWidgetsBundleServiceTest extends AbstractServiceTest { } loadedWidgetsBundles.clear(); - pageLink = new TextPageLink(18); + pageLink = new PageLink(18); do { pageData = widgetsBundleService.findAllTenantWidgetsBundlesByTenantIdAndPageLink(tenantId, pageLink); loadedWidgetsBundles.addAll(pageData.getData()); if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); + pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/event/BaseEventServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/event/BaseEventServiceTest.java index edb616dba8..9b34ee5fb7 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/event/BaseEventServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/event/BaseEventServiceTest.java @@ -25,7 +25,8 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EventId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TimePageData; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.SortOrder; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.dao.service.AbstractServiceTest; @@ -74,23 +75,23 @@ public abstract class BaseEventServiceTest extends AbstractServiceTest { Event savedEvent3 = saveEventWithProvidedTime(eventTime+2, customerId, tenantId); saveEventWithProvidedTime(timeAfterEndTime, customerId, tenantId); - TimePageData events = eventService.findEvents(tenantId, customerId, DataConstants.STATS, - new TimePageLink(2, startTime, endTime, true)); + TimePageLink timePageLink = new TimePageLink(2, 0, "", new SortOrder("createdTime"), startTime, endTime); + + PageData events = eventService.findEvents(tenantId, customerId, DataConstants.STATS, + timePageLink); Assert.assertNotNull(events.getData()); Assert.assertTrue(events.getData().size() == 2); Assert.assertTrue(events.getData().get(0).getUuidId().equals(savedEvent.getUuidId())); Assert.assertTrue(events.getData().get(1).getUuidId().equals(savedEvent2.getUuidId())); Assert.assertTrue(events.hasNext()); - Assert.assertNotNull(events.getNextPageLink()); - events = eventService.findEvents(tenantId, customerId, DataConstants.STATS, events.getNextPageLink()); + events = eventService.findEvents(tenantId, customerId, DataConstants.STATS, timePageLink.nextPageLink()); Assert.assertNotNull(events.getData()); Assert.assertTrue(events.getData().size() == 1); Assert.assertTrue(events.getData().get(0).getUuidId().equals(savedEvent3.getUuidId())); Assert.assertFalse(events.hasNext()); - Assert.assertNull(events.getNextPageLink()); } @Test @@ -109,23 +110,23 @@ public abstract class BaseEventServiceTest extends AbstractServiceTest { Event savedEvent3 = saveEventWithProvidedTime(eventTime+2, customerId, tenantId); saveEventWithProvidedTime(timeAfterEndTime, customerId, tenantId); - TimePageData events = eventService.findEvents(tenantId, customerId, DataConstants.STATS, - new TimePageLink(2, startTime, endTime, false)); + TimePageLink timePageLink = new TimePageLink(2, 0, "", new SortOrder("createdTime", SortOrder.Direction.DESC), startTime, endTime); + + PageData events = eventService.findEvents(tenantId, customerId, DataConstants.STATS, + timePageLink); Assert.assertNotNull(events.getData()); Assert.assertTrue(events.getData().size() == 2); Assert.assertTrue(events.getData().get(0).getUuidId().equals(savedEvent3.getUuidId())); Assert.assertTrue(events.getData().get(1).getUuidId().equals(savedEvent2.getUuidId())); Assert.assertTrue(events.hasNext()); - Assert.assertNotNull(events.getNextPageLink()); - events = eventService.findEvents(tenantId, customerId, DataConstants.STATS, events.getNextPageLink()); + events = eventService.findEvents(tenantId, customerId, DataConstants.STATS, timePageLink.nextPageLink()); Assert.assertNotNull(events.getData()); Assert.assertTrue(events.getData().size() == 1); Assert.assertTrue(events.getData().get(0).getUuidId().equals(savedEvent.getUuidId())); Assert.assertFalse(events.hasNext()); - Assert.assertNull(events.getNextPageLink()); } private Event saveEventWithProvidedTime(long time, EntityId entityId, TenantId tenantId) throws IOException { diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/asset/JpaAssetDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/asset/JpaAssetDaoTest.java index 84390e7eef..efeaff52c8 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/sql/asset/JpaAssetDaoTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/sql/asset/JpaAssetDaoTest.java @@ -24,7 +24,8 @@ import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.AbstractJpaDaoTest; import org.thingsboard.server.dao.asset.AssetDao; @@ -61,17 +62,17 @@ public class JpaAssetDaoTest extends AbstractJpaDaoTest { } assertEquals(60, assetDao.find(new TenantId(tenantId1)).size()); - TextPageLink pageLink1 = new TextPageLink(20, "ASSET_"); - List assets1 = assetDao.findAssetsByTenantId(tenantId1, pageLink1); - assertEquals(20, assets1.size()); + PageLink pageLink = new PageLink(20, 0, "ASSET_"); + PageData assets1 = assetDao.findAssetsByTenantId(tenantId1, pageLink); + assertEquals(20, assets1.getData().size()); - TextPageLink pageLink2 = new TextPageLink(20, "ASSET_", assets1.get(19).getId().getId(), null); - List assets2 = assetDao.findAssetsByTenantId(tenantId1, pageLink2); - assertEquals(10, assets2.size()); + pageLink = pageLink.nextPageLink(); + PageData assets2 = assetDao.findAssetsByTenantId(tenantId1, pageLink); + assertEquals(10, assets2.getData().size()); - TextPageLink pageLink3 = new TextPageLink(20, "ASSET_", assets2.get(9).getId().getId(), null); - List assets3 = assetDao.findAssetsByTenantId(tenantId1, pageLink3); - assertEquals(0, assets3.size()); + pageLink = pageLink.nextPageLink(); + PageData assets3 = assetDao.findAssetsByTenantId(tenantId1, pageLink); + assertEquals(0, assets3.getData().size()); } @Test @@ -87,17 +88,17 @@ public class JpaAssetDaoTest extends AbstractJpaDaoTest { saveAsset(assetId, tenantId, customerId, "ASSET_" + i, "TYPE_1"); } - TextPageLink pageLink1 = new TextPageLink(20, "ASSET_"); - List assets1 = assetDao.findAssetsByTenantIdAndCustomerId(tenantId1, customerId1, pageLink1); - assertEquals(20, assets1.size()); + PageLink pageLink = new PageLink(20, 0, "ASSET_"); + PageData assets1 = assetDao.findAssetsByTenantIdAndCustomerId(tenantId1, customerId1, pageLink); + assertEquals(20, assets1.getData().size()); - TextPageLink pageLink2 = new TextPageLink(20, "ASSET_", assets1.get(19).getId().getId(), null); - List assets2 = assetDao.findAssetsByTenantIdAndCustomerId(tenantId1, customerId1, pageLink2); - assertEquals(10, assets2.size()); + pageLink = pageLink.nextPageLink(); + PageData assets2 = assetDao.findAssetsByTenantIdAndCustomerId(tenantId1, customerId1, pageLink); + assertEquals(10, assets2.getData().size()); - TextPageLink pageLink3 = new TextPageLink(20, "ASSET_", assets2.get(9).getId().getId(), null); - List assets3 = assetDao.findAssetsByTenantIdAndCustomerId(tenantId1, customerId1, pageLink3); - assertEquals(0, assets3.size()); + pageLink = pageLink.nextPageLink(); + PageData assets3 = assetDao.findAssetsByTenantIdAndCustomerId(tenantId1, customerId1, pageLink); + assertEquals(0, assets3.getData().size()); } @Test diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/component/JpaBaseComponentDescriptorDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/component/JpaBaseComponentDescriptorDaoTest.java index 36f2288304..e47d39c28b 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/sql/component/JpaBaseComponentDescriptorDaoTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/sql/component/JpaBaseComponentDescriptorDaoTest.java @@ -19,7 +19,8 @@ import com.datastax.driver.core.utils.UUIDs; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.common.data.id.ComponentDescriptorId; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.plugin.ComponentDescriptor; import org.thingsboard.server.common.data.plugin.ComponentScope; import org.thingsboard.server.common.data.plugin.ComponentType; @@ -46,13 +47,13 @@ public class JpaBaseComponentDescriptorDaoTest extends AbstractJpaDaoTest { createComponentDescriptor(ComponentType.ACTION, ComponentScope.TENANT, i + 20); } - TextPageLink pageLink1 = new TextPageLink(15, "COMPONENT_"); - List components1 = componentDescriptorDao.findByTypeAndPageLink(AbstractServiceTest.SYSTEM_TENANT_ID, ComponentType.FILTER, pageLink1); - assertEquals(15, components1.size()); + PageLink pageLink = new PageLink(15, 0, "COMPONENT_"); + PageData components1 = componentDescriptorDao.findByTypeAndPageLink(AbstractServiceTest.SYSTEM_TENANT_ID, ComponentType.FILTER, pageLink); + assertEquals(15, components1.getData().size()); - TextPageLink pageLink2 = new TextPageLink(15, "COMPONENT_", components1.get(14).getId().getId(), null); - List components2 = componentDescriptorDao.findByTypeAndPageLink(AbstractServiceTest.SYSTEM_TENANT_ID,ComponentType.FILTER, pageLink2); - assertEquals(5, components2.size()); + pageLink = pageLink.nextPageLink(); + PageData components2 = componentDescriptorDao.findByTypeAndPageLink(AbstractServiceTest.SYSTEM_TENANT_ID,ComponentType.FILTER, pageLink); + assertEquals(5, components2.getData().size()); } @Test @@ -63,15 +64,15 @@ public class JpaBaseComponentDescriptorDaoTest extends AbstractJpaDaoTest { createComponentDescriptor(ComponentType.FILTER, ComponentScope.SYSTEM, i + 40); } - TextPageLink pageLink1 = new TextPageLink(15, "COMPONENT_"); - List components1 = componentDescriptorDao.findByScopeAndTypeAndPageLink(AbstractServiceTest.SYSTEM_TENANT_ID, - ComponentScope.SYSTEM, ComponentType.FILTER, pageLink1); - assertEquals(15, components1.size()); + PageLink pageLink = new PageLink(15, 0, "COMPONENT_"); + PageData components1 = componentDescriptorDao.findByScopeAndTypeAndPageLink(AbstractServiceTest.SYSTEM_TENANT_ID, + ComponentScope.SYSTEM, ComponentType.FILTER, pageLink); + assertEquals(15, components1.getData().size()); - TextPageLink pageLink2 = new TextPageLink(15, "COMPONENT_", components1.get(14).getId().getId(), null); - List components2 = componentDescriptorDao.findByScopeAndTypeAndPageLink(AbstractServiceTest.SYSTEM_TENANT_ID, - ComponentScope.SYSTEM, ComponentType.FILTER, pageLink2); - assertEquals(5, components2.size()); + pageLink = pageLink.nextPageLink(); + PageData components2 = componentDescriptorDao.findByScopeAndTypeAndPageLink(AbstractServiceTest.SYSTEM_TENANT_ID, + ComponentScope.SYSTEM, ComponentType.FILTER, pageLink); + assertEquals(5, components2.getData().size()); } private void createComponentDescriptor(ComponentType type, ComponentScope scope, int index) { diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/customer/JpaCustomerDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/customer/JpaCustomerDaoTest.java index dc8af1bc9a..3451a5c3be 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/sql/customer/JpaCustomerDaoTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/sql/customer/JpaCustomerDaoTest.java @@ -21,7 +21,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.AbstractJpaDaoTest; import org.thingsboard.server.dao.customer.CustomerDao; @@ -50,13 +51,13 @@ public class JpaCustomerDaoTest extends AbstractJpaDaoTest { createCustomer(tenantId2, i * 2); } - TextPageLink pageLink1 = new TextPageLink(15, "CUSTOMER"); - List customers1 = customerDao.findCustomersByTenantId(tenantId1, pageLink1); - assertEquals(15, customers1.size()); + PageLink pageLink = new PageLink(15, 0, "CUSTOMER"); + PageData customers1 = customerDao.findCustomersByTenantId(tenantId1, pageLink); + assertEquals(15, customers1.getData().size()); - TextPageLink pageLink2 = new TextPageLink(15, "CUSTOMER", customers1.get(14).getId().getId(), null); - List customers2 = customerDao.findCustomersByTenantId(tenantId1, pageLink2); - assertEquals(5, customers2.size()); + pageLink = pageLink.nextPageLink(); + PageData customers2 = customerDao.findCustomersByTenantId(tenantId1, pageLink); + assertEquals(5, customers2.getData().size()); } @Test diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDaoTest.java index 2837888202..8b28eb1af7 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDaoTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDaoTest.java @@ -22,7 +22,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.common.data.DashboardInfo; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.AbstractJpaDaoTest; import org.thingsboard.server.dao.dashboard.DashboardInfoDao; import org.thingsboard.server.dao.service.AbstractServiceTest; @@ -48,13 +49,12 @@ public class JpaDashboardInfoDaoTest extends AbstractJpaDaoTest { createDashboard(tenantId2, i * 2); } - TextPageLink pageLink1 = new TextPageLink(15, "DASHBOARD"); - List dashboardInfos1 = dashboardInfoDao.findDashboardsByTenantId(tenantId1, pageLink1); - Assert.assertEquals(15, dashboardInfos1.size()); + PageLink pageLink = new PageLink(15, 0, "DASHBOARD"); + PageData dashboardInfos1 = dashboardInfoDao.findDashboardsByTenantId(tenantId1, pageLink); + Assert.assertEquals(15, dashboardInfos1.getData().size()); - TextPageLink pageLink2 = new TextPageLink(15, "DASHBOARD", dashboardInfos1.get(14).getId().getId(), null); - List dashboardInfos2 = dashboardInfoDao.findDashboardsByTenantId(tenantId1, pageLink2); - Assert.assertEquals(5, dashboardInfos2.size()); + PageData dashboardInfos2 = dashboardInfoDao.findDashboardsByTenantId(tenantId1, pageLink.nextPageLink()); + Assert.assertEquals(5, dashboardInfos2.getData().size()); } private void createDashboard(UUID tenantId, int index) { diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/device/JpaDeviceDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/device/JpaDeviceDaoTest.java index f74415cefd..c94bc43af4 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/sql/device/JpaDeviceDaoTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/sql/device/JpaDeviceDaoTest.java @@ -25,7 +25,8 @@ import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.AbstractJpaDaoTest; import org.thingsboard.server.dao.device.DeviceDao; @@ -54,13 +55,14 @@ public class JpaDeviceDaoTest extends AbstractJpaDaoTest { UUID customerId2 = UUIDs.timeBased(); createDevices(tenantId1, tenantId2, customerId1, customerId2, 40); - TextPageLink pageLink1 = new TextPageLink(15, "SEARCH_TEXT"); - List devices1 = deviceDao.findDevicesByTenantId(tenantId1, pageLink1); - assertEquals(15, devices1.size()); + PageLink pageLink = new PageLink(15, 0, "SEARCH_TEXT"); + PageData devices1 = deviceDao.findDevicesByTenantId(tenantId1, pageLink); + assertEquals(15, devices1.getData().size()); - TextPageLink pageLink2 = new TextPageLink(15, "SEARCH_TEXT", devices1.get(14).getId().getId(), null); - List devices2 = deviceDao.findDevicesByTenantId(tenantId1, pageLink2); - assertEquals(5, devices2.size()); + pageLink = pageLink.nextPageLink(); + + PageData devices2 = deviceDao.findDevicesByTenantId(tenantId1, pageLink); + assertEquals(5, devices2.getData().size()); } @Test diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDaoTest.java index 443bfa1f1c..595d7f628c 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDaoTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDaoTest.java @@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EventId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.dao.AbstractJpaDaoTest; import org.thingsboard.server.dao.event.EventDao; @@ -88,26 +89,29 @@ public class JpaBaseEventDaoTest extends AbstractJpaDaoTest { long startTime = System.currentTimeMillis(); long endTime = createEventsTwoEntities(tenantId, entityId1, entityId2, startTime, 20); - TimePageLink pageLink1 = new TimePageLink(30, null, null, true); - List events1 = eventDao.findEvents(tenantId, new DeviceId(entityId1), pageLink1); - assertEquals(10, events1.size()); + TimePageLink pageLink1 = new TimePageLink(30); + PageData events1 = eventDao.findEvents(tenantId, new DeviceId(entityId1), pageLink1); + assertEquals(10, events1.getData().size()); - TimePageLink pageLink2 = new TimePageLink(30, startTime, null, true); - List events2 = eventDao.findEvents(tenantId, new DeviceId(entityId1), pageLink2); - assertEquals(10, events2.size()); + TimePageLink pageLink2 = new TimePageLink(30, 0, "", null, startTime, null); + PageData events2 = eventDao.findEvents(tenantId, new DeviceId(entityId1), pageLink2); + assertEquals(10, events2.getData().size()); - TimePageLink pageLink3 = new TimePageLink(30, startTime, endTime, true); - List events3 = eventDao.findEvents(tenantId, new DeviceId(entityId1), pageLink3); - assertEquals(10, events3.size()); + TimePageLink pageLink3 = new TimePageLink(30, 0, "", null, startTime, endTime); + PageData events3 = eventDao.findEvents(tenantId, new DeviceId(entityId1), pageLink3); + assertEquals(10, events3.getData().size()); - TimePageLink pageLink4 = new TimePageLink(5, startTime, endTime, true); - List events4 = eventDao.findEvents(tenantId, new DeviceId(entityId1), pageLink4); - assertEquals(5, events4.size()); + TimePageLink pageLink4 = new TimePageLink(5, 0, "", null, startTime, endTime); + PageData events4 = eventDao.findEvents(tenantId, new DeviceId(entityId1), pageLink4); + assertEquals(5, events4.getData().size()); - UUID idOffset = events4.get(4).getId().getId(); - TimePageLink pageLink5 = new TimePageLink(10, startTime, endTime, true, idOffset); - List events5 = eventDao.findEvents(tenantId, new DeviceId(entityId1), pageLink5); - assertEquals(5, events5.size()); + pageLink4 = pageLink4.nextPageLink(); + PageData events5 = eventDao.findEvents(tenantId, new DeviceId(entityId1), pageLink4); + assertEquals(5, events5.getData().size()); + + pageLink4 = pageLink4.nextPageLink(); + PageData events6 = eventDao.findEvents(tenantId, new DeviceId(entityId1), pageLink4); + assertEquals(0, events6.getData().size()); } @@ -119,26 +123,25 @@ public class JpaBaseEventDaoTest extends AbstractJpaDaoTest { long startTime = System.currentTimeMillis(); long endTime = createEventsTwoEntitiesTwoTypes(tenantId, entityId1, entityId2, startTime, 20); - TimePageLink pageLink1 = new TimePageLink(30, null, null, true); - List events1 = eventDao.findEvents(tenantId, new DeviceId(entityId1), ALARM, pageLink1); - assertEquals(5, events1.size()); + TimePageLink pageLink1 = new TimePageLink(30); + PageData events1 = eventDao.findEvents(tenantId, new DeviceId(entityId1), ALARM, pageLink1); + assertEquals(5, events1.getData().size()); - TimePageLink pageLink2 = new TimePageLink(30, startTime, null, true); - List events2 = eventDao.findEvents(tenantId, new DeviceId(entityId1), ALARM, pageLink2); - assertEquals(5, events2.size()); + TimePageLink pageLink2 = new TimePageLink(30, 0, "", null, startTime, null); + PageData events2 = eventDao.findEvents(tenantId, new DeviceId(entityId1), ALARM, pageLink2); + assertEquals(5, events2.getData().size()); - TimePageLink pageLink3 = new TimePageLink(30, startTime, endTime, true); - List events3 = eventDao.findEvents(tenantId, new DeviceId(entityId1), ALARM, pageLink3); - assertEquals(5, events3.size()); + TimePageLink pageLink3 = new TimePageLink(30, 0, "", null, startTime, endTime); + PageData events3 = eventDao.findEvents(tenantId, new DeviceId(entityId1), ALARM, pageLink3); + assertEquals(5, events3.getData().size()); - TimePageLink pageLink4 = new TimePageLink(4, startTime, endTime, true); - List events4 = eventDao.findEvents(tenantId, new DeviceId(entityId1), ALARM, pageLink4); - assertEquals(4, events4.size()); + TimePageLink pageLink4 = new TimePageLink(4, 0, "", null, startTime, endTime); + PageData events4 = eventDao.findEvents(tenantId, new DeviceId(entityId1), ALARM, pageLink4); + assertEquals(4, events4.getData().size()); - UUID idOffset = events3.get(2).getId().getId(); - TimePageLink pageLink5 = new TimePageLink(10, startTime, endTime, true, idOffset); - List events5 = eventDao.findEvents(tenantId, new DeviceId(entityId1), ALARM, pageLink5); - assertEquals(2, events5.size()); + pageLink4 = pageLink4.nextPageLink(); + PageData events5 = eventDao.findEvents(tenantId, new DeviceId(entityId1), ALARM, pageLink4); + assertEquals(2, events5.getData().size()); } private long createEventsTwoEntitiesTwoTypes(UUID tenantId, UUID entityId1, UUID entityId2, long startTime, int count) { diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/tenant/JpaTenantDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/tenant/JpaTenantDaoTest.java index 30c98af8e7..3cadf5cd95 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/sql/tenant/JpaTenantDaoTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/sql/tenant/JpaTenantDaoTest.java @@ -21,7 +21,8 @@ import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.AbstractJpaDaoTest; import org.thingsboard.server.dao.service.AbstractServiceTest; import org.thingsboard.server.dao.tenant.TenantDao; @@ -43,14 +44,20 @@ public class JpaTenantDaoTest extends AbstractJpaDaoTest { public void testFindTenantsByRegion() { createTenants(); assertEquals(60, tenantDao.find(AbstractServiceTest.SYSTEM_TENANT_ID).size()); - List tenants1 = tenantDao.findTenantsByRegion(AbstractServiceTest.SYSTEM_TENANT_ID, "REGION_1", new TextPageLink(20,"title")); - assertEquals(20, tenants1.size()); - List tenants2 = tenantDao.findTenantsByRegion(AbstractServiceTest.SYSTEM_TENANT_ID,"REGION_1", - new TextPageLink(20,"title", tenants1.get(19).getId().getId(), null)); - assertEquals(10, tenants2.size()); - List tenants3 = tenantDao.findTenantsByRegion(AbstractServiceTest.SYSTEM_TENANT_ID,"REGION_1", - new TextPageLink(20,"title", tenants2.get(9).getId().getId(), null)); - assertEquals(0, tenants3.size()); + + PageLink pageLink = new PageLink(20, 0, "title"); + PageData tenants1 = tenantDao.findTenantsByRegion(AbstractServiceTest.SYSTEM_TENANT_ID, "REGION_1", pageLink); + assertEquals(20, tenants1.getData().size()); + + pageLink = pageLink.nextPageLink(); + PageData tenants2 = tenantDao.findTenantsByRegion(AbstractServiceTest.SYSTEM_TENANT_ID,"REGION_1", + pageLink); + assertEquals(10, tenants2.getData().size()); + + pageLink = pageLink.nextPageLink(); + PageData tenants3 = tenantDao.findTenantsByRegion(AbstractServiceTest.SYSTEM_TENANT_ID,"REGION_1", + pageLink); + assertEquals(0, tenants3.getData().size()); } private void createTenants() { diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/user/JpaUserDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/user/JpaUserDaoTest.java index 8275b76508..7eed49f7ff 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/sql/user/JpaUserDaoTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/sql/user/JpaUserDaoTest.java @@ -25,7 +25,8 @@ import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.common.data.page.TextPageLink; +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.dao.AbstractJpaDaoTest; import org.thingsboard.server.dao.service.AbstractServiceTest; @@ -73,14 +74,17 @@ public class JpaUserDaoTest extends AbstractJpaDaoTest { UUID tenantId = UUIDs.timeBased(); UUID customerId = UUIDs.timeBased(); create30Adminsand60Users(tenantId, customerId); - List tenantAdmins1 = userDao.findTenantAdmins(tenantId, new TextPageLink(20)); - assertEquals(20, tenantAdmins1.size()); - List tenantAdmins2 = userDao.findTenantAdmins(tenantId, - new TextPageLink(20, null, tenantAdmins1.get(19).getId().getId(), null)); - assertEquals(10, tenantAdmins2.size()); - List tenantAdmins3 = userDao.findTenantAdmins(tenantId, - new TextPageLink(20, null, tenantAdmins2.get(9).getId().getId(), null)); - assertEquals(0, tenantAdmins3.size()); + PageLink pageLink = new PageLink(20); + PageData tenantAdmins1 = userDao.findTenantAdmins(tenantId, pageLink); + assertEquals(20, tenantAdmins1.getData().size()); + pageLink = pageLink.nextPageLink(); + PageData tenantAdmins2 = userDao.findTenantAdmins(tenantId, + pageLink); + assertEquals(10, tenantAdmins2.getData().size()); + pageLink = pageLink.nextPageLink(); + PageData tenantAdmins3 = userDao.findTenantAdmins(tenantId, + pageLink); + assertEquals(0, tenantAdmins3.getData().size()); } @Test @@ -89,14 +93,17 @@ public class JpaUserDaoTest extends AbstractJpaDaoTest { UUID tenantId = UUIDs.timeBased(); UUID customerId = UUIDs.timeBased(); create30Adminsand60Users(tenantId, customerId); - List customerUsers1 = userDao.findCustomerUsers(tenantId, customerId, new TextPageLink(40)); - assertEquals(40, customerUsers1.size()); - List customerUsers2 = userDao.findCustomerUsers(tenantId, customerId, - new TextPageLink(20, null, customerUsers1.get(39).getId().getId(), null)); - assertEquals(20, customerUsers2.size()); - List customerUsers3 = userDao.findCustomerUsers(tenantId, customerId, - new TextPageLink(20, null, customerUsers2.get(19).getId().getId(), null)); - assertEquals(0, customerUsers3.size()); + PageLink pageLink = new PageLink(40); + PageData customerUsers1 = userDao.findCustomerUsers(tenantId, customerId, pageLink); + assertEquals(40, customerUsers1.getData().size()); + pageLink = pageLink.nextPageLink(); + PageData customerUsers2 = userDao.findCustomerUsers(tenantId, customerId, + pageLink); + assertEquals(20, customerUsers2.getData().size()); + pageLink = pageLink.nextPageLink(); + PageData customerUsers3 = userDao.findCustomerUsers(tenantId, customerId, + pageLink); + assertEquals(0, customerUsers3.getData().size()); } @Test diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/widget/JpaWidgetsBundleDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/widget/JpaWidgetsBundleDaoTest.java index 9f8292f706..db0f1b5997 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/sql/widget/JpaWidgetsBundleDaoTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/sql/widget/JpaWidgetsBundleDaoTest.java @@ -23,7 +23,8 @@ import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.WidgetsBundleId; -import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.dao.AbstractJpaDaoTest; import org.thingsboard.server.dao.service.AbstractServiceTest; @@ -65,13 +66,13 @@ public class JpaWidgetsBundleDaoTest extends AbstractJpaDaoTest { createSystemWidgetBundles(30, "WB_"); assertEquals(30, widgetsBundleDao.find(AbstractServiceTest.SYSTEM_TENANT_ID).size()); // Get first page - TextPageLink textPageLink1 = new TextPageLink(10, "WB"); - List widgetsBundles1 = widgetsBundleDao.findSystemWidgetsBundles(AbstractServiceTest.SYSTEM_TENANT_ID, textPageLink1); - assertEquals(10, widgetsBundles1.size()); + PageLink pageLink = new PageLink(10, 0, "WB"); + PageData widgetsBundles1 = widgetsBundleDao.findSystemWidgetsBundles(AbstractServiceTest.SYSTEM_TENANT_ID, pageLink); + assertEquals(10, widgetsBundles1.getData().size()); // Get next page - TextPageLink textPageLink2 = new TextPageLink(10, "WB", widgetsBundles1.get(9).getId().getId(), null); - List widgetsBundles2 = widgetsBundleDao.findSystemWidgetsBundles(AbstractServiceTest.SYSTEM_TENANT_ID, textPageLink2); - assertEquals(10, widgetsBundles2.size()); + pageLink = pageLink.nextPageLink(); + PageData widgetsBundles2 = widgetsBundleDao.findSystemWidgetsBundles(AbstractServiceTest.SYSTEM_TENANT_ID, pageLink); + assertEquals(10, widgetsBundles2.getData().size()); } @Test @@ -87,18 +88,17 @@ public class JpaWidgetsBundleDaoTest extends AbstractJpaDaoTest { } assertEquals(180, widgetsBundleDao.find(AbstractServiceTest.SYSTEM_TENANT_ID).size()); - TextPageLink textPageLink1 = new TextPageLink(40, "WB"); - List widgetsBundles1 = widgetsBundleDao.findTenantWidgetsBundlesByTenantId(tenantId1, textPageLink1); - assertEquals(30, widgetsBundles1.size()); + PageLink pageLink1 = new PageLink(40, 0, "WB"); + PageData widgetsBundles1 = widgetsBundleDao.findTenantWidgetsBundlesByTenantId(tenantId1, pageLink1); + assertEquals(30, widgetsBundles1.getData().size()); - TextPageLink textPageLink2 = new TextPageLink(40, "WB"); - List widgetsBundles2 = widgetsBundleDao.findTenantWidgetsBundlesByTenantId(tenantId2, textPageLink2); - assertEquals(40, widgetsBundles2.size()); + PageLink pageLink2 = new PageLink(40, 0, "WB"); + PageData widgetsBundles2 = widgetsBundleDao.findTenantWidgetsBundlesByTenantId(tenantId2, pageLink2); + assertEquals(40, widgetsBundles2.getData().size()); - TextPageLink textPageLink3 = new TextPageLink(40, "WB", - widgetsBundles2.get(39).getId().getId(), null); - List widgetsBundles3 = widgetsBundleDao.findTenantWidgetsBundlesByTenantId(tenantId2, textPageLink3); - assertEquals(10, widgetsBundles3.size()); + pageLink2 = pageLink2.nextPageLink(); + PageData widgetsBundles3 = widgetsBundleDao.findTenantWidgetsBundlesByTenantId(tenantId2, pageLink2); + assertEquals(10, widgetsBundles3.getData().size()); } @Test @@ -113,25 +113,21 @@ public class JpaWidgetsBundleDaoTest extends AbstractJpaDaoTest { createSystemWidgetBundles(2, "WB_SYS_"); } - TextPageLink textPageLink1 = new TextPageLink(30, "WB"); - List widgetsBundles1 = widgetsBundleDao.findAllTenantWidgetsBundlesByTenantId(tenantId1, textPageLink1); - assertEquals(30, widgetsBundles1.size()); + PageLink pageLink = new PageLink(30, 0, "WB"); + PageData widgetsBundles1 = widgetsBundleDao.findAllTenantWidgetsBundlesByTenantId(tenantId1, pageLink); + assertEquals(30, widgetsBundles1.getData().size()); - TextPageLink textPageLink2 = new TextPageLink(30, "WB", - widgetsBundles1.get(29).getId().getId(), null); - List widgetsBundles2 = widgetsBundleDao.findAllTenantWidgetsBundlesByTenantId(tenantId1, textPageLink2); + pageLink = pageLink.nextPageLink(); + PageData widgetsBundles2 = widgetsBundleDao.findAllTenantWidgetsBundlesByTenantId(tenantId1, pageLink); + assertEquals(30, widgetsBundles2.getData().size()); - assertEquals(30, widgetsBundles2.size()); + pageLink = pageLink.nextPageLink(); + PageData widgetsBundles3 = widgetsBundleDao.findAllTenantWidgetsBundlesByTenantId(tenantId1, pageLink); + assertEquals(10, widgetsBundles3.getData().size()); - TextPageLink textPageLink3 = new TextPageLink(30, "WB", - widgetsBundles2.get(29).getId().getId(), null); - List widgetsBundles3 = widgetsBundleDao.findAllTenantWidgetsBundlesByTenantId(tenantId1, textPageLink3); - assertEquals(10, widgetsBundles3.size()); - - TextPageLink textPageLink4 = new TextPageLink(30, "WB", - widgetsBundles3.get(9).getId().getId(), null); - List widgetsBundles4 = widgetsBundleDao.findAllTenantWidgetsBundlesByTenantId(tenantId1, textPageLink4); - assertEquals(0, widgetsBundles4.size()); + pageLink = pageLink.nextPageLink(); + PageData widgetsBundles4 = widgetsBundleDao.findAllTenantWidgetsBundlesByTenantId(tenantId1, pageLink); + assertEquals(0, widgetsBundles4.getData().size()); } @Test @@ -142,9 +138,9 @@ public class JpaWidgetsBundleDaoTest extends AbstractJpaDaoTest { createWidgetBundles(5, tenantId, "ABC_"); createSystemWidgetBundles(5, "SYS_"); - TextPageLink textPageLink = new TextPageLink(30, "TEXT_NOT_FOUND"); - List widgetsBundles4 = widgetsBundleDao.findAllTenantWidgetsBundlesByTenantId(tenantId, textPageLink); - assertEquals(0, widgetsBundles4.size()); + PageLink textPageLink = new PageLink(30, 0, "TEXT_NOT_FOUND"); + PageData widgetsBundles4 = widgetsBundleDao.findAllTenantWidgetsBundlesByTenantId(tenantId, textPageLink); + assertEquals(0, widgetsBundles4.getData().size()); } private void createWidgetBundles(int count, UUID tenantId, String prefix) { diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java index 997bc1a789..8dd09114db 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java @@ -39,7 +39,7 @@ import org.thingsboard.mqtt.MqttClientConfig; import org.thingsboard.mqtt.MqttHandler; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.id.RuleChainId; -import org.thingsboard.server.common.data.page.TextPageData; +import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.rule.NodeConnectionInfo; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; @@ -372,11 +372,11 @@ public class MqttClientTest extends AbstractContainerTest { } private RuleChainId getDefaultRuleChainId() { - ResponseEntity> ruleChains = restClient.getRestTemplate().exchange( - HTTPS_URL + "/api/ruleChains?limit=40&textSearch=", + ResponseEntity> ruleChains = restClient.getRestTemplate().exchange( + HTTPS_URL + "/api/ruleChains?pageSize=40&page=0&textSearch=", HttpMethod.GET, null, - new ParameterizedTypeReference>() { + new ParameterizedTypeReference>() { }); Optional defaultRuleChain = ruleChains.getBody().getData() 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 5bd9be9b67..b2fb76fa25 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 @@ -38,8 +38,8 @@ import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; +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.EntitySearchDirection; import org.thingsboard.server.common.data.relation.RelationTypeGroup; @@ -231,7 +231,7 @@ public abstract class TbAbstractRelationActionNode dashboardInfoTextPageData = dashboardService.findDashboardsByTenantId(ctx.getTenantId(), new TextPageLink(200, entitykey.getEntityName())); + PageData dashboardInfoTextPageData = dashboardService.findDashboardsByTenantId(ctx.getTenantId(), new PageLink(200, 0, entitykey.getEntityName())); for (DashboardInfo dashboardInfo : dashboardInfoTextPageData.getData()) { if (dashboardInfo.getTitle().equals(entitykey.getEntityName())) { targetEntity.setEntityId(dashboardInfo.getId()); From fd02c9b96f71d8c2fa9324d2901fffa497b34e33 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 8 Aug 2019 19:39:06 +0300 Subject: [PATCH 004/133] ThingsBoard UI - Angular 8 - Initial structure. --- application/pom.xml | 2 +- msa/web-ui/pom.xml | 4 +- pom.xml | 3 +- ui-ngx/.editorconfig | 29 + ui-ngx/.gitignore | 46 + ui-ngx/angular.json | 167 + ui-ngx/e2e/protractor.conf.js | 43 + ui-ngx/e2e/src/app.e2e-spec.ts | 39 + ui-ngx/e2e/src/app.po.ts | 27 + ui-ngx/e2e/tsconfig.e2e.json | 13 + ui-ngx/extra-webpack.config.js | 43 + ui-ngx/package-lock.json | 11112 ++++++++++++++++ ui-ngx/package.json | 74 + ui-ngx/pom.xml | 141 + ui-ngx/proxy.conf.json | 6 + ui-ngx/src/app/app.module.ts | 43 + ui-ngx/src/assets/.gitkeep | 0 ui-ngx/src/assets/jstree/32px.png | Bin 0 -> 19444 bytes ui-ngx/src/assets/jstree/40px.png | Bin 0 -> 1880 bytes .../assets/locale/locale.constant-cs_CZ.json | 1656 +++ .../assets/locale/locale.constant-de_DE.json | 1647 +++ .../assets/locale/locale.constant-en_US.json | 1720 +++ .../assets/locale/locale.constant-es_ES.json | 1720 +++ .../assets/locale/locale.constant-fa_IR.json | 1643 +++ .../assets/locale/locale.constant-fr_FR.json | 1533 +++ .../assets/locale/locale.constant-it_IT.json | 1655 +++ .../assets/locale/locale.constant-ja_JA.json | 1527 +++ .../assets/locale/locale.constant-ko_KR.json | 1403 ++ .../assets/locale/locale.constant-ru_RU.json | 1649 +++ .../assets/locale/locale.constant-tr_TR.json | 1609 +++ .../assets/locale/locale.constant-uk_UA.json | 2190 +++ .../assets/locale/locale.constant-zh_CN.json | 1620 +++ ui-ngx/src/assets/logo_title_white.svg | 37 + ui-ngx/src/assets/logo_white.svg | 10 + ui-ngx/src/assets/mdi.svg | 1 + ui-ngx/src/browserslist | 11 + ui-ngx/src/environments/environment.prod.ts | 25 + ui-ngx/src/environments/environment.ts | 38 + ui-ngx/src/index.html | 31 + ui-ngx/src/karma.conf.js | 47 + ui-ngx/src/main.ts | 30 + ui-ngx/src/polyfills.ts | 79 + ui-ngx/src/scss/animations.scss | 48 + ui-ngx/src/scss/constants.scss | 29 + ui-ngx/src/styles.scss | 314 + ui-ngx/src/test.ts | 36 + ui-ngx/src/theme.scss | 557 + ui-ngx/src/thingsboard.ico | Bin 0 -> 4286 bytes ui-ngx/src/tsconfig.app.json | 11 + ui-ngx/src/tsconfig.spec.json | 18 + ui-ngx/src/tslint.json | 17 + ui-ngx/tsconfig.json | 31 + ui-ngx/tslint.json | 75 + 53 files changed, 34805 insertions(+), 4 deletions(-) create mode 100644 ui-ngx/.editorconfig create mode 100644 ui-ngx/.gitignore create mode 100644 ui-ngx/angular.json create mode 100644 ui-ngx/e2e/protractor.conf.js create mode 100644 ui-ngx/e2e/src/app.e2e-spec.ts create mode 100644 ui-ngx/e2e/src/app.po.ts create mode 100644 ui-ngx/e2e/tsconfig.e2e.json create mode 100644 ui-ngx/extra-webpack.config.js create mode 100644 ui-ngx/package-lock.json create mode 100644 ui-ngx/package.json create mode 100644 ui-ngx/pom.xml create mode 100644 ui-ngx/proxy.conf.json create mode 100644 ui-ngx/src/app/app.module.ts create mode 100644 ui-ngx/src/assets/.gitkeep create mode 100644 ui-ngx/src/assets/jstree/32px.png create mode 100644 ui-ngx/src/assets/jstree/40px.png create mode 100644 ui-ngx/src/assets/locale/locale.constant-cs_CZ.json create mode 100644 ui-ngx/src/assets/locale/locale.constant-de_DE.json create mode 100644 ui-ngx/src/assets/locale/locale.constant-en_US.json create mode 100644 ui-ngx/src/assets/locale/locale.constant-es_ES.json create mode 100644 ui-ngx/src/assets/locale/locale.constant-fa_IR.json create mode 100644 ui-ngx/src/assets/locale/locale.constant-fr_FR.json create mode 100644 ui-ngx/src/assets/locale/locale.constant-it_IT.json create mode 100644 ui-ngx/src/assets/locale/locale.constant-ja_JA.json create mode 100644 ui-ngx/src/assets/locale/locale.constant-ko_KR.json create mode 100644 ui-ngx/src/assets/locale/locale.constant-ru_RU.json create mode 100644 ui-ngx/src/assets/locale/locale.constant-tr_TR.json create mode 100644 ui-ngx/src/assets/locale/locale.constant-uk_UA.json create mode 100644 ui-ngx/src/assets/locale/locale.constant-zh_CN.json create mode 100644 ui-ngx/src/assets/logo_title_white.svg create mode 100644 ui-ngx/src/assets/logo_white.svg create mode 100644 ui-ngx/src/assets/mdi.svg create mode 100644 ui-ngx/src/browserslist create mode 100644 ui-ngx/src/environments/environment.prod.ts create mode 100644 ui-ngx/src/environments/environment.ts create mode 100644 ui-ngx/src/index.html create mode 100644 ui-ngx/src/karma.conf.js create mode 100644 ui-ngx/src/main.ts create mode 100644 ui-ngx/src/polyfills.ts create mode 100644 ui-ngx/src/scss/animations.scss create mode 100644 ui-ngx/src/scss/constants.scss create mode 100644 ui-ngx/src/styles.scss create mode 100644 ui-ngx/src/test.ts create mode 100644 ui-ngx/src/theme.scss create mode 100644 ui-ngx/src/thingsboard.ico create mode 100644 ui-ngx/src/tsconfig.app.json create mode 100644 ui-ngx/src/tsconfig.spec.json create mode 100644 ui-ngx/src/tslint.json create mode 100644 ui-ngx/tsconfig.json create mode 100644 ui-ngx/tslint.json diff --git a/application/pom.xml b/application/pom.xml index 23f2554ad4..24e8d681f7 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -116,7 +116,7 @@ org.thingsboard - ui + ui-ngx ${project.version} runtime diff --git a/msa/web-ui/pom.xml b/msa/web-ui/pom.xml index 43cf3a0e88..73fa4f922f 100644 --- a/msa/web-ui/pom.xml +++ b/msa/web-ui/pom.xml @@ -46,7 +46,7 @@ org.thingsboard - ui + ui-ngx ${project.version} jar provided @@ -105,7 +105,7 @@ org.thingsboard - ui + ui-ngx jar false ${project.build.directory}/web diff --git a/pom.xml b/pom.xml index 82c91deaf4..20a2e13531 100755 --- a/pom.xml +++ b/pom.xml @@ -100,10 +100,11 @@ rule-engine dao transport - ui + ui-ngx tools application msa + ui diff --git a/ui-ngx/.editorconfig b/ui-ngx/.editorconfig new file mode 100644 index 0000000000..af49295ee0 --- /dev/null +++ b/ui-ngx/.editorconfig @@ -0,0 +1,29 @@ +# +# Copyright © 2016-2019 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. +# + +# Editor configuration, see https://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/ui-ngx/.gitignore b/ui-ngx/.gitignore new file mode 100644 index 0000000000..f4f46a5fee --- /dev/null +++ b/ui-ngx/.gitignore @@ -0,0 +1,46 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# compiled output +/dist +/tmp +/out-tsc +# Only exists if Bazel was run +/bazel-out + +# dependencies +/node_modules + +# profiling files +chrome-profiler-events.json +speed-measure-plugin.json + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history/* + +# misc +/.sass-cache +/connect.lock +/coverage +/libpeerconnection.log +npm-debug.log +yarn-error.log +testem.log +/typings + +# System Files +.DS_Store +Thumbs.db diff --git a/ui-ngx/angular.json b/ui-ngx/angular.json new file mode 100644 index 0000000000..ac6c5d2f4e --- /dev/null +++ b/ui-ngx/angular.json @@ -0,0 +1,167 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "thingsboard": { + "root": "", + "sourceRoot": "src", + "projectType": "application", + "prefix": "tb", + "schematics": { + "@schematics/angular:component": { + "style": "scss" + } + }, + "architect": { + "build": { + "builder": "@angular-builders/custom-webpack:browser", + "options": { + "outputPath": "target/generated-resources/public", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "src/tsconfig.app.json", + "assets": [ + "src/thingsboard.ico", + "src/assets" + ], + "styles": [ + "src/styles.scss" + ], + "scripts": [ + "node_modules/ace-builds/src-min/ace.js", + "node_modules/ace-builds/src-min/ext-language_tools.js", + "node_modules/ace-builds/src-min/ext-searchbox.js", + "node_modules/ace-builds/src-min/theme-github.js", + "node_modules/ace-builds/src-min/mode-text.js", + "node_modules/ace-builds/src-min/mode-markdown.js", + "node_modules/ace-builds/src-min/mode-html.js", + "node_modules/ace-builds/src-min/mode-css.js", + "node_modules/ace-builds/src-min/mode-json.js", + "node_modules/ace-builds/src-min/mode-java.js", + "node_modules/ace-builds/src-min/mode-javascript.js", + "node_modules/ace-builds/src-min/snippets/text.js", + "node_modules/ace-builds/src-min/snippets/markdown.js", + "node_modules/ace-builds/src-min/snippets/html.js", + "node_modules/ace-builds/src-min/snippets/css.js", + "node_modules/ace-builds/src-min/snippets/json.js", + "node_modules/ace-builds/src-min/snippets/java.js", + "node_modules/ace-builds/src-min/snippets/javascript.js", + { "bundleName": "worker-html", "input": "node_modules/ace-builds/src-min/worker-html.js" }, + { "bundleName": "worker-css", "input": "node_modules/ace-builds/src-min/worker-css.js" }, + { "bundleName": "worker-json", "input": "node_modules/ace-builds/src-min/worker-json.js" }, + { "bundleName": "worker-javascript", "input": "node_modules/ace-builds/src-min/worker-javascript.js" } + ], + "es5BrowserSupport": true, + "customWebpackConfig": { + "path": "./extra-webpack.config.js" + } + }, + "configurations": { + "production": { + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + } + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "extractCss": true, + "namedChunks": false, + "aot": true, + "extractLicenses": true, + "vendorChunk": false, + "buildOptimizer": true, + "budgets": [ + { + "type": "initial", + "maximumWarning": "2mb", + "maximumError": "5mb" + } + ] + } + } + }, + "serve": { + "builder": "@angular-builders/dev-server:generic", + "options": { + "browserTarget": "thingsboard:build", + "proxyConfig": "proxy.conf.json" + }, + "configurations": { + "production": { + "browserTarget": "thingsboard:build:production" + } + } + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "thingsboard:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "src/tsconfig.spec.json", + "karmaConfig": "src/karma.conf.js", + "styles": [ + "src/styles.scss" + ], + "scripts": [], + "assets": [ + "src/favicon.ico", + "src/assets" + ] + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": [ + "src/tsconfig.app.json", + "src/tsconfig.spec.json" + ], + "exclude": [ + "**/node_modules/**" + ] + } + } + } + }, + "thingsboard-e2e": { + "root": "e2e/", + "projectType": "application", + "prefix": "", + "architect": { + "e2e": { + "builder": "@angular-devkit/build-angular:protractor", + "options": { + "protractorConfig": "e2e/protractor.conf.js", + "devServerTarget": "thingsboard:serve" + }, + "configurations": { + "production": { + "devServerTarget": "thingsboard:serve:production" + } + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": "e2e/tsconfig.e2e.json", + "exclude": [ + "**/node_modules/**" + ] + } + } + } + } + }, + "defaultProject": "thingsboard" +} diff --git a/ui-ngx/e2e/protractor.conf.js b/ui-ngx/e2e/protractor.conf.js new file mode 100644 index 0000000000..ad71bd0447 --- /dev/null +++ b/ui-ngx/e2e/protractor.conf.js @@ -0,0 +1,43 @@ +/* + * Copyright © 2016-2019 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. + */ +// Protractor configuration file, see link for more information +// https://github.com/angular/protractor/blob/master/lib/config.ts + +const { SpecReporter } = require('jasmine-spec-reporter'); + +exports.config = { + allScriptsTimeout: 11000, + specs: [ + './src/**/*.e2e-spec.ts' + ], + capabilities: { + 'browserName': 'chrome' + }, + directConnect: true, + baseUrl: 'http://localhost:4200/', + framework: 'jasmine', + jasmineNodeOpts: { + showColors: true, + defaultTimeoutInterval: 30000, + print: function() {} + }, + onPrepare() { + require('ts-node').register({ + project: require('path').join(__dirname, './tsconfig.e2e.json') + }); + jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); + } +}; \ No newline at end of file diff --git a/ui-ngx/e2e/src/app.e2e-spec.ts b/ui-ngx/e2e/src/app.e2e-spec.ts new file mode 100644 index 0000000000..9cb6d753fe --- /dev/null +++ b/ui-ngx/e2e/src/app.e2e-spec.ts @@ -0,0 +1,39 @@ +/// +/// Copyright © 2016-2019 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 { AppPage } from './app.po'; +import { browser, logging } from 'protractor'; + +describe('workspace-project App', () => { + let page: AppPage; + + beforeEach(() => { + page = new AppPage(); + }); + + it('should display welcome message', () => { + page.navigateTo(); + expect(page.getTitleText()).toEqual('Welcome to tb-license-server!'); + }); + + afterEach(async () => { + // Assert that there are no errors emitted from the browser + const logs = await browser.manage().logs().get(logging.Type.BROWSER); + expect(logs).not.toContain(jasmine.objectContaining({ + level: logging.Level.SEVERE, + } as logging.Entry)); + }); +}); diff --git a/ui-ngx/e2e/src/app.po.ts b/ui-ngx/e2e/src/app.po.ts new file mode 100644 index 0000000000..160936a334 --- /dev/null +++ b/ui-ngx/e2e/src/app.po.ts @@ -0,0 +1,27 @@ +/// +/// Copyright © 2016-2019 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 { browser, by, element } from 'protractor'; + +export class AppPage { + navigateTo() { + return browser.get(browser.baseUrl) as Promise; + } + + getTitleText() { + return element(by.css('tb-root h1')).getText() as Promise; + } +} diff --git a/ui-ngx/e2e/tsconfig.e2e.json b/ui-ngx/e2e/tsconfig.e2e.json new file mode 100644 index 0000000000..a6dd622028 --- /dev/null +++ b/ui-ngx/e2e/tsconfig.e2e.json @@ -0,0 +1,13 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/app", + "module": "commonjs", + "target": "es5", + "types": [ + "jasmine", + "jasminewd2", + "node" + ] + } +} \ No newline at end of file diff --git a/ui-ngx/extra-webpack.config.js b/ui-ngx/extra-webpack.config.js new file mode 100644 index 0000000000..c86417512a --- /dev/null +++ b/ui-ngx/extra-webpack.config.js @@ -0,0 +1,43 @@ +/* + * Copyright © 2016-2019 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. + */ +const CompressionPlugin = require('compression-webpack-plugin'); +const webpack = require('webpack'); +const dirTree = require('directory-tree'); + +var langs = []; + +dirTree('./src/assets/locale/', {extensions:/\.json$/}, (item) => { + /* It is expected what the name of a locale file has the following format: */ + /* 'locale.constant-LANG_CODE[_REGION_CODE].json', e.g. locale.constant-es.json or locale.constant-zh_CN.json*/ + langs.push(item.name.slice(item.name.lastIndexOf('-') + 1, -5)); +}); + +module.exports = { + plugins: [ + new webpack.DefinePlugin({ + TB_VERSION: JSON.stringify(require('./package.json').version), + SUPPORTED_LANGS: JSON.stringify(langs) + }), + new CompressionPlugin({ + filename: "[path].gz[query]", + algorithm: "gzip", + test: /\.js$|\.css$|\.svg$|\.ttf$|\.woff$|\.woff2$|\.eot$|\.json$/, + threshold: 10240, + minRatio: 0.8, + deleteOriginalAssets: false + }) + ] +}; diff --git a/ui-ngx/package-lock.json b/ui-ngx/package-lock.json new file mode 100644 index 0000000000..37ca17e5fc --- /dev/null +++ b/ui-ngx/package-lock.json @@ -0,0 +1,11112 @@ +{ + "name": "thingsboard", + "version": "3.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@angular-builders/custom-webpack": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@angular-builders/custom-webpack/-/custom-webpack-8.1.0.tgz", + "integrity": "sha512-5JOrq+J6BhCG8VK8R4vdWfEFXBeNfH32U/3CfAzb3YxnKpPJgNmyPMy+wnK0byLJKIp3IsF5rkpq43d/ZyPRaw==", + "dev": true, + "requires": { + "lodash": "^4.17.10", + "webpack-merge": "^4.2.1" + } + }, + "@angular-builders/dev-server": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@angular-builders/dev-server/-/dev-server-7.3.1.tgz", + "integrity": "sha512-rFr0NyFcwTb4RkkboYQN5JeR9ZraOkfUrQYljMSe/O01MM3SJvE8LYJbsyMwGtp71Rc8T6JrpdxaNEeYCV/4PA==", + "dev": true + }, + "@angular-devkit/architect": { + "version": "0.802.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.802.0.tgz", + "integrity": "sha512-Zd/ao7uE8ctV4n6drKl35cK5xrRsmgva7lsiBRc4J09vDWaRrCsxTKr6nw1gkFBDuSGZc9OmvtEFFPg2I/YHwQ==", + "dev": true, + "requires": { + "@angular-devkit/core": "8.2.0", + "rxjs": "6.4.0" + }, + "dependencies": { + "rxjs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", + "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + } + } + }, + "@angular-devkit/build-angular": { + "version": "0.802.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.802.0.tgz", + "integrity": "sha512-4knETvOkeeWrwddc+teo+tyocioh2imehLN97m87EJz1i0+uJaFYkQPUrTqp8yJE0OQI2Gg387EdTnqrz9Pc3Q==", + "dev": true, + "requires": { + "@angular-devkit/architect": "0.802.0", + "@angular-devkit/build-optimizer": "0.802.0", + "@angular-devkit/build-webpack": "0.802.0", + "@angular-devkit/core": "8.2.0", + "@ngtools/webpack": "8.2.0", + "ajv": "6.10.2", + "autoprefixer": "9.6.1", + "browserslist": "4.6.6", + "caniuse-lite": "1.0.30000986", + "circular-dependency-plugin": "5.0.2", + "clean-css": "4.2.1", + "copy-webpack-plugin": "5.0.4", + "core-js": "3.1.4", + "file-loader": "4.1.0", + "glob": "7.1.4", + "istanbul-instrumenter-loader": "3.0.1", + "karma-source-map-support": "1.4.0", + "less": "3.9.0", + "less-loader": "5.0.0", + "license-webpack-plugin": "2.1.1", + "loader-utils": "1.2.3", + "mini-css-extract-plugin": "0.8.0", + "minimatch": "3.0.4", + "open": "6.4.0", + "parse5": "4.0.0", + "postcss": "7.0.17", + "postcss-import": "12.0.1", + "postcss-loader": "3.0.0", + "raw-loader": "1.0.0", + "rxjs": "6.4.0", + "sass": "1.22.7", + "sass-loader": "7.1.0", + "semver": "6.3.0", + "source-map-loader": "0.2.4", + "source-map-support": "0.5.12", + "speed-measure-webpack-plugin": "1.3.1", + "style-loader": "0.23.1", + "stylus": "0.54.5", + "stylus-loader": "3.0.2", + "terser-webpack-plugin": "1.3.0", + "tree-kill": "1.2.1", + "webpack": "4.38.0", + "webpack-dev-middleware": "3.7.0", + "webpack-dev-server": "3.7.2", + "webpack-merge": "4.2.1", + "webpack-sources": "1.3.0", + "webpack-subresource-integrity": "1.1.0-rc.6", + "worker-plugin": "3.1.0" + }, + "dependencies": { + "ajv": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", + "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "parse5": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", + "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", + "dev": true + }, + "rxjs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", + "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.12", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz", + "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + } + } + }, + "@angular-devkit/build-optimizer": { + "version": "0.802.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.802.0.tgz", + "integrity": "sha512-eq47XkLvta6wJEwCT/fdQczZZA1hPH8hHbp/W1TBzzUcyeeHHSimdeMKM0Pw5rwHVrd+nQL0tbTZwX3qaVcTfg==", + "dev": true, + "requires": { + "loader-utils": "1.2.3", + "source-map": "0.5.6", + "typescript": "3.5.3", + "webpack-sources": "1.3.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", + "dev": true + } + } + }, + "@angular-devkit/build-webpack": { + "version": "0.802.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.802.0.tgz", + "integrity": "sha512-O5eUsvvjPxrc5ge5LVTtP6mh1lnAI3ra4LhgCGpdInfaGzzeFcyEeBCaS9IoyKQo2PJLEnOZ+BDd6Necu81oTA==", + "dev": true, + "requires": { + "@angular-devkit/architect": "0.802.0", + "@angular-devkit/core": "8.2.0", + "rxjs": "6.4.0", + "webpack-merge": "4.2.1" + }, + "dependencies": { + "rxjs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", + "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + } + } + }, + "@angular-devkit/core": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-8.2.0.tgz", + "integrity": "sha512-jZQn5hQ84++00+yuD/Ak303/Q06keFVyd+QbSfVrpHTFyOwPeNNSPLbN6A0S7X3bKOuoZhUHg+eQBa5BljVC2g==", + "dev": true, + "requires": { + "ajv": "6.10.2", + "fast-json-stable-stringify": "2.0.0", + "magic-string": "0.25.3", + "rxjs": "6.4.0", + "source-map": "0.7.3" + }, + "dependencies": { + "ajv": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", + "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "rxjs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", + "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + } + } + }, + "@angular-devkit/schematics": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-8.2.0.tgz", + "integrity": "sha512-/XUWJijLXzhtWdjoQ5ioLo5r5V5+sJ0SSnSP0N8MQyLOgTd1FDGtBMsAMJ3n2/uwUl2/O9WTlV1xNLlg7neYVQ==", + "dev": true, + "requires": { + "@angular-devkit/core": "8.2.0", + "rxjs": "6.4.0" + }, + "dependencies": { + "@angular-devkit/core": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-8.2.0.tgz", + "integrity": "sha512-jZQn5hQ84++00+yuD/Ak303/Q06keFVyd+QbSfVrpHTFyOwPeNNSPLbN6A0S7X3bKOuoZhUHg+eQBa5BljVC2g==", + "dev": true, + "requires": { + "ajv": "6.10.2", + "fast-json-stable-stringify": "2.0.0", + "magic-string": "0.25.3", + "rxjs": "6.4.0", + "source-map": "0.7.3" + } + }, + "ajv": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", + "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "rxjs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", + "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + } + } + }, + "@angular/animations": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-8.2.0.tgz", + "integrity": "sha512-G+4+F/T4VA6TlXsv73ZQBe6LKgl1PfxchzzaEiFcGkLqlEaSU7ZgVD+PW6rUvG9a8jWMxBLqqtty0H70JkDysQ==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/cdk": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-8.1.2.tgz", + "integrity": "sha512-jzPT1mMfGaA2xjnkMh1kd0uyWI7pz58/7BWBijLtSLKM9+HkhrnqXIHesKNE7EwpapzuPBtkGm3MGl661goIvA==", + "requires": { + "parse5": "^5.0.0", + "tslib": "^1.7.1" + } + }, + "@angular/cli": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-8.2.0.tgz", + "integrity": "sha512-KtjC5Mge93YjPQXxEKnXzQ7pmryizfVunrcKHSwhnzfNdwqSjcfL2evl4oBT07b6RfT0nF8HWn0ATWpiLWwrXQ==", + "dev": true, + "requires": { + "@angular-devkit/architect": "0.802.0", + "@angular-devkit/core": "8.2.0", + "@angular-devkit/schematics": "8.2.0", + "@schematics/angular": "8.2.0", + "@schematics/update": "0.802.0", + "@yarnpkg/lockfile": "1.1.0", + "ansi-colors": "4.1.1", + "debug": "^4.1.1", + "ini": "1.3.5", + "inquirer": "6.5.0", + "npm-package-arg": "6.1.0", + "open": "6.4.0", + "pacote": "9.5.4", + "read-package-tree": "5.3.1", + "semver": "6.3.0", + "symbol-observable": "1.2.0", + "universal-analytics": "^0.4.20", + "uuid": "^3.3.2" + }, + "dependencies": { + "@angular-devkit/architect": { + "version": "0.802.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.802.0.tgz", + "integrity": "sha512-Zd/ao7uE8ctV4n6drKl35cK5xrRsmgva7lsiBRc4J09vDWaRrCsxTKr6nw1gkFBDuSGZc9OmvtEFFPg2I/YHwQ==", + "dev": true, + "requires": { + "@angular-devkit/core": "8.2.0", + "rxjs": "6.4.0" + } + }, + "@angular-devkit/core": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-8.2.0.tgz", + "integrity": "sha512-jZQn5hQ84++00+yuD/Ak303/Q06keFVyd+QbSfVrpHTFyOwPeNNSPLbN6A0S7X3bKOuoZhUHg+eQBa5BljVC2g==", + "dev": true, + "requires": { + "ajv": "6.10.2", + "fast-json-stable-stringify": "2.0.0", + "magic-string": "0.25.3", + "rxjs": "6.4.0", + "source-map": "0.7.3" + } + }, + "ajv": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", + "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "open": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/open/-/open-6.4.0.tgz", + "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", + "dev": true, + "requires": { + "is-wsl": "^1.1.0" + } + }, + "rxjs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", + "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@angular/common": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-8.2.0.tgz", + "integrity": "sha512-4Na0DgqOX7FlGpC0upKpPr8cCOpHTDXh50uW9p1sLyfiR5kQxZsZPbTikQ9mMBWgS3tuG08bSieyvp+R8dJTZA==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/compiler": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-8.2.0.tgz", + "integrity": "sha512-5OlLfL6cie8XAY+pPc+iCouzO07V5Lahmyr6OVKMjePJO5SkPuVdm/OPdR43n3VNlOje4bwHHvoTok1BKepDTg==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/compiler-cli": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-8.2.0.tgz", + "integrity": "sha512-wwR/01DIAazynICQA7ge7tmXSTgY0rLn3vB9kF30mpg4IdytLqY4ExtfThxvbG8w+wnEGxzYLkpXkp5CQUD/Fw==", + "dev": true, + "requires": { + "canonical-path": "1.0.0", + "chokidar": "^2.1.1", + "convert-source-map": "^1.5.1", + "dependency-graph": "^0.7.2", + "magic-string": "^0.25.0", + "minimist": "^1.2.0", + "reflect-metadata": "^0.1.2", + "source-map": "^0.6.1", + "tslib": "^1.9.0", + "yargs": "13.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "chokidar": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.6.tgz", + "integrity": "sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + } + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + } + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "yargs": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.1.0.tgz", + "integrity": "sha512-1UhJbXfzHiPqkfXNHYhiz79qM/kZqjTE8yGlEjZa85Q+3+OwcV6NRkV7XOV1W2Eom2bzILeUn55pQYffjVOLAg==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "os-locale": "^3.1.0", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.0.0" + } + }, + "yargs-parser": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", + "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "@angular/core": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-8.2.0.tgz", + "integrity": "sha512-Sg7zPaaAeV73zobKmxvdQ0pDhZAigDKM9jOqm2q19ucdOLBBQJnZf7JkZYO+KWm56Ttz76Jetl+neR5zzGg/bg==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/flex-layout": { + "version": "8.0.0-beta.26", + "resolved": "https://registry.npmjs.org/@angular/flex-layout/-/flex-layout-8.0.0-beta.26.tgz", + "integrity": "sha512-lXDLlMSNQhidW0grvisIsj/3gqLuYyN2MvABuRYybnFTc233sXGZuOAaulqq663LA0/DP/GNcz6a+A4ZAAlmPA==", + "requires": { + "tslib": "^1.7.1" + } + }, + "@angular/forms": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-8.2.0.tgz", + "integrity": "sha512-ibvdCEr8n1Tfa0gaZDQFAIBeSZ0wle4O6BBaiai4wQdjnSQ0fTsmWjVIZ/UUi2diqbrTQFzfH1Cj41hPHwltxQ==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/language-service": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-8.2.0.tgz", + "integrity": "sha512-fv+BF4Fgy/JASi3lpfwxDFsByWfpsASr5wt2zFMozHSb0ZsJ22OpyvdnjPfEaAOLDV0qcXIZEZ6O+NjRq2qurQ==", + "dev": true + }, + "@angular/material": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-8.1.2.tgz", + "integrity": "sha512-c/EYufDPTClr7F2ZFv7KSaPpA/b/Bq+89oU5/AioNzx3KIZWBB24U/GaIsh3NI75mPTeVdJDhXhCcrE5WbsZnQ==", + "requires": { + "tslib": "^1.7.1" + } + }, + "@angular/platform-browser": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-8.2.0.tgz", + "integrity": "sha512-0VeNOspfjFZudMwca6ZCESCNloydqzifOn6fSetY0ILzO6MnM8GX/PiAByppYRXoIla3pwDtg1lcI+FUZoaT5Q==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/platform-browser-dynamic": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-8.2.0.tgz", + "integrity": "sha512-nPPnsJ+g6jvhdiPBQ3zBhdouBKnPdBx4Vm3gOgKSuQ1m7jW7aVFhNFi1IgkNOTZLKLyvIZ2F1EhiR3A3fUJteA==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/router": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-8.2.0.tgz", + "integrity": "sha512-cSp49wFT2w6e8BWfCV9yX02B0VA1dqW2DZf5d8UyadRMLm/zgZVT8AGuIo6VZ1KcdXswvIeRbc+M/BaBIxeonA==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@auth0/angular-jwt": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@auth0/angular-jwt/-/angular-jwt-3.0.0.tgz", + "integrity": "sha512-Ky8hghnEx+CtCd097YXji08/LvLTG98IAEX/j1UgnutRDhQ31eczOohDn98v3i3MHNfLjfI3HdyxPK1Qc0IkZw==", + "requires": { + "url": "^0.11.0" + } + }, + "@babel/code-frame": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", + "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/generator": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.5.5.tgz", + "integrity": "sha512-ETI/4vyTSxTzGnU2c49XHv2zhExkv9JHLTwDAFz85kmcwuShvYG2H08FwgIguQf4JC75CBnXAUM5PqeF4fj0nQ==", + "dev": true, + "requires": { + "@babel/types": "^7.5.5", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" + }, + "dependencies": { + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/helper-function-name": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", + "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", + "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz", + "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==", + "dev": true, + "requires": { + "@babel/types": "^7.4.4" + } + }, + "@babel/highlight": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz", + "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + } + } + }, + "@babel/parser": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.5.5.tgz", + "integrity": "sha512-E5BN68cqR7dhKan1SfqgPGhQ178bkVKpXTPEXnFJBrEt8/DKRZlybmy+IgYLTeN7tp1R5Ccmbm2rBk17sHYU3g==", + "dev": true + }, + "@babel/template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.4.tgz", + "integrity": "sha512-CiGzLN9KgAvgZsnivND7rkA+AeJ9JB0ciPOD4U59GKbQP2iQl+olF1l76kJOupqidozfZ32ghwBEJDhnk9MEcw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.4.4", + "@babel/types": "^7.4.4" + } + }, + "@babel/traverse": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.5.5.tgz", + "integrity": "sha512-MqB0782whsfffYfSjH4TM+LMjrJnhCNEDMDIjeTpl+ASaUvxcjoiVCo/sM1GhS1pHOXYfWVCYneLjMckuUxDaQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.5.5", + "@babel/generator": "^7.5.5", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.4", + "@babel/parser": "^7.5.5", + "@babel/types": "^7.5.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@babel/types": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.5.5.tgz", + "integrity": "sha512-s63F9nJioLqOlW3UkyMd+BYhXt44YuaFm/VV0VwuteqjYwRrObkU7ra9pY4wAJR3oXi8hJrMcrcJdO/HH33vtw==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + }, + "dependencies": { + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + } + } + }, + "@mat-datetimepicker/core": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@mat-datetimepicker/core/-/core-2.0.1.tgz", + "integrity": "sha1-4NsdtdTPe6Vrck7AQIF8totXdfI=", + "requires": { + "tslib": "^1.9.0" + } + }, + "@ngrx/effects": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-8.2.0.tgz", + "integrity": "sha512-eVho/YNHFDL3fo0Pb24GduU2OUHIEaUQjBBP/WlXO66QdqM9gXPJeEviWzA9RaC1tEu4sAZ0OYP1pP++CCn6Uw==" + }, + "@ngrx/store": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@ngrx/store/-/store-8.2.0.tgz", + "integrity": "sha512-RTmg7WstMhxEIWxtcK1dC4i/3OQeS11ilosQjmZyiRcbRQzvBMZqQzNdpwb5yL6rEQ19Cka/QEWm2sd2USzHeA==" + }, + "@ngrx/store-devtools": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@ngrx/store-devtools/-/store-devtools-8.2.0.tgz", + "integrity": "sha512-zNz9B641Jz8cGqaO6M7myXLkYDEUrdnLipzHuvn/dxbswxJrcaWsGUbb+5cCXCzbcoy2c8DKEHouFqPwKX86WQ==" + }, + "@ngtools/webpack": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-8.2.0.tgz", + "integrity": "sha512-vv4S1ERBDBgKyT+fERzqprU7VdyWPHAZYP/UGIjEqTBwrrQd3G9dd0Pnt8sRRscLsZoo+DexC9J+NRBd207pLA==", + "dev": true, + "requires": { + "@angular-devkit/core": "8.2.0", + "enhanced-resolve": "4.1.0", + "rxjs": "6.4.0", + "tree-kill": "1.2.1", + "webpack-sources": "1.3.0" + }, + "dependencies": { + "rxjs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", + "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + } + } + }, + "@ngx-translate/core": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/@ngx-translate/core/-/core-11.0.1.tgz", + "integrity": "sha512-nBCa1ZD9fAUY/3eskP3Lql2fNg8OMrYIej1/5GRsfcutx9tG/5fZLCv9m6UCw1aS+u4uK/vXjv1ctG/FdMvaWg==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@ngx-translate/http-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@ngx-translate/http-loader/-/http-loader-4.0.0.tgz", + "integrity": "sha512-x8LumqydWD7eX9yQTAVeoCM9gFUIGVTUjZqbxdAUavAA3qVnk9wCQux7iHLPXpydl8vyQmLoPQR+fFU+DUDOMA==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@schematics/angular": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-8.2.0.tgz", + "integrity": "sha512-DOo2wtk9fk0kHCDA/I+/mRrGKirgeqVhDbgOV4d2gbYSAiTl0s1Gb4eFAkJeovQTlARfaL2PIqDDkNeYjc7xpw==", + "dev": true, + "requires": { + "@angular-devkit/core": "8.2.0", + "@angular-devkit/schematics": "8.2.0" + }, + "dependencies": { + "@angular-devkit/core": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-8.2.0.tgz", + "integrity": "sha512-jZQn5hQ84++00+yuD/Ak303/Q06keFVyd+QbSfVrpHTFyOwPeNNSPLbN6A0S7X3bKOuoZhUHg+eQBa5BljVC2g==", + "dev": true, + "requires": { + "ajv": "6.10.2", + "fast-json-stable-stringify": "2.0.0", + "magic-string": "0.25.3", + "rxjs": "6.4.0", + "source-map": "0.7.3" + } + }, + "ajv": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", + "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "rxjs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", + "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + } + } + }, + "@schematics/update": { + "version": "0.802.0", + "resolved": "https://registry.npmjs.org/@schematics/update/-/update-0.802.0.tgz", + "integrity": "sha512-vMcFLTuw9jSlWQq6nNgMQi2fT/wGyaucvjkxFAs7pC+lyRwYws3IkOukbET7WeJ3ix0ZBEhMbPJ8EibUNDITjw==", + "dev": true, + "requires": { + "@angular-devkit/core": "8.2.0", + "@angular-devkit/schematics": "8.2.0", + "@yarnpkg/lockfile": "1.1.0", + "ini": "1.3.5", + "pacote": "9.5.4", + "rxjs": "6.4.0", + "semver": "6.3.0", + "semver-intersect": "1.4.0" + }, + "dependencies": { + "@angular-devkit/core": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-8.2.0.tgz", + "integrity": "sha512-jZQn5hQ84++00+yuD/Ak303/Q06keFVyd+QbSfVrpHTFyOwPeNNSPLbN6A0S7X3bKOuoZhUHg+eQBa5BljVC2g==", + "dev": true, + "requires": { + "ajv": "6.10.2", + "fast-json-stable-stringify": "2.0.0", + "magic-string": "0.25.3", + "rxjs": "6.4.0", + "source-map": "0.7.3" + } + }, + "ajv": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", + "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "rxjs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", + "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@types/events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", + "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", + "dev": true + }, + "@types/glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", + "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", + "dev": true, + "requires": { + "@types/events": "*", + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/jasmine": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.4.0.tgz", + "integrity": "sha512-6pUnBg6DuSB55xnxJ5+gW9JOkFrPsXkYAuqqEE8oyrpgDiPQ+TZ+1Zt4S+CHcRJcxyNYXeIXG4vHSzdF6y9Uvw==", + "dev": true + }, + "@types/jasminewd2": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/jasminewd2/-/jasminewd2-2.0.6.tgz", + "integrity": "sha512-2ZOKrxb8bKRmP/po5ObYnRDgFE4i+lQiEB27bAMmtMWLgJSqlIDqlLx6S0IRorpOmOPRQ6O80NujTmQAtBkeNw==", + "dev": true, + "requires": { + "@types/jasmine": "*" + } + }, + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "dev": true + }, + "@types/node": { + "version": "10.14.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.15.tgz", + "integrity": "sha512-CBR5avlLcu0YCILJiDIXeU2pTw7UK/NIxfC63m7d7CVamho1qDEzXKkOtEauQRPMy6MI8mLozth+JJkas7HY6g==", + "dev": true + }, + "@types/q": { + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/@types/q/-/q-0.0.32.tgz", + "integrity": "sha1-vShOV8hPEyXacCur/IKlMoGQwMU=", + "dev": true + }, + "@types/selenium-webdriver": { + "version": "3.0.16", + "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-3.0.16.tgz", + "integrity": "sha512-lMC2G0ItF2xv4UCiwbJGbnJlIuUixHrioOhNGHSCsYCJ8l4t9hMCUimCytvFv7qy6AfSzRxhRHoGa+UqaqwyeA==", + "dev": true + }, + "@types/source-list-map": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", + "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==", + "dev": true + }, + "@types/webpack-sources": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-0.1.5.tgz", + "integrity": "sha512-zfvjpp7jiafSmrzJ2/i3LqOyTYTuJ7u1KOXlKgDlvsj9Rr0x7ZiYu5lZbXwobL7lmsRNtPXlBfmaUD8eU2Hu8w==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/source-list-map": "*", + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "@webassemblyjs/ast": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz", + "integrity": "sha512-aJMfngIZ65+t71C3y2nBBg5FFG0Okt9m0XEgWZ7Ywgn1oMAT8cNwx00Uv1cQyHtidq0Xn94R4TAywO+LCQ+ZAQ==", + "dev": true, + "requires": { + "@webassemblyjs/helper-module-context": "1.8.5", + "@webassemblyjs/helper-wasm-bytecode": "1.8.5", + "@webassemblyjs/wast-parser": "1.8.5" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.8.5.tgz", + "integrity": "sha512-9p+79WHru1oqBh9ewP9zW95E3XAo+90oth7S5Re3eQnECGq59ly1Ri5tsIipKGpiStHsUYmY3zMLqtk3gTcOtQ==", + "dev": true + }, + "@webassemblyjs/helper-api-error": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.8.5.tgz", + "integrity": "sha512-Za/tnzsvnqdaSPOUXHyKJ2XI7PDX64kWtURyGiJJZKVEdFOsdKUCPTNEVFZq3zJ2R0G5wc2PZ5gvdTRFgm81zA==", + "dev": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.8.5.tgz", + "integrity": "sha512-Ri2R8nOS0U6G49Q86goFIPNgjyl6+oE1abW1pS84BuhP1Qcr5JqMwRFT3Ah3ADDDYGEgGs1iyb1DGX+kAi/c/Q==", + "dev": true + }, + "@webassemblyjs/helper-code-frame": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.8.5.tgz", + "integrity": "sha512-VQAadSubZIhNpH46IR3yWO4kZZjMxN1opDrzePLdVKAZ+DFjkGD/rf4v1jap744uPVU6yjL/smZbRIIJTOUnKQ==", + "dev": true, + "requires": { + "@webassemblyjs/wast-printer": "1.8.5" + } + }, + "@webassemblyjs/helper-fsm": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.8.5.tgz", + "integrity": "sha512-kRuX/saORcg8se/ft6Q2UbRpZwP4y7YrWsLXPbbmtepKr22i8Z4O3V5QE9DbZK908dh5Xya4Un57SDIKwB9eow==", + "dev": true + }, + "@webassemblyjs/helper-module-context": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.8.5.tgz", + "integrity": "sha512-/O1B236mN7UNEU4t9X7Pj38i4VoU8CcMHyy3l2cV/kIF4U5KoHXDVqcDuOs1ltkac90IM4vZdHc52t1x8Yfs3g==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.8.5", + "mamacro": "^0.0.3" + } + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.8.5.tgz", + "integrity": "sha512-Cu4YMYG3Ddl72CbmpjU/wbP6SACcOPVbHN1dI4VJNJVgFwaKf1ppeFJrwydOG3NDHxVGuCfPlLZNyEdIYlQ6QQ==", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.8.5.tgz", + "integrity": "sha512-VV083zwR+VTrIWWtgIUpqfvVdK4ff38loRmrdDBgBT8ADXYsEZ5mPQ4Nde90N3UYatHdYoDIFb7oHzMncI02tA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/helper-buffer": "1.8.5", + "@webassemblyjs/helper-wasm-bytecode": "1.8.5", + "@webassemblyjs/wasm-gen": "1.8.5" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.8.5.tgz", + "integrity": "sha512-aaCvQYrvKbY/n6wKHb/ylAJr27GglahUO89CcGXMItrOBqRarUMxWLJgxm9PJNuKULwN5n1csT9bYoMeZOGF3g==", + "dev": true, + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.8.5.tgz", + "integrity": "sha512-plYUuUwleLIziknvlP8VpTgO4kqNaH57Y3JnNa6DLpu/sGcP6hbVdfdX5aHAV716pQBKrfuU26BJK29qY37J7A==", + "dev": true, + "requires": { + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/utf8": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.8.5.tgz", + "integrity": "sha512-U7zgftmQriw37tfD934UNInokz6yTmn29inT2cAetAsaU9YeVCveWEwhKL1Mg4yS7q//NGdzy79nlXh3bT8Kjw==", + "dev": true + }, + "@webassemblyjs/wasm-edit": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.8.5.tgz", + "integrity": "sha512-A41EMy8MWw5yvqj7MQzkDjU29K7UJq1VrX2vWLzfpRHt3ISftOXqrtojn7nlPsZ9Ijhp5NwuODuycSvfAO/26Q==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/helper-buffer": "1.8.5", + "@webassemblyjs/helper-wasm-bytecode": "1.8.5", + "@webassemblyjs/helper-wasm-section": "1.8.5", + "@webassemblyjs/wasm-gen": "1.8.5", + "@webassemblyjs/wasm-opt": "1.8.5", + "@webassemblyjs/wasm-parser": "1.8.5", + "@webassemblyjs/wast-printer": "1.8.5" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.8.5.tgz", + "integrity": "sha512-BCZBT0LURC0CXDzj5FXSc2FPTsxwp3nWcqXQdOZE4U7h7i8FqtFK5Egia6f9raQLpEKT1VL7zr4r3+QX6zArWg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/helper-wasm-bytecode": "1.8.5", + "@webassemblyjs/ieee754": "1.8.5", + "@webassemblyjs/leb128": "1.8.5", + "@webassemblyjs/utf8": "1.8.5" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.8.5.tgz", + "integrity": "sha512-HKo2mO/Uh9A6ojzu7cjslGaHaUU14LdLbGEKqTR7PBKwT6LdPtLLh9fPY33rmr5wcOMrsWDbbdCHq4hQUdd37Q==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/helper-buffer": "1.8.5", + "@webassemblyjs/wasm-gen": "1.8.5", + "@webassemblyjs/wasm-parser": "1.8.5" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.8.5.tgz", + "integrity": "sha512-pi0SYE9T6tfcMkthwcgCpL0cM9nRYr6/6fjgDtL6q/ZqKHdMWvxitRi5JcZ7RI4SNJJYnYNaWy5UUrHQy998lw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/helper-api-error": "1.8.5", + "@webassemblyjs/helper-wasm-bytecode": "1.8.5", + "@webassemblyjs/ieee754": "1.8.5", + "@webassemblyjs/leb128": "1.8.5", + "@webassemblyjs/utf8": "1.8.5" + } + }, + "@webassemblyjs/wast-parser": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.8.5.tgz", + "integrity": "sha512-daXC1FyKWHF1i11obK086QRlsMsY4+tIOKgBqI1lxAnkp9xe9YMcgOxm9kLe+ttjs5aWV2KKE1TWJCN57/Btsg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/floating-point-hex-parser": "1.8.5", + "@webassemblyjs/helper-api-error": "1.8.5", + "@webassemblyjs/helper-code-frame": "1.8.5", + "@webassemblyjs/helper-fsm": "1.8.5", + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.8.5.tgz", + "integrity": "sha512-w0U0pD4EhlnvRyeJzBqaVSJAo9w/ce7/WPogeXLzGkO6hzhr4GnQIZ4W4uUt5b9ooAaXPtnXlj0gzsXEOUNYMg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/wast-parser": "1.8.5", + "@xtuc/long": "4.2.2" + } + }, + "@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "dev": true + }, + "JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "dev": true, + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "ace-builds": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.4.5.tgz", + "integrity": "sha512-wotVzxv5YClvwOjiuXNyGm4j/CnKoFIoTnnXNmi1nTHjr7hXMMjQeytcnbFua4thaJ5vvpVEDv0utmjqsrp3Jw==" + }, + "acorn": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.2.1.tgz", + "integrity": "sha512-JD0xT5FCRDNyjDda3Lrg/IxFscp9q4tiYtxE1/nOzlKCk7hIRuYjhq1kCNkbPjMRMZuFq20HNQn1I9k8Oj0E+Q==", + "dev": true + }, + "adm-zip": { + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.13.tgz", + "integrity": "sha512-fERNJX8sOXfel6qCBCMPvZLzENBEhZTzKqg6vrOW5pvoEaQuJhRU4ndTAh6lHOxn1I6jnz2NHra56ZODM751uw==", + "dev": true + }, + "after": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", + "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=", + "dev": true + }, + "agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "dev": true, + "requires": { + "es6-promisify": "^5.0.0" + } + }, + "agentkeepalive": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-3.5.2.tgz", + "integrity": "sha512-e0L/HNe6qkQ7H19kTlRRqUibEAwDK5AFk6y3PtMsuut2VAH6+Q4xZml1tNDJD7kSAyqmbG/K08K5WEJYtUrSlQ==", + "dev": true, + "requires": { + "humanize-ms": "^1.2.1" + } + }, + "ajv": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.9.1.tgz", + "integrity": "sha512-XDN92U311aINL77ieWHmqCcNlwjoP5cHXDxIxbf2MaPYuCXOHS7gHH8jktxeK5omgd52XbSTX6a4Piwd1pQmzA==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-errors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", + "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", + "dev": true + }, + "ajv-keywords": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz", + "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==", + "dev": true + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true + }, + "ansi-colors": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", + "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", + "dev": true + }, + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true + }, + "ansi-html": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", + "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "app-root-path": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-2.2.1.tgz", + "integrity": "sha512-91IFKeKk7FjfmezPKkwtaRvSpnUc4gDwPAjA1YZ9Gn0q0PPeW+vbeUsZuyDwjI7+QTHhcLen2v25fi/AmhvbJA==", + "dev": true + }, + "append-transform": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz", + "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==", + "dev": true, + "requires": { + "default-require-extensions": "^2.0.0" + } + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true + }, + "arg": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.1.tgz", + "integrity": "sha512-SlmP3fEA88MBv0PypnXZ8ZfJhwmDeIE3SP71j37AiXQBXYosPV0x6uISAaHYSlSVhmHOVkomen0tbGk6Anlebw==", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "aria-query": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz", + "integrity": "sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=", + "dev": true, + "requires": { + "ast-types-flow": "0.0.7", + "commander": "^2.11.0" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "dev": true + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "arraybuffer.slice": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", + "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", + "dev": true + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", + "dev": true + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", + "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", + "dev": true, + "requires": { + "object-assign": "^4.1.1", + "util": "0.10.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + }, + "util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "requires": { + "inherits": "2.0.1" + } + } + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "ast-types-flow": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", + "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=", + "dev": true + }, + "async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + }, + "async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", + "dev": true + }, + "async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "autoprefixer": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.6.1.tgz", + "integrity": "sha512-aVo5WxR3VyvyJxcJC3h4FKfwCQvQWb1tSI5VHNibddCVWrcD1NvlxEweg3TSgiPztMnWfjpy2FURKA2kvDE+Tw==", + "dev": true, + "requires": { + "browserslist": "^4.6.3", + "caniuse-lite": "^1.0.30000980", + "chalk": "^2.4.2", + "normalize-range": "^0.1.2", + "num2fraction": "^1.2.2", + "postcss": "^7.0.17", + "postcss-value-parser": "^4.0.0" + } + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", + "dev": true + }, + "axobject-query": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.2.tgz", + "integrity": "sha512-MCeek8ZH7hKyO1rWUbKNQBbl4l2eY0ntk7OGi+q0RlafrCnfPxC06WZA+uebCfmYp4mNU9jRBP1AhGyf8+W3ww==", + "dev": true, + "requires": { + "ast-types-flow": "0.0.7" + } + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "babel-generator": { + "version": "6.26.1", + "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", + "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", + "dev": true, + "requires": { + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "detect-indent": "^4.0.0", + "jsesc": "^1.3.0", + "lodash": "^4.17.4", + "source-map": "^0.5.7", + "trim-right": "^1.0.1" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "babel-messages": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + }, + "dependencies": { + "core-js": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz", + "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==", + "dev": true + } + } + }, + "babel-template": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", + "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" + } + }, + "babel-traverse": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", + "dev": true, + "requires": { + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true + }, + "backo2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", + "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "base64-arraybuffer": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", + "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=", + "dev": true + }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", + "dev": true + }, + "base64id": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", + "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=", + "dev": true + }, + "batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "better-assert": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", + "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", + "dev": true, + "requires": { + "callsite": "1.0.0" + } + }, + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true + }, + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true + }, + "blob": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", + "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==", + "dev": true + }, + "blocking-proxy": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/blocking-proxy/-/blocking-proxy-1.0.1.tgz", + "integrity": "sha512-KE8NFMZr3mN2E0HcvCgRtX7DjhiIQrwle+nSVJVC/yqFb9+xznHl2ZcoBp2L9qzkI4t4cBFJ1efXF8Dwi132RA==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "bluebird": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", + "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==", + "dev": true + }, + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", + "dev": true + }, + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "dev": true, + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + }, + "dependencies": { + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "dev": true + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "dev": true + } + } + }, + "bonjour": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", + "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", + "dev": true, + "requires": { + "array-flatten": "^2.1.0", + "deep-equal": "^1.0.1", + "dns-equal": "^1.0.0", + "dns-txt": "^2.0.2", + "multicast-dns": "^6.0.1", + "multicast-dns-service-types": "^1.1.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "requires": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "requires": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "browserify-rsa": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "randombytes": "^2.0.1" + } + }, + "browserify-sign": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", + "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", + "dev": true, + "requires": { + "bn.js": "^4.1.1", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.2", + "elliptic": "^6.0.0", + "inherits": "^2.0.1", + "parse-asn1": "^5.0.0" + } + }, + "browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, + "requires": { + "pako": "~1.0.5" + } + }, + "browserslist": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.6.6.tgz", + "integrity": "sha512-D2Nk3W9JL9Fp/gIcWei8LrERCS+eXu9AM5cfXA8WEZ84lFks+ARnZ0q/R69m2SV3Wjma83QDDPxsNKXUwdIsyA==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30000984", + "electron-to-chromium": "^1.3.191", + "node-releases": "^1.1.25" + } + }, + "browserstack": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.5.2.tgz", + "integrity": "sha512-+6AFt9HzhKykcPF79W6yjEUJcdvZOV0lIXdkORXMJftGrDl0OKWqRF4GHqpDNkxiceDT/uB7Fb/aDwktvXX7dg==", + "dev": true, + "requires": { + "https-proxy-agent": "^2.2.1" + } + }, + "buffer": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "dev": true, + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "dev": true, + "requires": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "dev": true + }, + "buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=", + "dev": true + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "buffer-indexof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", + "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==", + "dev": true + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "dev": true + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", + "dev": true + }, + "builtins": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", + "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=", + "dev": true + }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "dev": true + }, + "cacache": { + "version": "11.3.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.3.tgz", + "integrity": "sha512-p8WcneCytvzPxhDvYp31PD039vi77I12W+/KfR9S8AZbaiARFBCpsPJS+9uhWfeBfeAtW7o/4vt3MUqLkbY6nA==", + "dev": true, + "requires": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + }, + "dependencies": { + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "dev": true + } + } + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", + "dev": true, + "requires": { + "callsites": "^2.0.0" + } + }, + "caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", + "dev": true, + "requires": { + "caller-callsite": "^2.0.0" + } + }, + "callsite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", + "dev": true + }, + "callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30000986", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000986.tgz", + "integrity": "sha512-pM+LnkoAX0+QnIH3tpW5EnkmfpEoqOD8FAcoBvsl3Xh6DXkgctiCxeCbXphP/k3XJtJzm+zOAJbi6U6IVkpWZQ==", + "dev": true + }, + "canonical-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/canonical-path/-/canonical-path-1.0.0.tgz", + "integrity": "sha512-feylzsbDxi1gPZ1IjystzIQZagYYLvfKrSuygUCgf7z6x790VEzze5QEkdSV1U58RA7Hi0+v6fv4K54atOzATg==", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "chokidar": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.0.2.tgz", + "integrity": "sha512-c4PR2egjNjI1um6bamCQ6bUNPDiyofNQruHvKgHQ4gDUP/ITSVSzNsiI5OWtHOsX323i5ha/kk4YmOZ1Ktg7KA==", + "dev": true, + "requires": { + "anymatch": "^3.0.1", + "braces": "^3.0.2", + "fsevents": "^2.0.6", + "glob-parent": "^5.0.0", + "is-binary-path": "^2.1.0", + "is-glob": "^4.0.1", + "normalize-path": "^3.0.0", + "readdirp": "^3.1.1" + }, + "dependencies": { + "anymatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.0.3.tgz", + "integrity": "sha512-c6IvoeBECQlMVuYUjSwimnhmztImpErfxJzWZhIQinIvQWoGOnB0dLIgifbPHQt5heS6mNlaZG16f06H3C8t1g==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "binary-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", + "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", + "dev": true + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "fsevents": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.0.7.tgz", + "integrity": "sha512-a7YT0SV3RB+DjYcppwVDLtn13UQnmg0SWZS7ezZD0UjnLwXmy8Zm21GMVGLaFGimIqcvyMQaOJBrop8MyOp1kQ==", + "dev": true, + "optional": true + }, + "glob-parent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.0.0.tgz", + "integrity": "sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "readdirp": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.1.1.tgz", + "integrity": "sha512-XXdSXZrQuvqoETj50+JAitxz1UPdt5dupjT6T5nVB+WvjMv2XKYj+s7hPeAVCXvmJrL36O4YYyWlIC3an2ePiQ==", + "dev": true, + "requires": { + "picomatch": "^2.0.4" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } + } + }, + "chownr": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.2.tgz", + "integrity": "sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A==", + "dev": true + }, + "chrome-trace-event": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", + "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "circular-dependency-plugin": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/circular-dependency-plugin/-/circular-dependency-plugin-5.0.2.tgz", + "integrity": "sha512-oC7/DVAyfcY3UWKm0sN/oVoDedQDQiw/vIiAnuTWTpE5s0zWf7l3WY417Xw/Fbi/QbAjctAkxgMiS9P0s3zkmA==", + "dev": true + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "clean-css": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", + "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==", + "dev": true, + "requires": { + "source-map": "~0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true + }, + "clone-deep": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-2.0.2.tgz", + "integrity": "sha512-SZegPTKjCgpQH63E+eN6mVEEPdQBOUzjyJm5Pora4lrwWRFS8I0QAxV/KD6vV/i0WuijHZWQC1fMsPEdxfdVCQ==", + "dev": true, + "requires": { + "for-own": "^1.0.0", + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.0", + "shallow-clone": "^1.0.0" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "codelyzer": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/codelyzer/-/codelyzer-5.1.0.tgz", + "integrity": "sha512-QiyY2/oDQnYx4mAVEDqr+z9MwrOto18tQFjExiuRChXCy0yvngS5fQpWIxvAGpbOmZFiR1PRTRLbEI71u10maA==", + "dev": true, + "requires": { + "app-root-path": "^2.2.1", + "aria-query": "^3.0.0", + "axobject-query": "^2.0.2", + "css-selector-tokenizer": "^0.7.1", + "cssauron": "^1.4.0", + "damerau-levenshtein": "^1.0.4", + "semver-dsl": "^1.0.1", + "source-map": "^0.5.7", + "sprintf-js": "^1.1.2" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "sprintf-js": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", + "dev": true + } + } + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "colors": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "compare-versions": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.5.1.tgz", + "integrity": "sha512-9fGPIB7C6AyM18CJJBHt5EnCZDG3oiTJYy0NjfIAGjKpzv0tkxWko7TNQHF5ymqm7IH03tqmeuBxtvD+Izh6mg==", + "dev": true + }, + "compass-sass-mixins": { + "version": "0.12.7", + "resolved": "https://registry.npmjs.org/compass-sass-mixins/-/compass-sass-mixins-0.12.7.tgz", + "integrity": "sha1-KsTTEPLr5Ri31qykriTx0yVAnow=" + }, + "component-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", + "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=", + "dev": true + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "component-inherit": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", + "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=", + "dev": true + }, + "compressible": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.17.tgz", + "integrity": "sha512-BGHeLCK1GV7j1bSmQQAi26X+GgWcTjLr/0tzSvMCl3LH1w1IJ4PFSPoV5316b30cneTziC+B1a+3OjoSUcQYmw==", + "dev": true, + "requires": { + "mime-db": ">= 1.40.0 < 2" + } + }, + "compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "requires": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + } + }, + "compression-webpack-plugin": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/compression-webpack-plugin/-/compression-webpack-plugin-3.0.0.tgz", + "integrity": "sha512-ls+oKw4eRbvaSv/hj9NmctihhBcR26j76JxV0bLRLcWhrUBdQFgd06z/Kgg7exyQvtWWP484wZxs0gIUX3NO0Q==", + "dev": true, + "requires": { + "cacache": "^11.2.0", + "find-cache-dir": "^3.0.0", + "neo-async": "^2.5.0", + "schema-utils": "^1.0.0", + "serialize-javascript": "^1.4.0", + "webpack-sources": "^1.0.1" + }, + "dependencies": { + "cacache": { + "version": "11.3.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.3.tgz", + "integrity": "sha512-p8WcneCytvzPxhDvYp31PD039vi77I12W+/KfR9S8AZbaiARFBCpsPJS+9uhWfeBfeAtW7o/4vt3MUqLkbY6nA==", + "dev": true, + "requires": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, + "find-cache-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.0.0.tgz", + "integrity": "sha512-t7ulV1fmbxh5G9l/492O1p5+EBbr3uwpt6odhFTMc+nWyhmbloe+ja9BZ8pIBtqFWhOmCWVjx+pTW4zDkFoclw==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.0", + "pkg-dir": "^4.1.0" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "make-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.0.tgz", + "integrity": "sha512-grNJDhb8b1Jm1qeqW5R/O63wUo4UXo2v2HMic6YT9i/HBlF93S8jkMgH7yugvY9ABDShH4VZMn8I+U8+fCNegw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, + "mississippi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", + "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", + "dev": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "ssri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "dev": true + } + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "requires": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + } + }, + "connect-history-api-fallback": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", + "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", + "dev": true + }, + "console-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", + "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", + "dev": true, + "requires": { + "date-now": "^0.1.4" + } + }, + "constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", + "dev": true + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true + }, + "convert-source-map": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", + "dev": true + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", + "dev": true + }, + "copy-concurrently": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", + "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", + "dev": true, + "requires": { + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "copy-webpack-plugin": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-5.0.4.tgz", + "integrity": "sha512-YBuYGpSzoCHSSDGyHy6VJ7SHojKp6WHT4D7ItcQFNAYx2hrwkMe56e97xfVR0/ovDuMTrMffXUiltvQljtAGeg==", + "dev": true, + "requires": { + "cacache": "^11.3.3", + "find-cache-dir": "^2.1.0", + "glob-parent": "^3.1.0", + "globby": "^7.1.1", + "is-glob": "^4.0.1", + "loader-utils": "^1.2.3", + "minimatch": "^3.0.4", + "normalize-path": "^3.0.0", + "p-limit": "^2.2.0", + "schema-utils": "^1.0.0", + "serialize-javascript": "^1.7.0", + "webpack-log": "^2.0.0" + }, + "dependencies": { + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + } + } + }, + "core-js": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.1.4.tgz", + "integrity": "sha512-YNZN8lt82XIMLnLirj9MhKDFZHalwzzrL9YLt6eb0T5D0EDl4IQ90IGkua8mHbnxNrkj1d8hbdizMc0Qmg1WnQ==" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "dev": true, + "requires": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + } + }, + "create-ecdh": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", + "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "elliptic": "^6.0.0" + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "requires": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + } + }, + "css-parse": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/css-parse/-/css-parse-1.7.0.tgz", + "integrity": "sha1-Mh9s9zeCpv91ERE5D8BeLGV9jJs=", + "dev": true + }, + "css-selector-tokenizer": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.1.tgz", + "integrity": "sha512-xYL0AMZJ4gFzJQsHUKa5jiWWi2vH77WVNg7JYRyewwj6oPh4yb/y6Y9ZCw9dsj/9UauMhtuxR+ogQd//EdEVNA==", + "dev": true, + "requires": { + "cssesc": "^0.1.0", + "fastparse": "^1.1.1", + "regexpu-core": "^1.0.0" + } + }, + "cssauron": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/cssauron/-/cssauron-1.4.0.tgz", + "integrity": "sha1-pmAt/34EqDBtwNuaVR6S6LVmKtg=", + "dev": true, + "requires": { + "through": "X.X.X" + } + }, + "cssesc": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz", + "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=", + "dev": true + }, + "custom-event": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", + "dev": true + }, + "cyclist": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz", + "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=", + "dev": true + }, + "damerau-levenshtein": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.5.tgz", + "integrity": "sha512-CBCRqFnpu715iPmw1KrdOrzRqbdFwQTwAWyyyYS42+iAgHCuXZ+/TdMgQkUENPomxEz9z1BEzuQU2Xw0kUuAgA==", + "dev": true + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "date-format": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz", + "integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==", + "dev": true + }, + "date-now": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", + "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "debuglog": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz", + "integrity": "sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=", + "dev": true + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", + "dev": true + }, + "deep-freeze-strict": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/deep-freeze-strict/-/deep-freeze-strict-1.1.1.tgz", + "integrity": "sha1-d9BYPKJKab5LvZrC+uQV1VUj5bA=", + "dev": true + }, + "default-gateway": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz", + "integrity": "sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "ip-regex": "^2.1.0" + } + }, + "default-require-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz", + "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=", + "dev": true, + "requires": { + "strip-bom": "^3.0.0" + }, + "dependencies": { + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + } + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "del": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", + "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", + "dev": true, + "requires": { + "@types/glob": "^7.1.1", + "globby": "^6.1.0", + "is-path-cwd": "^2.0.0", + "is-path-in-cwd": "^2.0.0", + "p-map": "^2.0.0", + "pify": "^4.0.1", + "rimraf": "^2.6.3" + }, + "dependencies": { + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", + "dev": true + }, + "is-path-in-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", + "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", + "dev": true, + "requires": { + "is-path-inside": "^2.1.0" + } + }, + "is-path-inside": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", + "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", + "dev": true, + "requires": { + "path-is-inside": "^1.0.2" + } + } + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + }, + "dependency-graph": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.7.2.tgz", + "integrity": "sha512-KqtH4/EZdtdfWX0p6MGP9jljvxSY6msy/pRUD4jgNwVpv3v1QmNLlsB3LDSSUg79BRVSn7jI1QPRtArGABovAQ==", + "dev": true + }, + "des.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", + "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true + }, + "detect-indent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", + "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "detect-node": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", + "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==", + "dev": true + }, + "dezalgo": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", + "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=", + "dev": true, + "requires": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", + "dev": true + }, + "diff": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz", + "integrity": "sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==", + "dev": true + }, + "diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "dir-glob": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", + "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", + "dev": true, + "requires": { + "path-type": "^3.0.0" + } + }, + "directory-tree": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/directory-tree/-/directory-tree-2.2.3.tgz", + "integrity": "sha512-o2D5lYpQpsSCa2w9/NmGZ/d0GJhfa6+8aqLjeoYgVYIG8VViyom6MNvcuHvrcqJcOyS/IoZw4SO0JNq7QPjJOg==", + "dev": true + }, + "dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=", + "dev": true + }, + "dns-packet": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz", + "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==", + "dev": true, + "requires": { + "ip": "^1.1.0", + "safe-buffer": "^5.0.1" + } + }, + "dns-txt": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", + "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", + "dev": true, + "requires": { + "buffer-indexof": "^1.0.0" + } + }, + "dom-serialize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", + "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", + "dev": true, + "requires": { + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" + } + }, + "domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", + "dev": true + }, + "duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "electron-to-chromium": { + "version": "1.3.220", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.220.tgz", + "integrity": "sha512-ZsaFWi+9J9Nsm4OmGM/BvZF3HEeZL4bte1+CcN9vHUcqdkOOVAXP4SeacPZ/W5uCQZEKPYBXg6yUjZx8/jpD0Q==", + "dev": true + }, + "elliptic": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.0.tgz", + "integrity": "sha512-eFOJTMyCYb7xtE/caJ6JJu+bhi67WCYNbkGSknu20pmM8Ke/bqOfdnZWxyoGN26JgfxTbXrsCkEw4KheCT/KGg==", + "dev": true, + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "emojis-list": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", + "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true + }, + "encoding": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", + "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", + "dev": true, + "requires": { + "iconv-lite": "~0.4.13" + } + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "engine.io": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.2.1.tgz", + "integrity": "sha512-+VlKzHzMhaU+GsCIg4AoXF1UdDFjHHwMmMKqMJNDNLlUlejz58FCy4LBqB2YVJskHGYl06BatYWKP2TVdVXE5w==", + "dev": true, + "requires": { + "accepts": "~1.3.4", + "base64id": "1.0.0", + "cookie": "0.3.1", + "debug": "~3.1.0", + "engine.io-parser": "~2.1.0", + "ws": "~3.3.1" + }, + "dependencies": { + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", + "dev": true + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "engine.io-client": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz", + "integrity": "sha512-y5AbkytWeM4jQr7m/koQLc5AxpRKC1hEVUb/s1FUAWEJq5AzJJ4NLvzuKPuxtDi5Mq755WuDvZ6Iv2rXj4PTzw==", + "dev": true, + "requires": { + "component-emitter": "1.2.1", + "component-inherit": "0.0.3", + "debug": "~3.1.0", + "engine.io-parser": "~2.1.1", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "ws": "~3.3.1", + "xmlhttprequest-ssl": "~1.5.4", + "yeast": "0.1.2" + }, + "dependencies": { + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "engine.io-parser": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.3.tgz", + "integrity": "sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA==", + "dev": true, + "requires": { + "after": "0.8.2", + "arraybuffer.slice": "~0.0.7", + "base64-arraybuffer": "0.1.5", + "blob": "0.0.5", + "has-binary2": "~1.0.2" + } + }, + "enhanced-resolve": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz", + "integrity": "sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.4.0", + "tapable": "^1.0.0" + } + }, + "ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", + "dev": true + }, + "err-code": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-1.1.2.tgz", + "integrity": "sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA=", + "dev": true + }, + "errno": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "dev": true, + "requires": { + "prr": "~1.0.1" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", + "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.0", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-keys": "^1.0.12" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "dev": true + }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "dev": true, + "requires": { + "es6-promise": "^4.0.3" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "dev": true + }, + "eventemitter3": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", + "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==", + "dev": true + }, + "events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", + "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==", + "dev": true + }, + "eventsource": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.0.7.tgz", + "integrity": "sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==", + "dev": true, + "requires": { + "original": "^1.0.0" + } + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "requires": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "dev": true, + "requires": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", + "dev": true + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "dev": true + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "fastparse": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", + "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==", + "dev": true + }, + "faye-websocket": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", + "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "figgy-pudding": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", + "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==", + "dev": true + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-loader": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-4.1.0.tgz", + "integrity": "sha512-ajDk1nlByoalZAGR4b0H6oD+EGlWnyW1qbSxzaUc7RFiqmn+RbXQQRbTc72jsiUIlVusJ4Et58ltds8ZwTfnAw==", + "dev": true, + "requires": { + "loader-utils": "^1.2.3", + "schema-utils": "^2.0.0" + }, + "dependencies": { + "schema-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.1.0.tgz", + "integrity": "sha512-g6SViEZAfGNrToD82ZPUjq52KUPDYc+fN5+g6Euo5mLokl/9Yx14z0Cu4RR1m55HtBXejO0sBt+qw79axN+Fiw==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0" + } + } + } + }, + "fileset": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz", + "integrity": "sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=", + "dev": true, + "requires": { + "glob": "^7.0.3", + "minimatch": "^3.0.3" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + } + }, + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "flatted": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz", + "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", + "dev": true + }, + "flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" + } + }, + "follow-redirects": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.7.0.tgz", + "integrity": "sha512-m/pZQy4Gj287eNy94nivy5wchN3Kp+Q5WgUPNy5lJSZ3sgkVKSYV/ZChMAQVIgx1SqfZ2zBZtPA2YlXIWxxJOQ==", + "dev": true, + "requires": { + "debug": "^3.2.6" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "font-awesome": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz", + "integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM=" + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "dev": true, + "requires": { + "for-in": "^1.0.1" + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "dev": true + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true + }, + "from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "fs-minipass": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.6.tgz", + "integrity": "sha512-crhvyXcMejjv3Z5d2Fa9sf5xLYVCF5O1c71QxbVnbLsmYMBEvDAftewesN/HhY03YRoA7zOMxjNGrF5svGaaeQ==", + "dev": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs-write-stream-atomic": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", + "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", + "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", + "dev": true, + "optional": true, + "requires": { + "nan": "^2.12.1", + "node-pre-gyp": "^0.12.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "debug": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "^2.1.1" + } + }, + "deep-extend": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.24", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true, + "optional": true + }, + "minipass": { + "version": "2.3.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "needle": { + "version": "2.3.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "^4.1.0", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.12.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.4.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "optional": true + }, + "semver": { + "version": "5.7.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "yallist": { + "version": "3.0.3", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "genfun": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/genfun/-/genfun-5.0.0.tgz", + "integrity": "sha512-KGDOARWVga7+rnB3z9Sd2Letx515owfk0hSxHGuqjANb1M+x2bGZGqHLiozPsYMdM2OubeMni/Hpwmjq6qIUhA==", + "dev": true + }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + }, + "dependencies": { + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + }, + "globby": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz", + "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "dir-glob": "^2.0.0", + "glob": "^7.1.2", + "ignore": "^3.3.5", + "pify": "^3.0.0", + "slash": "^1.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } + } + }, + "graceful-fs": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.1.tgz", + "integrity": "sha512-b9usnbDGnD928gJB3LrCmxoibr3VE4U2SMo5PBuBnokWyDADTqDPXg4YpwKF1trpH+UbGp7QLicO3+aWEy0+mw==", + "dev": true + }, + "hammerjs": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz", + "integrity": "sha1-BO93hiz/K7edMPdpIJWTAiK/YPE=" + }, + "handle-thing": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.0.tgz", + "integrity": "sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ==", + "dev": true + }, + "handlebars": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz", + "integrity": "sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==", + "dev": true, + "requires": { + "neo-async": "^2.6.0", + "optimist": "^0.6.1", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "dev": true, + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-binary2": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", + "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", + "dev": true, + "requires": { + "isarray": "2.0.1" + }, + "dependencies": { + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", + "dev": true + } + } + }, + "has-cors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", + "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hash-base": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true, + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "hosted-git-info": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.2.tgz", + "integrity": "sha512-CyjlXII6LMsPMyUzxpTt8fzh5QwzGqPmQXgY/Jyf4Zfp27t/FvfhwoE/8laaMUcMy816CkWF20I7NeQhwwY88w==", + "dev": true, + "requires": { + "lru-cache": "^5.1.1" + }, + "dependencies": { + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "dev": true + } + } + }, + "hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "html-entities": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz", + "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=", + "dev": true + }, + "http-cache-semantics": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", + "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==", + "dev": true + }, + "http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=", + "dev": true + }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + } + } + }, + "http-parser-js": { + "version": "0.4.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.10.tgz", + "integrity": "sha1-ksnBN0w1CF912zWexWzCV8u5P6Q=", + "dev": true + }, + "http-proxy": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz", + "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", + "dev": true, + "requires": { + "eventemitter3": "^3.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, + "http-proxy-agent": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", + "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", + "dev": true, + "requires": { + "agent-base": "4", + "debug": "3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "http-proxy-middleware": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz", + "integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==", + "dev": true, + "requires": { + "http-proxy": "^1.17.0", + "is-glob": "^4.0.0", + "lodash": "^4.17.11", + "micromatch": "^3.1.10" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", + "dev": true + }, + "https-proxy-agent": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.2.tgz", + "integrity": "sha512-c8Ndjc9Bkpfx/vCJueCPy0jlP4ccCCSNDp8xwCZzPjKJUm+B+u9WX2x98Qx4n1PiMNTWo3D7KK5ifNV/yJyRzg==", + "dev": true, + "requires": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", + "dev": true, + "requires": { + "ms": "^2.0.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", + "dev": true + }, + "iferr": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", + "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", + "dev": true + }, + "ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "dev": true + }, + "ignore-walk": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", + "dev": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=", + "dev": true, + "optional": true + }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=", + "dev": true + }, + "import-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", + "integrity": "sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=", + "dev": true, + "requires": { + "import-from": "^2.1.0" + } + }, + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "dev": true, + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + } + }, + "import-from": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz", + "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=", + "dev": true, + "requires": { + "resolve-from": "^3.0.0" + } + }, + "import-local": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", + "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "dev": true, + "requires": { + "pkg-dir": "^3.0.0", + "resolve-cwd": "^2.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", + "dev": true + }, + "infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true + }, + "inquirer": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.0.tgz", + "integrity": "sha512-scfHejeG/lVZSpvCXpsB4j/wQNPM5JC8kiElOI0OUTwmc1RTpXr4H32/HOlQHcZiYl2z2VElwuCVDRG8vFmbnA==", + "dev": true, + "requires": { + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^2.0.0", + "lodash": "^4.17.12", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.4.0", + "string-width": "^2.1.0", + "strip-ansi": "^5.1.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + } + } + } + } + }, + "internal-ip": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz", + "integrity": "sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==", + "dev": true, + "requires": { + "default-gateway": "^4.2.0", + "ipaddr.js": "^1.9.0" + } + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, + "ip": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", + "dev": true + }, + "ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", + "dev": true + }, + "ipaddr.js": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", + "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==", + "dev": true + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "dev": true + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", + "dev": true + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-finite": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", + "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", + "dev": true, + "requires": { + "is-path-inside": "^1.0.0" + } + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "requires": { + "path-is-inside": "^1.0.1" + } + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dev": true, + "requires": { + "has": "^1.0.1" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-symbol": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.0" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isbinaryfile": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.3.tgz", + "integrity": "sha512-8cJBL5tTd2OS0dM4jz07wQd5g0dCCqIhUxPIGtZfa5L6hWlvV5MHTITy/DBAsF+Oe2LS1X3krBUhNwaGUWpWxw==", + "dev": true, + "requires": { + "buffer-alloc": "^1.2.0" + } + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "istanbul-api": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-2.1.6.tgz", + "integrity": "sha512-x0Eicp6KsShG1k1rMgBAi/1GgY7kFGEBwQpw3PXGEmu+rBcBNhqU8g2DgY9mlepAsLPzrzrbqSgCGANnki4POA==", + "dev": true, + "requires": { + "async": "^2.6.2", + "compare-versions": "^3.4.0", + "fileset": "^2.0.3", + "istanbul-lib-coverage": "^2.0.5", + "istanbul-lib-hook": "^2.0.7", + "istanbul-lib-instrument": "^3.3.0", + "istanbul-lib-report": "^2.0.8", + "istanbul-lib-source-maps": "^3.0.6", + "istanbul-reports": "^2.2.4", + "js-yaml": "^3.13.1", + "make-dir": "^2.1.0", + "minimatch": "^3.0.4", + "once": "^1.4.0" + }, + "dependencies": { + "istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", + "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", + "dev": true, + "requires": { + "@babel/generator": "^7.4.0", + "@babel/parser": "^7.4.3", + "@babel/template": "^7.4.0", + "@babel/traverse": "^7.4.3", + "@babel/types": "^7.4.0", + "istanbul-lib-coverage": "^2.0.5", + "semver": "^6.0.0" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } + } + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "istanbul-instrumenter-loader": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-instrumenter-loader/-/istanbul-instrumenter-loader-3.0.1.tgz", + "integrity": "sha512-a5SPObZgS0jB/ixaKSMdn6n/gXSrK2S6q/UfRJBT3e6gQmVjwZROTODQsYW5ZNwOu78hG62Y3fWlebaVOL0C+w==", + "dev": true, + "requires": { + "convert-source-map": "^1.5.0", + "istanbul-lib-instrument": "^1.7.3", + "loader-utils": "^1.1.0", + "schema-utils": "^0.3.0" + }, + "dependencies": { + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, + "requires": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true + }, + "schema-utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.3.0.tgz", + "integrity": "sha1-9YdyIs4+kx7a4DnxfrNxbnE3+M8=", + "dev": true, + "requires": { + "ajv": "^5.0.0" + } + } + } + }, + "istanbul-lib-coverage": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz", + "integrity": "sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==", + "dev": true + }, + "istanbul-lib-hook": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-2.0.7.tgz", + "integrity": "sha512-vrRztU9VRRFDyC+aklfLoeXyNdTfga2EI3udDGn4cZ6fpSXpHLV9X6CHvfoMCPtggg8zvDDmC4b9xfu0z6/llA==", + "dev": true, + "requires": { + "append-transform": "^1.0.0" + } + }, + "istanbul-lib-instrument": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz", + "integrity": "sha512-aWHxfxDqvh/ZlxR8BBaEPVSWDPUkGD63VjGQn3jcw8jCp7sHEMKcrj4xfJn/ABzdMEHiQNyvDQhqm5o8+SQg7A==", + "dev": true, + "requires": { + "babel-generator": "^6.18.0", + "babel-template": "^6.16.0", + "babel-traverse": "^6.18.0", + "babel-types": "^6.18.0", + "babylon": "^6.18.0", + "istanbul-lib-coverage": "^1.2.1", + "semver": "^5.3.0" + } + }, + "istanbul-lib-report": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz", + "integrity": "sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "supports-color": "^6.1.0" + }, + "dependencies": { + "istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", + "dev": true + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + } + } + }, + "istanbul-lib-source-maps": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", + "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "rimraf": "^2.6.3", + "source-map": "^0.6.1" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", + "dev": true + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "istanbul-reports": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.6.tgz", + "integrity": "sha512-SKi4rnMyLBKe0Jy2uUdx28h8oG7ph2PPuQPvIAh31d+Ci+lSiEu4C+h3oBPuJ9+mPKhOyW0M8gY4U5NM1WLeXA==", + "dev": true, + "requires": { + "handlebars": "^4.1.2" + } + }, + "jasmine": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz", + "integrity": "sha1-awicChFXax8W3xG4AUbZHU6Lij4=", + "dev": true, + "requires": { + "exit": "^0.1.2", + "glob": "^7.0.6", + "jasmine-core": "~2.8.0" + }, + "dependencies": { + "jasmine-core": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz", + "integrity": "sha1-vMl5rh+f0FcB5F5S5l06XWPxok4=", + "dev": true + } + } + }, + "jasmine-core": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.4.0.tgz", + "integrity": "sha512-HU/YxV4i6GcmiH4duATwAbJQMlE0MsDIR5XmSVxURxKHn3aGAdbY1/ZJFmVRbKtnLwIxxMJD7gYaPsypcbYimg==", + "dev": true + }, + "jasmine-spec-reporter": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-4.2.1.tgz", + "integrity": "sha512-FZBoZu7VE5nR7Nilzy+Np8KuVIOxF4oXDPDknehCYBDE080EnlPu0afdZNmpGDBRCUBv3mj5qgqCRmk6W/K8vg==", + "dev": true, + "requires": { + "colors": "1.1.2" + } + }, + "jasminewd2": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/jasminewd2/-/jasminewd2-2.2.0.tgz", + "integrity": "sha1-43zwsX8ZnM4jvqcbIDk5Uka07E4=", + "dev": true + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "jsesc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", + "dev": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "json3": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz", + "integrity": "sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==", + "dev": true + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "dev": true + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "jszip": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.2.2.tgz", + "integrity": "sha512-NmKajvAFQpbg3taXQXr/ccS2wcucR1AZ+NtyWp2Nq7HHVsXhcJFR8p0Baf32C2yVvBylFWVeKf+WI2AnvlPhpA==", + "dev": true, + "requires": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "set-immediate-shim": "~1.0.1" + } + }, + "karma": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/karma/-/karma-4.2.0.tgz", + "integrity": "sha512-fmCuxN1rwJxTdZfOXK5LjlmS4Ana/OvzNMpkyLL/TLE8hmgSkpVpMYQ7RTVa8TNKRVQDZNl5W1oF5cfKfgIMlA==", + "dev": true, + "requires": { + "bluebird": "^3.3.0", + "body-parser": "^1.16.1", + "braces": "^3.0.2", + "chokidar": "^3.0.0", + "colors": "^1.1.0", + "connect": "^3.6.0", + "core-js": "^3.1.3", + "di": "^0.0.1", + "dom-serialize": "^2.2.0", + "flatted": "^2.0.0", + "glob": "^7.1.1", + "graceful-fs": "^4.1.2", + "http-proxy": "^1.13.0", + "isbinaryfile": "^3.0.0", + "lodash": "^4.17.11", + "log4js": "^4.0.0", + "mime": "^2.3.1", + "minimatch": "^3.0.2", + "optimist": "^0.6.1", + "qjobs": "^1.1.4", + "range-parser": "^1.2.0", + "rimraf": "^2.6.0", + "safe-buffer": "^5.0.1", + "socket.io": "2.1.1", + "source-map": "^0.6.1", + "tmp": "0.0.33", + "useragent": "2.3.0" + }, + "dependencies": { + "anymatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.0.3.tgz", + "integrity": "sha512-c6IvoeBECQlMVuYUjSwimnhmztImpErfxJzWZhIQinIvQWoGOnB0dLIgifbPHQt5heS6mNlaZG16f06H3C8t1g==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "binary-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", + "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", + "dev": true + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chokidar": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.0.2.tgz", + "integrity": "sha512-c4PR2egjNjI1um6bamCQ6bUNPDiyofNQruHvKgHQ4gDUP/ITSVSzNsiI5OWtHOsX323i5ha/kk4YmOZ1Ktg7KA==", + "dev": true, + "requires": { + "anymatch": "^3.0.1", + "braces": "^3.0.2", + "fsevents": "^2.0.6", + "glob-parent": "^5.0.0", + "is-binary-path": "^2.1.0", + "is-glob": "^4.0.1", + "normalize-path": "^3.0.0", + "readdirp": "^3.1.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "fsevents": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.0.7.tgz", + "integrity": "sha512-a7YT0SV3RB+DjYcppwVDLtn13UQnmg0SWZS7ezZD0UjnLwXmy8Zm21GMVGLaFGimIqcvyMQaOJBrop8MyOp1kQ==", + "dev": true, + "optional": true + }, + "glob-parent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.0.0.tgz", + "integrity": "sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "mime": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", + "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "readdirp": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.1.1.tgz", + "integrity": "sha512-XXdSXZrQuvqoETj50+JAitxz1UPdt5dupjT6T5nVB+WvjMv2XKYj+s7hPeAVCXvmJrL36O4YYyWlIC3an2ePiQ==", + "dev": true, + "requires": { + "picomatch": "^2.0.4" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } + } + }, + "karma-chrome-launcher": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.0.0.tgz", + "integrity": "sha512-u/PnVgDOP97AUe/gJeABlC6Wa6aQ83MZsm0JgsJQ5bGQ9XcXON/7b2aRhl59A62Zom+q3PFveBkczc7E1RT7TA==", + "dev": true, + "requires": { + "which": "^1.2.1" + } + }, + "karma-coverage-istanbul-reporter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-2.1.0.tgz", + "integrity": "sha512-UH0mXPJFJyK5uiK7EkwGtQ8f30lCBAfqRResnZ4pzLJ04SOp4SPlYkmwbbZ6iVJ6sQFVzlDUXlntBEsLRdgZpg==", + "dev": true, + "requires": { + "istanbul-api": "^2.1.6", + "minimatch": "^3.0.4" + } + }, + "karma-jasmine": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-2.0.1.tgz", + "integrity": "sha512-iuC0hmr9b+SNn1DaUD2QEYtUxkS1J+bSJSn7ejdEexs7P8EYvA1CWkEdrDQ+8jVH3AgWlCNwjYsT1chjcNW9lA==", + "dev": true, + "requires": { + "jasmine-core": "^3.3" + } + }, + "karma-jasmine-html-reporter": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-1.4.2.tgz", + "integrity": "sha512-7g0gPj8+9JepCNJR9WjDyQ2RkZ375jpdurYQyAYv8PorUCadepl8vrD6LmMqOGcM17cnrynBawQYZHaumgDjBw==", + "dev": true + }, + "karma-source-map-support": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz", + "integrity": "sha512-RsBECncGO17KAoJCYXjv+ckIz+Ii9NCi+9enk+rq6XC81ezYkb4/RHE6CTXdA7IOJqoF3wcaLfVG0CPmE5ca6A==", + "dev": true, + "requires": { + "source-map-support": "^0.5.5" + } + }, + "killable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", + "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "less": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/less/-/less-3.9.0.tgz", + "integrity": "sha512-31CmtPEZraNUtuUREYjSqRkeETFdyEHSEPAGq4erDlUXtda7pzNmctdljdIagSb589d/qXGWiiP31R5JVf+v0w==", + "dev": true, + "requires": { + "clone": "^2.1.2", + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "mime": "^1.4.1", + "mkdirp": "^0.5.0", + "promise": "^7.1.1", + "request": "^2.83.0", + "source-map": "~0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + } + } + }, + "less-loader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-5.0.0.tgz", + "integrity": "sha512-bquCU89mO/yWLaUq0Clk7qCsKhsF/TZpJUzETRvJa9KSVEL9SO3ovCvdEHISBhrC81OwC8QSVX7E0bzElZj9cg==", + "dev": true, + "requires": { + "clone": "^2.1.1", + "loader-utils": "^1.1.0", + "pify": "^4.0.1" + } + }, + "license-webpack-plugin": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-2.1.1.tgz", + "integrity": "sha512-TiarZIg5vkQ2rGdYJn2+5YxO/zqlqjpK5IVglr7OfmrN1sBCakS+PQrsP2uC5gtve1ZDb9WMSUMlmHDQ0FoW4w==", + "dev": true, + "requires": { + "@types/webpack-sources": "^0.1.5", + "webpack-sources": "^1.2.0" + } + }, + "lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, + "requires": { + "immediate": "~3.0.5" + } + }, + "loader-runner": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", + "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", + "dev": true + }, + "loader-utils": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", + "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^2.0.0", + "json5": "^1.0.1" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, + "lodash.tail": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.tail/-/lodash.tail-4.1.1.tgz", + "integrity": "sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=", + "dev": true + }, + "log4js": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-4.5.1.tgz", + "integrity": "sha512-EEEgFcE9bLgaYUKuozyFfytQM2wDHtXn4tAN41pkaxpNjAykv11GVdeI4tHtmPWW4Xrgh9R/2d7XYghDVjbKKw==", + "dev": true, + "requires": { + "date-format": "^2.0.0", + "debug": "^4.1.1", + "flatted": "^2.0.0", + "rfdc": "^1.1.4", + "streamroller": "^1.0.6" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "loglevel": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.3.tgz", + "integrity": "sha512-LoEDv5pgpvWgPF4kNYuIp0qqSJVWak/dML0RY74xlzMZiT9w77teNAwKYKWBTYjlokMirg+o3jBwp+vlLrcfAA==", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "magic-string": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.3.tgz", + "integrity": "sha512-6QK0OpF/phMz0Q2AxILkX2mFhi7m+WMwTRg0LQKq/WBB0cDP4rYH3Wp4/d3OTXlrPLVJT/RFqj8tFeAR4nk8AA==", + "dev": true, + "requires": { + "sourcemap-codec": "^1.4.4" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "make-error": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", + "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", + "dev": true + }, + "make-fetch-happen": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-5.0.0.tgz", + "integrity": "sha512-nFr/vpL1Jc60etMVKeaLOqfGjMMb3tAHFVJWxHOFCFS04Zmd7kGlMxo0l1tzfhoQje0/UPnd0X8OeGUiXXnfPA==", + "dev": true, + "requires": { + "agentkeepalive": "^3.4.1", + "cacache": "^12.0.0", + "http-cache-semantics": "^3.8.1", + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.1", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "node-fetch-npm": "^2.0.2", + "promise-retry": "^1.1.1", + "socks-proxy-agent": "^4.0.0", + "ssri": "^6.0.0" + }, + "dependencies": { + "cacache": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.2.tgz", + "integrity": "sha512-ifKgxH2CKhJEg6tNdAwziu6Q33EvuG26tYcda6PT3WKisZcYDXsnEdnRv67Po3yCzFfaSoMjGZzJyD2c3DT1dg==", + "dev": true, + "requires": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "mississippi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", + "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", + "dev": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "ssri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "dev": true + } + } + }, + "make-plural": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-4.3.0.tgz", + "integrity": "sha512-xTYd4JVHpSCW+aqDof6w/MebaMVNTVYBZhbB/vi513xXdiPT92JMVCo0Jq8W2UZnzYRFeVbQiQ+I25l13JuKvA==", + "requires": { + "minimist": "^1.2.0" + } + }, + "mamacro": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/mamacro/-/mamacro-0.0.3.tgz", + "integrity": "sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA==", + "dev": true + }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dev": true, + "requires": { + "p-defer": "^1.0.0" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "material-design-icons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/material-design-icons/-/material-design-icons-3.0.1.tgz", + "integrity": "sha1-mnHEh0chjrylHlGmbaaCA4zct78=" + }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true + }, + "mem": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + } + }, + "memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "dev": true + }, + "messageformat": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/messageformat/-/messageformat-2.3.0.tgz", + "integrity": "sha512-uTzvsv0lTeQxYI2y1NPa1lItL5VRI8Gb93Y2K2ue5gBPyrbJxfDi/EYWxh2PKv5yO42AJeeqblS9MJSh/IEk4w==", + "requires": { + "make-plural": "^4.3.0", + "messageformat-formatters": "^2.0.1", + "messageformat-parser": "^4.1.2" + } + }, + "messageformat-formatters": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/messageformat-formatters/-/messageformat-formatters-2.0.1.tgz", + "integrity": "sha512-E/lQRXhtHwGuiQjI7qxkLp8AHbMD5r2217XNe/SREbBlSawe0lOqsFb7rflZJmlQFSULNLIqlcjjsCPlB3m3Mg==" + }, + "messageformat-parser": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/messageformat-parser/-/messageformat-parser-4.1.2.tgz", + "integrity": "sha512-7dWuifeyldz7vhEuL96Kwq1fhZXBW+TUfbnHN4UCrCxoXQTYjHnR78eI66Gk9LaLLsAvzPNVJBaa66DRfFNaiA==" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", + "dev": true + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "dev": true, + "requires": { + "mime-db": "1.40.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "mini-css-extract-plugin": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.8.0.tgz", + "integrity": "sha512-MNpRGbNA52q6U92i0qbVpQNsgk7LExy41MdAlG84FeytfDOtRIf/mCHdEgG8rpTKOaNKiqUnZdlptF469hxqOw==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "normalize-url": "1.9.1", + "schema-utils": "^1.0.0", + "webpack-sources": "^1.1.0" + } + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + }, + "minipass": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", + "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + }, + "dependencies": { + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "dev": true + } + } + }, + "minizlib": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", + "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", + "dev": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mississippi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", + "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", + "dev": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + }, + "dependencies": { + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mixin-object": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", + "integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=", + "dev": true, + "requires": { + "for-in": "^0.1.3", + "is-extendable": "^0.1.1" + }, + "dependencies": { + "for-in": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", + "integrity": "sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=", + "dev": true + } + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + } + } + }, + "move-concurrently": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", + "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", + "dev": true, + "requires": { + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "multicast-dns": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", + "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", + "dev": true, + "requires": { + "dns-packet": "^1.3.1", + "thunky": "^1.0.2" + } + }, + "multicast-dns-service-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", + "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", + "dev": true + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", + "dev": true, + "optional": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "dev": true + }, + "neo-async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", + "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", + "dev": true + }, + "ngrx-store-freeze": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/ngrx-store-freeze/-/ngrx-store-freeze-0.2.4.tgz", + "integrity": "sha512-90awpbbMa/x2H81eWWYniyli3LJ1PZU/FaztL10d9Rp/4kw2+97pqyLjdxSPxcOv9St//m9kfuWZ7gyoVDjgcg==", + "dev": true, + "requires": { + "deep-freeze-strict": "^1.1.1" + } + }, + "ngx-clipboard": { + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/ngx-clipboard/-/ngx-clipboard-12.2.0.tgz", + "integrity": "sha512-1moe/2dIUUSGVgTTeItOY8fcULPl47ilSSF2+88Adf91PYMPmilZv7jljlQaLADck5sDDsvYlTTZZTgDqjRHgA==", + "requires": { + "ngx-window-token": "^2.0.0", + "tslib": "^1.9.0" + } + }, + "ngx-translate-messageformat-compiler": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/ngx-translate-messageformat-compiler/-/ngx-translate-messageformat-compiler-4.5.0.tgz", + "integrity": "sha512-FasG50bAcF39d+tRa4QRqxtSY9N2xfPelJHSn/OpZF4uU08ioC+eERo4x9HcOXZDtZH2vrbw9GNv/Xwzg/Irbw==", + "requires": { + "tslib": "^1.9.0" + } + }, + "ngx-window-token": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ngx-window-token/-/ngx-window-token-2.0.1.tgz", + "integrity": "sha512-rvqdqJEfnWXQFU5fyfYt06E10tR/UtFOYdF3QebfcOh5VIJhnTKiprX8e4B9OrX7WEVFm9BT8uV72xXcEgsaKA==", + "requires": { + "tslib": "^1.9.0" + } + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node-fetch-npm": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-fetch-npm/-/node-fetch-npm-2.0.2.tgz", + "integrity": "sha512-nJIxm1QmAj4v3nfCvEeCrYSoVwXyxLnaPBK5W1W5DGEJwjlKuC2VEUycGw5oxk+4zZahRrB84PUJJgEmhFTDFw==", + "dev": true, + "requires": { + "encoding": "^0.1.11", + "json-parse-better-errors": "^1.0.0", + "safe-buffer": "^5.1.1" + } + }, + "node-forge": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.5.tgz", + "integrity": "sha512-MmbQJ2MTESTjt3Gi/3yG1wGpIMhUfcIypUCGtTizFR9IiccFwxSpfp0vtIZlkFclEqERemxfnSdZEMR9VqqEFQ==", + "dev": true + }, + "node-libs-browser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", + "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", + "dev": true, + "requires": { + "assert": "^1.1.1", + "browserify-zlib": "^0.2.0", + "buffer": "^4.3.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.11.0", + "domain-browser": "^1.1.1", + "events": "^3.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "0.0.1", + "process": "^0.11.10", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.3.3", + "stream-browserify": "^2.0.1", + "stream-http": "^2.7.2", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", + "tty-browserify": "0.0.0", + "url": "^0.11.0", + "util": "^0.11.0", + "vm-browserify": "^1.0.1" + } + }, + "node-releases": { + "version": "1.1.26", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.26.tgz", + "integrity": "sha512-fZPsuhhUHMTlfkhDLGtfY80DSJTjOcx+qD1j5pqPkuhUHVS7xHZIg9EE4DHK8O3f0zTxXHX5VIkDG8pu98/wfQ==", + "dev": true, + "requires": { + "semver": "^5.3.0" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", + "dev": true + }, + "normalize-url": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", + "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", + "dev": true, + "requires": { + "object-assign": "^4.0.1", + "prepend-http": "^1.0.0", + "query-string": "^4.1.0", + "sort-keys": "^1.0.0" + } + }, + "npm-bundled": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.6.tgz", + "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==", + "dev": true + }, + "npm-package-arg": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.0.tgz", + "integrity": "sha512-zYbhP2k9DbJhA0Z3HKUePUgdB1x7MfIfKssC+WLPFMKTBZKpZh5m13PgexJjCq6KW7j17r0jHWcCpxEqnnncSA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.6.0", + "osenv": "^0.1.5", + "semver": "^5.5.0", + "validate-npm-package-name": "^3.0.0" + } + }, + "npm-packlist": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.4.tgz", + "integrity": "sha512-zTLo8UcVYtDU3gdeaFu2Xu0n0EvelfHDGuqtNIn5RO7yQj4H1TqNdBc/yZjxnWA0PVB8D3Woyp0i5B43JwQ6Vw==", + "dev": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npm-pick-manifest": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-2.2.3.tgz", + "integrity": "sha512-+IluBC5K201+gRU85vFlUwX3PFShZAbAgDNp2ewJdWMVSppdo/Zih0ul2Ecky/X7b51J7LrrUAP+XOmOCvYZqA==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1", + "npm-package-arg": "^6.0.0", + "semver": "^5.4.1" + } + }, + "npm-registry-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-4.0.0.tgz", + "integrity": "sha512-Jllq35Jag8dtv0M17ue74XtdQTyqKzuAYGiX9mAjOhkmNjib3bBUgK6mUY61+AHnXeSRobQkpY3/xIOS/omptw==", + "dev": true, + "requires": { + "JSONStream": "^1.3.4", + "bluebird": "^3.5.1", + "figgy-pudding": "^3.4.1", + "lru-cache": "^5.1.1", + "make-fetch-happen": "^5.0.0", + "npm-package-arg": "^6.1.0" + }, + "dependencies": { + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "dev": true + } + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "num2fraction": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", + "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", + "dev": true + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "object-component": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", + "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=", + "dev": true + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", + "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + }, + "dependencies": { + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true + } + } + }, + "open": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/open/-/open-6.4.0.tgz", + "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", + "dev": true, + "requires": { + "is-wsl": "^1.1.0" + } + }, + "opn": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", + "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==", + "dev": true, + "requires": { + "is-wsl": "^1.1.0" + } + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + }, + "dependencies": { + "minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", + "dev": true + } + } + }, + "original": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", + "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", + "dev": true, + "requires": { + "url-parse": "^1.4.3" + } + }, + "os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", + "dev": true + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "dev": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", + "dev": true + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true + }, + "p-retry": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-3.0.1.tgz", + "integrity": "sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w==", + "dev": true, + "requires": { + "retry": "^0.12.0" + }, + "dependencies": { + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", + "dev": true + } + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "pacote": { + "version": "9.5.4", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-9.5.4.tgz", + "integrity": "sha512-nWr0ari6E+apbdoN0hToTKZElO5h4y8DGFa2pyNA5GQIdcP0imC96bA0bbPw1gpeguVIiUgHHaAlq/6xfPp8Qw==", + "dev": true, + "requires": { + "bluebird": "^3.5.3", + "cacache": "^12.0.0", + "figgy-pudding": "^3.5.1", + "get-stream": "^4.1.0", + "glob": "^7.1.3", + "lru-cache": "^5.1.1", + "make-fetch-happen": "^5.0.0", + "minimatch": "^3.0.4", + "minipass": "^2.3.5", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "normalize-package-data": "^2.4.0", + "npm-package-arg": "^6.1.0", + "npm-packlist": "^1.1.12", + "npm-pick-manifest": "^2.2.3", + "npm-registry-fetch": "^4.0.0", + "osenv": "^0.1.5", + "promise-inflight": "^1.0.1", + "promise-retry": "^1.1.1", + "protoduck": "^5.0.1", + "rimraf": "^2.6.2", + "safe-buffer": "^5.1.2", + "semver": "^5.6.0", + "ssri": "^6.0.1", + "tar": "^4.4.8", + "unique-filename": "^1.1.1", + "which": "^1.3.1" + }, + "dependencies": { + "cacache": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.2.tgz", + "integrity": "sha512-ifKgxH2CKhJEg6tNdAwziu6Q33EvuG26tYcda6PT3WKisZcYDXsnEdnRv67Po3yCzFfaSoMjGZzJyD2c3DT1dg==", + "dev": true, + "requires": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + }, + "dependencies": { + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "mississippi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", + "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", + "dev": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "ssri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "tar": { + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.10.tgz", + "integrity": "sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA==", + "dev": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.5", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" + } + }, + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "dev": true + } + } + }, + "pako": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz", + "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==", + "dev": true + }, + "parallel-transform": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz", + "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=", + "dev": true, + "requires": { + "cyclist": "~0.2.2", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" + } + }, + "parse-asn1": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.4.tgz", + "integrity": "sha512-Qs5duJcuvNExRfFZ99HDD3z4mAi3r9Wl/FOjEOijlxwCZs7E7mW2vjTpgQ4J8LpTF8x5v+1Vn5UQFejmWT11aw==", + "dev": true, + "requires": { + "asn1.js": "^4.0.0", + "browserify-aes": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "parse5": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz", + "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==", + "optional": true + }, + "parseqs": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", + "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", + "dev": true, + "requires": { + "better-assert": "~1.0.0" + } + }, + "parseuri": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", + "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", + "dev": true, + "requires": { + "better-assert": "~1.0.0" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", + "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", + "dev": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", + "dev": true + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } + } + }, + "pbkdf2": { + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", + "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", + "dev": true, + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "picomatch": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.0.7.tgz", + "integrity": "sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==", + "dev": true + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + }, + "portfinder": { + "version": "1.0.21", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.21.tgz", + "integrity": "sha512-ESabpDCzmBS3ekHbmpAIiESq3udRsCBGiBZLsC+HgBKv2ezb0R4oG+7RnYEVZ/ZCfhel5Tx3UzdNWA0Lox2QCA==", + "dev": true, + "requires": { + "async": "^1.5.2", + "debug": "^2.2.0", + "mkdirp": "0.5.x" + }, + "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + } + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "postcss-import": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-12.0.1.tgz", + "integrity": "sha512-3Gti33dmCjyKBgimqGxL3vcV8w9+bsHwO5UrBawp796+jdardbcFl4RP5w/76BwNL7aGzpKstIfF9I+kdE8pTw==", + "dev": true, + "requires": { + "postcss": "^7.0.1", + "postcss-value-parser": "^3.2.3", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-load-config": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.0.tgz", + "integrity": "sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q==", + "dev": true, + "requires": { + "cosmiconfig": "^5.0.0", + "import-cwd": "^2.0.0" + } + }, + "postcss-loader": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-3.0.0.tgz", + "integrity": "sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "postcss": "^7.0.0", + "postcss-load-config": "^2.0.0", + "schema-utils": "^1.0.0" + } + }, + "postcss-value-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.2.tgz", + "integrity": "sha512-LmeoohTpp/K4UiyQCwuGWlONxXamGzCMtFxLq4W1nZVGIQLYvMCJx3yAF9qyyuFpflABI9yVdtJAqbihOsCsJQ==", + "dev": true + }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", + "dev": true + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "dev": true, + "optional": true, + "requires": { + "asap": "~2.0.3" + } + }, + "promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", + "dev": true + }, + "promise-retry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-1.1.1.tgz", + "integrity": "sha1-ZznpaOMFHaIM5kl/srUPaRHfPW0=", + "dev": true, + "requires": { + "err-code": "^1.0.0", + "retry": "^0.10.0" + } + }, + "protoduck": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/protoduck/-/protoduck-5.0.1.tgz", + "integrity": "sha512-WxoCeDCoCBY55BMvj4cAEjdVUFGRWed9ZxPlqTKYyw1nDDTQ4pqmnIMAGfJlg7Dx35uB/M+PHJPTmGOvaCaPTg==", + "dev": true, + "requires": { + "genfun": "^5.0.0" + } + }, + "protractor": { + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/protractor/-/protractor-5.4.2.tgz", + "integrity": "sha512-zlIj64Cr6IOWP7RwxVeD8O4UskLYPoyIcg0HboWJL9T79F1F0VWtKkGTr/9GN6BKL+/Q/GmM7C9kFVCfDbP5sA==", + "dev": true, + "requires": { + "@types/q": "^0.0.32", + "@types/selenium-webdriver": "^3.0.0", + "blocking-proxy": "^1.0.0", + "browserstack": "^1.5.1", + "chalk": "^1.1.3", + "glob": "^7.0.3", + "jasmine": "2.8.0", + "jasminewd2": "^2.1.0", + "optimist": "~0.6.0", + "q": "1.4.1", + "saucelabs": "^1.5.0", + "selenium-webdriver": "3.6.0", + "source-map-support": "~0.4.0", + "webdriver-js-extender": "2.1.0", + "webdriver-manager": "^12.0.6" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "del": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "dev": true, + "requires": { + "globby": "^5.0.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "rimraf": "^2.2.8" + } + }, + "globby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "dev": true, + "requires": { + "source-map": "^0.5.6" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "webdriver-manager": { + "version": "12.1.6", + "resolved": "https://registry.npmjs.org/webdriver-manager/-/webdriver-manager-12.1.6.tgz", + "integrity": "sha512-B1mOycNCrbk7xODw7Jgq/mdD3qzPxMaTsnKIQDy2nXlQoyjTrJTTD0vRpEZI9b8RibPEyQvh9zIZ0M1mpOxS3w==", + "dev": true, + "requires": { + "adm-zip": "^0.4.9", + "chalk": "^1.1.1", + "del": "^2.2.0", + "glob": "^7.0.3", + "ini": "^1.3.4", + "minimist": "^1.2.0", + "q": "^1.4.1", + "request": "^2.87.0", + "rimraf": "^2.5.2", + "semver": "^5.3.0", + "xml2js": "^0.4.17" + } + } + } + }, + "proxy-addr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", + "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", + "dev": true, + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.0" + } + }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "psl": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.3.0.tgz", + "integrity": "sha512-avHdspHO+9rQTLbv1RO+MPYeP/SzsCoxofjVnHanETfQhTJrmB0HlDoW+EiN/R+C0BZ+gERab9NY0lPN2TxNag==", + "dev": true + }, + "public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + }, + "q": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz", + "integrity": "sha1-VXBbzZPF82c1MMLCy8DCs63cKG4=", + "dev": true + }, + "qjobs": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", + "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", + "dev": true + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, + "query-string": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", + "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", + "dev": true, + "requires": { + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" + } + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" + }, + "querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true + }, + "querystringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", + "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "requires": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "dev": true, + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "dependencies": { + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "dev": true + } + } + }, + "raw-loader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-1.0.0.tgz", + "integrity": "sha512-Uqy5AqELpytJTRxYT4fhltcKPj0TyaEpzJDcGz7DFJi+pQOOi3GjR/DOdxTkTsF+NzhnldIoG6TORaBlInUuqA==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "schema-utils": "^1.0.0" + } + }, + "read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha1-5mTvMRYRZsl1HNvo28+GtftY93Q=", + "dev": true, + "requires": { + "pify": "^2.3.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "read-package-json": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.0.13.tgz", + "integrity": "sha512-/1dZ7TRZvGrYqE0UAfN6qQb5GYBsNcqS1C0tNK601CFOJmtHI7NIGXwetEPU/OtoFHZL3hDxm4rolFFVE9Bnmg==", + "dev": true, + "requires": { + "glob": "^7.1.1", + "graceful-fs": "^4.1.2", + "json-parse-better-errors": "^1.0.1", + "normalize-package-data": "^2.0.0", + "slash": "^1.0.0" + } + }, + "read-package-tree": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/read-package-tree/-/read-package-tree-5.3.1.tgz", + "integrity": "sha512-mLUDsD5JVtlZxjSlPPx1RETkNjjvQYuweKwNVt1Sn8kP5Jh44pvYuUHCp6xSVDZWbNxVxG5lyZJ921aJH61sTw==", + "dev": true, + "requires": { + "read-package-json": "^2.0.0", + "readdir-scoped-modules": "^1.0.0", + "util-promisify": "^2.1.0" + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdir-scoped-modules": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz", + "integrity": "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==", + "dev": true, + "requires": { + "debuglog": "^1.0.1", + "dezalgo": "^1.0.0", + "graceful-fs": "^4.1.2", + "once": "^1.3.0" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + }, + "reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", + "dev": true + }, + "regenerate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", + "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", + "dev": true + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "regexpu-core": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", + "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=", + "dev": true, + "requires": { + "regenerate": "^1.2.1", + "regjsgen": "^0.2.0", + "regjsparser": "^0.1.4" + } + }, + "regjsgen": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", + "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", + "dev": true + }, + "regjsparser": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", + "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + } + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, + "requires": { + "is-finite": "^1.0.0" + } + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "resolve": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", + "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", + "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "dev": true, + "requires": { + "resolve-from": "^3.0.0" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "retry": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz", + "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=", + "dev": true + }, + "rfdc": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.1.4.tgz", + "integrity": "sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug==", + "dev": true + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true, + "requires": { + "is-promise": "^2.1.0" + } + }, + "run-queue": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", + "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", + "dev": true, + "requires": { + "aproba": "^1.1.1" + } + }, + "rxjs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.2.tgz", + "integrity": "sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg==", + "requires": { + "tslib": "^1.9.0" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "sass": { + "version": "1.22.7", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.22.7.tgz", + "integrity": "sha512-ahREi0AdG7RTovSv14+yd1prQSfIvFcrDpOsth5EQf1+RM7SvOxsSttzNQaFmK1aa/k/3vyYwlYF5l0Xl+6c+g==", + "dev": true, + "requires": { + "chokidar": ">=2.0.0 <4.0.0" + } + }, + "sass-loader": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-7.1.0.tgz", + "integrity": "sha512-+G+BKGglmZM2GUSfT9TLuEp6tzehHPjAMoRRItOojWIqIGPloVCMhNIQuG639eJ+y033PaGTSjLaTHts8Kw79w==", + "dev": true, + "requires": { + "clone-deep": "^2.0.1", + "loader-utils": "^1.0.1", + "lodash.tail": "^4.1.1", + "neo-async": "^2.5.0", + "pify": "^3.0.0", + "semver": "^5.5.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } + } + }, + "saucelabs": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/saucelabs/-/saucelabs-1.5.0.tgz", + "integrity": "sha512-jlX3FGdWvYf4Q3LFfFWS1QvPg3IGCGWxIc8QBFdPTbpTJnt/v17FHXYVAn7C8sHf1yUXo2c7yIM0isDryfYtHQ==", + "dev": true, + "requires": { + "https-proxy-agent": "^2.2.1" + } + }, + "sax": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/sax/-/sax-0.5.8.tgz", + "integrity": "sha1-1HLbIo6zMcJQaw6MFVJK25OdEsE=", + "dev": true + }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + }, + "screenfull": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/screenfull/-/screenfull-4.2.1.tgz", + "integrity": "sha512-PLSp6f5XdhvjCCCO8OjavRfzkSGL3Qmdm7P82bxyU8HDDDBhDV3UckRaYcRa/NDNTYt8YBpzjoLWHUAejmOjLg==" + }, + "select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=", + "dev": true + }, + "selenium-webdriver": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.6.0.tgz", + "integrity": "sha512-WH7Aldse+2P5bbFBO4Gle/nuQOdVwpHMTL6raL3uuBj/vPG07k6uzt3aiahu352ONBr5xXh0hDlM3LhtXPOC4Q==", + "dev": true, + "requires": { + "jszip": "^3.1.3", + "rimraf": "^2.5.4", + "tmp": "0.0.30", + "xml2js": "^0.4.17" + }, + "dependencies": { + "tmp": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.30.tgz", + "integrity": "sha1-ckGdSovn1s51FI/YsyTlk6cRwu0=", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.1" + } + } + } + }, + "selfsigned": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.4.tgz", + "integrity": "sha512-9AukTiDmHXGXWtWjembZ5NDmVvP2695EtpgbCsxCa68w3c88B+alqbmZ4O3hZ4VWGXeGWzEVdvqgAJD8DQPCDw==", + "dev": true, + "requires": { + "node-forge": "0.7.5" + } + }, + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "dev": true + }, + "semver-dsl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/semver-dsl/-/semver-dsl-1.0.1.tgz", + "integrity": "sha1-02eN5VVeimH2Ke7QJTZq5fJzQKA=", + "dev": true, + "requires": { + "semver": "^5.3.0" + } + }, + "semver-intersect": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/semver-intersect/-/semver-intersect-1.4.0.tgz", + "integrity": "sha512-d8fvGg5ycKAq0+I6nfWeCx6ffaWJCsBYU0H2Rq56+/zFePYfT8mXkB3tWBSjR5BerkHNZ5eTPIk1/LBYas35xQ==", + "dev": true, + "requires": { + "semver": "^5.0.0" + } + }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "serialize-javascript": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.7.0.tgz", + "integrity": "sha512-ke8UG8ulpFOxO8f8gRYabHQe/ZntKlcig2Mp+8+URDP1D8vJZ0KUt7LYo07q25Z/+JVSgpr/cui9PIp5H6/+nA==", + "dev": true + }, + "serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", + "dev": true, + "requires": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "dependencies": { + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + } + } + }, + "serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "dev": true, + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "dev": true + }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "dev": true + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "dev": true + }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "shallow-clone": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-1.0.0.tgz", + "integrity": "sha512-oeXreoKR/SyNJtRJMAKPDSvd28OqEwG4eR/xc856cRGBII7gX9lvAqDxusPm0846z/w/hWYjI1NpKwJ00NHzRA==", + "dev": true, + "requires": { + "is-extendable": "^0.1.1", + "kind-of": "^5.0.0", + "mixin-object": "^2.0.1" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "dev": true + }, + "smart-buffer": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.0.2.tgz", + "integrity": "sha512-JDhEpTKzXusOqXZ0BUIdH+CjFdO/CR3tLlf5CN34IypI+xMmXW1uB16OOY8z3cICbJlDAVJzNbwBhNO0wt9OAw==", + "dev": true + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "socket.io": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.1.1.tgz", + "integrity": "sha512-rORqq9c+7W0DAK3cleWNSyfv/qKXV99hV4tZe+gGLfBECw3XEhBy7x85F3wypA9688LKjtwO9pX9L33/xQI8yA==", + "dev": true, + "requires": { + "debug": "~3.1.0", + "engine.io": "~3.2.0", + "has-binary2": "~1.0.2", + "socket.io-adapter": "~1.1.0", + "socket.io-client": "2.1.1", + "socket.io-parser": "~3.2.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "socket.io-adapter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz", + "integrity": "sha1-KoBeihTWNyEk3ZFZrUUC+MsH8Gs=", + "dev": true + }, + "socket.io-client": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.1.1.tgz", + "integrity": "sha512-jxnFyhAuFxYfjqIgduQlhzqTcOEQSn+OHKVfAxWaNWa7ecP7xSNk2Dx/3UEsDcY7NcFafxvNvKPmmO7HTwTxGQ==", + "dev": true, + "requires": { + "backo2": "1.0.2", + "base64-arraybuffer": "0.1.5", + "component-bind": "1.0.0", + "component-emitter": "1.2.1", + "debug": "~3.1.0", + "engine.io-client": "~3.2.0", + "has-binary2": "~1.0.2", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "object-component": "0.0.3", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "socket.io-parser": "~3.2.0", + "to-array": "0.1.4" + }, + "dependencies": { + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "socket.io-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz", + "integrity": "sha512-FYiBx7rc/KORMJlgsXysflWx/RIvtqZbyGLlHZvjfmPTPeuD/I8MaW7cfFrj5tRltICJdgwflhfZ3NVVbVLFQA==", + "dev": true, + "requires": { + "component-emitter": "1.2.1", + "debug": "~3.1.0", + "isarray": "2.0.1" + }, + "dependencies": { + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", + "dev": true + } + } + }, + "sockjs": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz", + "integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==", + "dev": true, + "requires": { + "faye-websocket": "^0.10.0", + "uuid": "^3.0.1" + } + }, + "sockjs-client": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.3.0.tgz", + "integrity": "sha512-R9jxEzhnnrdxLCNln0xg5uGHqMnkhPSTzUZH2eXcR03S/On9Yvoq2wyUZILRUhZCNVu2PmwWVoyuiPz8th8zbg==", + "dev": true, + "requires": { + "debug": "^3.2.5", + "eventsource": "^1.0.7", + "faye-websocket": "~0.11.1", + "inherits": "^2.0.3", + "json3": "^3.3.2", + "url-parse": "^1.4.3" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "faye-websocket": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", + "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "socks": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.3.2.tgz", + "integrity": "sha512-pCpjxQgOByDHLlNqlnh/mNSAxIUkyBBuwwhTcV+enZGbDaClPvHdvm6uvOwZfFJkam7cGhBNbb4JxiP8UZkRvQ==", + "dev": true, + "requires": { + "ip": "^1.1.5", + "smart-buffer": "4.0.2" + } + }, + "socks-proxy-agent": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.2.tgz", + "integrity": "sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg==", + "dev": true, + "requires": { + "agent-base": "~4.2.1", + "socks": "~2.3.2" + }, + "dependencies": { + "agent-base": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", + "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", + "dev": true, + "requires": { + "es6-promisify": "^5.0.0" + } + } + } + }, + "sort-keys": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", + "dev": true, + "requires": { + "is-plain-obj": "^1.0.0" + } + }, + "source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", + "dev": true + }, + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true + }, + "source-map-loader": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-0.2.4.tgz", + "integrity": "sha512-OU6UJUty+i2JDpTItnizPrlpOIBLmQbWMuBg9q5bVtnHACqw1tn9nNwqJLbv0/00JjnJb/Ee5g5WS5vrRv7zIQ==", + "dev": true, + "requires": { + "async": "^2.5.0", + "loader-utils": "^1.1.0" + } + }, + "source-map-resolve": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "dev": true, + "requires": { + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-support": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.10.tgz", + "integrity": "sha512-YfQ3tQFTK/yzlGJuX8pTwa4tifQj4QS2Mj7UegOu8jAz59MqIiMGPXxQhVQiIMNzayuUSF/jEuVnfFF5JqybmQ==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, + "sourcemap-codec": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.6.tgz", + "integrity": "sha512-1ZooVLYFxC448piVLBbtOxFcXwnymH9oUF8nRd3CuYDVvkRBxRl6pB4Mtas5a4drtL+E8LDgFkQNcgIw6tc8Hg==", + "dev": true + }, + "spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "dev": true + }, + "spdy": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.1.tgz", + "integrity": "sha512-HeZS3PBdMA+sZSu0qwpCxl3DeALD5ASx8pAX0jZdKXSpPWbQ6SYGnlg3BBmYLx5LtiZrmkAZfErCm2oECBcioA==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "readable-stream": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", + "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "speed-measure-webpack-plugin": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/speed-measure-webpack-plugin/-/speed-measure-webpack-plugin-1.3.1.tgz", + "integrity": "sha512-qVIkJvbtS9j/UeZumbdfz0vg+QfG/zxonAjzefZrqzkr7xOncLVXkeGbTpzd1gjCBM4PmVNkWlkeTVhgskAGSQ==", + "dev": true, + "requires": { + "chalk": "^2.0.1" + } + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "ssri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true + }, + "stream-browserify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", + "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", + "dev": true, + "requires": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + } + }, + "stream-each": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", + "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + } + }, + "stream-http": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", + "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", + "dev": true, + "requires": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.3.6", + "to-arraybuffer": "^1.0.0", + "xtend": "^4.0.0" + } + }, + "stream-shift": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", + "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", + "dev": true + }, + "streamroller": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-1.0.6.tgz", + "integrity": "sha512-3QC47Mhv3/aZNFpDDVO44qQb9gwB9QggMEE0sQmkTAwBVYdBRWISdsywlkfm5II1Q5y/pmrHflti/IgmIzdDBg==", + "dev": true, + "requires": { + "async": "^2.6.2", + "date-format": "^2.0.0", + "debug": "^3.2.6", + "fs-extra": "^7.0.1", + "lodash": "^4.17.14" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "strict-uri-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", + "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", + "dev": true + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "style-loader": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.23.1.tgz", + "integrity": "sha512-XK+uv9kWwhZMZ1y7mysB+zoihsEj4wneFWAS5qoiLwzW0WzSqMrrsIy+a3zkQJq0ipFtBpX5W3MqyRIBF/WFGg==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "schema-utils": "^1.0.0" + } + }, + "stylus": { + "version": "0.54.5", + "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.54.5.tgz", + "integrity": "sha1-QrlWCTHKcJDOhRWnmLqeaqPW3Hk=", + "dev": true, + "requires": { + "css-parse": "1.7.x", + "debug": "*", + "glob": "7.0.x", + "mkdirp": "0.5.x", + "sax": "0.5.x", + "source-map": "0.1.x" + }, + "dependencies": { + "glob": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz", + "integrity": "sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo=", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.2", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "source-map": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "dev": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "stylus-loader": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/stylus-loader/-/stylus-loader-3.0.2.tgz", + "integrity": "sha512-+VomPdZ6a0razP+zinir61yZgpw2NfljeSsdUF5kJuEzlo3khXhY19Fn6l8QQz1GRJGtMCo8nG5C04ePyV7SUA==", + "dev": true, + "requires": { + "loader-utils": "^1.0.2", + "lodash.clonedeep": "^4.5.0", + "when": "~3.6.x" + } + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "symbol-observable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", + "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", + "dev": true + }, + "tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "dev": true + }, + "terser": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.1.3.tgz", + "integrity": "sha512-on13d+cnpn5bMouZu+J8tPYQecsdRJCJuxFJ+FVoPBoLJgk5bCBkp+Uen2hWyi0KIUm6eDarnlAlH+KgIx/PuQ==", + "dev": true, + "requires": { + "commander": "^2.20.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.12" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + } + } + }, + "terser-webpack-plugin": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.3.0.tgz", + "integrity": "sha512-W2YWmxPjjkUcOWa4pBEv4OP4er1aeQJlSo2UhtCFQCuRXEHjOFscO8VyWHj9JLlA0RzQb8Y2/Ta78XZvT54uGg==", + "dev": true, + "requires": { + "cacache": "^11.3.2", + "find-cache-dir": "^2.0.0", + "is-wsl": "^1.1.0", + "loader-utils": "^1.2.3", + "schema-utils": "^1.0.0", + "serialize-javascript": "^1.7.0", + "source-map": "^0.6.1", + "terser": "^4.0.0", + "webpack-sources": "^1.3.0", + "worker-farm": "^1.7.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "thunky": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.0.3.tgz", + "integrity": "sha512-YwT8pjmNcAXBZqrubu22P4FYsh2D4dxRmnWBOL8Jk8bUcRUtc5326kx32tuTmFDAZtLOGEVNl8POAR8j896Iow==", + "dev": true + }, + "timers-browserify": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz", + "integrity": "sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg==", + "dev": true, + "requires": { + "setimmediate": "^1.0.4" + } + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "to-array": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", + "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=", + "dev": true + }, + "to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", + "dev": true + }, + "to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "dev": true + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "dev": true, + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + } + } + }, + "tree-kill": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.1.tgz", + "integrity": "sha512-4hjqbObwlh2dLyW4tcz0Ymw0ggoaVDMveUB9w8kFSQScdRLo0gxO9J7WFcUBo+W3C1TLdFIEwNOWebgZZ0RH9Q==", + "dev": true + }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true + }, + "ts-node": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.3.0.tgz", + "integrity": "sha512-dyNS/RqyVTDcmNM4NIBAeDMpsAdaQ+ojdf0GOLqE6nwJOgzEkdRNzJywhDfwnuvB10oa6NLVG1rUJQCpRN7qoQ==", + "dev": true, + "requires": { + "arg": "^4.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.6", + "yn": "^3.0.0" + } + }, + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" + }, + "tslint": { + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.18.0.tgz", + "integrity": "sha512-Q3kXkuDEijQ37nXZZLKErssQVnwCV/+23gFEMROi8IlbaBG6tXqLPQJ5Wjcyt/yHPKBC+hD5SzuGaMora+ZS6w==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^3.2.0", + "glob": "^7.1.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.29.0" + }, + "dependencies": { + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + } + } + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "tty-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", + "dev": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "typeface-roboto": { + "version": "0.0.75", + "resolved": "https://registry.npmjs.org/typeface-roboto/-/typeface-roboto-0.0.75.tgz", + "integrity": "sha512-VrR/IiH00Z1tFP4vDGfwZ1esNqTiDMchBEXYY9kilT6wRGgFoCAlgkEUMHb1E3mB0FsfZhv756IF0+R+SFPfdg==" + }, + "typescript": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.3.tgz", + "integrity": "sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==", + "dev": true + }, + "uglify-js": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.0.tgz", + "integrity": "sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==", + "dev": true, + "optional": true, + "requires": { + "commander": "~2.20.0", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + } + } + }, + "ultron": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", + "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", + "dev": true + }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + } + }, + "unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "dev": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "universal-analytics": { + "version": "0.4.20", + "resolved": "https://registry.npmjs.org/universal-analytics/-/universal-analytics-0.4.20.tgz", + "integrity": "sha512-gE91dtMvNkjO+kWsPstHRtSwHXz0l2axqptGYp5ceg4MsuurloM0PU3pdOfpb5zBXUvyjT4PwhWK2m39uczZuw==", + "dev": true, + "requires": { + "debug": "^3.0.0", + "request": "^2.88.0", + "uuid": "^3.0.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + } + } + }, + "upath": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz", + "integrity": "sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==", + "dev": true + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + } + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "url-parse": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz", + "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==", + "dev": true, + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true + }, + "useragent": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.3.0.tgz", + "integrity": "sha512-4AoH4pxuSvHCjqLO04sU6U/uE65BYza8l/KKBS0b0hnUPWi+cQ2BpeTEwejCSx9SPV5/U03nniDTrWx5NrmKdw==", + "dev": true, + "requires": { + "lru-cache": "4.1.x", + "tmp": "0.0.x" + } + }, + "util": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", + "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", + "dev": true, + "requires": { + "inherits": "2.0.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + } + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "util-promisify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/util-promisify/-/util-promisify-2.1.0.tgz", + "integrity": "sha1-PCI2R2xNMsX/PEcAKt18E7moKlM=", + "dev": true, + "requires": { + "object.getownpropertydescriptors": "^2.0.3" + } + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "validate-npm-package-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", + "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=", + "dev": true, + "requires": { + "builtins": "^1.0.3" + } + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "vm-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.0.tgz", + "integrity": "sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw==", + "dev": true + }, + "void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", + "dev": true + }, + "watchpack": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", + "integrity": "sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==", + "dev": true, + "requires": { + "chokidar": "^2.0.2", + "graceful-fs": "^4.1.2", + "neo-async": "^2.5.0" + }, + "dependencies": { + "chokidar": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.6.tgz", + "integrity": "sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + } + } + }, + "wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "requires": { + "minimalistic-assert": "^1.0.0" + } + }, + "webdriver-js-extender": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/webdriver-js-extender/-/webdriver-js-extender-2.1.0.tgz", + "integrity": "sha512-lcUKrjbBfCK6MNsh7xaY2UAUmZwe+/ib03AjVOpFobX4O7+83BUveSrLfU0Qsyb1DaKJdQRbuU+kM9aZ6QUhiQ==", + "dev": true, + "requires": { + "@types/selenium-webdriver": "^3.0.0", + "selenium-webdriver": "^3.0.1" + } + }, + "webpack": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.38.0.tgz", + "integrity": "sha512-lbuFsVOq8PZY+1Ytz/mYOvYOo+d4IJ31hHk/7iyoeWtwN33V+5HYotSH+UIb9tq914ey0Hot7z6HugD+je3sWw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/helper-module-context": "1.8.5", + "@webassemblyjs/wasm-edit": "1.8.5", + "@webassemblyjs/wasm-parser": "1.8.5", + "acorn": "^6.2.0", + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0", + "chrome-trace-event": "^1.0.0", + "enhanced-resolve": "^4.1.0", + "eslint-scope": "^4.0.0", + "json-parse-better-errors": "^1.0.2", + "loader-runner": "^2.3.0", + "loader-utils": "^1.1.0", + "memory-fs": "~0.4.1", + "micromatch": "^3.1.8", + "mkdirp": "~0.5.0", + "neo-async": "^2.5.0", + "node-libs-browser": "^2.0.0", + "schema-utils": "^1.0.0", + "tapable": "^1.1.0", + "terser-webpack-plugin": "^1.1.0", + "watchpack": "^1.5.0", + "webpack-sources": "^1.3.0" + } + }, + "webpack-core": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/webpack-core/-/webpack-core-0.6.9.tgz", + "integrity": "sha1-/FcViMhVjad76e+23r3Fo7FyvcI=", + "dev": true, + "requires": { + "source-list-map": "~0.1.7", + "source-map": "~0.4.1" + }, + "dependencies": { + "source-list-map": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-0.1.8.tgz", + "integrity": "sha1-xVCyq1Qn9rPyH1r+rYjE9Vh7IQY=", + "dev": true + }, + "source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "webpack-dev-middleware": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.0.tgz", + "integrity": "sha512-qvDesR1QZRIAZHOE3iQ4CXLZZSQ1lAUsSpnQmlB1PBfoN/xdRjmge3Dok0W4IdaVLJOGJy3sGI4sZHwjRU0PCA==", + "dev": true, + "requires": { + "memory-fs": "^0.4.1", + "mime": "^2.4.2", + "range-parser": "^1.2.1", + "webpack-log": "^2.0.0" + }, + "dependencies": { + "mime": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", + "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", + "dev": true + } + } + }, + "webpack-dev-server": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.7.2.tgz", + "integrity": "sha512-mjWtrKJW2T9SsjJ4/dxDC2fkFVUw8jlpemDERqV0ZJIkjjjamR2AbQlr3oz+j4JLhYCHImHnXZK5H06P2wvUew==", + "dev": true, + "requires": { + "ansi-html": "0.0.7", + "bonjour": "^3.5.0", + "chokidar": "^2.1.6", + "compression": "^1.7.4", + "connect-history-api-fallback": "^1.6.0", + "debug": "^4.1.1", + "del": "^4.1.1", + "express": "^4.17.1", + "html-entities": "^1.2.1", + "http-proxy-middleware": "^0.19.1", + "import-local": "^2.0.0", + "internal-ip": "^4.3.0", + "ip": "^1.1.5", + "killable": "^1.0.1", + "loglevel": "^1.6.3", + "opn": "^5.5.0", + "p-retry": "^3.0.1", + "portfinder": "^1.0.20", + "schema-utils": "^1.0.0", + "selfsigned": "^1.10.4", + "semver": "^6.1.1", + "serve-index": "^1.9.1", + "sockjs": "0.3.19", + "sockjs-client": "1.3.0", + "spdy": "^4.0.0", + "strip-ansi": "^3.0.1", + "supports-color": "^6.1.0", + "url": "^0.11.0", + "webpack-dev-middleware": "^3.7.0", + "webpack-log": "^2.0.0", + "yargs": "12.0.5" + }, + "dependencies": { + "chokidar": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.6.tgz", + "integrity": "sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "webpack-log": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz", + "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==", + "dev": true, + "requires": { + "ansi-colors": "^3.0.0", + "uuid": "^3.3.2" + } + }, + "webpack-merge": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.1.tgz", + "integrity": "sha512-4p8WQyS98bUJcCvFMbdGZyZmsKuWjWVnVHnAS3FFg0HDaRVrPbkivx2RYCre8UiemD67RsiFFLfn4JhLAin8Vw==", + "dev": true, + "requires": { + "lodash": "^4.17.5" + } + }, + "webpack-sources": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.3.0.tgz", + "integrity": "sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA==", + "dev": true, + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "webpack-subresource-integrity": { + "version": "1.1.0-rc.6", + "resolved": "https://registry.npmjs.org/webpack-subresource-integrity/-/webpack-subresource-integrity-1.1.0-rc.6.tgz", + "integrity": "sha512-Az7y8xTniNhaA0620AV1KPwWOqawurVVDzQSpPAeR5RwNbL91GoBSJAAo9cfd+GiFHwsS5bbHepBw1e6Hzxy4w==", + "dev": true, + "requires": { + "webpack-core": "^0.6.8" + } + }, + "websocket-driver": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.3.tgz", + "integrity": "sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg==", + "dev": true, + "requires": { + "http-parser-js": ">=0.4.0 <0.4.11", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + } + }, + "websocket-extensions": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", + "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==", + "dev": true + }, + "when": { + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/when/-/when-3.6.4.tgz", + "integrity": "sha1-RztRfsFZ4rhQBUl6E5g/CVQS404=", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true + }, + "worker-farm": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", + "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", + "dev": true, + "requires": { + "errno": "~0.1.7" + } + }, + "worker-plugin": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/worker-plugin/-/worker-plugin-3.1.0.tgz", + "integrity": "sha512-iQ9KTTmmN5fhfc2KMR7CcDblvcrg1QQ4pXymqZ3cRZF8L0890YLBcEqlIsGPdxoFwghyN8RA1pCEhCKuTF4Lkw==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0" + } + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "ws": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0", + "safe-buffer": "~5.1.0", + "ultron": "~1.1.0" + } + }, + "xml2js": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "dev": true, + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~9.0.1" + }, + "dependencies": { + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + } + } + }, + "xmlbuilder": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=", + "dev": true + }, + "xmlhttprequest-ssl": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", + "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=", + "dev": true + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + }, + "yargs": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "yeast": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", + "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=", + "dev": true + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + }, + "zone.js": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.9.1.tgz", + "integrity": "sha512-GkPiJL8jifSrKReKaTZ5jkhrMEgXbXYC+IPo1iquBjayRa0q86w3Dipjn8b415jpitMExe9lV8iTsv8tk3DGag==" + } + } +} diff --git a/ui-ngx/package.json b/ui-ngx/package.json new file mode 100644 index 0000000000..c3fe04638d --- /dev/null +++ b/ui-ngx/package.json @@ -0,0 +1,74 @@ +{ + "name": "thingsboard", + "version": "3.0.0", + "scripts": { + "ng": "ng", + "start": "ng serve --host 0.0.0.0 --open", + "build": "ng build", + "build:prod": "ng build --prod --vendor-chunk", + "test": "ng test", + "lint": "ng lint", + "e2e": "ng e2e" + }, + "private": true, + "dependencies": { + "@angular/animations": "~8.2.0", + "@angular/cdk": "~8.1.2", + "@angular/common": "~8.2.0", + "@angular/compiler": "~8.2.0", + "@angular/core": "~8.2.0", + "@angular/flex-layout": "^8.0.0-beta.26", + "@angular/forms": "~8.2.0", + "@angular/material": "^8.1.2", + "@angular/platform-browser": "~8.2.0", + "@angular/platform-browser-dynamic": "~8.2.0", + "@angular/router": "~8.2.0", + "@auth0/angular-jwt": "^3.0.0", + "@mat-datetimepicker/core": "^2.0.1", + "@ngrx/effects": "^8.2.0", + "@ngrx/store": "^8.2.0", + "@ngrx/store-devtools": "^8.2.0", + "@ngx-translate/core": "^11.0.1", + "@ngx-translate/http-loader": "^4.0.0", + "ace-builds": "^1.4.5", + "compass-sass-mixins": "^0.12.7", + "core-js": "^3.1.4", + "font-awesome": "^4.7.0", + "hammerjs": "^2.0.8", + "material-design-icons": "^3.0.1", + "messageformat": "^2.3.0", + "ngx-clipboard": "^12.2.0", + "ngx-translate-messageformat-compiler": "^4.5.0", + "rxjs": "~6.5.2", + "screenfull": "^4.2.1", + "tslib": "^1.10.0", + "typeface-roboto": "^0.0.75", + "zone.js": "~0.9.1" + }, + "devDependencies": { + "@angular-builders/custom-webpack": "^8.1.0", + "@angular-builders/dev-server": "^7.3.1", + "@angular-devkit/build-angular": "^0.802.0", + "@angular/cli": "~8.2.0", + "@angular/compiler-cli": "~8.2.0", + "@angular/language-service": "~8.2.0", + "@types/jasmine": "~3.4.0", + "@types/jasminewd2": "~2.0.6", + "@types/node": "~10.14.15", + "codelyzer": "~5.1.0", + "compression-webpack-plugin": "^3.0.0", + "directory-tree": "^2.2.3", + "jasmine-core": "~3.4.0", + "jasmine-spec-reporter": "~4.2.1", + "karma": "~4.2.0", + "karma-chrome-launcher": "~3.0.0", + "karma-coverage-istanbul-reporter": "~2.1.0", + "karma-jasmine": "~2.0.1", + "karma-jasmine-html-reporter": "^1.4.2", + "ngrx-store-freeze": "^0.2.4", + "protractor": "~5.4.2", + "ts-node": "~8.3.0", + "tslint": "~5.18.0", + "typescript": "~3.5.3" + } +} diff --git a/ui-ngx/pom.xml b/ui-ngx/pom.xml new file mode 100644 index 0000000000..e9f2d147a5 --- /dev/null +++ b/ui-ngx/pom.xml @@ -0,0 +1,141 @@ + + + 4.0.0 + + org.thingsboard + 3.0.0-SNAPSHOT + thingsboard + + org.thingsboard + ui-ngx + jar + + ThingsBoard Server UI + https://thingsboard.io + + + UTF-8 + ${basedir}/.. + + + + + + ${project.build.directory}/generated-resources + + + + + com.github.eirslett + frontend-maven-plugin + 1.7.5 + + target + ${basedir} + + + + install node and npm + + install-node-and-npm + + + v10.16.0 + 6.9.0 + + + + npm install + + npm + + + install + + + + + + + + + npm-build + + true + + + + + com.github.eirslett + frontend-maven-plugin + 1.7.5 + + target + ${basedir} + + + + npm build + + npm + + + run build:prod + + + + + + + + + npm-start + + + npm-start + + + + + + com.github.eirslett + frontend-maven-plugin + 1.7.5 + + target + ${basedir} + + + + npm start + + npm + + + start + + + + + + + + + diff --git a/ui-ngx/proxy.conf.json b/ui-ngx/proxy.conf.json new file mode 100644 index 0000000000..7d4976bc4b --- /dev/null +++ b/ui-ngx/proxy.conf.json @@ -0,0 +1,6 @@ +{ + "/api": { + "target": "http://localhost:8080", + "secure": false + } +} diff --git a/ui-ngx/src/app/app.module.ts b/ui-ngx/src/app/app.module.ts new file mode 100644 index 0000000000..aa76e9b7a0 --- /dev/null +++ b/ui-ngx/src/app/app.module.ts @@ -0,0 +1,43 @@ +/// +/// Copyright © 2016-2019 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 { BrowserModule } from '@angular/platform-browser'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { NgModule } from '@angular/core'; + +/* import { AppRoutingModule } from './app-routing.module'; +import { CoreModule } from './core/core.module'; +import { LoginModule } from './modules/login/login.module'; +import { HomeModule } from './modules/home/home.module'; + +import { AppComponent } from './app.component'; */ + +@NgModule({ + declarations: [ + /* AppComponent */ + ], + imports: [ + /* BrowserModule, + BrowserAnimationsModule, + AppRoutingModule, + CoreModule, + LoginModule, + HomeModule */ + ], + providers: [], + bootstrap: [/*AppComponent*/] +}) +export class AppModule { } diff --git a/ui-ngx/src/assets/.gitkeep b/ui-ngx/src/assets/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ui-ngx/src/assets/jstree/32px.png b/ui-ngx/src/assets/jstree/32px.png new file mode 100644 index 0000000000000000000000000000000000000000..719a6bcdbd14938a3ed94de95842c40472a6f624 GIT binary patch literal 19444 zcmeI4cTm$y*T;WBq>6wQ1tc^J8d69ok={WO6c9vELJEYE#3WP^LKCkdc)f@U2q;!i z5xIbh3N}b~8iEW>!dN770F{gMq59GpMr!ZM%GHo`ijkTd!2#pIvY`6WZ6nxJ z=ZIA<1Uz(~yuPKb1O za6(!z*;FJ!TNg#fVzEd)18popUk686hQwj81T@9~jm4laSRxif#9@$MA8N)@;GH3x zLL;uRwEmh7w2ajJxLg(yjSdYB)ehCsX0mrjr60?W$z^iK+-Td3cpe7Cu-*f+RzCl61OwHk1 zg#ZL!k^a(?;}XuIqSsJ4%wRT|Y83*OqW-JZbLq5+;`|T^w)}SP)G+#Q++fSN+t^kZ zuaF^FE75{YC2^T-7bY{%czl;ReM5+}uo&A_$i)sMGMxc)U1Eslcbcg9L_De`iAyzx zcO(jlj)>J?iqRorFq49ekK~Ktz@*S=;ooBDqA)lVhTww35i$BiJZ^G~ zNhA4VfD=R^aY_Fw%H*+=;!wy$8j~GF;u_O~NWN4wi{WdCp6Hx3^83CbS}+5dY%ndV zv5q17SKYt#vSgBj;f-y^AX7P@9GppI0>v6Ty5=O`AA7$U0_n!%!a-q@=@jBbQmPLX zMPxA(2rO3_%Zz!Qk-Vb!>0^ z=sq#Cuc5zX2cdtxa0=Ogpr?b=gBOnd?ZU_BIVspri5%HJg zPjdf#j!-`;Ly&5WhZ-C8`yd=9jT=g0Q_XzAdHQ#fHXip!NB#uAJ|}?E*x3xE{}-j; zkV5{uLj1dP^?xYD@1*ztvk)-y@%&^z62q5DF-Cve#NRspUG2sz`s3;Pc4B`%jfU_i zFAtr*(>pH=%lg<3D6P~UN zI@cKYH5*^|Bro`SF#gHq3~sViV_gD4hhT{2@8!Ic)=rqSQ8(?5yjcPaZ{zL{9j|1v6HEMXEp zAcC7hT!Q)VS%tXxfCz31aS7(bXBFb&10uL7#3h&ypH+y94~XEV5SL&+d{!YYJ|Kdd zLR^CR@L7ep_<#s*3ULYM!)F!Z;sYYMDa0k151&H)1;HD6lU_N|SAuc{3f}28Ig8A@S zg}C^D2yO~-3FgCR72@IpBDg8UC72JNRfvlZh~TCWmta18Rv|7vAcC7hT!Q)VS%tXx zfCz31aS7(bXBFb&10uL7#3h&ypH+y94~XEVKwMH2KTxGIz^_t6!Ou^94x`|ot|G~{ zYaAdbLKA{`aS+r$3SK)QXpIDfnC%XJ^|}{=<}&L&RCYm-++jOQGncTYw=G*%y7(Tt z+2gx4=J8R<)6LX_C1vZHno`#)rr&tnvacAAT3=>eCRxavvE8bfYvm9~sAueZN}cW1 zt=XdG!QFEo={)Or-PFI^6>VqbdF>;qy0t#9?VwsGXyrWY2pz4_$-06l%$$N7TG%f} zk&yM}rBv_#kf|fWQ_y0Tw*(C)=AAx0Jvca6ePF&)c479&pk8f(sss|D#!G@`n2K0J z^AH+5(QOb;#1@j2Lr_F_K{Jpdx4;MuQ_)@WrXsdH38Xp?bSF23Es>O;@A$4wwuRXy z3%k{G=61jDj~tVb^7KW&0G9GtF&r zABU6v>9aWUyie3gV)dFeu^mUnT>N*MY=AE8?fz2IBO^9tzG4RJMHMwAI5_ltYe;bD z760O^-Wb!{J!KclYB;BE5{`DCy@z`J%&0!1rmp2kRi6n_$s(lIGx=_l%&pPSyhG1c zpxnU2S<`p+@ic8N~RuM14($^y(T*yN`qV25Gw&qGbg=w9gZ!O(I=LYI757aGF z3 zT>IW^AaVPMQpmg3oyU(aE-jchdv-!gS!f*0_IPa{3@rCSXJAl})v$b0w zm9tk^EaUXq*tz^3&1NfA`SRt9@>9K{Z}w*ymToUIz6M^Odo{$Y%1~|`T)ADu-F;nB z`5q0m(dH>PUYl~)p>}QUdrM#_lu+3j5IqK@X(e=&dnyyda$!Jd9v*$&+ ze3jy<#*Tec!nzQuJhEuJcS?CFVj04(*yBNDk6Jo*()w$;t?+Fp&p6$mJ(2U6$$Sx7IBKPy^T44FFlFjfqig2FIT#!esriO`EmNS z9tL&sh-dH8>kGmhRzt;wW1$xYVY;# z62}{Mobyk`%_NY%yf}n@?W}A>^5ecZqY?WQw0KS62{Q+W1B$V=+JzsV?32 zGSzz%UE*m~Hs{_2ICiGU#=BKt{+e>Up)cU!Zn=^zd5YZHF!XZ?w;56PqvEE3p|=TS zwFXhKdS0{yG+VjN8&lz5e5kNcvaXKi;+EkhQKgy+xdezQ98gT&e?Ds8{;tGz{iS~y z>+00YJ~-czrzCnCy*%mFNRN(Twx*}U=+h9@6DiLQpZDC;y@+Qh6}eGF-veqvL-u{H zZW$!(dhZh@=kAFqDrR^Wd$=NMXhrG^b zSq=CX$(tmRpZm9=h*{$^O{W>h!v8dv-bJMCZTvU`&Y_2LLPZ2%zA$?nF^nhoC{4S zrNGguR8v3n#hRE4{*ZC%gD;pzdhblnKPy)T4sSr2OxtrL_l{0|bor;!)BCd{#ZG?g z%epT*PpiOF{1zvs(VdEU;`wEv$uqKduk+PMkZn`(N3L#VMe#QO%HEq9I z<&%luTV}klps>(xHWZE(6}eT3eC-2)Gnw^9r4Ri70oIsXU)ptb(OW@vkYrd-D;0n& zCSie=&LacK#8mU~2)?dj#A6v~x^@Z^2H$2rDoWb^~!>{pG=q zAQJ5POb2$osbX?O!_v>tDs{}hz5YyqWR$&qa$Dwyto$8=ZJ`nNBL@D-CYh;9ql{A{ zEakIb_UAaRx^YNz=|RU;3C0Gq|CA=TyFwAqmR{Srh_~>~(rbjb9lWLmg`2HB$~f}QHuQ>{SCIAds|uFdNmoG9OR^OmPbwbz~Mqwr0^WHFSYh(w^R{?pxU!Omf~$39-+_^RZ0 z>>y5Kk5@zF2>OK@EJ zcnBJ}A)au_RsUMyW2NNTuQr&oyA3t6BBxMqh8}#|rwzY-w|1)!(5g9mw;~UIw7S@T z!NQ(PJ{Iji0R8&eNXSz+QpSFzDQ!?8g+C2x zK6dEPKdD+kUUTN&hnNW6T5GA!Gi%6V`n5eaL1GQuIc{B!x%n?8qRz2CE{t{X^^&WS zcbQR<8ZWU^v>n#=SGzZ=o*ptNg_ZVXq)Ge>$s35&keF*Fj=^z^RWWGVi@}Zrr4FsY zK^GSf`}Vg*GfhSG1EJY;QIqSWV>5W_^P};hF$IsaEBEBZs>)y7bi(eyyqrsjLh%jP z?s}S}*kMi}ygBzbg9`|hHF&f7ePkMoFubEVAt-yh&MRpz$0>5A3#MktLCLdnz!gMx zZt_!m*O+`2ZKYtdu;P)w1NOSpp(Alf?EXK89iy}{F8+wlPKHg?gSCc*bg8V?ZeyD5 zn`io*%T6W3Z@>-<`jpru(Qdh10+;S^or>wrtzZv{nJ>1fdC+pY3b&7hIHy;8Vh+zO zPiohJGI__q^wgoONiIS^s0m-1(z8+yZ9J3mHzM=6Z>%k( z>ylpML^mHc_{(P65gqGC`ZKS)Q!GEW+WM}fQKG@?v134VmyRfTf^~y4?4{Fsh*_fg zO?VeCzkDZ%?!?SrSNsY>Y^$tXIBjL}#tj>!ckh;xKaUZ6y}XuG)ObnUG)ww!rP}M2 za=zKh_ghy1+0kpO+MPY)Xytz7l({OhX=rz#yuNtnejd-GVJ@v{z85-9$;O>w1P5 zS^WcJCCTBxIB6uFl*idkD`MZm8 zzS&#W4k(SpU__2<@L-{wKUnwVqIm0ciO>2Uicb11Z%Er}Bojf=W0-r~v+pZNAIO^l zEeLymB}q%QMoqMdVb)O2U80nsS7shDaF5OAStPQa>Xr91k02fy9hRwi%|$l{hA8Yv z%H7`H0zK;b)Y;jIj7@^HWO8(M{T{#R4gU;l=%+sx@7OjFzAN%&>Fc4v?s=#`Y>Tgx zEb5vY+?7*qU2OaWOQ`w{w5F;#dB#(0uH0Zg1JSTh^g?{ap2ngM&myt=gaBTOG21MR zHt=zHq3QzXnHBYE<+wbF>EIRyl2$l78YSe~q|7k0hHRvAHby!%tWVHU3b@zdzUd!L z`1V?>Ua;QsQFR=A2VT3kx~P2pyzMuRsJeU3kBzUL+cPw3xbB!})D-!>F_gGZzE?IuzCzwaT;YS?^`hk}jJ+0m$13I)^$x!` zPiB4UavbvCli4qGkxIw!&{`1Hml)CbA%-m)bSEu%vxnNVAn54PqY;KPGSk!T!52!G z7mHbUbLA0=-0o^$Nn7};S#H~t?;P-QQAenGes{qPk>Jah6}IJg<(fJAEheTdJ0FFX zh`6SW@de*XlPhui)=L;)THk)Vn)zkNta4(pZ?`l_qU&HR1* z+UD$2buS~ASiawDE~=Q&5WP0n?9QqE9Wml6cb&-d?hc=oONaBzjeX>R?U>f2W8x^W zAQOL^KI>}Gf$YCm!9mKa>q84X?s!-By6E4B=4DjOwXn6@Rs-U)(yWU*SE$CmsE`TH zF7=#dR#30LWC`6JTBxZhZ)y!)>h{OL-z86MK3}bPK^x}L8xo#OE#OkB~$LGr;}=FbpxeWUvbGmLn1DXa;HsQK~D7_{^sq2 z^fni3chvJ5$YKUAlPB<%YYsGqZ3*PyX7OjPSmNI*c$WG!up*Jd*}3 zyaNUJH)Q(CkAGRYx9(QF6|av~uGv)1Y;;XvzcpOJ6RwjX&Aq_Aw}!?@ks55jzGfWq zHi2b^qzQ-8bl-&6a#)X#NWKfdin__%Ol?}CAS&@2z3m-#8{JmeH=J{JF%S+MQ5YWYP20_w8qRF6(+-b#Trf> zo_2Ahj>H8mS@~xq^k9LXSkqS5UA65dkI8zx!mlGSfKyE;s4Src2P4*>GKW637{uPZ zn41TS!b0I%aCMOGtR7_5i0R^u?Z1kp6{@2~AH^7slz&0Qj|5)78I0s6dlVU{)cQrP zeNGP#HlU^~-R%ecwQ5d4f89gT`1oM|(Ofc=m=oyYg2-k3n{hQ$7?G# z&FPF6FFv(B1kEI;9zR$1bF}BYcUcz6lkHP>F~*Z@^?L7Dwi265tsN>ESDZo%}%mSZ>gVZrQEHe#NWr4C*NSf*0NrTPY+o|6J6YC;=zHNu2 z8bz_U4ETk(5(4ZKzU=r-bX5C~YoQu69_!Kg_;tIz@|bcZeUDp?>LM|N ziJp5yVxnWe6Y7OqZ0sqz9>rV_e8%6p;7r{y(wfYhJK>k?%R9^#}q@xkWO6(y$(5^YBRpql1{)zuK zbrtwwtit>e;@%w>h7p&2<37Z3Q~Of2IoVshosA0BYcm}a31kTD@b5Y^cq1>Lz~v#* z=I3$&3b7mLgtwj%M~`+Qvt=#z{qJiwXm0kiXWM^#JaGk!bF6zb=stF+fn62*;^IMV z;8jniInrmfUG6KNlUC^lV!pmFFQ+Bb!%x?kA|oelOOeQZy&ZYzuw;)RIQ$l#&^K8* z0OFJoe{kr3Me>S$Wc!cxtzZ7K-m>EtO!YQ<*Jwhovtf3=v?o4fP-T|cmxOL~h!|$H iV&_@$q2+YC87j9T{b5}Pokud si stále přejete získat přístup do tohoto umístění, zkuste se zaregistrovat pod jiným uživatelem.", + "refresh-token-expired": "Relace vypršela", + "refresh-token-failed": "Nemohu aktualizovat relaci" + }, + "action": { + "activate": "Aktivovat", + "suspend": "Deaktivovat", + "save": "Uložit", + "saveAs": "Uložit jako", + "cancel": "Storno", + "ok": "OK", + "delete": "Smazat", + "add": "Přidat", + "yes": "Ano", + "no": "Ne", + "update": "Aktualizovat", + "remove": "Odstranit", + "search": "Vyhledat", + "clear-search": "Zrušit hledání", + "assign": "Přiřadit", + "unassign": "Odebrat", + "share": "Sdílet", + "make-private": "Učinit soukromým", + "apply": "Použít", + "apply-changes": "Uložit změny", + "edit-mode": "Režim editace", + "enter-edit-mode": "Vstoupit do režimu editace", + "decline-changes": "Zahodit změny", + "close": "Zavřít", + "back": "Zpět", + "run": "Spustit", + "sign-in": "Zaregistrovat!", + "edit": "Editovat", + "view": "Zobrazit", + "create": "Vytvořit", + "drag": "Táhnout", + "refresh": "Obnovit", + "undo": "Vrátit", + "copy": "Kopírovat", + "paste": "Vložit", + "copy-reference": "Kopírovat referenci", + "paste-reference": "Vložit referenci", + "import": "Importovat", + "export": "Exportovat", + "share-via": "Sdílet přes {{provider}}" + }, + "aggregation": { + "aggregation": "Agregace", + "function": "Funkce pro agregaci dat", + "limit": "Maximální hodnoty", + "group-interval": "Interval seskupení", + "min": "Min", + "max": "Max", + "avg": "Průměr", + "sum": "Suma", + "count": "Počet", + "none": "Žádná" + }, + "admin": { + "general": "Obecné", + "general-settings": "Obecná nastavení", + "outgoing-mail": "Odchozí email", + "outgoing-mail-settings": "Nastavení odchozího emailu", + "system-settings": "Systémová nastavení", + "test-mail-sent": "Testovací zpráva byla úspěšně odeslána!", + "base-url": "Základní URL", + "base-url-required": "Hodnota Základní URL je povinná.", + "mail-from": "Email od", + "mail-from-required": "Hodnota Email od je povinná.", + "smtp-protocol": "SMTP protokol", + "smtp-host": "SMTP host", + "smtp-host-required": "Hodnota SMTP host je povinná.", + "smtp-port": "SMTP port", + "smtp-port-required": "Musíte zadat smtp port.", + "smtp-port-invalid": "Tohle nevypadá jako platný smtp port.", + "timeout-msec": "Časový limit (msec)", + "timeout-required": "Hodnota Časový limit je povinná.", + "timeout-invalid": "Tohle nevypadá jako platný časový limit.", + "enable-tls": "Povolit TLS", + "send-test-mail": "Odeslat testovací zprávu" + }, + "alarm": { + "alarm": "Alarm", + "alarms": "Alarmy", + "select-alarm": "Vybrat alarm", + "no-alarms-matching": "Žádné alarmy odpovídající '{{entity}}' nebyly nalezeny.", + "alarm-required": "Alarm je povinný", + "alarm-status": "Stav alarmu", + "search-status": { + "ANY": "Všechny", + "ACTIVE": "Aktivní", + "CLEARED": "Odstraněné", + "ACK": "Přijaté", + "UNACK": "Nepřijaté" + }, + "display-status": { + "ACTIVE_UNACK": "Aktivní nepřijaté", + "ACTIVE_ACK": "Aktivní přijaté", + "CLEARED_UNACK": "Odstraněné nepřijaté", + "CLEARED_ACK": "Odstraněné přijaté" + }, + "no-alarms-prompt": "Žádné alarmy nebyly nalezeny", + "created-time": "Datum vytvoření", + "type": "Typ", + "severity": "Závažnost", + "originator": "Původce", + "originator-type": "Typ původce", + "details": "Detail", + "status": "Stav", + "alarm-details": "Detail alarmu", + "start-time": "Datum zahájení", + "end-time": "Datum ukončení", + "ack-time": "Datum přijetí", + "clear-time": "Datum vyřešení", + "severity-critical": "Kritická", + "severity-major": "Vysoká", + "severity-minor": "Nízká", + "severity-warning": "Varování", + "severity-indeterminate": "Střední", + "acknowledge": "Přijmout", + "clear": "Vyřešit", + "search": "Vyhledat alarmy", + "selected-alarms": "Vybráno { count, plural, 1 {1 alarmů} other {# alarmů} }", + "no-data": "Nejsou zde žádná data", + "polling-interval": "Interval frekvence příjmu alarmů (vteřin)", + "polling-interval-required": "Interval frekvence příjmu alarmů je povinný.", + "min-polling-interval-message": "Minimální povolený interval frekvence příjmu alarmů je 1 vteřina.", + "aknowledge-alarms-title": "Přijmout { count, plural, 1 {1 alarm} other {# alarmů} }", + "aknowledge-alarms-text": "Jste si jisti že chcete přijmout { count, plural, 1 {1 alarm} other {# alarmů} }?", + "aknowledge-alarm-title": "Přijmout alarm", + "aknowledge-alarm-text": "Jste si jisti, že chcete přijmout alarm?", + "clear-alarms-title": "Odstranit { count, plural, 1 {1 alarm} other {# alarmů} }", + "clear-alarms-text": "Jste si jisti, že chcete odstranit { count, plural, 1 {1 alarm} other {# alarmů} }?", + "clear-alarm-title": "Odstranit alarm", + "clear-alarm-text": "Jste si jisti, že chcete alarm odstranit?", + "alarm-status-filter": "Filtr stavu alarmu" + }, + "alias": { + "add": "Přidat alias", + "edit": "Editovat alias", + "name": "Název aliasu", + "name-required": "Název aliasu je povinný", + "duplicate-alias": "Alias s identickým názvem již existuje.", + "filter-type-single-entity": "Jedna entita", + "filter-type-entity-list": "Seznam entit", + "filter-type-entity-name": "Název entity", + "filter-type-state-entity": "Entita ze stavu dashboardu", + "filter-type-state-entity-description": "Entita převzata z parametrů stavu dashboardu", + "filter-type-asset-type": "Typ aktiva", + "filter-type-asset-type-description": "Aktiva typu '{{assetType}}'", + "filter-type-asset-type-and-name-description": "Aktiva typu '{{assetType}}' s názvem začínajícím '{{prefix}}'", + "filter-type-device-type": "Typ zařízení", + "filter-type-device-type-description": "Zařízení typu '{{deviceType}}'", + "filter-type-device-type-and-name-description": "Zařízení typu '{{deviceType}}' s názvem začínajícím '{{prefix}}'", + "filter-type-entity-view-type": "Typ entitního pohledu", + "filter-type-entity-view-type-description": "Entitní pohledy typu '{{entityView}}'", + "filter-type-entity-view-type-and-name-description": "Entitní pohledy typu '{{entityView}}' s názvem začínajícím '{{prefix}}'", + "filter-type-relations-query": "Dotaz na vztahy", + "filter-type-relations-query-description": "{{entities}} se {{relationType}} vztahem {{direction}} {{rootEntity}}", + "filter-type-asset-search-query": "Dotaz na vyhledání aktiva", + "filter-type-asset-search-query-description": "Aktiva typů {{assetTypes}} se {{relationType}} vztahem {{direction}} {{rootEntity}}", + "filter-type-device-search-query": "Dotaz na vyhledání zařízení", + "filter-type-device-search-query-description": "Zařízení typů {{deviceTypes}} se {{relationType}} vztahem {{direction}} {{rootEntity}}", + "filter-type-entity-view-search-query": "Dotaz na vyhledání zobrazení entity", + "filter-type-entity-view-search-query-description": "Entitní pohledy typů {{entityViewTypes}} se {{relationType}} vztahem {{direction}} {{rootEntity}}", + "entity-filter": "Filtr entity", + "resolve-multiple": "Použít jako více entit", + "filter-type": "Typ filtru", + "filter-type-required": "Typ filtru je povinný.", + "entity-filter-no-entity-matched": "Žádné entity odpovídající specifikovanému filtru nebyly nalezeny.", + "no-entity-filter-specified": "Nebyl specifikován žádný filtr entit", + "root-state-entity": "Použít stav entity dashboard jako kořenovou", + "root-entity": "Kořenová entita", + "state-entity-parameter-name": "Název parametru stavu entity", + "default-state-entity": "Defaultní stav entity", + "default-entity-parameter-name": "Defaultně", + "max-relation-level": "Maximální úroveň vazeb", + "unlimited-level": "Neomezená úroveň", + "state-entity": "Dashboard stav entity", + "all-entities": "Všechny entity", + "any-relation": "všechny" + }, + "asset": { + "asset": "Aktivum", + "assets": "Aktiva", + "management": "Správa aktiv", + "view-assets": "Zobrazit aktiva", + "add": "Přidat aktivum", + "assign-to-customer": "Přiřadit zákazníkovi", + "assign-asset-to-customer": "Přiřadit aktiva zákazníkovi", + "assign-asset-to-customer-text": "Prosím vyberte aktiva, která mají být přiřazena zákazníkovi", + "no-assets-text": "Žádná aktiva nenalezena", + "assign-to-customer-text": "Prosím vyberte zákazníka, kterému mají být aktiva přiřazena", + "public": "Veřejné", + "assignedToCustomer": "Přiřazeno zákazníkovi", + "make-public": "Zveřejnit aktivum", + "make-private": "Učinit aktivum neveřejným", + "unassign-from-customer": "Odebrat aktivum zákazníkovi", + "delete": "Smazat aktivum", + "asset-public": "Aktivum je veřejné", + "asset-type": "Typ aktiva", + "asset-type-required": "Typ aktiva je povinný.", + "select-asset-type": "Vyberte typ aktiva", + "enter-asset-type": "Zadejte typ aktiva", + "any-asset": "Všechna aktiva", + "no-asset-types-matching": "Žádné typy aktiv odpovídající '{{entitySubtype}}' nebyly nalezeny.", + "asset-type-list-empty": "Žádné typy aktiv nebyly vybrány.", + "asset-types": "Typy aktiv", + "name": "Název", + "name-required": "Název je povinný.", + "description": "Popis", + "type": "Typ", + "type-required": "Typ je povinný.", + "details": "Detail", + "events": "Události", + "add-asset-text": "Přidat nové aktivum", + "asset-details": "Detail aktiva", + "assign-assets": "Přiřadit aktiva", + "assign-assets-text": "Přiřadit { count, plural, 1 {1 aktivum} other {# aktiva} } zákazníkovi", + "delete-assets": "Smazat aktiva", + "unassign-assets": "Odebrat aktiva", + "unassign-assets-action-title": "Odebrat { count, plural, 1 {1 aktivum} other {# aktiva} } zákazníkovi", + "assign-new-asset": "Přiřadit nové aktivum", + "delete-asset-title": "Jste si jisti, že chcete smazat aktivum '{{assetName}}'?", + "delete-asset-text": "Buďte opatrní, protože po potvrzení nebude možné aktivum ani žádná související data obnovit.", + "delete-assets-title": "Jste si jisti, že chcete smazat { count, plural, 1 {1 aktivum} other {# aktiva} }?", + "delete-assets-action-title": "Smazat { count, plural, 1 {1 aktivum} other {# aktiva} }", + "delete-assets-text": "Buďte opatrní, protože po potvrzení budou všechna vybraná aktiva odstraněna a žádná související data nebude možné obnovit.", + "make-public-asset-title": "Jste si jisti, že chcete aktivum '{{assetName}}' zveřejnit?", + "make-public-asset-text": "Po potvrzení se aktivum a všechna související data stanou veřejnými a dostupnými pro ostatní.", + "make-private-asset-title": "Jste si jisti, že chcete aktivum '{{assetName}}' učinit neveřejným?", + "make-private-asset-text": "Po potvrzení se aktivum a všechna související data stanou neveřejnými a nedostupnými pro ostatní.", + "unassign-asset-title": "Jste si jisti, že chcete odebrat aktivum '{{assetName}}'?", + "unassign-asset-text": "Po potvrzení bude aktivum odebráno a nebude pro zákazníka dostupné.", + "unassign-asset": "Odebrat aktivum", + "unassign-assets-title": "Jste si jisti, že chcete odebrat { count, plural, 1 {1 aktivum} other {# aktiva} }?", + "unassign-assets-text": "Po potvrzení budou všechna vybraná aktiva odebrána a nebudou pro zákazníka dostupná.", + "copyId": "Kopírovat Id aktiva", + "idCopiedMessage": "Id aktiva bylo zkopírováno do schránky", + "select-asset": "Vybrat aktivum", + "no-assets-matching": "Žádná aktiva odpovídající '{{entity}}' nebyla nalezena.", + "asset-required": "Aktivum je povinné", + "name-starts-with": "Název aktiva začíná" + }, + "attribute": { + "attributes": "Atributy", + "latest-telemetry": "Poslední telemetrie", + "attributes-scope": "Rozsah atributů entity", + "scope-latest-telemetry": "Poslední telemetrie", + "scope-client": "Atributy klienta", + "scope-server": "Atributy serveru", + "scope-shared": "Sdílené atributy", + "add": "Přidat atribut", + "key": "Klíč", + "last-update-time": "Čas poslední aktualizace", + "key-required": "atribut klíč je povinný.", + "value": "Hodnota", + "value-required": "Atribut hodnota je povinný.", + "delete-attributes-title": "Jste si jisti, že chcete smazat { count, plural, 1 {1 atribut} other {# atributů} }?", + "delete-attributes-text": "Buďte opatrní, protože po potvrzení budou všechny vybrané atributy odstraněny.", + "delete-attributes": "Smazat atributy", + "enter-attribute-value": "Zadejte hodnotu atributu", + "show-on-widget": "Zobrazit ve widgetu", + "widget-mode": "Režim widgetu", + "next-widget": "Další widget", + "prev-widget": "Předchozí widget", + "add-to-dashboard": "Přidat na dashboard", + "add-widget-to-dashboard": "Přidat widget na dashboard", + "selected-attributes": "Vybráno { count, plural, 1 {1 atributů} other {# atributů} }", + "selected-telemetry": "Vybráno { count, plural, 1 {1 jednotek telemetrie} other {# jednotek telemetrie} }" + }, + "audit-log": { + "audit": "Audit", + "audit-logs": "Záznamy auditu", + "timestamp": "Časová značka", + "entity-type": "Typ entity", + "entity-name": "Název entity", + "user": "Uživatel", + "type": "Typ", + "status": "Stav", + "details": "Detail", + "type-added": "Přidáno", + "type-deleted": "Smazáno", + "type-updated": "Aktualizováno", + "type-attributes-updated": "Atributy aktualizovány", + "type-attributes-deleted": "Atributy smazány", + "type-rpc-call": "RPC volání", + "type-credentials-updated": "Přístupové údaje aktualizovány", + "type-assigned-to-customer": "Přiřazeno zákazníkovi", + "type-unassigned-from-customer": "Odebráno zákazníkovi", + "type-activated": "Aktivováno", + "type-suspended": "Deaktivováno", + "type-credentials-read": "Zobrazení přístupových údajů", + "type-attributes-read": "Zobrazení atributů", + "type-relation-add-or-update": "Vztah aktualizován", + "type-relation-delete": "Vztah smazán", + "type-relations-delete": "Všechny vztahy smazány", + "type-alarm-ack": "Přijato", + "type-alarm-clear": "Odstraněno", + "status-success": "Úspěch", + "status-failure": "Chyba", + "audit-log-details": "Detail záznamu auditu", + "no-audit-logs-prompt": "Žádné záznamy nenalezeny", + "action-data": "Data akce", + "failure-details": "Detail chyby", + "search": "Prohledat záznamy auditu", + "clear-search": "Vymazat vyhledávání" + }, + "confirm-on-exit": { + "message": "Některé změny nebyly uloženy. Jste si jisti, že chcete tuto stránku opustit?", + "html-message": "Některé změny nebyly uloženy.
Jste si jisti, že chcete tuto stránku opustit?", + "title": "Neuložené změny" + }, + "contact": { + "country": "Stát", + "city": "Město", + "state": "Region", + "postal-code": "PSČ", + "postal-code-invalid": "Formát PSČ neplatný.", + "address": "Adresa", + "address2": "Adresa 2", + "phone": "Telefon", + "email": "Email", + "no-address": "Žádná adresa" + }, + "common": { + "username": "Uživatelské jméno", + "password": "Heslo", + "enter-username": "Zadejte uživatelské jméno", + "enter-password": "Zadejte heslo", + "enter-search": "Zadejte hledaný řetězec" + }, + "content-type": { + "json": "JSON", + "text": "Text", + "binary": "Binární (Base64)" + }, + "customer": { + "customer": "Zákazník", + "customers": "Zákazníci", + "management": "Správa zákazníků", + "dashboard": "Dashboard zákazníka", + "dashboards": "Dashboardy zákazníka", + "devices": "Zařízení zákazníka", + "entity-views": "Entitní pohledy zákazníka", + "assets": "Aktiva zákazníka", + "public-dashboards": "Veřejné dashboardy", + "public-devices": "Veřejná zařízení", + "public-assets": "Veřejná aktiva", + "public-entity-views": "Veřejné entitní pohledy", + "add": "Přidat zákazníka", + "delete": "Smazat zákazníka", + "manage-customer-users": "Spravovat uživatele zákazníka", + "manage-customer-devices": "Spravovat zařízení zákazníka", + "manage-customer-dashboards": "Spravovat dashboardy zákazníka", + "manage-public-devices": "Spravovat veřejná zařízení", + "manage-public-dashboards": "Spravovat veřejné dashboardy", + "manage-customer-assets": "Spravovat aktiva zákazníka", + "manage-public-assets": "Spravovat veřejná aktiva", + "add-customer-text": "Přidat nového zákazníka", + "no-customers-text": "Žádní zákazníci nenalezeni", + "customer-details": "Detail zákazníka", + "delete-customer-title": "Jste si jisti, že chcete smazat zákazníka '{{customerTitle}}'?", + "delete-customer-text": "Buďte opatrní, protože po potvrzení nebude možné zákazníka ani žádná související data obnovit.", + "delete-customers-title": "Jste si jisti, že chcete smazat { count, plural, 1 {1 zákazníka} other {# zákazníků} }?", + "delete-customers-action-title": "Smazat { count, plural, 1 {1 zákazníka} other {# zákazníků} }", + "delete-customers-text": "Buďte opatrní, protože po potvrzení budou všichni vybraní zákazníci odstraněni a žádná související data nebude možné obnovit.", + "manage-users": "Spravovat uživatele", + "manage-assets": "Spravovat aktiva", + "manage-devices": "Spravovat zařízení", + "manage-dashboards": "Spravovat dashboardy", + "title": "Název", + "title-required": "Název je povinný.", + "description": "Popis", + "details": "Detail", + "events": "Události", + "copyId": "Kopírovat Id zákazníka", + "idCopiedMessage": "Id zákazníka bylo zkopírováno do schránky", + "select-customer": "Vybrat zákazníka", + "no-customers-matching": "Žádní zákazníky odpovídající '{{entity}}' nebyli nalezeni.", + "customer-required": "Zákazník je povinný", + "select-default-customer": "Vybrat defaultního zákazníka", + "default-customer": "Defaultní zákazník", + "default-customer-required": "Aby bylo možné ladit dashboard na úrovni tenanta, je nutné zadat defaultního zákazníka." + }, + "datetime": { + "date-from": "Datum od", + "time-from": "Čas od", + "date-to": "Datum do", + "time-to": "Čas do" + }, + "dashboard": { + "dashboard": "Dashboard", + "dashboards": "Dashboardy", + "management": "Správa dashboardů", + "view-dashboards": "Zobrazit dashboardy", + "add": "Přidat dashboard", + "assign-dashboard-to-customer": "Přiřadit dashboard(y) zákazníkovi", + "assign-dashboard-to-customer-text": "Prosím vyberte dashboardy, které mají být přiřazeny zákazníkovi", + "assign-to-customer-text": "Prosím vyberete zákazníka, kterému má být dashboard(y) přiřazen", + "assign-to-customer": "Přiřadit zákazníkovi", + "unassign-from-customer": "Odebrat zákazníkovi", + "make-public": "Zveřejnit dashboard", + "make-private": "Učinit dashboard neveřejným", + "manage-assigned-customers": "Spravovat přiřazené zákazníky", + "assigned-customers": "Přiřazení zákazníci", + "assign-to-customers": "Přiřadit dashboard(y) zákazníkovi", + "assign-to-customers-text": "Prosím vyberte zákazníky, kterým má být dashboard(y) přiřazen(y)", + "unassign-from-customers": "Odebrat dashboard(y) zákazníkům", + "unassign-from-customers-text": "Prosím vyberte zákazníky, kterým má být dashboard(y) odebrán(y)", + "no-dashboards-text": "Nebyly nalezeny žádné dashboardy", + "no-widgets": "Nejsou nastaveny žádné widgety", + "add-widget": "Přidat nový widget", + "title": "Název", + "select-widget-title": "Vybrat widget", + "select-widget-subtitle": "Seznam dostupných typů widgetu", + "delete": "Smazat dashboard", + "title-required": "Název je povinný.", + "description": "Popis", + "details": "Detail", + "dashboard-details": "Detail dashboardu", + "add-dashboard-text": "Přidat nový dashboard", + "assign-dashboards": "Přiřadit dashboardy", + "assign-new-dashboard": "Přiřadit nový dashboard", + "assign-dashboards-text": "Přiřadit { count, plural, 1 {1 dashboard} other {# dashboardů} } zákazníkům", + "unassign-dashboards-action-text": "Odebrat { count, plural, 1 {1 dashboard} other {# dashboardů} } zákazníkům", + "delete-dashboards": "Smazat dashboardy", + "unassign-dashboards": "Odebrat dashboardy", + "unassign-dashboards-action-title": "Odebrat { count, plural, 1 {1 dashboard} other {# dashboardů} } zákazníkovi", + "delete-dashboard-title": "Jste si jisti, že chcete odstranit dashboard '{{dashboardTitle}}'?", + "delete-dashboard-text": "Buďte opatrní, protože po potvrzení nebude možné dashboard ani žádná soubisející data obnovit.", + "delete-dashboards-title": "Jste si jisti, že chcete smazat { count, plural, 1 {1 dashboard} other {# dashboardů} }?", + "delete-dashboards-action-title": "Smazat { count, plural, 1 {1 dashboard} other {# dashboardů} }", + "delete-dashboards-text": "Buďte opatrní, protože po potvrzení budou všechny vybrané dashboardy smazány a žádná související data nebude možné obnovit.", + "unassign-dashboard-title": "Jste si jistí, že chcete odebrat dashboard '{{dashboardTitle}}'?", + "unassign-dashboard-text": "Po potvrzení bude dashboard odebrán a nebude pro zákazníka dostupný.", + "unassign-dashboard": "Odebrat dashboard", + "unassign-dashboards-title": "Jste si jisti, že chcete odebrat { count, plural, 1 {1 dashboard} other {# dashboardů} }?", + "unassign-dashboards-text": "Po potvrzení budou všechny vybrané dashboardy odebrány a nebudou pro zákazníka dostupné.", + "public-dashboard-title": "Dashboard je nyní veřejný", + "public-dashboard-text": "Váš dashboard {{dashboardTitle}} je nyní veřejný a dostupný prostřednictvím následujícího veřejného odkazu:", + "public-dashboard-notice": "Poznámka: Nezapomeňte zveřejnit také příslušná zařízení, aby bylo možné přistupovat k jejich datům.", + "make-private-dashboard-title": "Jste si jisti, že chcete dashboard '{{dashboardTitle}}' zneveřejnit?", + "make-private-dashboard-text": "Po potvrzení bude dashboard neveřejný a nebude pro ostatní dostupný.", + "make-private-dashboard": "Učinit dashboard neveřejným", + "socialshare-text": "'{{dashboardTitle}}' powered by ThingsBoard", + "socialshare-title": "'{{dashboardTitle}}' powered by ThingsBoard", + "select-dashboard": "Vybrat dashboard", + "no-dashboards-matching": "Žádné dashboardy odpovídající '{{entity}}' nebyly nalezeny.", + "dashboard-required": "Dashboard je povinný.", + "select-existing": "Vybrat existující dashboard", + "create-new": "Vytvořit nový dashboard", + "new-dashboard-title": "Název nového dashboardu", + "open-dashboard": "Otevřít dashboard", + "set-background": "Nastavit pozadí", + "background-color": "Barva pozadí", + "background-image": "Obrázek pozadí", + "background-size-mode": "Režim velikosti pozadí", + "no-image": "Žádný obrázek nebyl vybrán", + "drop-image": "Přetáhněte sem obrázek nebo klikněte pro výběr souboru pro nahrání.", + "settings": "Nastavení", + "columns-count": "Počet sloupců", + "columns-count-required": "Počet sloupců je povinný.", + "min-columns-count-message": "Minimální povolený počet sloupců je 10.", + "max-columns-count-message": "Maximální povolený počet sloupců je 1000.", + "widgets-margins": "Okraj mezi widgety", + "horizontal-margin": "Horizontální okraj", + "horizontal-margin-required": "Hodnota horizontálního okraje je povinná.", + "min-horizontal-margin-message": "Minimální povolená hodnota horizontálního okraje je 0.", + "max-horizontal-margin-message": "Maximální povolená hodnota horizontálního okraje je 50.", + "vertical-margin": "Vertikální okraj", + "vertical-margin-required": "Hodnota vertikálního okraje je povinná.", + "min-vertical-margin-message": "Minimální povolená hodnota vertikálního okraje je 0.", + "max-vertical-margin-message": "Maximální povolená hodnota vertikálního okraje je 50.", + "autofill-height": "Automaticky vyplnit na výšku rozmístění", + "mobile-layout": "Nastavení rozmístění pro mobilní zařízení", + "mobile-row-height": "Výška řádku pro mobilní zařízení, px", + "mobile-row-height-required": "Hodnota výšku řádku pro mobilní zařízení je povinná.", + "min-mobile-row-height-message": "Minimální povolená hodnota výšku řádku pro mobilní zařízení je 5 pixelů.", + "max-mobile-row-height-message": "Maximální povolená hodnota výšku řádku pro mobilní zařízení je 200 pixelů.", + "display-title": "Zobrazit název dashboardu", + "toolbar-always-open": "Ponechat nástrojovou lištu otevřenou", + "title-color": "Barva názvu", + "display-dashboards-selection": "Zobrazit výběr dashboardů", + "display-entities-selection": "Zobrazit výběr entit", + "display-dashboard-timewindow": "Zobrazit časové okno", + "display-dashboard-export": "Zobrazit export", + "import": "Importovat dashboard", + "export": "Exportovat dashboard", + "export-failed-error": "Dashboard nebylo možné exportovat: {{error}}", + "create-new-dashboard": "Vytvořit nový dashboard", + "dashboard-file": "Soubor dashboardu", + "invalid-dashboard-file-error": "Dashboard nebylo možné importovat: Neplatná datová struktura dashboardu.", + "dashboard-import-missing-aliases-title": "Konfigurovat aliasy používané importovaným dashboardem", + "create-new-widget": "Přidat nový widget", + "import-widget": "Importovat widget", + "widget-file": "Soubor widgetu", + "invalid-widget-file-error": "Widget nebylo možné importovat: Neplatná datová struktura widgetu.", + "widget-import-missing-aliases-title": "Konfigurovat aliasy používané importovaným widgetem", + "open-toolbar": "Otevřít nástrojovou lištu dashboardu", + "close-toolbar": "Zavřít nástrojovou lištu", + "configuration-error": "Chyba konfigurace", + "alias-resolution-error-title": "Chyba konfigurace aliasů dashboardu", + "invalid-aliases-config": "Nebylo možné nalézt žádná zařízení odpovídající některému z aliasů ve filtru.
Pro vyřešení tohoto problému prosím kontaktujte vašeho administrátora.", + "select-devices": "Vybrat zařízení", + "assignedToCustomer": "Přiřazeno zákazníkovi", + "assignedToCustomers": "Přiřazeno zákazníkům", + "public": "Veřejné", + "public-link": "Veřejný odkaz", + "copy-public-link": "Kopírovat veřejný odkaz", + "public-link-copied-message": "Veřejný odkaz na dashboard byl zkopírován do schránky", + "manage-states": "Spravovat stavy dashboardu", + "states": "Stavy dashboardu", + "search-states": "Vyhledat stavy dashboardu", + "selected-states": "Vybráno { count, plural, 1 {1 stavů dashboardu} other {# stavů dashboardu} }", + "edit-state": "Editovat stav dashboardu", + "delete-state": "Smazat stav dashboardu", + "add-state": "Přidat stav dashboardu", + "state": "Stav dashboardu", + "state-name": "Název", + "state-name-required": "Název stavu dashboardu je povinný.", + "state-id": "Id stavu", + "state-id-required": "Id stavu dashboardu je povinné.", + "state-id-exists": "Stav dashboardu s identickým Id již existuje.", + "is-root-state": "Základní stav", + "delete-state-title": "Smazat stav dashboardu", + "delete-state-text": "Jste si jisti, že chcete odstranit stav dashboardu s názvem '{{stateName}}'?", + "show-details": "Zobrazit detaily", + "hide-details": "Skrýt detaily", + "select-state": "Vybrat cílový stav", + "state-controller": "Kontrolér stavu" + }, + "datakey": { + "settings": "Nastavení", + "advanced": "Rozšířené", + "label": "Název", + "color": "Barva", + "units": "Speciální symbol, který bude zobrazen vedle hodnoty", + "decimals": "Počet číslic za desetinnou čárkou", + "data-generation-func": "Funkce pro generování dat", + "use-data-post-processing-func": "Použít funkci pro následné zpracování", + "configuration": "Konfigurace datového klíče", + "timeseries": "Časové řady", + "attributes": "Atributy", + "alarm": "Pole alarmu", + "timeseries-required": "Časové řady entity jsou povinné.", + "timeseries-or-attributes-required": "Časové řady / atributy entity jsou povinné.", + "maximum-timeseries-or-attributes": "Maximálně { count, plural, 1 {1 časová řada/atribut je povolena.} other {# časových řad/atributů je povoleno} }", + "alarm-fields-required": "Pole alarmu jsou povinná.", + "function-types": "Typy funkcí", + "function-types-required": "Typy funkcí jsou povinné.", + "maximum-function-types": "Maximálně { count, plural, 1 {1 typ funkce je povolen.} other {# typů funkce je povoleno} }", + "time-description": "Časová značka aktuální hodnoty;", + "value-description": "Aktuální hodnota;", + "prev-value-description": "Výsledek předchozího volání funkce;", + "time-prev-description": "Časová značka předchozí hodnoty;", + "prev-orig-value-description": "Původní předchozí hodnota;" + }, + "datasource": { + "type": "Typ datového zdroje", + "name": "Název", + "add-datasource-prompt": "Přidejte prosím datový zdroj" + }, + "details": { + "edit-mode": "Režim editace", + "toggle-edit-mode": "Přepnout do režimu editace" + }, + "device": { + "device": "Zařízení", + "device-required": "Zařízení je povinné.", + "devices": "Zařízení", + "management": "Správa zařízení", + "view-devices": "Zobrazit zařízení", + "device-alias": "Alias zařízení", + "aliases": "Aliasy zařízení", + "no-alias-matching": "'{{alias}}' nenalezen.", + "no-aliases-found": "Žádné aliasy nebyly nalezeny.", + "no-key-matching": "'{{key}}' nenalezen.", + "no-keys-found": "Žádné klíče nenalezeny.", + "create-new-alias": "Vytvořit nový!", + "create-new-key": "Vytvořit nový!", + "duplicate-alias-error": "Byl nalezen duplicitní alias '{{alias}}'.
Aliasy zařízení musí být v rámci dashboardu unikátní.", + "configure-alias": "Konfigurovat '{{alias}}' alias", + "no-devices-matching": "Žádná zařízení odpovídající '{{entity}}' nebyla nalezena.", + "alias": "Alias", + "alias-required": "Alias zařízení je povinný.", + "remove-alias": "Odebrat alias zařízení", + "add-alias": "Přidat alias zařízení", + "name-starts-with": "Název zařízení začíná", + "device-list": "Seznam zařízení", + "use-device-name-filter": "Použít filtr", + "device-list-empty": "Nebyla vybrána žádná zařízení.", + "device-name-filter-required": "Název filtru zařízení je povinný.", + "device-name-filter-no-device-matched": "Žádná zařízení začínající '{{device}}' nebyla nalezena.", + "add": "Přidat zařízení", + "assign-to-customer": "Přiřadit zákazníkovi", + "assign-device-to-customer": "Přiřadit zařízení zákazníkovi", + "assign-device-to-customer-text": "Vyberte prosím zařízení, která mají být přiřazena zákazníkovi", + "make-public": "Zveřejnit zařízení", + "make-private": "Učinit zařízení neveřejným", + "no-devices-text": "Žádná zařízení nebyla nalezena", + "assign-to-customer-text": "Vyberte prosím zákazníka, který má být přiřazen zařízení(m)", + "device-details": "Detail zařízení", + "add-device-text": "Přidat nové zařízení", + "credentials": "Přístupové údaje", + "manage-credentials": "Spravovat přístupové údaje", + "delete": "Smazat zařízení", + "assign-devices": "Přiřadit zařízení", + "assign-devices-text": "Přiřadit { count, plural, 1 {1 zařízení} other {# zařízení} } zákazníkovi", + "delete-devices": "Smazat zařízení", + "unassign-from-customer": "Odebrat zákazníkovi", + "unassign-devices": "Odebrat zařízení", + "unassign-devices-action-title": "Odebrat { count, plural, 1 {1 zařízení} other {# zařízení} } zákazníkovi", + "assign-new-device": "Přiřadit nové zařízení", + "make-public-device-title": "Jste si jisti, že chcete zařízení '{{deviceName}}' zveřejnit?", + "make-public-device-text": "Po potvrzení bude zařízení a všechna jeho data veřejná a dostupná pro ostatní.", + "make-private-device-title": "Jste si jisti, že chcete zařízení '{{deviceName}}' učinit neveřejným?", + "make-private-device-text": "Po potvrzení budou zařízení a všechna jeho data neveřejné a nedostupné pro ostatní.", + "view-credentials": "Zobrazit přístupové údaje", + "delete-device-title": "Jste si jisti, že chcete smazat zařízení '{{deviceName}}'?", + "delete-device-text": "Buďte opatrní, protože po potvrzení nebude možné zařízení ani žádná související data obnovit.", + "delete-devices-title": "Jste si jisti, že chcete smazat { count, plural, 1 {1 zařízení} other {# zařízení} }?", + "delete-devices-action-title": "Smazat { count, plural, 1 {1 zařízení} other {# zařízení} }", + "delete-devices-text": "Buďte opatrní, protože po potvrzení budou vybraná zařízení odstraněna a žádná související data nebude možné obnovit.", + "unassign-device-title": "Jste si jisti, že chcete odebrat zařízení '{{deviceName}}'?", + "unassign-device-text": "Po potvrzení bude zařízení odebráno a nebude pro zákazníka dostupné.", + "unassign-device": "Odebrat zařízení", + "unassign-devices-title": "Jste si jisti, že chcete odebrat { count, plural, 1 {1 zařízení} other {# zařízení} }?", + "unassign-devices-text": "Po potvrzení budou všechna vybraná zařízení odebrána a nebudou pro zákazníka dostupná.", + "device-credentials": "Přístupové údaje zařízení", + "credentials-type": "Typ přístupových údajů", + "access-token": "Přístupový token", + "access-token-required": "Přístupový token je povinný.", + "access-token-invalid": "Délka přístupového tokenu musí být od 1 do 20 znaků.", + "rsa-key": "RSA veřejný klíč", + "rsa-key-required": "RSA veřejný klíč je povinný.", + "secret": "Heslo", + "secret-required": "Heslo je povinné.", + "device-type": "Typ zařízení", + "device-type-required": "Typ zařízení je povinný.", + "select-device-type": "Vybrat typ zařízení", + "enter-device-type": "Zadejte typ zařízení", + "any-device": "Všechna zařízení", + "no-device-types-matching": "Žádné typy zařízení odpovídající '{{entitySubtype}}' nebyly nalezeny.", + "device-type-list-empty": "Nebyl vybrán typ zařízení.", + "device-types": "Typy zařízení", + "name": "Název", + "name-required": "Název je povinný.", + "description": "Popis", + "events": "Události", + "details": "Detail", + "copyId": "Kopírovat Id zařízení", + "copyAccessToken": "Kopírovat přístupový token", + "idCopiedMessage": "Id zařízení bylo zkopírováno do schránky", + "accessTokenCopiedMessage": "Přístupový token zařízení byl zkopírován do schránky", + "assignedToCustomer": "Přiřazeno zákazníkovi", + "unable-delete-device-alias-title": "Nebylo možné smazat alias zařízení", + "unable-delete-device-alias-text": "Alias zařízení '{{deviceAlias}}' nelze smazat, protože je používán následujícími widgety:
{{widgetsList}}", + "is-gateway": "Je bránou", + "public": "Veřejné", + "device-public": "Zařízení je veřejné", + "select-device": "vybrat zařízení" + }, + "dialog": { + "close": "Zavřít dialog" + }, + "direction": { + "column": "Sloupec", + "row": "Řádek" + }, + "error": { + "unable-to-connect": "Nebylo možné se připojit k serveru! Zkontrolujte internetové připojení.", + "unhandled-error-code": "Neošetřený chybový kód: {{errorCode}}", + "unknown-error": "Neznámá chyba" + }, + "entity": { + "entity": "Entita", + "entities": "Entity", + "aliases": "Entitní aliasy", + "entity-alias": "Alias entity", + "unable-delete-entity-alias-title": "Alias entity nebylo možné smazat", + "unable-delete-entity-alias-text": "Alias entity '{{entityAlias}}' nelze smazat, protože je používán následujícími widgety:
{{widgetsList}}", + "duplicate-alias-error": "Nalezen dupliticní alias '{{alias}}'.
Aliasy entit musí být v rámci dashboardu unikátní.", + "missing-entity-filter-error": "Ve filtru chybí alias '{{alias}}'.", + "configure-alias": "Konfigurovat '{{alias}}' alias", + "alias": "Alias", + "alias-required": "Alias entity je povinný", + "remove-alias": "Odebrat alias entity", + "add-alias": "Přidat alias entity", + "entity-list": "Seznam entit", + "entity-type": "Typ entity", + "entity-types": "Typy entit", + "entity-type-list": "Seznam typů entit", + "any-entity": "Všechny entity", + "enter-entity-type": "Zadat typ entity", + "no-entities-matching": "Žádné entity odpovídající '{{entity}}' nebyly nalezeny.", + "no-entity-types-matching": "Žádné entity odpovídající '{{entityType}}' nebyly nalezeny.", + "name-starts-with": "Název začíná", + "use-entity-name-filter": "Použít filtr", + "entity-list-empty": "Žádné entity nebyly nalezeny.", + "entity-type-list-empty": "Nebyl vybrán žádný typ entity.", + "entity-name-filter-required": "Filtr názvu entity je povinný.", + "entity-name-filter-no-entity-matched": "Žádné entity začínající '{{entity}}' nebyly nalezeny.", + "all-subtypes": "Vše", + "select-entities": "Vybrat entity", + "no-aliases-found": "Žádné aliasy nebyly nalezeny.", + "no-alias-matching": "'{{alias}}' nebyl nalezen.", + "create-new-alias": "Vytvořit nový!", + "key": "Klíč", + "key-name": "Název klíče", + "no-keys-found": "Nebyly nalezeny žádné klíče.", + "no-key-matching": "'{{key}}' nebyl nalezen.", + "create-new-key": "Vytvořit nový!", + "type": "Typ", + "type-required": "Typ entity je povinný.", + "type-device": "Zařízení", + "type-devices": "Zařízení", + "list-of-devices": "{ count, plural, 1 {Jedno zařízení} other {Seznam # zařízení} }", + "device-name-starts-with": "Zařízení, jejichž název začíná '{{prefix}}'", + "type-asset": "Aktivum", + "type-assets": "Aktiva", + "list-of-assets": "{ count, plural, 1 {Jedno aktivum} other {Seznam # aktiv} }", + "asset-name-starts-with": "Aktiva, jejichž název začíná '{{prefix}}'", + "type-entity-view": "Entitní pohled", + "type-entity-views": "Entitní pohledy", + "list-of-entity-views": "{ count, plural, 1 {Jeden entitní pohled} other {Seznam # entitních pohledů} }", + "entity-view-name-starts-with": "Entitní pohledy, jejichž název začíná '{{prefix}}'", + "type-rule": "Pravidlo", + "type-rules": "Pravidla", + "list-of-rules": "{ count, plural, 1 {Jedno pravidlo} other {Seznam # pravidel} }", + "rule-name-starts-with": "Pravidla, jejichž název začíná '{{prefix}}'", + "type-plugin": "Zásuvný modul", + "type-plugins": "Zásuvné moduly", + "list-of-plugins": "{ count, plural, 1 {Jeden zásuvný modul} other {Seznam # zásuvných modulů} }", + "plugin-name-starts-with": "Závusné moduly, jejichž název začíná '{{prefix}}'", + "type-tenant": "Tenant", + "type-tenants": "Tenanti", + "list-of-tenants": "{ count, plural, 1 {Jeden tenant} other {Seznam # tenantů} }", + "tenant-name-starts-with": "Tenanti, jejichž název začíná '{{prefix}}'", + "type-customer": "Zákazník", + "type-customers": "Zákazníci", + "list-of-customers": "{ count, plural, 1 {Jeden zákazník} other {Seznam # zákazníků} }", + "customer-name-starts-with": "Zákazníci, jejichž název začíná '{{prefix}}'", + "type-user": "Uživatel", + "type-users": "Uživatelé", + "list-of-users": "{ count, plural, 1 {Jeden uživatel} other {Seznam # uživatelů} }", + "user-name-starts-with": "Uživatelé, jejichž název začíná '{{prefix}}'", + "type-dashboard": "Dashboard", + "type-dashboards": "Dashboardy", + "list-of-dashboards": "{ count, plural, 1 {Jeden dashboard} other {Seznam # dashboardů} }", + "dashboard-name-starts-with": "Dashboardy, jejichž název začíná '{{prefix}}'", + "type-alarm": "Alarm", + "type-alarms": "Alarmy", + "list-of-alarms": "{ count, plural, 1 {Jeden alarm} other {Seznam # alarmů} }", + "alarm-name-starts-with": "Alarmy, jejichž název začíná '{{prefix}}'", + "type-rulechain": "Řetěz pravidel", + "type-rulechains": "Řetězy pravidel", + "list-of-rulechains": "{ count, plural, 1 {Jeden řetěz pravidel} other {Seznam # řetězů pravidel} }", + "rulechain-name-starts-with": "Řetězy pravidel, jejichž název začíná '{{prefix}}'", + "type-rulenode": "Uzel pravidla", + "type-rulenodes": "Uzly pravidel", + "list-of-rulenodes": "{ count, plural, 1 {Jeden uzel pravidla} other {Seznam # uzlů pravidel} }", + "rulenode-name-starts-with": "Uzly pravidel, jejichž název začíná '{{prefix}}'", + "type-current-customer": "Stávající zákazník", + "search": "Vyhledat entity", + "selected-entities": "{ count, plural, 1 {1 entita} other {# entit} } zvoleno", + "entity-name": "Název entity", + "details": "Detail entity", + "no-entities-prompt": "Nebyly nalezeny žádné entity", + "no-data": "Nelze zobrazit žádná data", + "columns-to-display": "Zobrazit sloupce" + }, + "entity-view": { + "entity-view": "Entitní pohled", + "entity-view-required": "Entitní pohled je povinný.", + "entity-views": "Entitní pohledy", + "management": "Správa entitních pohledů", + "view-entity-views": "Zobrazit entitní pohledy", + "entity-view-alias": "Alias entitního pohledu", + "aliases": "Aliasy entitního pohledu", + "no-alias-matching": "'{{alias}}' nenalezen.", + "no-aliases-found": "Žádné aliasy nebyly nalezeny.", + "no-key-matching": "'{{key}}' nenalezen.", + "no-keys-found": "Nebyly nalezeny žádné klíče.", + "create-new-alias": "Vytvořit nový!", + "create-new-key": "Vytvořit nový!", + "duplicate-alias-error": "Byl nalezen duplicitní alias '{{alias}}'.
Aliasy entitních pohledů musí být v rámci dashboardu unikátní.", + "configure-alias": "Konfigurovat '{{alias}}' alias", + "no-entity-views-matching": "Žádné entitní pohledy odpovídající '{{entity}}' nebyly nalezeny.", + "alias": "Alias", + "alias-required": "Alias entitního pohledu je povinný.", + "remove-alias": "Odebrat alias entitního pohledu", + "add-alias": "Přidat alias entitního pohledu", + "name-starts-with": "Název entitního pohledu začíná", + "entity-view-list": "Seznam entitních pohledů", + "use-entity-view-name-filter": "Použít filtr", + "entity-view-list-empty": "Nebyly vybrány žádné entitní pohledy.", + "entity-view-name-filter-required": "Filtr názvu entitního pohledu je povinný.", + "entity-view-name-filter-no-entity-view-matched": "Žádné entitní pohledy začínající '{{entityView}}' nebyly nalezeny.", + "add": "Přidat entitní pohled", + "assign-to-customer": "Přiřadit zákazníkovi", + "assign-entity-view-to-customer": "Přiřadit entitní pohled(y) zákazníkovi", + "assign-entity-view-to-customer-text": "Vyberte prosím entitní pohledy, které mají být přiřazeny zákazníkovi", + "no-entity-views-text": "Žádné entitní pohledy nebyly nalezeny", + "assign-to-customer-text": "Vyberte prosím zákazníka, kterému má být entitní pohled(y) přiřazen(y)", + "entity-view-details": "Detail entitního pohledu", + "add-entity-view-text": "Přidat nový entitní pohled", + "delete": "Smazat entitní pohled", + "assign-entity-views": "Přiřadit entitní pohledy", + "assign-entity-views-text": "Přiřadit { count, plural, 1 {1 entitní pohled} other {# entitních pohledů} } zákazníkovi", + "delete-entity-views": "Smazat entitní pohledy", + "unassign-from-customer": "Odebrat zákazníkovi", + "unassign-entity-views": "Odebrat entitní pohledy", + "unassign-entity-views-action-title": "Odebrat { count, plural, 1 {1 entitní pohled} other {# entitních pohledů} } zákazníkovi", + "assign-new-entity-view": "Přiřadit nový entitní pohled", + "delete-entity-view-title": "Jste si jisti, že chcete smazat entitní pohled '{{entityViewName}}'?", + "delete-entity-view-text": "Buďte opatrní, protože po potvrzení nebude možné entitní pohled ani žádná související data obnovit.", + "delete-entity-views-title": "Jste si jisti, že chcete odstranit entitní pohled { count, plural, 1 {1 entitní pohled} other {# entitních pohledů} }?", + "delete-entity-views-action-title": "Smazat { count, plural, 1 {1 entitní pohled} other {# entitních pohledů} }", + "delete-entity-views-text": "Buďte opatrní, protože po potvrzení budou všechny vybrané entitní pohledy smazány a žádná související data nebude možné obnovit.", + "unassign-entity-view-title": "Jste si jisti, že chcete odebrat entitní pohled '{{entityViewName}}'?", + "unassign-entity-view-text": "Po potvrzení bude entitní pohled odebrán a nebude pro zákazníka dostupný.", + "unassign-entity-view": "Odebrat entitní pohled", + "unassign-entity-views-title": "Jste si jisti, že chcete odebrat { count, plural, 1 {1 entitní pohled} other {# entitních pohledů} }?", + "unassign-entity-views-text": "Po potvrzení budou všechny vybrané entitní pohledy odebrány a nebudou pro zákazníka dostupné.", + "entity-view-type": "Typ entitního pohledu", + "entity-view-type-required": "Typ entitního pohledu je povinný.", + "select-entity-view-type": "Vybrat typ entitního pohledu", + "enter-entity-view-type": "Zadat typ entitního pohledu", + "any-entity-view": "Všechny entitní pohledy", + "no-entity-view-types-matching": "Žádné typy entitních pohledů odpovídající '{{entitySubtype}}' nebyly nalezeny.", + "entity-view-type-list-empty": "Žádné typy entitních pohledů nebyly nalezeny.", + "entity-view-types": "Typy entitních pohledů", + "name": "Název", + "name-required": "Název je povinný.", + "description": "Popis", + "events": "Události", + "details": "Detail", + "copyId": "Kopírovat Id entitního pohledu", + "assignedToCustomer": "Přiřazeno zákazníkovi", + "unable-entity-view-device-alias-title": "Alias entitního typu nebylo možné smazat", + "unable-entity-view-device-alias-text": "Alias zařízení '{{entityViewAlias}}' nelze smazat, protože je používán následujícími widgety:
{{widgetsList}}", + "select-entity-view": "Vybrat entitní pohled", + "make-public": "Zveřejnit entitní pohled", + "make-private": "Učinit entitní pohled neveřejným", + "start-date": "Datum zahájení", + "start-ts": "Čas zahájení", + "end-date": "Datum ukončení", + "end-ts": "Čas ukončení", + "date-limits": "Omezení data", + "client-attributes": "Klientské atributy", + "shared-attributes": "Sdílené atributy", + "server-attributes": "Serverové atributy", + "timeseries": "Časové řady", + "client-attributes-placeholder": "Klientské atributy", + "shared-attributes-placeholder": "Sdílené atributy", + "server-attributes-placeholder": "Serverové atributy", + "timeseries-placeholder": "Časové řady", + "target-entity": "Cílová entita", + "attributes-propagation": "Propagace atributů", + "attributes-propagation-hint": "Entitní pohled bude automaticky kopírovat specifikované atributy z cílové entity vždy, když uložíte nebo aktualizujete tento entitní pohled. Z výkonnostních důvodů nejsou atributy cílové entity propagovány při každé změně atributu. Automatickou propagaci můžete povolit konfigurací \"copy to view\" uzlu pravidla v rámci vašeho řetězu pravidel a provázáním \"Post attributes\" a \"Attributes Updated\" zpráv na nový uzel pravidla.", + "timeseries-data": "Data časových řad", + "timeseries-data-hint": "Nakonfigurujte klíče dat časových řad cílové entity, která budou dostupná pro entitní pohled. Tato data časových řad jsou pouze pro čtení.", + "make-public-entity-view-title": "Jste si jisti, že chcete entitní pohled '{{entityViewName}}' zveřejnit?", + "make-public-entity-view-text": "Po potvrzení bude entitní pohled a všechna jeho data veřejné a dostupné pro ostatní.", + "make-private-entity-view-title": "Jste si jisti, že chcete entitní pohled '{{entityViewName}}' učinit neveřejným?", + "make-private-entity-view-text": "Po potvrzení bude entitní pohled a všechna jeho data neveřejné a nebudou dostupné pro ostatní." + }, + "event": { + "event-type": "Typ události", + "type-error": "Chyba", + "type-lc-event": "Událost životního cyklu", + "type-stats": "Statistika", + "type-debug-rule-node": "Ladění", + "type-debug-rule-chain": "Ladění", + "no-events-prompt": "Nebyly nalezeny žádné události", + "error": "Chyba", + "alarm": "Alarm", + "event-time": "Čas události", + "server": "Server", + "body": "Tělo", + "method": "Metoda", + "type": "Typ", + "entity": "Entita", + "message-id": "Id zprávy", + "message-type": "Typ zprávy", + "data-type": "Typ dat", + "relation-type": "Typ vztahu", + "metadata": "Metadata", + "data": "Data", + "event": "Událost", + "status": "Stav", + "success": "Úspěch", + "failed": "Neúspěch", + "messages-processed": "Zpracované zprávy", + "errors-occurred": "Vyskytly se chyby" + }, + "extension": { + "extensions": "Rozšíření", + "selected-extensions": "Vybráno { count, plural, 1 {1 rozšíření} other {# rozšíření} }", + "type": "Typ", + "key": "Klíč", + "value": "Hodnota", + "id": "Id", + "extension-id": "Id rozšíření", + "extension-type": "Typ rozšíření", + "transformer-json": "JSON *", + "unique-id-required": "Id stávajícího rozšíření již existuje.", + "delete": "Smazat rozšíření", + "add": "Přidat rozšíření", + "edit": "Editovat rozšíření", + "delete-extension-title": "Jste si jisti, že chcete smazat rozšíření '{{extensionId}}'?", + "delete-extension-text": "Buďte opatrní, protože po potvrzení nebude možné rozšíření ani související data obnovit.", + "delete-extensions-title": "Jste si jisti, že chcete smazat { count, plural, 1 {1 rozšíření} other {# rozšíření} }?", + "delete-extensions-text": "Buďte opatrní, protože po potvrzení budou všechna vybraná rozšíření odstraněna.", + "converters": "Převodník", + "converter-id": "Id převodníku", + "configuration": "Konfigurace", + "converter-configurations": "Konfigurace převodníku", + "token": "Bezpečnostní token", + "add-converter": "Přidat převodník", + "add-config": "Přidat konfiguraci převodníku", + "device-name-expression": "Výraz názvu zařízení", + "device-type-expression": "Výraz typu zařízení", + "custom": "Vlastní", + "to-double": "Zdvojnásobit", + "transformer": "Transformátor", + "json-required": "JSON transformátoru je povinný.", + "json-parse": "Nebylo možné parsovat JSON transformátoru.", + "attributes": "Atributy", + "add-attribute": "Přidat atributy", + "add-map": "Přidat mapovací prvek", + "timeseries": "Časové řady", + "add-timeseries": "Přidat časové řady", + "field-required": "Pole je povinné", + "brokers": "Message brokers", + "add-broker": "Přidat message brokera", + "host": "Host", + "port": "Port", + "port-range": "Port by měl být v rozsahu mezi 1 a 65535.", + "ssl": "Ssl", + "credentials": "Přístupové údaje", + "username": "Uživatelské jméno", + "password": "Heslo", + "retry-interval": "Interval pro další pokus v milisekundách", + "anonymous": "Anonymní", + "basic": "Základní", + "pem": "PEM", + "ca-cert": "soubor CA certifikátu *", + "private-key": "Soubor privátního klíče *", + "cert": "Soubor certifikátu *", + "no-file": "Žádný soubor nebyl vybrán.", + "drop-file": "Přetáhněte sem soubor nebo klikněte pro výběr souboru pro nahrání.", + "mapping": "Mapování", + "topic-filter": "Filtr MQ fronty", + "converter-type": "Typ převodníku", + "converter-json": "JSON", + "json-name-expression": "JSON výraz názvu zařízení", + "topic-name-expression": "Výraz názvu MQ fronty", + "json-type-expression": "JSON Výraz typu zařízení", + "topic-type-expression": "Výraz MQ fronty typu zařízení", + "attribute-key-expression": "Výraz klíče atributu", + "attr-json-key-expression": "JSON výraz klíče atributu", + "attr-topic-key-expression": "Výraz MQ fronty klíče atributu", + "request-id-expression": "Výraz Id požadavku", + "request-id-json-expression": "JSON výraz Id požadavku", + "request-id-topic-expression": "Výraz MQ fronty ID požadavku", + "response-topic-expression": "Výraz fronty odpovědi", + "value-expression": "Výraz hodnoty", + "topic": " MQ fronta", + "timeout": "Timeout v milisekundách", + "converter-json-required": "JSON převodníku je povinný.", + "converter-json-parse": "JSON převodníku nebylo možné parsovat.", + "filter-expression": "Výraz filtru", + "connect-requests": "Požadavky na spojení", + "add-connect-request": "Přidat požadavek na spojení", + "disconnect-requests": "Požadavky na odpojení", + "add-disconnect-request": "Přidat požadavek na odpojení", + "attribute-requests": "Požadavky na atribut", + "add-attribute-request": "Přidat požadavek na atribut", + "attribute-updates": "Aktualizace atributu", + "add-attribute-update": "Přidat aktualizaci atributu", + "server-side-rpc": "RPC na straně serveru", + "add-server-side-rpc-request": "Přidat požadavek na RPC na straně serveru", + "device-name-filter": "Filtr názvu zařízení", + "attribute-filter": "Filtr atributu", + "method-filter": "Filtr metody", + "request-topic-expression": "Výraz požadavku na MQ frontu", + "response-timeout": "Timeout odpovědi v milisekundách", + "topic-expression": "Výraz MQ fronty", + "client-scope": "Scope klienta", + "add-device": "Přidat zařízení", + "opc-server": "Servery", + "opc-add-server": "Přidat server", + "opc-add-server-prompt": "Prosím přidejte server", + "opc-application-name": "Název aplikace", + "opc-application-uri": "URI aplikace", + "opc-scan-period-in-seconds": "Interval skenování ve vteřinách", + "opc-security": "Bezpečnost", + "opc-identity": "Identita", + "opc-keystore": "Úložiště klíčů", + "opc-type": "Typ", + "opc-keystore-type": "Typ", + "opc-keystore-location": "Umístění *", + "opc-keystore-password": "Heslo", + "opc-keystore-alias": "Alias", + "opc-keystore-key-password": "Heslo klíče", + "opc-device-node-pattern": "Vzor uzlu zařízení", + "opc-device-name-pattern": "Vzor názvu zařízení", + "modbus-server": "Servery/slaves", + "modbus-add-server": "Přidat server/slave", + "modbus-add-server-prompt": "Prosím přidejte server/slave", + "modbus-transport": "Transport", + "modbus-tcp-reconnect": "Automaticky znovu připojit", + "modbus-rtu-over-tcp": "RTU přes TCP", + "modbus-port-name": "Název sériového portu", + "modbus-encoding": "Šifrování", + "modbus-parity": "Parita", + "modbus-baudrate": "Baud rate", + "modbus-databits": "Data bits", + "modbus-stopbits": "Stop bits", + "modbus-databits-range": "Data bits by měly být v rozsahu od 7 do 8.", + "modbus-stopbits-range": "Stop bits by měly být v rozsahu od 1 do 2.", + "modbus-unit-id": "ID jednotky", + "modbus-unit-id-range": "ID jednotky by mělo být v rozsahu od 1 do 247.", + "modbus-device-name": "Název zařízení", + "modbus-poll-period": "Interval kontroly (ms)", + "modbus-attributes-poll-period": "Interval kontroly atributů (ms)", + "modbus-timeseries-poll-period": "Interval kontroly časových řad (ms)", + "modbus-poll-period-range": "Interval kontroly by měl mít kladnou hodnotu.", + "modbus-tag": "Štítek", + "modbus-function": "Funkce", + "modbus-register-address": "Adresa registrace", + "modbus-register-address-range": "Adresa registrace by měla být v rozsahu od 0 do 65535.", + "modbus-register-bit-index": "Bit index", + "modbus-register-bit-index-range": "Bit index by měl být v rozsahu od 0 do 15.", + "modbus-register-count": "Počet registrací", + "modbus-register-count-range": "Počet registrací by měl mít kladnou hodnotu.", + "modbus-byte-order": "Byte order", + "sync": { + "status": "Stav", + "sync": "Synchronizováno", + "not-sync": "Nesynchronizováno", + "last-sync-time": "Čas poslední synchronizace", + "not-available": "Nedostupné" + }, + "export-extensions-configuration": "Exportovat konfiguraci rozšíření", + "import-extensions-configuration": "Importovat konfiguraci rozšíření", + "import-extensions": "Importovat rozšíření", + "import-extension": "Importovat rozšíření", + "export-extension": "Exportovat rozšíření", + "file": "Soubor rozšíření", + "invalid-file-error": "Neplatný soubor rozšíření" + }, + "fullscreen": { + "expand": "Rozšířit do režimu celé obrazovky", + "exit": "Ukončit režim celé obrazovky", + "toggle": "Přepnout do režimu celé obrazovky", + "fullscreen": "Celá obrazovka" + }, + "function": { + "function": "Funkce" + }, + "grid": { + "delete-item-title": "Jste si jisti, že chcete smazat tuto položku?", + "delete-item-text": "Buďte opatrní, protože po potvrzení nebude možné tuto položku ani žádná související data obnovit.", + "delete-items-title": "Jste si jisti, že chcete smazat { count, plural, 1 {1 položku} other {# položek} }?", + "delete-items-action-title": "Smazat { count, plural, 1 {1 položku} other {# položek} }", + "delete-items-text": "Buďte opatrní, protože po potvrzení budou všechny vybrané položky odstraněny a žádná související data nebude možné obnovit.", + "add-item-text": "Přidat novou položku", + "no-items-text": "Žádné položky nebyly nalezeny", + "item-details": "Detail položky", + "delete-item": "Smazat položku", + "delete-items": "Smazat položky", + "scroll-to-top": "Nahoru" + }, + "help": { + "goto-help-page": "Jít na stránku nápovědy" + }, + "home": { + "home": "Domů", + "profile": "Profil", + "logout": "Odhlásit", + "menu": "Menu", + "avatar": "Avatar", + "open-user-menu": "Otevřít uživatelské menu" + }, + "import": { + "no-file": "Nebyl vybrán žádný soubor", + "drop-file": "Přetáhněte sem JSON soubor nebo klikněte pro výběr souboru pro nahrání." + }, + "item": { + "selected": "Vybráno" + }, + "js-func": { + "no-return-error": "Funkce musí vrátit hodnotu!", + "return-type-mismatch": "Funkce musí vrátit hodnotu '{{type}}' typu!", + "tidy": "Tidy" + }, + "key-val": { + "key": "Klíč", + "value": "Hodnota", + "remove-entry": "Odstranit záznam", + "add-entry": "Přidat záznam", + "no-data": "Žádné záznamy" + }, + "layout": { + "layout": "Rozmístění", + "manage": "Spravovat rozmístění", + "settings": "Nastavení rozmístění", + "color": "Barva", + "main": "Hlavní", + "right": "Vpravo", + "select": "Vybrat cílové rozmístění" + }, + "legend": { + "direction": "Směr legendy", + "position": "Pozice legendy", + "show-max": "Zobrazit max hodnotu", + "show-min": "Zobrazit min hodnotu", + "show-avg": "Zobrazit průměrnou hodnotu", + "show-total": "Zobrazit celkovou hodnotu", + "settings": "Nastavení legendy", + "min": "min", + "max": "max", + "avg": "průměr", + "total": "celkem" + }, + "login": { + "login": "Přihlásit", + "request-password-reset": "Vyžádat reset hesla", + "reset-password": "Reset hesla", + "create-password": "Vytvořit heslo", + "passwords-mismatch-error": "Zadaná hesla se musí shodovat!", + "password-again": "Heslo znovu", + "sign-in": "Prosím zaregistrujte se", + "username": "Uživatelské jméno (email)", + "remember-me": "Zapamatovat si mě", + "forgot-password": "Zapomněli jste heslo?", + "password-reset": "Reset hesla", + "new-password": "Nové heslo", + "new-password-again": "Nové heslo znovu", + "password-link-sent-message": "Odkaz pro reset hesla byl úspěšně odeslán!", + "email": "Email" + }, + "position": { + "top": "Nahoře", + "bottom": "Dole", + "left": "Vlevo", + "right": "Vpravo" + }, + "profile": { + "profile": "Profil", + "change-password": "Změnit heslo", + "current-password": "Stávající heslo" + }, + "relation": { + "relations": "Vztahy", + "direction": "Směr", + "search-direction": { + "FROM": "Od", + "TO": "K" + }, + "direction-type": { + "FROM": "od", + "TO": "k" + }, + "from-relations": "Odchozí vztahy", + "to-relations": "Příchozí vztahy", + "selected-relations": "Vybráno { count, plural, 1 {1 vztahů} other {# vztahů} }", + "type": "Typ", + "to-entity-type": "K typ entity", + "to-entity-name": "K název entity", + "from-entity-type": "Z typ entity", + "from-entity-name": "Z název entity", + "to-entity": "K entitě", + "from-entity": "Od entity", + "delete": "Smazat vztah", + "relation-type": "Typ vztahu", + "relation-type-required": "Typ vztahu je povinný.", + "any-relation-type": "Všechny typy", + "add": "Přidat vztah", + "edit": "Editovat vztah", + "delete-to-relation-title": "Jste si jisti, že chcete smazat vztah k entitě '{{entityName}}'?", + "delete-to-relation-text": "Buďte opatrní, protože po potvrzení bude vtah entity '{{entityName}}' k aktuální entitě zrušen.", + "delete-to-relations-title": "Jste si jisti, že chcete smazat { count, plural, 1 {1 vztah} other {# vztahů} }?", + "delete-to-relations-text": "Buďte opatrní, protože po potvrzení budou všechny vybrané vztahy odstraněny a vztah odpovídajících entit k aktuální entitě bude zrušen.", + "delete-from-relation-title": "Jste si jisti, že chcete smazat vztah z entity '{{entityName}}'?", + "delete-from-relation-text": "Buďte opatrní, protože po potvrzení bude zrušen vztah aktuální entity k entitě '{{entityName}}'.", + "delete-from-relations-title": "Jste si jisti, že chcete smazat { count, plural, 1 {1 vztah} other {# vztahů} }?", + "delete-from-relations-text": "Buďte opatrní, protože po potvrzení budou všechny vybrané vztahy odstraněny a bude zrušen vztah aktuální entity k odpovídajícím entitám.", + "remove-relation-filter": "Odebrat filtr vztahů", + "add-relation-filter": "Přidat filtr vztahu", + "any-relation": "Všechny vztahy", + "relation-filters": "Filtry vztahů", + "additional-info": "Další info (JSON)", + "invalid-additional-info": "Další informace v JSON nebylo možné parsovat." + }, + "rulechain": { + "rulechain": "Řetěz pravidel", + "rulechains": "Řetězy pravidel", + "root": "Základní", + "delete": "Smazat řetěz pravidel", + "name": "Název", + "name-required": "Název je povinný.", + "description": "Popis", + "add": "Přidat řetěz pravidel", + "set-root": "Učinit řetěz pravidel základním", + "set-root-rulechain-title": "Jste si jisti, že chcete učinit řetěz pravidel '{{ruleChainName}}' základním?", + "set-root-rulechain-text": "Po potvrzení se stane řetěz pravidel základním a bude zajišťovat zpracování všech příchozích transportních zpráv.", + "delete-rulechain-title": "Jste si jisti, že chcete smazat řetěz pravidel '{{ruleChainName}}'?", + "delete-rulechain-text": "Buďte opatrní, protože po potvrzení nebude možné řetěz pravidel ani žádná související data obnovit.", + "delete-rulechains-title": "Jste si jisti, že chcete smazat { count, plural, 1 {1 řetěz pravidel} other {# řetězů pravidel} }?", + "delete-rulechains-action-title": "Smazat { count, plural, 1 {1 řetěz pravidel} other {# řetězy pravidel} }", + "delete-rulechains-text": "Buďte opatrní, protože po potvrzení budou všechny vybrané řetězy pravidel odstraněny a žádná související data nebude možné obnovit.", + "add-rulechain-text": "Přidat nový řetěz pravidel", + "no-rulechains-text": "Žádné řetězy pravidel nebyly nalezeny", + "rulechain-details": "Detail řetězu pravidel", + "details": "Detail", + "events": "Události", + "system": "Systém", + "import": "Importovat řetěz pravidel", + "export": "Exportovat řetěz pravidel", + "export-failed-error": "Řetěz pravidel nebylo možné smazat: {{error}}", + "create-new-rulechain": "Vytvořit nový řetěz pravidel", + "rulechain-file": "Soubor řetězu pravidel", + "invalid-rulechain-file-error": "Řetěz pravidel nebylo možné importovat: neplatná datová struktura řetězu pravidel.", + "copyId": "Kopírovat Id řetězu pravidel", + "idCopiedMessage": "Id řetězu pravidel bylo zkopírováno do schránky", + "select-rulechain": "Vybrat řetěz pravidel", + "no-rulechains-matching": "Žádné řetězy pravidel odpovídající '{{entity}}' nebyly nalezeny.", + "rulechain-required": "Řetěz pravidel je povinný", + "management": "Správa pravidel", + "debug-mode": "Režim ladění" + }, + "rulenode": { + "details": "Detail", + "events": "Události", + "search": "Vyhledat uzly", + "open-node-library": "Otevřít knihovnu uzlů", + "add": "Přidat uzel pravidla", + "name": "Název", + "name-required": "Název je povinný.", + "type": "Typ", + "description": "Popis", + "delete": "Smazat uzel pravidla", + "select-all-objects": "Vybrat všechny uzly a spojení", + "deselect-all-objects": "Zrušit výběr všech uzlů a spojení", + "delete-selected-objects": "Smazat vybrané uzly a spojení", + "delete-selected": "Smazat vybrané", + "select-all": "Vybrat vše", + "copy-selected": "Kopírovat vybrané", + "deselect-all": "Zrušit výběr všech", + "rulenode-details": "Detail uzlu pravidla", + "debug-mode": "Režim ladění", + "configuration": "Konfigurace", + "link": "Odkaz", + "link-details": "Detail odkazu uzlu pravidla", + "add-link": "Přidat odkaz", + "link-label": "Název odkazu", + "link-label-required": "Název odkazu je povinný.", + "custom-link-label": "Vlastní název odkazu", + "custom-link-label-required": "Název vlastního odkazu je povinný.", + "link-labels": "Názvy odkazu", + "link-labels-required": "Názvy odkazu jsou povinné.", + "no-link-labels-found": "Žádné názvy odkazů nebyly nalezeny", + "no-link-label-matching": "'{{label}}' nenalezen.", + "create-new-link-label": "Vytvořit nový!", + "type-filter": "Filtr", + "type-filter-details": "Filtruje příchozí zprávy na základě definovaných podmínek", + "type-enrichment": "Obohacení", + "type-enrichment-details": "Přidá doplňující informace do metadat zprávy", + "type-transformation": "Transformace", + "type-transformation-details": "Změní zprávu a metadata", + "type-action": "Akce", + "type-action-details": "Provede speciální akci", + "type-external": "Externí", + "type-external-details": "Interaguje s externím systémem", + "type-rule-chain": "Řetěz pravidel", + "type-rule-chain-details": "Předá příchozí zprávy specifikovanému řetězu pravidel", + "type-input": "Vstup", + "type-input-details": "Logický vstup řetězu pravidel, předává příchozí zprávy dalšímu navazujícímu uzlu pravidla", + "type-unknown": "Neznámý", + "type-unknown-details": "Nevyřešený uzel pravidla", + "directive-is-not-loaded": "Definovaná konfigurační direktiva '{{directiveName}}' není dostupná.", + "ui-resources-load-error": "Nepodařilo se nahrát konfigurační ui zdroje.", + "invalid-target-rulechain": "Není možné interagovat s cílovým řetězem pravidel!", + "test-script-function": "Testovat funkci skriptu", + "message": "Zpráva", + "message-type": "Typ zprávy", + "select-message-type": "Vybrat typ zprávy", + "message-type-required": "Typ zprávy je povinný", + "metadata": "Metadata", + "metadata-required": "Záznam metadat nemůže být prázdný.", + "output": "Výstup", + "test": "Test", + "help": "Nápověda", + "reset-debug-mode": "Resetovat režim ladění na všech uzlech" + }, + "tenant": { + "tenant": "Tenant", + "tenants": "Tenanti", + "management": "Správa tenantů", + "add": "Přidat tenanta", + "admins": "Administrátoři", + "manage-tenant-admins": "Spravovat administrátory tenanta", + "delete": "Smazat tenanta", + "add-tenant-text": "Přidat nového tenanta", + "no-tenants-text": "Žádní tenanti nebyli nalezeni", + "tenant-details": "Detail tenanta", + "delete-tenant-title": "Jste si jisti, že chcete smazat tenanta '{{tenantTitle}}'?", + "delete-tenant-text": "Buďte opatrní, protože po potvrzení nebude možné tenanta ani žádná související data obnovit.", + "delete-tenants-title": "Jste si jisti, že chcete smazat { count, plural, 1 {1 tenanta} other {# tenantů} }?", + "delete-tenants-action-title": "Smazat { count, plural, 1 {1 tenanta} other {# tenantů} }", + "delete-tenants-text": "Buďte opatrní, protože po potvrzení budou všichni vybraní tenanti odstranění a žádná související data nebude možné obnovit.", + "title": "Název", + "title-required": "Název je povinný.", + "description": "Popis", + "details": "Detail", + "events": "Události", + "copyId": "Kopírovat Id tenanta", + "idCopiedMessage": "Id tenanta bylo zkopírováno do schránky", + "select-tenant": "Vybrat tenanta", + "no-tenants-matching": "Žádní tenanti odpovídající '{{entity}}' nebyli nalezeni.", + "tenant-required": "Tenant je povinný" + }, + "timeinterval": { + "seconds-interval": "{ seconds, plural, 1 {1 vteřina} other {# vteřin} }", + "minutes-interval": "{ minutes, plural, 1 {1 minuta} other {# minut} }", + "hours-interval": "{ hours, plural, 1 {1 hodina} other {# hodin} }", + "days-interval": "{ days, plural, 1 {1 den} other {# dnů} }", + "days": "Dny", + "hours": "Hodiny", + "minutes": "Minuty", + "seconds": "Vteřiny", + "advanced": "Rozšířené" + }, + "timewindow": { + "days": "{ days, plural, 1 { den } other {# days } }", + "hours": "{ hours, plural, 0 { hodina } 1 {1 hodina } other {# hodin } }", + "minutes": "{ minutes, plural, 0 { minuta } 1 {1 minuta } other {# minut } }", + "seconds": "{ seconds, plural, 0 { vteřina } 1 {1 vteřina } other {# vteřin } }", + "realtime": "V reálném čase", + "history": "Historie", + "last-prefix": "poslední", + "period": "od {{ startTime }} do {{ endTime }}", + "edit": "Editovat časové okno", + "date-range": "Rozsah data", + "last": "Poslední", + "time-period": "Časový interval" + }, + "user": { + "user": "Uživatel", + "users": "Uživatelé", + "customer-users": "Uživatelé zákazníka", + "tenant-admins": "Administrátoři tenanta", + "sys-admin": "Systémový administrátor", + "tenant-admin": "Administrátor tenanta", + "customer": "Zákazník", + "anonymous": "Anonymní", + "add": "Přidat uživatele", + "delete": "Smazat uživatele", + "add-user-text": "Přidat nového uživatele", + "no-users-text": "Žádní uživatelé nebyli nalezeni", + "user-details": "Detail uživatele", + "delete-user-title": "Jste si jisti, že chcete smazat uživatele '{{userEmail}}'?", + "delete-user-text": "Buďte opatrní, protože po potvrzení nebude možné uživatele ani žádná související data obnovit.", + "delete-users-title": "Jste si jisti, že chcete smazat { count, plural, 1 {1 uživatele} other {# uživatele} }?", + "delete-users-action-title": "Smazat { count, plural, 1 {1 uživatele} other {# uživatele} }", + "delete-users-text": "Buďte opatrní, protože po potvrzení budou všichni vybraní uživatelé odstraněni a žádná související data nebude možné obnovit.", + "activation-email-sent-message": "Aktivační email byl úspěšně odeslán!", + "resend-activation": "Znovu poslat aktivační email", + "email": "Email", + "email-required": "Email je povinný.", + "invalid-email-format": "Neplatný formát emailu.", + "first-name": "Jméno", + "last-name": "Příjmení", + "description": "Popis", + "default-dashboard": "Defaultní dashboard", + "always-fullscreen": "Zobrazení vždy na celé obrazovce", + "select-user": "Vybrat uživatele", + "no-users-matching": "Žádní uživatelé odpovídající '{{entity}}' nebyli nalezeni.", + "user-required": "Uživatel je povinný", + "activation-method": "Metoda aktivace", + "display-activation-link": "Zobrazit aktivační odkaz", + "send-activation-mail": "Odeslat aktivační email", + "activation-link": "Aktivační odkaz uživatele", + "activation-link-text": "Pro aktivaci uživatele použijte následující aktivační odkaz :", + "copy-activation-link": "Kopírovat aktivační odkaz", + "activation-link-copied-message": "Aktivační odkaz uživatele byl zkopírován do schránky", + "details": "Detail", + "login-as-tenant-admin": "Přihlásit se jako administrátor tenanta", + "login-as-customer-user": "Přihlásit se jako uživatel zákazníka" + }, + "value": { + "type": "Typ hodnoty", + "string": "Řetězec", + "string-value": "Hodnota řetězce", + "integer": "Celé číslo", + "integer-value": "Hodnota celého čísla", + "invalid-integer-value": "Neplatná hodnota celého čísla", + "double": "Číslo s plovoucí řádovou s dvojitou přesností", + "double-value": "Hodnota čísla s plovoucí řádovou řádkou", + "boolean": "Pravdivostní hodnota", + "boolean-value": "Hodnota pravdivostní hodnoty", + "false": "Nepravda", + "true": "Pravda", + "long": "Vysoké celé číslo" + }, + "widget": { + "widget-library": "Knihovna widgetů", + "widget-bundle": "Kategorie widgetů", + "select-widgets-bundle": "Vybrat kategorii widgetů", + "management": "Správa widgetů", + "editor": "Editor widgetů", + "widget-type-not-found": "Problém s nahráním konfigurace widgetu.
Pravděpodobně byl asociovaný\n typ widgetu odstraněn.", + "widget-type-load-error": "Widget nebyl nahrán z důvodu následujících chyb:", + "remove": "Odebrat widget", + "edit": "Editovat widget", + "remove-widget-title": "Jste si jisti, že chcete odebrat widget '{{widgetTitle}}'?", + "remove-widget-text": "Po potvrzení nebude možné widget ani žádná související data obnovit.", + "timeseries": "Časové řady", + "search-data": "Vyhledat data", + "no-data-found": "Žádná data nebyla nalezena", + "latest-values": "Poslední hodnoty", + "rpc": "Ovládací widget", + "alarm": "Widgety alarmu", + "static": "Statické widgety", + "select-widget-type": "Vybrat typ widgetu", + "missing-widget-title-error": "Název widgetu musí být specifikován!", + "widget-saved": "Widget uložen", + "unable-to-save-widget-error": "Widget nebylo možné uložit! Widget obsahuje chyby!", + "save": "Uložit widget", + "saveAs": "Uložit widget jako", + "save-widget-type-as": "Uložit typ widgetu jako", + "save-widget-type-as-text": "Zadejte prosím název nového widgetu a/nebo vyberte cílovou kategorii widgetů", + "toggle-fullscreen": "Přepnout na celou obrazovku", + "run": "Spustit widget", + "title": "Název widgetu", + "title-required": "Název widgetu je povinný.", + "type": "Typ widgetu", + "resources": "Zdroje", + "resource-url": "JavaScript/CSS URL", + "remove-resource": "Odebrat zdroj", + "add-resource": "Přidat zdroj", + "html": "HTML", + "tidy": "Tidy", + "css": "CSS", + "settings-schema": "Schéma nastavení", + "datakey-settings-schema": "Schéma nastavení datového klíče", + "javascript": "Javascript", + "remove-widget-type-title": "Jste si jisti, že chcete odebrat typ widgetu '{{widgetName}}'?", + "remove-widget-type-text": "Po potvrzení nebude možné typ widgetu ani žádná související data obnovit.", + "remove-widget-type": "Odebrat typ widgetu", + "add-widget-type": "Přidat nový typ widgetu", + "widget-type-load-failed-error": "Nahrání typu widgetu selhalo!", + "widget-template-load-failed-error": "Nahrání šablony widgetu selhalo!", + "add": "Přidat widget", + "undo": "Vrátit změny widgetu", + "export": "Exportovat widget" + }, + "widget-action": { + "header-button": "Tlačítko hlavičky widgetu", + "open-dashboard-state": "Přejít k novému stavu dashboardu", + "update-dashboard-state": "Aktualizovat stávající stav dashboardu", + "open-dashboard": "Přejít k jinému dashboardu", + "custom": "Vlastní akce", + "target-dashboard-state": "Cílový stav dashboardu", + "target-dashboard-state-required": "Cílový stav dashboardu je povinný", + "set-entity-from-widget": "Nastavit entitu z widgetu", + "target-dashboard": "Cílový dashboard", + "open-right-layout": "Otevřít rozmístění dashboardu vpravo (mobilní zobrazení)" + }, + "widgets-bundle": { + "current": "Vybraná kategorie", + "widgets-bundles": "Kategorie widgetů", + "add": "Přidat kategorii widgetů", + "delete": "Smazat kategorii widgetů", + "title": "Název", + "title-required": "Název je povinný.", + "add-widgets-bundle-text": "Přidat novou kategorii widgetů", + "no-widgets-bundles-text": "Žádné kategorie widgetů nebyly nalezeny", + "empty": "Kategorie widgetů je prázdná", + "details": "Detail", + "widgets-bundle-details": "Detail kategorie widgetů", + "delete-widgets-bundle-title": "Jste si jisti, že chcete smazat kategorii widgetů '{{widgetsBundleTitle}}'?", + "delete-widgets-bundle-text": "Buďte opatrní, po potvrzení nebude možné kategorii widgetů ani žádná související data obnovit.", + "delete-widgets-bundles-title": "Jste si jisti, že chcete odstranit { count, plural, 1 {1 kategorii widgetů} other {# kategorií widgetů} }?", + "delete-widgets-bundles-action-title": "Smazat { count, plural, 1 {1 kategorii widgetů} other {# kategorií widgetů} }", + "delete-widgets-bundles-text": "Buďte opatrní, po potvrzení budou všechny vybrané kategorie widgetů odstraněny a žádná související data nebude možné obnovit.", + "no-widgets-bundles-matching": "Žádné kategorie widgetů odpovídající '{{widgetsBundle}}' nebyly nalezeny.", + "widgets-bundle-required": "Kategorie widgetů je povinná.", + "system": "Systém", + "import": "Importovat kategorii widgetů", + "export": "Exportovat kategorii widgetů", + "export-failed-error": "Kategorii widgetů nebylo možné exportovat: {{error}}", + "create-new-widgets-bundle": "Vytvořit novou kategorii widgetů", + "widgets-bundle-file": "Soubor kategorie widgetů", + "invalid-widgets-bundle-file-error": "Kategorii widgetů nebylo možné importovat: Neplatná datová struktura kategorie widgetů." + }, + "widget-config": { + "data": "Data", + "settings": "Nastavení", + "advanced": "Rozšířené", + "title": "Název", + "general-settings": "Obecná nastavení", + "display-title": "Zobrazovaný název", + "drop-shadow": "Stín", + "enable-fullscreen": "Povolit zobrazení na celé obrazovce", + "background-color": "Barva pozadí", + "text-color": "Barva textu", + "padding": "Šířka vnitřního okraje", + "margin": "Okraj", + "widget-style": "Styl widgetu", + "title-style": "Název stylu", + "mobile-mode-settings": "Nastavení mobilního režimu", + "order": "Pořadí", + "height": "Výška", + "units": "Speciální symbol zobrazovaný za hodnotou", + "decimals": "Počet číslic za desetinnou čárkou", + "timewindow": "Časové okno", + "use-dashboard-timewindow": "Použít časové okno dashboardu", + "display-legend": "Zobrazit legendu", + "datasources": "Datové zdroje", + "maximum-datasources": "Maximum { count, plural, 1 {1 datový zdroj je povolen.} other {# datových zdrojů je povoleno} }", + "datasource-type": "Typ", + "datasource-parameters": "Parametry", + "remove-datasource": "Odebrat datový zdroj", + "add-datasource": "Přidat datový zdroj", + "target-device": "Cílové zařízení", + "alarm-source": "Zdroj alarmu", + "actions": "Akce", + "action": "Akce", + "add-action": "Přidat akci", + "search-actions": "Vyhledat akce", + "action-source": "Zdroj akce", + "action-source-required": "Zdroj akce je povinný.", + "action-name": "Název", + "action-name-required": "Název akce je povinný.", + "action-name-not-unique": "Jiná akce s identickým názvem již existuje.
Název akce by měl být v rámci zdroje akce unikátní.", + "action-icon": "Ikona", + "action-type": "Typ", + "action-type-required": "Typ akce je povinný.", + "edit-action": "Editovat akci", + "delete-action": "Smazat akci", + "delete-action-title": "Smazat akci widgetu", + "delete-action-text": "Jste si jisti, že chcete smazat akci widgetu s názvem '{{actionName}}'?" + }, + "widget-type": { + "import": "Importovat typ widgetu", + "export": "Exportovat typ widgetu", + "export-failed-error": "Typ widgetu nebylo možné exportovat: {{error}}", + "create-new-widget-type": "Vytvořit nový typ widgetu", + "widget-type-file": "Soubor typu widgetu", + "invalid-widget-type-file-error": "Typ widgetu nebylo možné importovat: Neplatná datová struktura typu widgetu." + }, + "widgets": { + "date-range-navigator": { + "localizationMap": { + "Sun": "Ne", + "Mon": "Po", + "Tue": "Út", + "Wed": "St", + "Thu": "Čt", + "Fri": "Pá", + "Sat": "So", + "Jan": "Led", + "Feb": "Úno", + "Mar": "Bře", + "Apr": "Dub", + "May": "Květen", + "Jun": "Čvn", + "Jul": "Čvc", + "Aug": "Srp", + "Sep": "Zář", + "Oct": "Říj", + "Nov": "Lis", + "Dec": "Pro", + "January": "Leden", + "February": "Únor", + "March": "Březen", + "April": "Duben", + "June": "Červen", + "July": "Červenec", + "August": "Srpen", + "September": "Září", + "October": "Říjen", + "November": "Listopad", + "December": "Prosinec", + "Custom Date Range": "Vlastní rozsah data", + "Date Range Template": "Šablona rozsahu data", + "Today": "Dnes", + "Yesterday": "Včera", + "This Week": "Tento týden", + "Last Week": "Minulý týden", + "This Month": "Tento měsíc", + "Last Month": "Minulý měsíc", + "Year": "Rok", + "This Year": "Tento rok", + "Last Year": "Minulý rok", + "Date picker": "Výběr data", + "Hour": "Hodina", + "Day": "Den", + "Week": "Týden", + "2 weeks": "2 týdny", + "Month": "Měsíc", + "3 months": "3 měsíce", + "6 months": "6 měsíců", + "Custom interval": "Vlastní interval", + "Interval": "Interval", + "Step size": "Velikost kroku", + "Ok": "Ok" + } + } + }, + "icon": { + "icon": "Ikona", + "select-icon": "Vybrat ikonu", + "material-icons": "Ikony Material", + "show-all": "Zobrazit všechny ikony" + }, + "custom": { + "widget-action": { + "action-cell-button": "Tlačítko buňky akce", + "row-click": "Při kliknutí na řádek", + "polygon-click": "Při kliknutí na polygon", + "marker-click": "Při kliknutí na značku", + "tooltip-tag-action": "Akce štítku nápovědy", + "node-selected": "Při výběru uzlu" + } + }, + "language": { + "language": "Jazyk", + "locales": { + "de_DE": "German", + "fr_FR": "French", + "zh_CN": "Chinese", + "en_US": "English", + "it_IT": "Italian", + "ko_KR": "Korean", + "ru_RU": "Russian", + "es_ES": "Spanish", + "ja_JA": "Japanese", + "tr_TR": "Turkish", + "fa_IR": "Persian", + "uk_UA": "Ukrainian", + "cs_CZ": "Česky" + } + } +} diff --git a/ui-ngx/src/assets/locale/locale.constant-de_DE.json b/ui-ngx/src/assets/locale/locale.constant-de_DE.json new file mode 100644 index 0000000000..f7048368b4 --- /dev/null +++ b/ui-ngx/src/assets/locale/locale.constant-de_DE.json @@ -0,0 +1,1647 @@ +{ + "access": { + "unauthorized": "Nicht autorisiert", + "unauthorized-access": "Unautorisierter Zugriff", + "unauthorized-access-text": "Sie sollten sich anmelden, um Zugriff auf diese Daten zu erhalten!", + "access-forbidden": "Keine Zugangsberechtigung", + "access-forbidden-text": "Sie haben keine Zugangsberechtigung für diesen Bereich!
Versuchen Sie sich mit einem anderen Benutzer anzumelden um Zugriff auf diesen Bereich zu bekommen.", + "refresh-token-expired": "Sitzung ist abgelaufen", + "refresh-token-failed": "Sitzung kann nicht aktualisiert werden" + }, + "action": { + "activate": "Aktivieren", + "suspend": "Unterbrechen", + "save": "Speichern", + "saveAs": "Speichern unter", + "cancel": "Abbrechen", + "ok": "OK", + "delete": "Löschen", + "add": "Hinzufügen", + "yes": "Ja", + "no": "Nein", + "update": "Aktualisieren", + "remove": "Löschen", + "search": "Suche", + "clear-search": "Suchanfrage löschen", + "assign": "Zuordnen", + "unassign": "Zuordnung aufheben", + "share": "Teilen", + "make-private": "Privat machen", + "apply": "Anwenden", + "apply-changes": "Änderungen übernehmen", + "edit-mode": "Bearbeitungsmodus", + "enter-edit-mode": "Zum Bearbeitungsmodus wechseln", + "decline-changes": "Änderungen nicht übernehmen", + "close": "Schließen", + "back": "Zurück", + "run": "Ausführen", + "sign-in": "Anmelden!", + "edit": "Bearbeiten", + "view": "Ansicht", + "create": "Erstellen", + "drag": "Ziehen", + "refresh": "Aktualisieren", + "undo": "Rückgängig machen", + "copy": "Kopieren", + "paste": "Einfügen", + "copy-reference": "Zeichen kopieren", + "paste-reference": "Zeichen einfügen", + "import": "Importieren", + "export": "Exportieren", + "share-via": "Teilen mit {{provider}}" + }, + "aggregation": { + "aggregation": "Aggregation", + "function": "Datenaggregationsfunktion", + "limit": "Höchstwerte", + "group-interval": "Gruppierungsintervall", + "min": "Minimal", + "max": "Maximal", + "avg": "Durchschnitt", + "sum": "Summe", + "count": "Anzahl", + "none": "kein Wert" + }, + "admin": { + "general": "Allgemein", + "general-settings": "Allgemeine Einstellungen", + "outgoing-mail": "E-Mail Versand", + "outgoing-mail-settings": "Konfiguration des Postausgangsservers", + "system-settings": "Systemeinstellungen", + "test-mail-sent": "Test E-Mail wurde erfolgreich versendet!", + "base-url": "Basis-URL", + "base-url-required": "Basis-URL ist erforderlich.", + "mail-from": "E-Mail von", + "mail-from-required": "E-Mail von ist erforderlich.", + "smtp-protocol": "SMTP Protokoll", + "smtp-host": "SMTP Host", + "smtp-host-required": "SMTP Host ist erforderlich.", + "smtp-port": "SMTP Port", + "smtp-port-required": "Sie müssen einen SMTP Port angeben.", + "smtp-port-invalid": "Das ist kein gültiger SMTP Port.", + "timeout-msec": "Wartezeit (msec)", + "timeout-required": "Wartezeit ist erforderlich.", + "timeout-invalid": "Das ist keine gültige Wartezeit.", + "enable-tls": "TLS aktivieren", + "send-test-mail": "Test E-Mail senden" + }, + "alarm": { + "alarm": "Alarm", + "alarms": "Alarme", + "select-alarm": "Alarm auswählen", + "no-alarms-matching": "Keine passenden Alarme zu '{{entity}}' wurden gefunden.", + "alarm-required": "Alarm ist erforderlich", + "alarm-status": "Alarm Status", + "search-status": { + "ANY": "Jeder", + "ACTIVE": "Aktiv", + "CLEARED": "Gelöscht", + "ACK": "Bestätigt", + "UNACK": "Nicht bestätigt" + }, + "display-status": { + "ACTIVE_UNACK": "Nicht bestätigt aktiv", + "ACTIVE_ACK": "Bestätigt aktiv", + "CLEARED_UNACK": "Nicht bestätigt", + "CLEARED_ACK": "Bestätigung gelöscht" + }, + "no-alarms-prompt": "Keine Alarme gefunden", + "created-time": "Erstellungszeit", + "type": "Typ", + "severity": "Schwere", + "originator": "Urheber", + "originator-type": "Urheber-Typ", + "details": "Details", + "status": "Status", + "alarm-details": "Alarm-Details", + "start-time": "Startzeit", + "end-time": "Endzeit", + "ack-time": "Bestätigungszeit", + "clear-time": "Zeit gelöscht", + "severity-critical": "Kritisch", + "severity-major": "Groß", + "severity-minor": "Klein", + "severity-warning": "Warnung", + "severity-indeterminate": "Unbestimmt", + "acknowledge": "Bestätigen", + "clear": "Löschen", + "search": "Alarme Suchen", + "selected-alarms": "{ count, plural, 1 {1 Alarm} other {# Alarme} } ausgewählt", + "no-data": "Keine Daten zum Anzeigen", + "polling-interval": "Alarmabfrageintervall (sec)", + "polling-interval-required": "Alarmabfrageintervall ist erforderlich.", + "min-polling-interval-message": "Mindestens 1 sec Abrufintervall ist zulässig.", + "aknowledge-alarms-title": "{ count, plural, 1 {1 Alarm} other {# Alarme} } bestätigen", + "aknowledge-alarms-text": "Sind Sie sicher, dass Sie { count, plural, 1 {1 Alarm} other {# Alarme} } bestätigen möchten?", + "aknowledge-alarm-title": "Alarm bestätigen", + "aknowledge-alarm-text": "Möchten Sie den Alarm wirklich bestätigen?", + "clear-alarms-title": "{ count, plural, 1 {1 Alarm} other {# Alarme} } löschen", + "clear-alarms-text": "Möchten Sie wirklich { count, plural, 1 {1 Alarm} other {# Alarme} } löschen?", + "clear-alarm-title": "Alarm löschen", + "clear-alarm-text": "Möchten Sie den Alarm wirklich löschen?", + "alarm-status-filter": "Alarm Status Filter" + }, + "alias": { + "add": "Alias hinzufügen", + "edit": "Alias bearbeiten", + "name": "Aliasname", + "name-required": "Aliasname ist erforderlich", + "duplicate-alias": "Ein Alias mit demselben Namen ist bereits vorhanden.", + "filter-type-single-entity": "Einzelne Entität", + "filter-type-entity-list": "Entitätsliste", + "filter-type-entity-name": "Entitätsname", + "filter-type-state-entity": "Entität aus dem Dashboard Status", + "filter-type-state-entity-description": "Entität aus den Dashboard Status Parametern", + "filter-type-asset-type": "Objekttyp", + "filter-type-asset-type-description": "Objekte vom Typ '{{assetType}}'", + "filter-type-asset-type-and-name-description": "Objekte vom Typ '{{assetType}}' und Name beginnend mit '{{prefix}}'", + "filter-type-device-type": "Gerätetyp", + "filter-type-device-type-description": "Geräte vom Typ '{{deviceType}}'", + "filter-type-device-type-and-name-description": "Geräte vom Typ '{{deviceType}}' und Name beginnend mit '{{prefix}}'", + "filter-type-entity-view-type": "Entitätsansichtstyp", + "filter-type-entity-view-type-description": "Entitätsansichten vom Typ '{{entityView}}'", + "filter-type-entity-view-type-and-name-description": "Entitätsansichten vom Typ '{{entityView}}' und Name beginnend mit '{{prefix}}'", + "filter-type-relations-query": "Beziehungsabfrage", + "filter-type-relations-query-description": "{{entities}} mit {{relationType}} Beziehung {{direction}} {{rootEntity}}", + "filter-type-asset-search-query": "Objektabfrage", + "filter-type-asset-search-query-description": "Objekte vom Typ {{assetTypes}} mit {{relationType}} Beziehung {{direction}} {{rootEntity}}", + "filter-type-device-search-query": "Geräteabfrage", + "filter-type-device-search-query-description": "Geräte vom Typ {{deviceTypes}} mit {{relationType}} Beziehung {{direction}} {{rootEntity}}", + "filter-type-entity-view-search-query": "Entitätsansichtsabfrage", + "filter-type-entity-view-search-query-description": "Entitätsansichten vom Typ {{entityViewTypes}} mit {{relationType}} Beziehung {{direction}} {{rootEntity}}", + "entity-filter": "Entitätsfilter", + "resolve-multiple": "Als mehrere Entitäten auflösen", + "filter-type": "Filtertyp", + "filter-type-required": "Filtertyp ist erforderlich.", + "entity-filter-no-entity-matched": "Es wurden keine Entitäten gefunden, die dem angegebenen Filter entsprechen.", + "no-entity-filter-specified": "Es wurde kein Entitätsfilter angegeben", + "root-state-entity": "Dashboard Status Entität als Wurzel verwenden", + "root-entity": "Wurzelentität", + "state-entity-parameter-name": "Parameter-Name der Statusentität", + "default-state-entity": "Standard Statusentität", + "default-entity-parameter-name": "Standartmäßig", + "max-relation-level": "Maximale Beziehungstiefe", + "unlimited-level": "Unbegrenzte Ebenen", + "state-entity": "Dashboard Status Entität", + "all-entities": "Alle Entitäten", + "any-relation": "Jede Beziehung" + }, + "asset": { + "asset": "Objekt", + "assets": "Objekte", + "management": "Objektverwaltung", + "view-assets": "Objekte anzeigen", + "add": "Objekt hinzufügen", + "assign-to-customer": "Einem Kunden zuordnen", + "assign-asset-to-customer": "Objekte dem Kunden zuordnen", + "assign-asset-to-customer-text": "Bitte wählen Sie die Objekte aus, die dem Kunden zugeordnet werden sollen", + "no-assets-text": "Keine Objekte gefunden", + "assign-to-customer-text": "Bitte wählen Sie den Kunden aus, dem die Objekte zugeordnet werden sollen", + "public": "Öffentlich", + "assignedToCustomer": "Kunden zugeordnet", + "make-public": "Objekt öffentlich machen", + "make-private": "Objekt privat machen", + "unassign-from-customer": "Kundenzuordnung aufheben", + "delete": "Objekt löschen", + "asset-public": "Objekt ist öffentlich", + "asset-type": "Objekttyp", + "asset-type-required": "Objekttyp ist erforderlich.", + "select-asset-type": "Objekttyp auswählen", + "enter-asset-type": "Objekttyp bestätigen", + "any-asset": "Jedes Objekt", + "no-asset-types-matching": "Es wurden keine zu '{{entitySubtype}}' passenden Objekte gefunden.", + "asset-type-list-empty": "Keine Objekttypen ausgewählt.", + "asset-types": "Objekttypen", + "name": "Name", + "name-required": "Name ist erforderlich.", + "description": "Beschreibung", + "type": "Typ", + "type-required": "Typ ist erforderlich.", + "details": "Details", + "events": "Ereignisse", + "add-asset-text": "Neues Objekt hinzufügen", + "asset-details": "Objektdetails", + "assign-assets": "Objekte zuordnen", + "assign-assets-text": "Kunden { count, plural, 1 {1 Objekt} other {# Objekte} } zuordnen", + "delete-assets": "Objekte löschen", + "unassign-assets": "Objektzuordnungen aufheben", + "unassign-assets-action-title": "Kunden { count, plural, 1 {1 Objektzuordnung} other {# Objektzuordnungen} } aufheben", + "assign-new-asset": "Neues Objekt zuordnen", + "delete-asset-title": "Sind Sie sicher, dass Sie das Objekt '{{assetName}}' löschen möchten?", + "delete-asset-text": "Vorsicht, nach Bestätigung wird das Objekt und alle zugehörigen Daten nicht wiederherstellbar gelöscht.", + "delete-assets-title": "Sind Sie sicher, dass Sie { count, plural, 1 {1 Objekt} other {# Objekte} } löschen möchten?", + "delete-assets-action-title": "{ count, plural, 1 {1 Objekt} other {# Objekte} } löschen", + "delete-assets-text": "Vorsicht, nach Bestätigung werden die ausgewählten Objekte und alle zugehörigen Daten nicht wiederherstellbar gelöscht", + "make-public-asset-title": "Sind Sie sicher, dass Sie das Objekt '{{assetName}}' öffentlich machen möchten?", + "make-public-asset-text": "Nach Bestätigung wird das Objekt und alle zugehörigen Daten anderen zugänglich gemacht.", + "make-private-asset-title": "Sind Sie sicher, dass Sie das Objekt '{{assetName}}' privat machen möchten?", + "make-private-asset-text": "Nach Bestätigung wird das Objekt und alle zugehörigen Daten privat und ist für andere nicht mehr zugänglich.", + "unassign-asset-title": "Sind Sie sicher, dass Sie die Zuordnung für das Objekt '{{assetName}}' aufheben möchten?", + "unassign-asset-text": "Nach Bestätigung wird die Zuordnung des Objekts aufgehoben und es ist für den Kunden nicht mehr zugänglich.", + "unassign-asset": "Zuordnung des Objekts aufheben", + "unassign-assets-title": "Möchten Sie die Zuordnung von { count, plural, 1 {1 Objekt} other {# Objekte} } aufheben?", + "unassign-assets-text": "Nach Bestätigung wird die Zuordnung der ausgewählten Objekte aufgehoben und sie sind für den Kunden nicht mehr zugänglich.", + "copyId": "Objekt-ID kopieren", + "idCopiedMessage": "Objekt-ID wurde in die Zwischenablage kopiert", + "select-asset": "Objekt auswählen", + "no-assets-matching": "Es wurden keine zu '{{entity}}' passenden Objekte gefunden.", + "asset-required": "Objekt ist erforderlich", + "name-starts-with": "Name des Objekts beginnt mit" + }, + "attribute": { + "attributes": "Eigenschaften", + "latest-telemetry": "Neueste Telemetrie", + "attributes-scope": "Entitätseigenschaftsbereich", + "scope-latest-telemetry": "Neueste Telemetrie", + "scope-client": "Client Eigenschaften", + "scope-server": "Server Eigenschaften", + "scope-shared": "Gemeinsame Eigenschaften", + "add": "Eigenschaft hinzufügen", + "key": "Schlüssel", + "last-update-time": "Datum der letzten Aktualisierung", + "key-required": "Eigenschaftsschlüssel ist erforderlich.", + "value": "Wert", + "value-required": "Eigenschaftswert ist erforderlich.", + "delete-attributes-title": "Sind Sie sicher, dass Sie { count, plural, 1 {1 Eigenschaft} other {# Eigenschaften} } löschen möchten?", + "delete-attributes-text": "Seien Sie vorsichtig, nach der Bestätigung werden alle ausgewählten Eigenschaften entfernt.", + "delete-attributes": "Eigenschaften löschen", + "enter-attribute-value": "Geben Sie den Eigenschaftswert ein", + "show-on-widget": "Im Widget anzeigen", + "widget-mode": "Widget-Modus", + "next-widget": "Nächstes Widget", + "prev-widget": "Vorheriges Widget", + "add-to-dashboard": "Zum Dashboard hinzufügen", + "add-widget-to-dashboard": "Widget zum Dashboard hinzufügen", + "selected-attributes": "{ count, plural, 1 {1 Eigenschaft} other {# Eigenschaften} } ausgewählt", + "selected-telemetry": "{ count, plural, 1 {1 Telemetrieeinheit } other {# Telemetrieeinheiten} } ausgewählt" + }, + "audit-log": { + "audit": "Audit", + "audit-logs": "Audit-Protokolle", + "timestamp": "Zeitstempel", + "entity-type": "Entitätstype", + "entity-name": "Entitätsname", + "user": "User", + "type": "Typ", + "status": "Status", + "details": "Details", + "type-added": "Hinzugefügt", + "type-deleted": "Gelöscht", + "type-updated": "Aktualisiert", + "type-attributes-updated": "Eigenschaften aktualisiert", + "type-attributes-deleted": "Eigenschaften gelöscht", + "type-rpc-call": "RPC Aufruf", + "type-credentials-updated": "Anmeldeinformationen wurden aktualisiert", + "type-assigned-to-customer": "Kunden Zuordnung", + "type-unassigned-from-customer": "Kunden Zuordnung aufgehoben", + "type-activated": "Aktiviert", + "type-suspended": "Ausgesetzt", + "type-credentials-read": "Anmeldeinformationen gelesen", + "type-attributes-read": "Eigenschaften gelesen", + "type-relation-add-or-update": "Beziehung aktualisiert", + "type-relation-delete": "Beziehung gelöscht", + "type-relations-delete": "Alle Beziehungen gelöscht", + "type-alarm-ack": "Bestätigt", + "type-alarm-clear": "Gelöscht", + "status-success": "Erfolg", + "status-failure": "Fehler", + "audit-log-details": "Audit-Protokoll Details", + "no-audit-logs-prompt": "Keine Protokolle gefunden", + "action-data": "Aktionsdaten", + "failure-details": "Fehlerdetails", + "search": "Audit-Protokolle durchsuchen", + "clear-search": "Suche leeren" + }, + "confirm-on-exit": { + "message": "Sie haben nicht gespeicherte Änderungen. Möchten Sie diese Seite wirklich verlassen?", + "html-message": "Sie haben nicht gespeicherte Änderungen.
Möchten Sie diese Seite wirklich verlassen?", + "title": "Nicht gespeicherte Änderungen" + }, + "contact": { + "country": "Land", + "city": "Stadt", + "state": "Bundesland", + "postal-code": "Postleitzahl", + "postal-code-invalid": "Ungültiges Format der Postleitzahl.", + "address": "Adresse", + "address2": "Adresse 2", + "phone": "Telefon", + "email": "E-Mail", + "no-address": "Keine Adresse" + }, + "common": { + "username": "Benutzername", + "password": "Passwort", + "enter-username": "Benutzername eingeben", + "enter-password": "Passwort eingeben", + "enter-search": "Suche eingeben" + }, + "content-type": { + "json": "Json", + "text": "Text", + "binary": "Binär (Base64)" + }, + "customer": { + "customer": "Kunde", + "customers": "Kunden", + "management": "Kundenverwaltung", + "dashboard": "Kunden Dashboard", + "dashboards": "Kunden Dashboards", + "devices": "Kundengeräte", + "entity-views": "Kunden Entitätsansichten", + "assets": "Kundenobjekte", + "public-dashboards": "Öffentliche Dashboards", + "public-devices": "Öffentliche Geräte", + "public-assets": "Öffentliche Objekte", + "public-entity-views": "Öffentliche Entitätsansichten", + "add": "Kunde hinzufügen", + "delete": "Kunde löschen", + "manage-customer-users": "Kundenbenutzer verwalten", + "manage-customer-devices": "Kundengeräte verwalten", + "manage-customer-dashboards": "Kunden-Dashboards verwalten", + "manage-public-devices": "Öffentliche Geräte verwalten", + "manage-public-dashboards": "Öffentliche Dashboards verwalten", + "manage-customer-assets": "Kundenobjekte verwalten", + "manage-public-assets": "Öffentliche Objekte verwalten", + "add-customer-text": "Neuen Kunden hinzufügen", + "no-customers-text": "Keine Kunden gefunden", + "customer-details": "Kundendetails", + "delete-customer-title": "Sind Sie sicher, dass der Kunde '{{customerTitle}}' gelöscht werden soll?", + "delete-customer-text": "Vorsicht, nach Bestätigung wird der Kunde und alle zugehörigen Daten nicht wiederherstellbar gelöscht.", + "delete-customers-title": "Sind Sie sicher, dass Sie { count, plural, 1 {1 Kunde} other {# Kunden} } löschen möchten?", + "delete-customers-action-title": "{ count, plural, 1 {1 Kunde} other {# Kunden} } löschen", + "delete-customers-text": "Seien Sie vorsichtig, nach der Bestätigung werden alle ausgewählten Kunden und alle zugehörigen Daten nicht wiederherstellbar gelöscht.", + "manage-users": "User verwalten", + "manage-assets": "Objekte verwalten", + "manage-devices": "Geräte verwalten", + "manage-dashboards": "Dashboards verwalten", + "title": "Titel", + "title-required": "Titel ist erforderlich.", + "description": "Beschreibung", + "details": "Details", + "events": "Ereignisse", + "copyId": "Kunden-ID kopieren", + "idCopiedMessage": "Kunden-ID wurde in die Zwischenablage kopiert", + "select-customer": "Kunden auswählen", + "no-customers-matching": "Keine Kunden für '{{entity}}' gefunden.", + "customer-required": "Kunde ist erforderlich", + "select-default-customer": "Wählen Sie den Standardkunden aus.", + "default-customer": "Standardkunde", + "default-customer-required": "Ein Standardkunde ist erforderlich, um das Dashboard auf Mandantenebene zu testen." + }, + "datetime": { + "date-from": "Datum von", + "time-from": "Zeit von", + "date-to": "Datum bis", + "time-to": "Zeit bis" + }, + "dashboard": { + "dashboard": "Dashboard", + "dashboards": "Dashboards", + "management": "Dashboardverwaltung", + "view-dashboards": "Dashboards anzeigen", + "add": "Dashboard hinzufügen", + "assign-dashboard-to-customer": "Dashboard(s) dem Kunden zuordnen", + "assign-dashboard-to-customer-text": "Bitte wählen Sie die Dashboards aus, die Sie dem Kunden zuordnen möchten", + "assign-to-customer-text": "Bitte wählen Sie den Kunden aus, dem die Dashboards zugeordnet werden sollen", + "assign-to-customer": "Kunden zuordnen", + "unassign-from-customer": "Zuordnung zum Kunden aufheben", + "make-public": "Dashboard öffentlich machen", + "make-private": "Dashboard privat machen", + "manage-assigned-customers": "Zugeordnete Kunden verwalten", + "assigned-customers": "Zugeordnete Kunden", + "assign-to-customers": "Dashboard(s) zu Kunden zuweisen", + "assign-to-customers-text": "Bitte wählen Sie den Kunden aus, dem Sie das Dashboard(s) zuweisen möchten", + "unassign-from-customers": "Zuordnung von Dashboard(s) zu Kunden aufheben", + "unassign-from-customers-text": "Bitte wählen Sie die Kunden aus, für die die Zuordnung von Dashboard(s) aufgehoben werden soll", + "no-dashboards-text": "Keine Dashboard(s) gefunden", + "no-widgets": "Keine Widgets konfiguriert", + "add-widget": "Neues Widget hinzufügen", + "title": "Titel", + "select-widget-title": "Widget auswählen", + "select-widget-subtitle": "Liste der verfügbaren Widget-Typen", + "delete": "Dashboard löschen", + "title-required": "Titel ist erforderlich.", + "description": "Beschreibung", + "details": "Details", + "dashboard-details": "Dashboard-Details", + "add-dashboard-text": "Neues Dashboard hinzufügen", + "assign-dashboards": "Dashboards zuweisen", + "assign-new-dashboard": "Neues Dashboard zuweisen", + "assign-dashboards-text": "Zuordnen { count, plural, 1 {1 Dashboard} other {# Dashboards} } zu Kunden", + "unassign-dashboards-action-text": "Zuordnung { count, plural, 1 {1 Dashboard} other {# Dashboards} } vom Kunden aufheben", + "delete-dashboards": "Dashboards löschen", + "unassign-dashboards": "Zuordnung von Dashboards aufheben", + "unassign-dashboards-action-title": "Zuordnung { count, plural, 1 {1 Dashboard} other {# Dashboards} } vom Kunden aufheben", + "delete-dashboard-title": "Sind Sie sicher, dass Sie das Dashboard '{{dashboardTitle}}' löschen möchten?", + "delete-dashboard-text": "Vorsicht, nach Bestätigung werden das Dashboard und alle zugehörigen Daten nicht mehr wiederhergestellt.", + "delete-dashboards-title": "Sind Sie sicher, dass Sie { count, plural, 1 {1 Dashboard} other {# Dashboards} } löschen möchten?", + "delete-dashboards-action-title": "Löschen { count, plural, 1 {1 Dashboard} other {# Dashboards} }", + "delete-dashboards-text": "Vorsicht, nach Bestätigung werden alle ausgewählten Dashboards entfernt und alle zugehörigen Daten nicht mehr wiederhergestellt.", + "unassign-dashboard-title": "Sind Sie sicher, dass Sie die Zuordnung zum Dashboard '{{dashboardTitle}}' aufheben möchten?", + "unassign-dashboard-text": "Nach der Bestätigung wird die Zuordnung des Dashboards aufgehoben und es ist für den Kunden nicht mehr zugänglich.", + "unassign-dashboard": "Zuordnung zum Kunden aufheben", + "unassign-dashboards-title": "Sind Sie sicher, dass Sie die Zuordug aufheben möchten { count, plural, 1 {1 Dashboard} other {# Dashboards} }?", + "unassign-dashboards-text": "Nach der Bestätigung wird die Zuordnung aller ausgewählten Dashboards aufgehoben und sie sind für den Kunden nicht mehr zugänglich.", + "public-dashboard-title": "Dashboard wurde veröffentlicht", + "public-dashboard-text": "Ihr Dashboard {{dashboardTitle}} ist jetzt öffentlich und über nächste Öffentlichkeit zugänglich link:", + "public-dashboard-notice": "Note: Vergessen Sie nicht, verwandte Geräte öffentlich zu machen, um auf Ihre Daten zugreifen zu können.", + "make-private-dashboard-title": "Sind Sie sicher, dass Sie das Dashboard '{{dashboardTitle}}' privatisieren möchten?", + "make-private-dashboard-text": "Nach der Bestätigung wird das Dashboard privatisiert und ist für andere nicht zugänglich.", + "make-private-dashboard": "Dashboard privatisieren", + "socialshare-text": "'{{dashboardTitle}}' Bereitgestellt vom ThingsBoard", + "socialshare-title": "'{{dashboardTitle}}' Bereitgestellt vom ThingsBoard", + "select-dashboard": "Dashboard auswählen", + "no-dashboards-matching": "Es wurden keine passenden Dashboards '{{entity}}' gefunden.", + "dashboard-required": "Dashboard ist erforderlich.", + "select-existing": "Existierendes Dashboard auswählen", + "create-new": "Neues Dashboard erstellen", + "new-dashboard-title": "Neuer Dashboard Titel", + "open-dashboard": "Dashboard öffnen", + "set-background": "Hintergrund einstellen", + "background-color": "Hintergrundfarbe", + "background-image": "Hintergrundbild", + "background-size-mode": "Hintergrundgrößenmodus", + "no-image": "Kein Bild ausgewählt", + "drop-image": "Legen Sie ein Bild ab oder klicken Sie, um eine hochzuladende Datei auszuwählen.", + "settings": "Einstellungen", + "columns-count": "Spalten zählen", + "columns-count-required": "Die Anzahl der Spalten ist erforderlich.", + "min-columns-count-message": "Es müssen mindestens 10 Spalten vorhanden sein.", + "max-columns-count-message": "Es sind maximal 100 Spalten zulässig.", + "widgets-margins": "Abstand zwischen den Widgets", + "horizontal-margin": "Horizontaler Abstand", + "horizontal-margin-required": "Horizontaler Abstandswert ist erforderlich.", + "min-horizontal-margin-message": "Der horizontale Abstandswert muss mindestens 0 betragen.", + "max-horizontal-margin-message": "Der horizontale Abstandswert beträgt maximal 50.", + "vertical-margin": "Vertikaler Abstand", + "vertical-margin-required": "Vertikaler Abstandswert ist erforderlich.", + "min-vertical-margin-message": "Der vertikale Abstandswert muss mindestens 0 betragen.", + "max-vertical-margin-message": "Der vertikale Abstandswert beträgt maximal 50.", + "autofill-height": "Layouthöhe automatisch füllen", + "mobile-layout": "Mobile Layouteinstellungen", + "mobile-row-height": "Mobile Zeilenhöhe, px", + "mobile-row-height-required": "Ein mobiler Zeilenhöhenwert ist erforderlich.", + "min-mobile-row-height-message": "Der Mindestwert für die mobile Zeilenhöhe beträgt 5 Pixel.", + "max-mobile-row-height-message": "Der Höchstwert für die mobile Zeilenhöhe beträgt 200 Pixel.", + "display-title": "Display Dashboard Titel", + "toolbar-always-open": "Werkzeugleiste geöffnet lassen", + "title-color": "Titelfarbe ", + "display-dashboards-selection": "Auswahl der Dashboards anzeigen", + "display-entities-selection": "Auswahl der Einheiten zulassen", + "display-dashboard-timewindow": "Zeitfenster anzeigen", + "display-dashboard-export": "Export anzeigen", + "import": "Dashboard importieren", + "export": "Dashboard exportieren", + "export-failed-error": "Dashboard kann nicht exportiert werden: {{error}}", + "create-new-dashboard": "Neues Dashboard erstellen", + "dashboard-file": "Dashboard-Datei", + "invalid-dashboard-file-error": "Dashboard kann nicht importiert werden: Ungültige Dashboard-Datenstruktur.", + "dashboard-import-missing-aliases-title": "Konfigurieren Sie die von importierten Dashboards verwendeten Aliasnamen", + "create-new-widget": "Neues Widget erstellen", + "import-widget": "Widget importieren", + "widget-file": "Widget-Datei", + "invalid-widget-file-error": "Widget kann nicht importiert werden: Ungültige Widget-Datenstruktur.", + "widget-import-missing-aliases-title": "Konfigurieren Sie die von importierten Widgets verwendeten Aliase", + "open-toolbar": "Dashboard-Werkzeugleiste öffnen", + "close-toolbar": "Werkzeugleiste schließen", + "configuration-error": "Konfigurationsfehler", + "alias-resolution-error-title": "Konfigurationsfehler für Dashboard-Aliasnamen", + "invalid-aliases-config": "Es konnten keine Geräte gefunden werden, die mit dem Aliase-Filter übereinstimmen.
Bitte wenden Sie sich an Ihren Administrator, um dieses problem zu beheben.", + "select-devices": "Geräte auswählen", + "assignedToCustomer": "Dem Kunden zugewiesen", + "assignedToCustomers": "Kunden zugwiesen", + "public": "Öffentlich", + "public-link": "Öffentlicher Link", + "copy-public-link": "Öffentlichen Link kopieren", + "public-link-copied-message": "Der öffentliche Link des Dashboards wurde in die Zwischenablage kopiert", + "manage-states": "Dashboard-Status verwalten", + "states": "Dashboard-Status", + "search-states": "Dashboard-Status suchen", + "selected-states": "{ count, plural, 1 {1 dashboard state} other {# dashboard states} } ausgewählt", + "edit-state": "Dashboard-Status bearbeiten", + "delete-state": "Dashboard-Status löschen", + "add-state": "Dashboard-Status hinzufügen", + "state": "Dashboard-Status", + "state-name": "Name", + "state-name-required": "Name des Dashboard-Status ist erforderlich.", + "state-id": "Status-Id", + "state-id-required": "Dashboard-Status-ID ist erforderlich.", + "state-id-exists": "Dashboard-Status mit derselben ID ist bereits vorhanden .", + "is-root-state": "Grundzustand", + "delete-state-title": "Dashboard-Status löschen", + "delete-state-text": "Sind Sie sicher, dass Sie den Dashboard-Status '{{stateName}}' löschen möchten?", + "show-details": "Details anzeigen", + "hide-details": "Details ausblenden", + "select-state": "Soll-Zustand auswählen", + "state-controller": "Zustandssteuerung" + }, + "datakey": { + "settings": "Einstellungen", + "advanced": "Erweitert", + "label": "Bezeichnung", + "color": "Farbe", + "units": "Maßeinheit die neben dem Wert angezeigt wird", + "decimals": "Anzahl der Stellen nach dem Komma", + "data-generation-func": "Daten generieren", + "use-data-post-processing-func": "Datenverarbeitungsfunktion verwenden", + "configuration": "Datenschlüsselkonfiguration", + "timeseries": "Zeitreihe", + "attributes": "Eigenschaften", + "alarm": "Alarmfelder", + "timeseries-required": "Entity-Zeitreihen sind erforderlich.", + "timeseries-or-attributes-required": "Entity-Zeitreihen/Eigenschaften sind erforderlich.", + "maximum-timeseries-or-attributes": "Maximum { count, plural, 1 {1 Zeitreihe/Eigenschaft ist erlaubt} other {# Zeitreihen/Eigenschaften sind erlaubt} }.", + "alarm-fields-required": "Alarmfelder sind erforderlich.", + "function-types": "Funktionsarten", + "function-types-required": "Funktionstypen sind erforderlich.", + "maximum-function-types": "Maximal { count, plural, 1 {1 Funktionstyp ist erlaubt} other {# Funktionstypen sind erlaubt} }.", + "time-description": "Zeitstempel des aktuellen Wertes;", + "value-description": "Der aktuelle Wert;", + "prev-value-description": "Ergebnis des vorherigen Funktionsaufrufs;", + "time-prev-description": "Zeitmarke des vorherigen Wertes;", + "prev-orig-value-description": "Ursprünglicher vorheriger Wert;" + }, + "datasource": { + "type": "Datenquellentyp", + "name": "Name", + "add-datasource-prompt": "Bitte Datenquelle hinzufügen" + }, + "details": { + "edit-mode": "Bearbeitungsmodus", + "toggle-edit-mode": "Bearbeitungsmodus umschalten" + }, + "device": { + "device": "Gerät", + "device-required": "Gerät ist erforderlich.", + "devices": "Geräte", + "management": "Geräte verwalten", + "view-devices": "Geräte anzeigen", + "device-alias": "Geräte-Alias", + "aliases": "Gerätealiasnamen", + "no-alias-matching": "'{{alias}}' nicht gefunden.", + "no-aliases-found": "Keine Aliase gefunden.", + "no-key-matching": "'{{key}}' nicht gefunden.", + "no-keys-found": "Keine Schlüssel gefunden.", + "create-new-alias": "Neues Alias erstellen!", + "create-new-key": "Neuen Schlüssel erstellen!", + "duplicate-alias-error": "Doppelter Alias gefunden '{{alias}}'.
Gerätealiasnamen müssen innerhalb des Dashboards eindeutig sein.", + "configure-alias": "Alias '{{alias}}' konfigurieren", + "no-devices-matching": "Keine passenden Geräte '{{entity}}' gefunden.", + "alias": "Alias", + "alias-required": "Geräte-Alias ist erforderlich.", + "remove-alias": "Geräte-Alias entfernen", + "add-alias": "Geräte-Alias hinzufügen", + "name-starts-with": "Gerätename beginnt mit", + "device-list": "Geräteliste", + "use-device-name-filter": "Filter verwenden", + "device-list-empty": "Keine Geräte ausgewählt.", + "device-name-filter-required": "Der Gerätenamefilter ist erforderlich.", + "device-name-filter-no-device-matched": "Keine Geräte beginnend mit '{{device}}' gefunden.", + "add": "Gerät hinzufügen", + "assign-to-customer": "Kunden zuordnen", + "assign-device-to-customer": "Gerät(e) dem Kunden zuordnen", + "assign-device-to-customer-text": "Bitte wählen Sie die Geräte aus, die Sie dem Kunden zuordnen möchten", + "make-public": "Gerät veröffentlichen", + "make-private": "Gerät privatisieren", + "no-devices-text": "Keine Geräte gefunden", + "assign-to-customer-text": "Bitte wählen Sie einen Kunden aus, dem die Geräte zugeordnet werden sollen", + "device-details": "Gerätedetails", + "add-device-text": "Neues Gerät hinzufügen", + "credentials": "Zugangsdaten", + "manage-credentials": "Zugangsdaten verwalten", + "delete": "Gerät löschen", + "assign-devices": "Gerät zuordnen", + "assign-devices-text": "{ count, plural, 1 {1 Gerät} other {# Geräte} } dem Kunden zuordnen", + "delete-devices": "Geräte löschen", + "unassign-from-customer": "Zuordnung zum Kunden aufheben", + "unassign-devices": "Nicht zugeordnete Geräte", + "unassign-devices-action-title": "Zuordnung von { count, plural, 1 {1 Gerät} other {# Geräte} } zum Kunden aufheben", + "assign-new-device": "Neues Gerät zuordnen", + "make-public-device-title": "Sind Sie sicher, dass Sie das Gerät '{{deviceName}}' öffentlich machen möchten?", + "make-public-device-text": "Nach der Bestätigung werden das Gerät und dessen Daten öffentlich und für andere zugänglich.", + "make-private-device-title": "Sind Sie sicher, dass Sie das Gerät '{{deviceName}}' privat machen möchten?", + "make-private-device-text": "Nach der Bestätigung werden das Gerät und dessen Daten privat und sind für andere nicht mehr zugänglich.", + "view-credentials": "Zugangsdaten anzeigen", + "delete-device-title": "Möchten Sie das Gerät '{{deviceName}}' wirklich löschen?", + "delete-device-text": "Vorsicht, nach Bestätigung werden das Gerät und alle zugehörigen Daten nicht mehr wiederhergestellt.", + "delete-devices-title": "Sind Sie sicher, dass Sie löschen möchten { count, plural, 1 {1 Gerät} other {# Geräte} }?", + "delete-devices-action-title": "Löschen { count, plural, 1 {1 Gerät} other {# Geräte} }", + "delete-devices-text": "Vorsicht, nach Bestätigung werden alle ausgewählten Geräte entfernt und alle zugehörigen Daten werden nicht mehr wiederhergestellt.", + "unassign-device-title": "Sind Sie sicher, dass Sie die Zuordnung zum Gerät '{{deviceName}}' wirklich aufheben möchten?", + "unassign-device-text": "Nach der Bestätigung ist das Gerät nicht zugeordnet und für den Kunden nicht zugänglich.", + "unassign-device": "Nicht zugeordnete Geräte", + "unassign-devices-title": "Sind Sie sicher, dass Sie { count, plural, 1 {1 Gerät} other {# Geräte} } nicht mehr zuordnen möchten?", + "unassign-devices-text": "Nach der Bestätigung werden alle ausgewählten Geräte nicht zugewiesen und sind für den Kunden nicht zugänglich.", + "device-credentials": "Geräte Zugangsdaten", + "credentials-type": "Art der Zugangsdaten", + "access-token": "Zugangs-Token", + "access-token-required": "Zugangs-Token ist erforderlich.", + "access-token-invalid": "Die Länge des Zugangs-Tokens muss zwischen 1 und 20 Zeichen betragen.", + "rsa-key": "RSA öffentlicher Schlüssel", + "rsa-key-required": "RSA öffentlicher Schlüssel ist erforderlich.", + "secret": "Geheimnis", + "secret-required": "Geheimnis ist erforderlich.", + "device-type": "Gerätetyp", + "device-type-required": "Gerätetyp ist erforderlich.", + "select-device-type": "Gerätetyp auswählen", + "enter-device-type": "Gerätetyp eingeben", + "any-device": "Jedes Gerät", + "no-device-types-matching": "Keine passenden Gerätetypen '{{entitySubtype}}' gefunden.", + "device-type-list-empty": "Kein Gerätetyp ausgewählt.", + "device-types": "Gerätetypen", + "name": "Name", + "name-required": "Name ist erforderlich.", + "description": "Beschreibung", + "events": "Ereignisse", + "details": "Details", + "copyId": "Geräte-ID kopieren", + "copyAccessToken": "Zugangs-Token kopieren", + "idCopiedMessage": "Geräte-ID wurde in die Zwischenablage kopiert", + "accessTokenCopiedMessage": "Geräte-Zugangs-Token wurde in die Zwischenablage kopiert", + "assignedToCustomer": "Dem Kunden zuordnen", + "unable-delete-device-alias-title": "Geräte-Alias kann nicht gelöscht werden", + "unable-delete-device-alias-text": "Geräte-Alias '{{deviceAlias}}' kann nicht gelöscht werden, da er von den folgenden Widgets verwendet wird:
{{widgetsList}}", + "is-gateway": "Ist ein Gateway", + "public": "Öffentlich", + "device-public": "Gerät ist öffentlich", + "select-device": "Gerät auswählen" + }, + "dialog": { + "close": "Dialog schließen" + }, + "error": { + "unable-to-connect": "Es konnte keine Verbindung zum Server hergestellt werden! Bitte überprüfen Sie Ihre Internetverbindung.", + "unhandled-error-code": "Unbehandelter Fehlercode: {{errorCode}}", + "unknown-error": "Unbekannter Fehler" + }, + "entity": { + "entity": "Entität", + "entities": "Entitäten", + "aliases": "Entitäts-Aliasnamen", + "entity-alias": "Entitätsalias", + "unable-delete-entity-alias-title": "Alias der Entität kann nicht gelöscht werden", + "unable-delete-entity-alias-text": "Alias der Entität '{{entityAlias}}' kann nicht gelöscht werden, da es von den folgenden Widget(s) verwendet wird:
{{widgetsList}}", + "duplicate-alias-error": "Doppelte Alias gefunden '{{alias}}'.
Die Aliase der Entität müssen innerhalb des Dashboards eindeutig sein.", + "missing-entity-filter-error": "Fehlender Filter für Alias '{{alias}}'.", + "configure-alias": "Alias '{{alias}}' konfigurieren", + "alias": "Alias", + "alias-required": "Alias der Entität ist erforderlich.", + "remove-alias": "Alias der Entität entfernen", + "add-alias": "Alias der Entität erforderlich", + "entity-list": "Entitätsliste", + "entity-type": "Entitätstyp", + "entity-types": "Entitätstypen", + "entity-type-list": "Liste der Entitätstyp", + "any-entity": "Jede Entität", + "enter-entity-type": "Entitätstyp eingeben", + "no-entities-matching": "Keine passenden Entitäten für '{{entity}}' gefunden.", + "no-entity-types-matching": "Keine passende Entitätstypen für '{{entityType}}' gefunden.", + "name-starts-with": "Name beginnt mit", + "use-entity-name-filter": "Filter verwenden", + "entity-list-empty": "Keine Entitäten ausgewählt.", + "entity-type-list-empty": "Keine Entitättypen ausgewählt.", + "entity-name-filter-required": "Entitätsnamenfilter ist erforderlich.", + "entity-name-filter-no-entity-matched": "Keine Entitäten beginnend mit '{{entity}}' gefunden.", + "all-subtypes": "Alle", + "select-entities": "Entitäten auswählen", + "no-aliases-found": "Keine Aliase gefunden.", + "no-alias-matching": "'{{alias}}' nicht gefunden.", + "create-new-alias": "Erstellen Sie einen neuen Alias!", + "key": "Schlüssel", + "key-name": "Name des Schlüssels", + "no-keys-found": "Kein Schlüssel gefunden.", + "no-key-matching": "'{{key}}' nicht gefunden.", + "create-new-key": "Erstellen Sie einen neuen Schlüssel!", + "type": "Typ", + "type-required": "Typ der Entität ist erforderlich.", + "type-device": "Gerät", + "type-devices": "Geräte", + "list-of-devices": "{ count, plural, 1 {Ein Gerät} other {Liste von # Geräten} }", + "device-name-starts-with": "Geräte beginnend mit '{{prefix}}'", + "type-asset": "Objekt", + "type-assets": "Objekte", + "list-of-assets": "{ count, plural, 1 {Ein Objekt} other {Liste von # Objekten} }", + "asset-name-starts-with": "Objekte beginnend mit '{{prefix}}'", + "type-entity-view": "Entitätsansicht", + "type-entity-views": "Entitätsansichten", + "list-of-entity-views": "{ count, plural, 1 {Eine Entitätsansicht} other {Liste von # Entitätsansichten} }", + "entity-view-name-starts-with": "Entitätsansichten beginnend mit'{{prefix}}'", + "type-rule": "Regel", + "type-rules": "Regeln", + "list-of-rules": "{ count, plural, 1 {Eine Regel} other {Liste von # Regeln} }", + "rule-name-starts-with": "Regeln beginnend mit '{{prefix}}'", + "type-plugin": "Plugin", + "type-plugins": "Plugins", + "list-of-plugins": "{ count, plural, 1 {Ein Plugin} other {Liste von # Plugins} }", + "plugin-name-starts-with": "Plugins beginnend mit '{{prefix}}'", + "type-tenant": "Mandant", + "type-tenants": "Mandanten", + "list-of-tenants": "{ count, plural, 1 {Ein Mandant} other {Liste von # Mandanten} }", + "tenant-name-starts-with": "Mandanten beginnend mit '{{prefix}}'", + "type-customer": "Kunde", + "type-customers": "Kunden", + "list-of-customers": "{ count, plural, 1 {Ein Kunde} other {Liste von # Kunden} }", + "customer-name-starts-with": "Kunden beginnend mit '{{prefix}}'", + "type-user": "Benutzer", + "type-users": "Benutzer", + "list-of-users": "{ count, plural, 1 {Ein Benutzer} other {Liste von # Benutzern} }", + "user-name-starts-with": "Benutzer beginnend mit '{{prefix}}'", + "type-dashboard": "Dashboard", + "type-dashboards": "Dashboards", + "list-of-dashboards": "{ count, plural, 1 {Ein Dashboard} other {Liste von # Dashboards} }", + "dashboard-name-starts-with": "Dashboards beginnend mit '{{prefix}}'", + "type-alarm": "Alarm", + "type-alarms": "Alarme", + "list-of-alarms": "{ count, plural, 1 {Ein Alarm} other {Liste von # Alarmen} }", + "alarm-name-starts-with": "Alarme, beginnend mit '{{prefix}}'", + "type-rulechain": "Regelkette", + "type-rulechains": "Regelketten", + "list-of-rulechains": "{ count, plural, 1 {Eine Regelkette} other {Liste von # Regelketten} }", + "rulechain-name-starts-with": "Regelketten beginnend mit '{{prefix}}'", + "type-rulenode": "Regelknoten", + "type-rulenodes": "Regelknoten", + "list-of-rulenodes": "{ count, plural, 1 {Ein Regelknoten} other {Liste von # Regelknoten} }", + "rulenode-name-starts-with": "Regelknoten beginnend mit '{{prefix}}'", + "type-current-customer": "Aktueller Kunde", + "search": "Entitäten suchen", + "selected-entities": "{ count, plural, 1 {Entität} other {# Entitäten} } ausgewählt", + "entity-name": "Entitätsname", + "details": "Entitätsdetails", + "no-entities-prompt": "Keine Entitäten gefunden", + "no-data": "Keine Daten zum Anzeigen", + "columns-to-display": "Anzuzeigende Spalten" + }, + "entity-view": { + "entity-view": "Entitätsansicht", + "entity-view-required": "Entitätsansicht ist erforderlich.", + "entity-views": "Entitätsansichten", + "management": "Entitätsansichten verwalten", + "view-entity-views": "Entitätsansichten anzeigen", + "entity-view-alias": "Entitätsansichtsalias", + "aliases": "Entitätsansichten-Aliase", + "no-alias-matching": "'{{alias}}' nicht gefunden.", + "no-aliases-found": "Keine Aliase gefunden.", + "no-key-matching": "'{{key}}' nicht gefunden.", + "no-keys-found": "Keine Schlüssel gefunden.", + "create-new-alias": "Neuen Alias erstellen!", + "create-new-key": "Neuen Schlüssel erstellen!", + "duplicate-alias-error": "Doppelter Alias gefunden '{{alias}}'.
Aliase der Entitätsansicht müssen innerhalb des Dashboards eindeutig sein.", + "configure-alias": "Alias '{{alias}}' konfigurieren", + "no-entity-views-matching": "Keine passenden Entitätsansichten für '{{entity}}' gefunden.", + "alias": "Alias", + "alias-required": "Alias der Entitätsansicht erforderlich.", + "remove-alias": "Alias der Entitätsansicht entfernen", + "add-alias": "Alias für die Entitätsansicht hinzufügen", + "name-starts-with": "Entitätsansichtsname beginnend mit", + "entity-view-list": "Liste der Entitätsansichten", + "use-entity-view-name-filter": "Filter anwenden", + "entity-view-list-empty": "Keine der Entitätsansichten ausgewählt.", + "entity-view-name-filter-required": "Filterung nach Entitätsansichtenname erforderlich.", + "entity-view-name-filter-no-entity-view-matched": "Keine Entitätsansichten beginnend mit '{{entityView}}' wurden gefunden.", + "add": "Entitätsansicht hinzufügen", + "assign-to-customer": "Einem Kunden zuordnen", + "assign-entity-view-to-customer": "Entitätsansichten dem Kunden zuordnen", + "assign-entity-view-to-customer-text": "Bitte wählen Sie die Entitätsansichten aus, die dem Kunden zugeordnet werden sollen", + "no-entity-views-text": "Keine Entitätsansichten gefunden", + "assign-to-customer-text": "Bitte wählen Sie den Kunden aus, dem die Entitätsansichten zugeordnet werden sollen", + "entity-view-details": "Details der Entitätsansicht", + "add-entity-view-text": "Neue Entitätsansicht hinzufügen", + "delete": "Entitätsansicht löschen", + "assign-entity-views": "Entitätsansicht zuordnen", + "assign-entity-views-text": "Dem Kunden { count, plural, 1 {1 Entitätsansicht} other {# Entitätsansichten} } zuordnen", + "delete-entity-views": "Entitätsansichten löschen", + "unassign-from-customer": "Zuordnung zum Kunden aufheben", + "unassign-entity-views": "Zuordnung der Entitätsansichten aufheben", + "unassign-entity-views-action-title": "Die Zuordnung { count, plural, 1 {1 Entitätsansicht} other {# Entitätsansichten} } zum Kunden aufheben", + "assign-new-entity-view": "Neue Entitätsansicht zuordnen", + "delete-entity-view-title": "Möchten Sie die Entitätsansicht wirklich löschen '{{entityViewName}}'?", + "delete-entity-view-text": "Seien Sie vorsichtig, nach der Bestätigung werden die Entitätsansicht und alle zugehörigen Daten nicht wiederhergestellt.", + "delete-entity-views-title": "Sind Sie sicher, dass Sie die Entitätsansichten löschen möchten { count, plural, 1 {1 Entitätsansicht} other {# Entitätsansichten} }?", + "delete-entity-views-action-title": "Löschen { count, plural, 1 {1 Entitätsansicht} other {# Entitätsansichten} }", + "delete-entity-views-text": "Vorsicht, nach Bestätigung werden alle ausgewählten Entitätsansichten entfernt und alle zugehörigen Daten werden nicht wiederhergestellt.", + "unassign-entity-view-title": "Möchten Sie die Zuordnung der Entitätsansicht '{{entityViewName}}' wirklich aufheben?", + "unassign-entity-view-text": "Nach der Bestätigung wird die Zuordnung der Entitätsansicht aufgehoben und ist für den Kunden nicht mehr zugänglich.", + "unassign-entity-view": "Zuordnung der Entitätsansicht aufheben", + "unassign-entity-views-title": "Sind Sie sicher, dass Sie die Zuordnung aufheben möchten { count, plural, 1 {1 Entitätsansicht} other {# Entitätsansichten} }?", + "unassign-entity-views-text": "Nach der Bestätigung werden die Zuordnungen der ausgewählten Entitätsansichten aufgehoben und sind für den Kunden nicht mehr zugänglich.", + "entity-view-type": "Entitätsansichtstyp", + "entity-view-type-required": "Entitätsansichtstyp ist erforderlich.", + "select-entity-view-type": "Entitätsansichtstyp auswählen", + "enter-entity-view-type": "Entitätsansichtstyp eingeben", + "any-entity-view": "Jede Entitätsansicht", + "no-entity-view-types-matching": "Es wurden keine passenden Entitätsansichtstypen für '{{entitySubtype}}' gefunden.", + "entity-view-type-list-empty": "Keine Entitätsansichtstypen ausgewählt.", + "entity-view-types": "Entitätsansichtstypen", + "name": "Name", + "name-required": "Name ist erforderlich.", + "description": "Beschreibung", + "events": "Ereignisse", + "details": "Details", + "copyId": "Entitätsansichts-ID kopieren", + "assignedToCustomer": "Dem Kunden zuordnen", + "unable-entity-view-device-alias-title": "Alias der Entitätsansicht kann nicht gelöscht werden", + "unable-entity-view-device-alias-text": "Geräte-Alias '{{entityViewAlias}}' kann nicht gelöscht werden, da es von den folgenden widget(s):
{{widgetsList}} verwendet wird", + "select-entity-view": "Entitätsansicht auswählen", + "make-public": "Entitätsansicht öffentlich machen", + "start-date": "Start-Datum", + "start-ts": "Start-Zeit", + "end-date": "Ende-Datum", + "end-ts": "Ende-Zeit", + "date-limits": "Datumslimits", + "client-attributes": "Client Eigenschaften", + "shared-attributes": "Gemeinsame Eigenschaften", + "server-attributes": "Server Eigenschaften", + "timeseries": "Zeitreihe", + "client-attributes-placeholder": "Client Eigenschaften", + "shared-attributes-placeholder": "Gemeinsame Eigenschaften", + "server-attributes-placeholder": "Server Eigenschaften", + "timeseries-placeholder": "Zeitreihe", + "target-entity": "Zielentität", + "attributes-propagation": "Eigenschaftsübertragung", + "attributes-propagation-hint": "Die Entitätsansicht kopiert automatisch die angegebenen Eigenschaften der Ziel-Entität, wenn Sie diese Entitätsansicht speichern oder aktualisieren. Aus Performance-Gründen werden die Attribute der Ziel-Entität nicht bei jeder Eigenschaftsänderung in die Entitätsansicht übertragen. Sie können die automatische Weitergabe aktivieren, indem Sie den Regelknoten \"copy to view\" in Ihrer Regelkette konfigurieren und die Nachrichten \"Post attributes\" und \"Attributes updated\" mit dem neuen Regelknoten verknüpfen.", + "timeseries-data": "Zeitreihendaten", + "timeseries-data-hint": "Konfigurieren Sie die Datensatzschlüssel der Zeitreihe der Zielentität, auf die die Entitätsansicht zugreifen kann. Die Daten dieser Zeitreihe sind schreibgeschützt." + }, + "event": { + "event-type": "Ereignistyp", + "type-error": "Fehler", + "type-lc-event": "Lebenszyklusereignis", + "type-stats": "Statistiken", + "type-debug-rule-node": "Fehlersuche", + "type-debug-rule-chain": "Fehlersuche", + "no-events-prompt": "Keine Ereignisse gefunden", + "error": "Fehler", + "alarm": "Alarm", + "event-time": "Ereigniszeit", + "server": "Server", + "body": "Inhalt", + "method": "Methode", + "type": "Typ", + "entity": "Entität", + "message-id": "Nachrichten-Id", + "message-type": "Nachrichten-Typ", + "data-type": "Datentyp", + "relation-type": "Beziehungstyp", + "metadata": "Meta-Daten", + "data": "Daten", + "event": "Ereignis", + "status": "Status", + "success": "Erfolg", + "failed": "Fehlgeschlagen", + "messages-processed": "Nachrichten verarbeitet", + "errors-occurred": "Fehler aufgetreten" + }, + "extension": { + "extensions": "Erweiterungen", + "selected-extensions": "{ count, plural, 1 {Erweiterung} other {# extensions} } ausgewählt", + "type": "Typ", + "key": "Schlüssel", + "value": "Wert", + "id": "ID", + "extension-id": "Erweiterungs-ID", + "extension-type": "Erweiterungstyp", + "transformer-json": "JSON *", + "unique-id-required": "Die aktuelle Erweiterungs-ID ist bereits vorhanden.", + "delete": "Erweiterung löschen", + "add": "Erweiterung hinzufügen", + "edit": "Erweiterung bearbeiten", + "delete-extension-title": "Möchten Sie die Erweiterung '{{extensionId}}' wirklich löschen?", + "delete-extension-text": "Vorsicht, nach Bestätigung werden die Erweiterung und alle zugehörigen Daten nicht wiederhergestellt.", + "delete-extensions-title": "Möchten Sie wirklich löschen? { count, plural, 1 {1 extension} other {# extensions} }?", + "delete-extensions-text": "Vorsicht, nach der Bestätigung werden alle ausgewählten Erweiterungen entfernt.", + "converters": "Konverter", + "converter-id": "Konverter-ID", + "configuration": "Konfiguration", + "converter-configurations": "Konvertierte Konfigurationen", + "token": "Sicherheitszeichen", + "add-converter": "Konverter hinzufügen", + "add-config": "Konvertierte Konfigurationen hinzufügen", + "device-name-expression": "Angabe des Gerätenamens", + "device-type-expression": "Angabe des Gerätetyps", + "custom": "Regel", + "to-double": "Duplizieren", + "transformer": "Transformator", + "json-required": "Transformer json ist erforderlich.", + "json-parse": "Transformer json kann nicht analysiert werden.", + "attributes": "Eigenschaften", + "add-attribute": "Eigenschaften hinzufügen", + "add-map": "Mapping-Element hinzufügen", + "timeseries": "Zeitreihe", + "add-timeseries": "Zeitreihe hinzufügen", + "field-required": "Feld ist erforderlich", + "brokers": "Vermittler", + "add-broker": "Vermittler hinzufügen", + "host": "Host", + "port": "Port", + "port-range": "Der Port sollte im Bereich von 1 bis 65535 liegen.", + "ssl": "Ssl", + "credentials": "Zugangsdaten", + "username": "Benutzername", + "password": "Passwort", + "retry-interval": "Wiederholungsintervall in Millisekunden", + "anonymous": "Anonym", + "basic": "Basic", + "pem": "PEM", + "ca-cert": "CA-Zertifikatsdatei *", + "private-key": "Privatschlüsseldatei *", + "cert": "Zertifikatsdatei *", + "no-file": "Keine Datei ausgewählt.", + "drop-file": "Legen Sie eine Datei ab oder wählen Sie eine Datei aus um diese hochzuladen.", + "mapping": "Mapping", + "topic-filter": "Themenfilter", + "converter-type": "Konvertierungstyp", + "converter-json": "Json", + "json-name-expression": "Angabe des Gerätenamens json", + "topic-name-expression": "Themenangabe des Gerätenamens", + "json-type-expression": "Angabe des Gerätenamens json", + "topic-type-expression": "Themenangabe des Gerätetyps", + "attribute-key-expression": "Angabe des Eigenschaftenschlüssels", + "attr-json-key-expression": "Angabe des Eigenschaftenschlüssels", + "attr-topic-key-expression": "Themenangabe des Eigenschaftenschlüssels", + "request-id-expression": "ID-Angabe anfordern", + "request-id-json-expression": "ID-Angabe anforern json", + "request-id-topic-expression": "Themenangabe der ID anfordern", + "response-topic-expression": "Antwort Themenangabe", + "value-expression": "Wertangabe", + "topic": "Thema", + "timeout": "Unterbrechung in Millisekunden", + "converter-json-required": "Konvertierte json ist erforderlich.", + "converter-json-parse": "Konvertierte json konnte nicht analysiert werden.", + "filter-expression": "Filterangabe", + "connect-requests": "Abfragen verbinden", + "add-connect-request": "Verbindungsabfrage hinzufügen", + "disconnect-requests": "Abfrage trennen", + "add-disconnect-request": "Trennung der Abfrage hinzufügen", + "attribute-requests": "Abfrage der Eigenschaften", + "add-attribute-request": "Abfrage der Eigenschaften hinzufügen", + "attribute-updates": "Aktualisierungen der Eigenschaften", + "add-attribute-update": "Aktualisierung der Eigenschaften hinzufügen", + "server-side-rpc": "Serverseite RPC", + "add-server-side-rpc-request": "Abfrage der Serverseite RPC hinzufügen", + "device-name-filter": "Gerätenamefilter", + "attribute-filter": "Eigenschaftenfilter", + "method-filter": "Methodenfilter", + "request-topic-expression": "Themenabgabe anfordern", + "response-timeout": "Antwortzeit in Millisekunden", + "topic-expression": "Themenangabe", + "client-scope": "Kundenumfrage", + "add-device": "Gerät hinzufügen", + "opc-server": "Servers", + "opc-add-server": "Server hinzufügen", + "opc-add-server-prompt": "Bitte einen Server hinzufügen", + "opc-application-name": "Anwendungsname", + "opc-application-uri": "Anwendung uri", + "opc-scan-period-in-seconds": "Scanzeitraum in Sekunden", + "opc-security": "Sicherheit", + "opc-identity": "Identifizierung", + "opc-keystore": "Schlüsselspeicher", + "opc-type": "Typ", + "opc-keystore-type": "Typ", + "opc-keystore-location": "Standort *", + "opc-keystore-password": "Passwort", + "opc-keystore-alias": "Alias", + "opc-keystore-key-password": "Schlüsselpasswort", + "opc-device-node-pattern": "Geräteknotenmuster", + "opc-device-name-pattern": "Gerätenamensmuster", + "modbus-server": "Servers/Folgegerät", + "modbus-add-server": "Server/Folgegerät hinzufügen", + "modbus-add-server-prompt": "Bitte Server/Folgegerät hinzufügen", + "modbus-transport": "Transport", + "modbus-tcp-reconnect": "Verbindung automatisch wiederherstellen", + "modbus-rtu-over-tcp": "RTU über TCP", + "modbus-port-name": "Name des Seriellen Anschlusses", + "modbus-encoding": "Verschlüsselung", + "modbus-parity": "Übereinstimmung", + "modbus-baudrate": "Datenübertragungsgeschwindigkeit", + "modbus-databits": "Daten Bits", + "modbus-stopbits": "Stopp-Bits", + "modbus-databits-range": "Datenbits sollten im Bereich von 7 bis 8 liegen.", + "modbus-stopbits-range": "Stoppbits sollten im Bereich von 1 bis 2 liegen.", + "modbus-unit-id": "ID der Einheit", + "modbus-unit-id-range": "Die Einheiten-ID sollte im Bereich von 1 bis 247 liegen.", + "modbus-device-name": "Gerätename", + "modbus-poll-period": "Abfragezeitraum in Millisekunden", + "modbus-attributes-poll-period": "Abfrageintervall der Eigenschaften in Millisekunden", + "modbus-timeseries-poll-period": "Abfrageintervall der Zeitreihen in Millisekunden", + "modbus-poll-period-range": "Das Abfrageintervall sollte einen positiven Wert haben.", + "modbus-tag": "Kennzeichnung", + "modbus-function": "Funktion", + "modbus-register-address": "Registeradresse", + "modbus-register-address-range": "Die Registeradresse sollte im Bereich zwischen 0 und 65535 liegen.", + "modbus-register-bit-index": "Bitindex", + "modbus-register-bit-index-range": "Der Bitindex sollte im Bereich von 0 bis 15 liegen.", + "modbus-register-count": "Registeranzahl", + "modbus-register-count-range": "Die Registeranzahl sollten einen positiven Wert haben.", + "modbus-byte-order": "Byte-Reihenfolge", + "sync": { + "status": "Status", + "sync": "Synchronisiert", + "not-sync": "Nicht synchronisiert", + "last-sync-time": "Zeit der letzten Synchronisierung", + "not-available": "Nicht verfügbar" + }, + "export-extensions-configuration": "Erweiterungskonfiguration exportieren", + "import-extensions-configuration": "Erweiterungskonfiguration importieren", + "import-extensions": "Erweiterungen importieren", + "import-extension": "Erweiterung importieren", + "export-extension": "Erweiterung exportieren", + "file": "Erweiterungsdatei", + "invalid-file-error": "Ungültige Erweiterungsdatei" + }, + "fullscreen": { + "expand": "Auf Vollbildmodus erweitern", + "exit": "Vollbildmodus verlassen", + "toggle": "Vollbildmodus umschalten", + "fullscreen": "Vollbild" + }, + "function": { + "function": "Funktion" + }, + "grid": { + "delete-item-title": "Möchten Sie dieses Element wirklich löschen?", + "delete-item-text": "Vorsicht, nach Bestätigung wird das Element und alle zugehörigen Daten nicht wiederhergestellt.", + "delete-items-title": "Sind Sie sicher, dass Sie löschen möchten { count, plural, 1 {Symbol} other {Symbole} }?", + "delete-items-action-title": "Löschen { count, plural, 1 {Symbol} other {# Symbole} }", + "delete-items-text": "Vorsicht, nach Bestätigung werden alle ausgewählten Elemente entfernt und alle zugehörigen Daten nicht wiederhergestellt.", + "add-item-text": "Neues Element hinzufügen", + "no-items-text": "Keine Elemente gefunden", + "item-details": "Elementdetails", + "delete-item": "Element löschen", + "delete-items": "Elemente löschen", + "scroll-to-top": "zum Seitenanfang" + }, + "help": { + "goto-help-page": "Gehen Sie zur Hilfeseite" + }, + "home": { + "home": "Startseite", + "profile": "Profil", + "logout": "Abmelden", + "menu": "Menü", + "avatar": "Benutzerbild", + "open-user-menu": "Benutzermenü öffnen" + }, + "import": { + "no-file": "Keine Datei ausgewählt", + "drop-file": "Legen Sie eine JSON-Datei ab oder wählen Sie eine Datei zum hochladen aus." + }, + "item": { + "selected": "Ausgewählt" + }, + "js-func": { + "no-return-error": "Funktion muss einen Wert zurückgeben!", + "return-type-mismatch": "Funktion muss einen Wert vom Typ '{{type}}' zurückgeben!", + "tidy": "Aufräumen" + }, + "key-val": { + "key": "Schlüssel", + "value": "Wert", + "remove-entry": "Eintrag entfernen", + "add-entry": "Eintag hinzufügen", + "no-data": "Keine Einträge" + }, + "layout": { + "layout": "Layout", + "manage": "Layouts verwalten", + "settings": "Layout-Einstellungen", + "color": "Farbe", + "main": "Hauptbereich", + "right": "Recht", + "select": "Wählen Sie das Ziellayout aus" + }, + "legend": { + "position": "Legendenposition", + "show-max": "Maximalwert anzeigen", + "show-min": "Minimalwert anzeigen", + "show-avg": "Durchschnittswert anzeigen", + "show-total": "Gesamtwert anzeigen", + "settings": "Legendeneinstellungen", + "min": "min.", + "max": "max.", + "avg": "mittelw.", + "total": "Gesamt" + }, + "login": { + "login": "Login", + "request-password-reset": "Passwortzurücksetzung anfordern", + "reset-password": "Passwort zurücksetzen", + "create-password": "Passwort erstellen", + "passwords-mismatch-error": "Eingegebene Passwörter müssen identisch sein!", + "password-again": "Passwort wiederholen", + "sign-in": "Bitte anmelden", + "username": "Benutzername (E-Mail)", + "remember-me": "Login speichern", + "forgot-password": "Passwort vergessen?", + "password-reset": "Passwort zurücksetzen", + "new-password": "Neues Passwort", + "new-password-again": "Neues Passwort wiederholen", + "password-link-sent-message": "Der Link zum Zurücksetzen des Passworts wurde erfolgreich versendet!", + "email": "E-Mail" + }, + "position": { + "top": "Oben", + "bottom": "Unten", + "left": "Links", + "right": "Rechts" + }, + "profile": { + "profile": "Profil", + "change-password": "Passwort ändern", + "current-password": "Aktuelles Passwort" + }, + "relation": { + "relations": "Beziehungen", + "direction": "Richtung", + "search-direction": { + "FROM": "Von", + "TO": "Zu" + }, + "direction-type": { + "FROM": "von", + "TO": "zu" + }, + "from-relations": "Ausgehende Verbindungen", + "to-relations": "Eingehende Verbindungen", + "selected-relations": "{ count, plural, 1 {1 Beziehung} other {# Beziehungen} } ausgewählt", + "type": "Typ", + "to-entity-type": "Zum Entitätstyp", + "to-entity-name": "Zum Entitätsnamen", + "from-entity-type": "Vom Entitätstyp", + "from-entity-name": "Vom Entitätsnamen", + "to-entity": "Zur Entität", + "from-entity": "Von Entität", + "delete": "Beziehung löschen", + "relation-type": "Art der Beziehung", + "relation-type-required": "Art der Beziehung erforderlich.", + "any-relation-type": "Jede Art", + "add": "Beziehung hinzufügen", + "edit": "Beziehung bearbeiten", + "delete-to-relation-title": "Möchten Sie die Beziehung zur Einheit'{{entityName}}' wirklich löschen?", + "delete-to-relation-text": "Vorsicht, nach Bestätigung ist die Entität '{{entityName}}' nicht mehr mit der aktuellen Entität verbunden.", + "delete-to-relations-title": "Sind Sie sicher, dass Sie { count, plural, 1 {1 Beziehung} other {# Beziehungen} } wirklich löschen?", + "delete-to-relations-text": "Vorsicht, nach Bestätigung werden alle ausgewählten Beziehungen entfernt und die entsprechenden Entitäten sind nicht mehr mit der aktuellen Entität verbunden.", + "delete-from-relation-title": "Sind Sie sicher, dass Sie die Verbindung aus der Entität '{{entityName}}' löschen möchten?", + "delete-from-relation-text": "Vorsicht, nach Bestätigung wird die aktuelle Entität '{{entityName}}' von der Entität unabhängig sein.", + "delete-from-relations-title": "Sind Sie sicher, dass Sie { count, plural, 1 {1 Beziehung} other {# Beziehungen} } löschen möchten?", + "delete-from-relations-text": "Vorsicht, nach Bestätigung werden alle ausgewählten Beziehungen entfernt und die aktuellen Entität wird nicht mehr mit den entsprechenden Entitäten verknüpft sein.", + "remove-relation-filter": "Beziehungsfilter entfernen", + "add-relation-filter": "Beziehungsfilter hinzufügen", + "any-relation": "Jede Beziehung", + "relation-filters": "Beziehungsfilter", + "additional-info": "Zusätzliche Information (JSON)", + "invalid-additional-info": "Json der Zusätzlichen Informationen konnte nicht gelesen werden." + }, + "rulechain": { + "rulechain": "Regelkette", + "rulechains": "Regelketten", + "root": "Wurzel", + "delete": "Regelkette löschen", + "name": "Name", + "name-required": "Name ist erforderlich.", + "description": "Beschreibung", + "add": "Regelkette hinzufügen", + "set-root": "Regelkette zur Wurzel machen", + "set-root-rulechain-title": "Sind Sie sicher, dass Sie die Regelkette '{{ruleChainName}}' zur Wurzel machen möchten?", + "set-root-rulechain-text": "Nach der Bestätigung wird die Regelkette zur Wurzel und bearbeitet alle eingehenden Transportnachrichten.", + "delete-rulechain-title": "Sind Sie sicher, dass Sie die Regelkette '{{ruleChainName}}' löschen möchten?", + "delete-rulechain-text": "Vorsichtig, nach Bestätigung werden die Regelkette und alle zugehörigen Daten gelöscht.", + "delete-rulechains-title": "Sind Sie sicher, dass Sie { count, plural, 1 {1 Regelkette} other {# Regelketten} } löschen möchten?", + "delete-rulechains-action-title": "{ count, plural, 1 {1 Regelkette} other {# Regelketten} } löschen", + "delete-rulechains-text": "Vorsichtig, nach Bestätigung werden alle ausgewählten Regelketten entfernt und alle zugehörigen Daten werden gelöscht.", + "add-rulechain-text": "Neue Regelkette hinzufügen", + "no-rulechains-text": "Keine Regelkette gefunden", + "rulechain-details": "Regelketten-Details", + "details": "Details", + "events": "Ereignisse", + "system": "System", + "import": "Regelkette importieren", + "export": "Regelkette exportieren", + "export-failed-error": "Regelkette konnte nicht exportiert werden: {{error}}", + "create-new-rulechain": "Neue Regelkette erstellen", + "rulechain-file": "Regelkettendatei", + "invalid-rulechain-file-error": "Regelkette konnte nicht importiert werden: Ungültige Regelkettendatenstruktur.", + "copyId": "Regelketten-ID kopieren", + "idCopiedMessage": "Regelketten-ID wurde in die Zwischenablage kopiert", + "select-rulechain": "Regelkette auswählen", + "no-rulechains-matching": "Es wurden keine passenden Regelketten für '{{entity}}' gefunden.", + "rulechain-required": "Regelkette ist erforderlich", + "management": "Regelverwaltung", + "debug-mode": "Modus zur Fehlersuche" + }, + "rulenode": { + "details": "Details", + "events": "Ereignisse", + "search": "Knoten suchen", + "open-node-library": "Knotenbibliothek öffnen", + "add": "Neuen Regelknoten hinzufügen", + "name": "Name", + "name-required": "Name ist erforderlich.", + "type": "Typ", + "description": "Beschreibung", + "delete": "Regelknoten löschen", + "select-all-objects": "Alle Knoten und Verbindungen auswählen", + "deselect-all-objects": "Auswahl aller Knoten und Verbindungen aufheben", + "delete-selected-objects": "Ausgewählte Knoten und Verbindungen löschen", + "delete-selected": "Auswahl löschen", + "select-all": "Alle auswählen", + "copy-selected": "Auswahl kopieren", + "deselect-all": "Nichts auswählen", + "rulenode-details": "Details der Regelknoten", + "debug-mode": "Modus zur Fehlersuche", + "configuration": "Konfiguration", + "link": "Verbindung", + "link-details": "Verbindungsdetails der Regelknoten", + "add-link": "Verbindung hinzufügen", + "link-label": "Verbindungsbeschriftung", + "link-label-required": "Verbindungsbeschriftung ist erforderlich.", + "custom-link-label": "Benutzerdefinierte Verbindungsbeschriftung", + "custom-link-label-required": "Benutzerdefinierte Verbindungsbeschriftung ist erforderlich.", + "link-labels": "Verbindungsbeschriftungen", + "link-labels-required": "Verbindungsbeschriftungen sind erforderlich.", + "no-link-labels-found": "Keine Verbindungsbeschriftungen gefunden", + "no-link-label-matching": "'{{label}}' nicht gefunden.", + "create-new-link-label": "Bitte erstellen Sie eine neue Verbindungsbeschriftung!", + "type-filter": "Filter", + "type-filter-details": "Eingehende Nachrichten mit konfigurierten Bedingungen filtern", + "type-enrichment": "Anreicherung", + "type-enrichment-details": "Fügen Sie zusätzliche Informationen zu den Nachrichtenmetadaten hinzu", + "type-transformation": "Transformation", + "type-transformation-details": "Ändern Sie die Nutzerdaten und Metadaten der Nachricht", + "type-action": "Aktion", + "type-action-details": "Besondere Aktion ausführen", + "type-external": "Extern", + "type-external-details": "Interagiert mit externem System", + "type-rule-chain": "Regelkette", + "type-rule-chain-details": "Leitet eingehende Nachrichten an die angegebene Regelkette weiter", + "type-input": "Input", + "type-input-details": "Logische Eingabe der Regelkette, leitet eingehende Nachrichten an die nächste zugehörige Regelkette weiter", + "type-unknown": "Unbekannt", + "type-unknown-details": "Nicht aufgelöster Regelknoten", + "directive-is-not-loaded": "Definierte Konfigurationsanweisung '{{directiveName}}' ist nicht verfügbar.", + "ui-resources-load-error": "Fehler beim Laden der Konfigurations-UI-Ressourcen.", + "invalid-target-rulechain": "Zielregelkette kann nicht aufgelöst werden!", + "test-script-function": "Skriptfunktion testen", + "message": "Nachricht", + "message-type": "Nachrichtentyp", + "select-message-type": "Nachrichtentyp auswählen", + "message-type-required": "Nachrichtentyp ist erforderlich", + "metadata": "Metadaten", + "metadata-required": "Metadateneinträge dürfen nicht leer sein.", + "output": "Ausgabe", + "test": "Test", + "help": "Hilfe" + }, + "tenant": { + "tenant": "Mandant", + "tenants": "Mandanten", + "management": "Mandantenverwaltung", + "add": "Mandant hinzufügen", + "admins": "Administratoren", + "manage-tenant-admins": "Mandantenadministratoren verwalten", + "delete": "Mandant löschen", + "add-tenant-text": "Neuen Mandanten hinzufügen", + "no-tenants-text": "Keine Mandanten gefunden", + "tenant-details": "Mandantendetails", + "delete-tenant-title": "Möchten Sie den Mandanten '{{tenantTitle}}' wirklich löschen?", + "delete-tenant-text": "Vorsicht, nach Bestätigung werden der Mandant und alle zugehörigen Daten gelöscht.", + "delete-tenants-title": "Sind Sie sicher, dass Sie { count, plural, 1 {1 Mandant} other {# Mandanten} } löschen möchten?", + "delete-tenants-action-title": "{ count, plural, 1 {1 Mandant} other {# Mandanten} } löschen", + "delete-tenants-text": "Vorsicht, nach Bestätigung werden alle ausgewählten Mandanten entfernt und alle zugehörigen Daten werden gelöscht.", + "title": "Titel", + "title-required": "Titel ist erforderlich.", + "description": "Beschreibung", + "details": "Details", + "events": "Ereignisse", + "copyId": "Mandanten-ID kopieren", + "idCopiedMessage": "Mandanten-ID wurde in die Zwischenablage kopiert", + "select-tenant": "Mandant auswählen", + "no-tenants-matching": "Es wurden keine passenden Mandanten für '{{entity}}' gefunden.", + "tenant-required": "Mandant ist erforderlich" + }, + "timeinterval": { + "seconds-interval": "{ seconds, plural, 1 {1 Sekunde} other {# Sekunden} }", + "minutes-interval": "{ minutes, plural, 1 {1 Minute} other {# Minuten} }", + "hours-interval": "{ hours, plural, 1 {1 Stunde} other {# Stunden} }", + "days-interval": "{ days, plural, 1 {1 Tag} other {# Tage} }", + "days": "Tage", + "hours": "Stunden", + "minutes": "Minuten", + "seconds": "Sekunden", + "advanced": "Erweitert" + }, + "timewindow": { + "days": "{ days, plural, 1 { Tag } other {# Tage } }", + "hours": "{ hours, plural, 0 { Stunde } 1 {1 Stunde } other {# Stunden } }", + "minutes": "{ minutes, plural, 0 { Minute } 1 {1 Minute } other {# Minuten } }", + "seconds": "{ seconds, plural, 0 { Sekunde } 1 {1 Sekunde } other {# Sekunden } }", + "realtime": "Echtzeit", + "history": "Historie", + "last-prefix": "letzte", + "period": "von {{ startTime }} bis {{ endTime }}", + "edit": "Zeitfenster bearbeiten", + "date-range": "Datumsbereich", + "last": "Letzte", + "time-period": "Zeitfenster" + }, + "user": { + "user": "User", + "users": "Users", + "customer-users": "Kunden Users", + "tenant-admins": "Mandanten-Administratoren", + "sys-admin": "System-Administrator", + "tenant-admin": "Mandanten-Administrator", + "customer": "Kunde", + "anonymous": "Anonym", + "add": "Benutzer hinzufügen", + "delete": "Benutzer löschen", + "add-user-text": "Neuen Benutzer hinzufügen", + "no-users-text": "Keine Benutzer gefunden", + "user-details": "Benutzer-Details", + "delete-user-title": "Möchten Sie den Benutzer '{{userEmail}}' wirklich löschen?", + "delete-user-text": "Vorsicht, nach Bestätigung werden der Benutzer und alle zugehörigen Daten gelöscht.", + "delete-users-title": "Sind Sie sicher, dass Sie { count, plural, 1 {1 Benutzer} other {# Benutzer} } löschen möchten??", + "delete-users-action-title": "{ count, plural, 1 {1 Benutzer} other {# Benutzer} } löschen", + "delete-users-text": "Vorsicht, nach Bestätigung werden alle ausgewählten Benutzer entfernt und alle zugehörigen Daten werden gelöscht.", + "activation-email-sent-message": "Aktivierungs E-Mail wurde erfolgreich gesendet!", + "resend-activation": "Aktivierung erneut senden", + "email": "E-Mail", + "email-required": "E-Mail ist erforderlich.", + "invalid-email-format": "Ungültiges E-Mail Format.", + "first-name": "Vorname", + "last-name": "Nachname", + "description": "Beschreibung", + "default-dashboard": "Standard-Dashboard", + "always-fullscreen": "Immer Vollbild", + "select-user": "Benutzer auswählen", + "no-users-matching": "Keine passenden Benutzer für '{{entity}}' gefunden.", + "user-required": "Benutzer ist erforderlich", + "activation-method": "Aktivierungsmethode", + "display-activation-link": "Aktivierungslink anzeigen", + "send-activation-mail": "Aktivierungs E-Mail senden", + "activation-link": "Link zur Benutzer-Aktivierung", + "activation-link-text": "Um den Benutzer zu aktivieren, verwenden Sie bitte folgenden Aktivierungslink:", + "copy-activation-link": "Aktivierungslink kopieren", + "activation-link-copied-message": "Der Link zur Benutzer-Aktivierung wurde in die Zwischenablage kopiert ", + "details": "Details", + "login-as-tenant-admin": "Als Mandanten-Administrator anmelden", + "login-as-customer-user": "Als Kunden-Benutzer anmelden" + }, + "value": { + "type": "Wertetyp", + "string": "Text", + "string-value": "Textwert", + "integer": "Ganzzahlig", + "integer-value": "Ganzzahliger Wert", + "invalid-integer-value": "Ungültiger ganzzahliger Wert", + "double": "Gleitkommazahl", + "double-value": "Gleitkomma Wert", + "boolean": "Binär", + "boolean-value": "Binärwert", + "false": "Falsch", + "true": "Wahr", + "long": "Lang" + }, + "widget": { + "widget-library": "Widget-Bibliothek", + "widget-bundle": "Widget-Paket", + "select-widgets-bundle": "Widget-Paket auswählen", + "management": "Widget Verwaltung", + "editor": "Widget Editor", + "widget-type-not-found": "Problem beim Laden der Widget-Konfiguration.
Zugeordneter Widget-Typ wurde entfernt.", + "widget-type-load-error": "Widget wurde aufgrund der folgenden Fehler nicht geladen:", + "remove": "Widget entfernen", + "edit": "Widget bearbeiten", + "remove-widget-title": "Möchten Sie das Widget '{{widgetTitle}}' wirklich entfernen?", + "remove-widget-text": "Nach der Bestätigung werden das Widget und alle zugehörigen Daten nicht wiederhergestellt.", + "timeseries": "Zeitreihe", + "search-data": "Daten suchen", + "no-data-found": "Keine Daten gefunden", + "latest-values": "Neueste Werte", + "rpc": "Steuerungswidget", + "alarm": "Alarm-Widget", + "static": "Statisches Widget", + "select-widget-type": "Widget-Typ auswählen", + "missing-widget-title-error": "Widget-Titel muss angegeben werden!", + "widget-saved": "Widget gespeichert", + "unable-to-save-widget-error": "Das Widget kann nicht gespeichert werden! Fehlermeldung!", + "save": "Widget speichern", + "saveAs": "Widget speichern unter", + "save-widget-type-as": "Widget-Typ speichern unter", + "save-widget-type-as-text": "Bitte geben Sie den neuen Widget-Titel ein und/oder wählen Sie das Ziel-Widget-Paket aus", + "toggle-fullscreen": "Vollbild umschalten", + "run": "Widget ausführen", + "title": "Widget-Titel", + "title-required": "Widget-Titel ist erforderlich.", + "type": "Widget-Typ", + "resources": "Ressourcen", + "resource-url": "JavaScript/CSS URL", + "remove-resource": "Ressource entfernen", + "add-resource": "Ressource hinzufügen", + "html": "HTML", + "tidy": "Aufgeräumt", + "css": "CSS", + "settings-schema": "Einstellungsschema", + "datakey-settings-schema": "Datenschlüssel-Einstellungsschema", + "javascript": "Javascript", + "remove-widget-type-title": "Möchten Sie den Widget-Typ '{{widgetName}}' wirklich entfernen?", + "remove-widget-type-text": "Nach der Bestätigung werden der Widget-Typ und alle zugehörigen Daten nicht wiederhergestellt.", + "remove-widget-type": "Widget-Typ entfernen", + "add-widget-type": "Neuen Widget-Typ hinzufügen", + "widget-type-load-failed-error": "Widget-Typ konnte nicht geladen werden!", + "widget-template-load-failed-error": "Widget-Vorlage konnte nicht geladen werden!", + "add": "Widget hinzufügen", + "undo": "Widget-Änderungen widerrufen ", + "export": "Widget exportieren" + }, + "widget-action": { + "header-button": "Widget-Header-Schaltfläche", + "open-dashboard-state": "Zum neuen Dashboard-Status navigieren", + "update-dashboard-state": "Aktuellen Dashboard-Status aktualisieren", + "open-dashboard": "Zu einem anderen Dashboard navigieren", + "custom": "Benutzerdefinierte Aktion", + "target-dashboard-state": "Zielstatus des Dashboards", + "target-dashboard-state-required": "Der Zielstatus ist erforderlich", + "set-entity-from-widget": "Widget-Entität festlegen", + "target-dashboard": "Ziel-Dashboard", + "open-right-layout": "Das rechte Dashboard-Layout öffnen (mobile Ansicht)" + }, + "widgets-bundle": { + "current": "Aktuelles Paket", + "widgets-bundles": "Widget-Pakete", + "add": "Widget-Pakete hinzufügen", + "delete": "Widget-Pakete löschen", + "title": "Titel", + "title-required": "Titel ist erforderlich.", + "add-widgets-bundle-text": "Neues Widget-Paket hinzufügen", + "no-widgets-bundles-text": "Keine Widget-Pakete gefunden", + "empty": "Widget-Paket ist leer ", + "details": "Details", + "widgets-bundle-details": "Widget-Paket-Details", + "delete-widgets-bundle-title": "Möchten Sie das Widget-Paket '{{widgetsBundleTitle}}' wirklich löschen? ", + "delete-widgets-bundle-text": "Seien Sie vorsichtig, nach der Bestätigung werden das Widget-Paket und alle zugehörigen Daten gelöscht.", + "delete-widgets-bundles-title": "Sind Sie sicher, dass Sie { count, plural, 1 {1 Widget-Paket} other {# Widget-Pakete} } löschen möchten?", + "delete-widgets-bundles-action-title": "{ count, plural, 1 {1 Widgets-Paket} other {# Widget-Pakete} } löschen", + "delete-widgets-bundles-text": "Vorsicht, nach Bestätigung werden alle ausgewählten Widget-Pakete entfernt und alle zugehörigen Daten werden gelöscht.", + "no-widgets-bundles-matching": "Keine passenden Widget-Pakete '{{widgetsBundle}}' gefunden.", + "widgets-bundle-required": "Widget-Paket ist erforderlich.", + "system": "System", + "import": "Widget-Paket importieren", + "export": "Widget-Paket exportieren", + "export-failed-error": "Widget-Paket kann nicht exportiert werden: {{error}}", + "create-new-widgets-bundle": "Neues Widget-Paket erstellen", + "widgets-bundle-file": "Widget-Paket-Datei", + "invalid-widgets-bundle-file-error": "Widget-Paket kann nicht importiert werden: Ungültige Widget-Paket-Datenstruktur." + }, + "widget-config": { + "data": "Daten", + "settings": "Einstellungen", + "advanced": "Erweitert ", + "title": "Titel", + "general-settings": "Allgemeine Einstellungen", + "display-title": "Titel anzeigen", + "drop-shadow": "Schlagschatten", + "enable-fullscreen": "Vollbild aktivieren", + "background-color": "Hintergrundfarbe", + "text-color": "Textfarbe", + "padding": "Pufferung", + "margin": "Rand", + "widget-style": "Widget-Stil", + "title-style": "Titel-Stil", + "mobile-mode-settings": "Einstellungen für den mobilen Modus", + "order": "Reihenfolge", + "height": "Größe", + "units": "Spezielles Symbol, das neben dem Wert angezeigt wird", + "decimals": "Anzahl der Stellen nach dem Fließkomma", + "timewindow": "Zeitfenster", + "use-dashboard-timewindow": "Dashboard-Zeitfenster verwenden", + "display-timewindow": "Zeitfenster anzeigen", + "display-legend": "Legende anzeigen", + "datasources": "Datenquellen", + "maximum-datasources": "Maximal { count, plural, 1 {1 Datenquelle ist erlaubt} other {# Datenquellen sind erlaubt} }.", + "datasource-type": "Typ", + "datasource-parameters": "Parameter", + "remove-datasource": "Datenquelle entfernen", + "add-datasource": "Datenquelle hinzufügen ", + "target-device": "Zielgerät", + "alarm-source": "Alarmquelle", + "actions": "Aktionen", + "action": "Aktion", + "add-action": "Aktion hinzufügen", + "search-actions": "Aktion suchen", + "action-source": "Aktionsquelle", + "action-source-required": "Aktionsquelle ist erforderlich.", + "action-name": "Name", + "action-name-required": "Aktionsname ist erforderlich.", + "action-name-not-unique": "Eine andere Aktion mit demselben Namen ist bereits vorhanden.
Der Aktionsname sollte innerhalb derselben Aktionsquelle eindeutig sein.", + "action-icon": "Symbol ", + "action-type": "Art", + "action-type-required": "Aktionsart ist erforderlich.", + "edit-action": "Aktion bearbeiten", + "delete-action": "Aktion löschen", + "delete-action-title": "Widget-Aktion löschen", + "delete-action-text": "Möchten Sie die Widget-Aktion mit Namen '{{actionName}}' wirklich löschen?" + }, + "widget-type": { + "import": "Widget-Typ importieren", + "export": "Widget-Typ exportieren", + "export-failed-error": "Widget-Typ kann nicht exportiert werden: {{error}}", + "create-new-widget-type": "Neuen Widget-Typ erstellen", + "widget-type-file": "Widget-Typdatei", + "invalid-widget-type-file-error": "Widget-Typ kann nicht importiert werden: Ungültige Datenstruktur des Widget-Typs." + }, + "widgets": { + "date-range-navigator": { + "localizationMap": { + "Sun": "So.", + "Mon": "Mo.", + "Tue": "Di.", + "Wed": "Mi.", + "Thu": "Do.", + "Fri": "Fr.", + "Sat": "Sa.", + "Jan": "Jan.", + "Feb": "Feb.", + "Mar": "März", + "Apr": "Apr.", + "May": "Mai", + "Jun": "Juni", + "Jul": "Juli", + "Aug": "Aug.", + "Sep": "Sep.", + "Oct": "Okt.", + "Nov": "Nov.", + "Dec": "Dez.", + "January": "Januar", + "February": "Februar", + "March": "März", + "April": "April", + "June": "Juni", + "July": "Juli", + "August": "August", + "September": "September", + "October": "Oktober", + "November": "November", + "December": "Dezember", + "Custom Date Range": "Benutzerdefinierter Datumsbereich", + "Date Range Template": "Datumsbereichsvorlage", + "Today": "Heute", + "Yesterday": "Gestern", + "This Week": "Diese Woche", + "Last Week": "Letzte Woche", + "This Month": "Diesen Monat", + "Last Month": "Im vergangenen Monat", + "Year": "Jahr", + "This Year": "Dieses Jahr", + "Last Year": "Vergangenes Jahr", + "Date picker": "Datumsauswahl", + "Hour": "Stunde", + "Day": "Tag", + "Week": "Woche", + "2 weeks": "2 Wochen", + "Month": "Monat", + "3 months": "3 Monate", + "6 months": "6 Monate", + "Custom interval": "Benutzerdefiniertes Intervall", + "Interval": "Intervall", + "Step size": "Schrittlänge", + "Ok": "Ok" + } + } + }, + "icon": { + "icon": "Symbol", + "select-icon": "Symbol auswählen", + "material-icons": "Material-Symbole", + "show-all": "Alle Symbole anzeigen" + }, + "custom": { + "widget-action": { + "action-cell-button": "Aktionszellenschaltfläche", + "row-click": "Klick auf Zeile", + "polygon-click": "Klick auf Polygon", + "marker-click": "Klick auf Marker", + "tooltip-tag-action": "Tooltip-Tag-Aktion", + "node-selected": "Klick auf Node", + "element-click": "Klick auf HTML element" + } + }, + "language": { + "language": "Sprache", + "locales": { + "de_DE": "Deutsch", + "fr_FR": "Französisch", + "zh_CN": "Chinesisch", + "en_US": "Englisch", + "it_IT": "Italienisch", + "ko_KR": "Koreanisch", + "ru_RU": "Russisch", + "es_ES": "Spanisch", + "ja_JA": "Japanisch", + "tr_TR": "Türkisch", + "fa_IR": "Persisch", + "uk_UA": "Ukrainisch", + "cs_CZ": "Tschechisch" + } + } +} diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json new file mode 100644 index 0000000000..00b3ca21e0 --- /dev/null +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -0,0 +1,1720 @@ +{ + "access": { + "unauthorized": "Unauthorized", + "unauthorized-access": "Unauthorized Access", + "unauthorized-access-text": "You should sign in to have access to this resource!", + "access-forbidden": "Access Forbidden", + "access-forbidden-text": "You haven't access rights to this location!
Try to sign in with different user if you still wish to gain access to this location.", + "refresh-token-expired": "Session has expired", + "refresh-token-failed": "Unable to refresh session" + }, + "action": { + "activate": "Activate", + "suspend": "Suspend", + "save": "Save", + "saveAs": "Save as", + "cancel": "Cancel", + "ok": "OK", + "delete": "Delete", + "add": "Add", + "yes": "Yes", + "no": "No", + "update": "Update", + "remove": "Remove", + "search": "Search", + "clear-search": "Clear search", + "assign": "Assign", + "unassign": "Unassign", + "share": "Share", + "make-private": "Make private", + "apply": "Apply", + "apply-changes": "Apply changes", + "edit-mode": "Edit mode", + "enter-edit-mode": "Enter edit mode", + "decline-changes": "Decline changes", + "close": "Close", + "back": "Back", + "run": "Run", + "sign-in": "Sign in!", + "edit": "Edit", + "view": "View", + "create": "Create", + "drag": "Drag", + "refresh": "Refresh", + "undo": "Undo", + "copy": "Copy", + "paste": "Paste", + "copy-reference": "Copy reference", + "paste-reference": "Paste reference", + "import": "Import", + "export": "Export", + "share-via": "Share via {{provider}}", + "continue": "Continue" + }, + "aggregation": { + "aggregation": "Aggregation", + "function": "Data aggregation function", + "limit": "Max values", + "group-interval": "Grouping interval", + "min": "Min", + "max": "Max", + "avg": "Average", + "sum": "Sum", + "count": "Count", + "none": "None" + }, + "admin": { + "general": "General", + "general-settings": "General Settings", + "outgoing-mail": "Outgoing Mail", + "outgoing-mail-settings": "Outgoing Mail Settings", + "system-settings": "System Settings", + "test-mail-sent": "Test mail was successfully sent!", + "base-url": "Base URL", + "base-url-required": "Base URL is required.", + "mail-from": "Mail From", + "mail-from-required": "Mail From is required.", + "smtp-protocol": "SMTP 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-invalid": "That doesn't look like a valid smtp port.", + "timeout-msec": "Timeout (msec)", + "timeout-required": "Timeout is required.", + "timeout-invalid": "That doesn't look like a valid timeout.", + "enable-tls": "Enable TLS", + "send-test-mail": "Send test mail", + "security-settings": "Security settings", + "password-policy": "Password policy", + "minimum-password-length": "Minimum password length", + "minimum-password-length-required": "Minimum password length is required", + "minimum-password-length-range": "Minimum password length should be in a range from 5 to 50", + "minimum-uppercase-letters": "Minimum number of uppercase letters", + "minimum-uppercase-letters-range": "Minimum number of uppercase letters can't be negative", + "minimum-lowercase-letters": "Minimum number of lowercase letters", + "minimum-lowercase-letters-range": "Minimum number of lowercase letters can't be negative", + "minimum-digits": "Minimum number of digits", + "minimum-digits-range": "Minimum number of digits can't be negative", + "minimum-special-characters": "Minimum number of special characters", + "minimum-special-characters-range": "Minimum number of special characters can't be negative", + "password-expiration-period-days": "Password expiration period in days", + "password-expiration-period-days-range": "Password expiration period in days can't be negative" + }, + "alarm": { + "alarm": "Alarm", + "alarms": "Alarms", + "select-alarm": "Select alarm", + "no-alarms-matching": "No alarms matching '{{entity}}' were found.", + "alarm-required": "Alarm is required", + "alarm-status": "Alarm status", + "search-status": { + "ANY": "Any", + "ACTIVE": "Active", + "CLEARED": "Cleared", + "ACK": "Acknowledged", + "UNACK": "Unacknowledged" + }, + "display-status": { + "ACTIVE_UNACK": "Active Unacknowledged", + "ACTIVE_ACK": "Active Acknowledged", + "CLEARED_UNACK": "Cleared Unacknowledged", + "CLEARED_ACK": "Cleared Acknowledged" + }, + "no-alarms-prompt": "No alarms found", + "created-time": "Created time", + "type": "Type", + "severity": "Severity", + "originator": "Originator", + "originator-type": "Originator type", + "details": "Details", + "status": "Status", + "alarm-details": "Alarm details", + "start-time": "Start time", + "end-time": "End time", + "ack-time": "Acknowledged time", + "clear-time": "Cleared time", + "severity-critical": "Critical", + "severity-major": "Major", + "severity-minor": "Minor", + "severity-warning": "Warning", + "severity-indeterminate": "Indeterminate", + "acknowledge": "Acknowledge", + "clear": "Clear", + "search": "Search alarms", + "selected-alarms": "{ count, plural, 1 {1 alarm} other {# alarms} } selected", + "no-data": "No data to display", + "polling-interval": "Alarms polling interval (sec)", + "polling-interval-required": "Alarms polling interval is required.", + "min-polling-interval-message": "At least 1 sec polling interval is allowed.", + "aknowledge-alarms-title": "Acknowledge { count, plural, 1 {1 alarm} other {# alarms} }", + "aknowledge-alarms-text": "Are you sure you want to acknowledge { count, plural, 1 {1 alarm} other {# alarms} }?", + "aknowledge-alarm-title": "Acknowledge Alarm", + "aknowledge-alarm-text": "Are you sure you want to acknowledge Alarm?", + "clear-alarms-title": "Clear { count, plural, 1 {1 alarm} other {# alarms} }", + "clear-alarms-text": "Are you sure you want to clear { count, plural, 1 {1 alarm} other {# alarms} }?", + "clear-alarm-title": "Clear Alarm", + "clear-alarm-text": "Are you sure you want to clear Alarm?", + "alarm-status-filter": "Alarm Status Filter" + }, + "alias": { + "add": "Add alias", + "edit": "Edit alias", + "name": "Alias name", + "name-required": "Alias name is required", + "duplicate-alias": "Alias with same name is already exists.", + "filter-type-single-entity": "Single entity", + "filter-type-entity-list": "Entity list", + "filter-type-entity-name": "Entity name", + "filter-type-state-entity": "Entity from dashboard state", + "filter-type-state-entity-description": "Entity taken from dashboard state parameters", + "filter-type-asset-type": "Asset type", + "filter-type-asset-type-description": "Assets of type '{{assetType}}'", + "filter-type-asset-type-and-name-description": "Assets of type '{{assetType}}' and with name starting with '{{prefix}}'", + "filter-type-device-type": "Device type", + "filter-type-device-type-description": "Devices of type '{{deviceType}}'", + "filter-type-device-type-and-name-description": "Devices of type '{{deviceType}}' and with name starting with '{{prefix}}'", + "filter-type-entity-view-type": "Entity View type", + "filter-type-entity-view-type-description": "Entity Views of type '{{entityView}}'", + "filter-type-entity-view-type-and-name-description": "Entity Views of type '{{entityView}}' and with name starting with '{{prefix}}'", + "filter-type-relations-query": "Relations query", + "filter-type-relations-query-description": "{{entities}} that have {{relationType}} relation {{direction}} {{rootEntity}}", + "filter-type-asset-search-query": "Asset search query", + "filter-type-asset-search-query-description": "Assets with types {{assetTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}", + "filter-type-device-search-query": "Device search query", + "filter-type-device-search-query-description": "Devices with types {{deviceTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}", + "filter-type-entity-view-search-query": "Entity view search query", + "filter-type-entity-view-search-query-description": "Entity views with types {{entityViewTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}", + "entity-filter": "Entity filter", + "resolve-multiple": "Resolve as multiple entities", + "filter-type": "Filter type", + "filter-type-required": "Filter type is required.", + "entity-filter-no-entity-matched": "No entities matching specified filter were found.", + "no-entity-filter-specified": "No entity filter specified", + "root-state-entity": "Use dashboard state entity as root", + "root-entity": "Root entity", + "state-entity-parameter-name": "State entity parameter name", + "default-state-entity": "Default state entity", + "default-entity-parameter-name": "By default", + "max-relation-level": "Max relation level", + "unlimited-level": "Unlimited level", + "state-entity": "Dashboard state entity", + "all-entities": "All entities", + "any-relation": "any" + }, + "asset": { + "asset": "Asset", + "assets": "Assets", + "management": "Asset management", + "view-assets": "View Assets", + "add": "Add Asset", + "assign-to-customer": "Assign to customer", + "assign-asset-to-customer": "Assign Asset(s) To Customer", + "assign-asset-to-customer-text": "Please select the assets to assign to the customer", + "no-assets-text": "No assets found", + "assign-to-customer-text": "Please select the customer to assign the asset(s)", + "public": "Public", + "assignedToCustomer": "Assigned to customer", + "make-public": "Make asset public", + "make-private": "Make asset private", + "unassign-from-customer": "Unassign from customer", + "delete": "Delete asset", + "asset-public": "Asset is public", + "asset-type": "Asset type", + "asset-type-required": "Asset type is required.", + "select-asset-type": "Select asset type", + "enter-asset-type": "Enter asset type", + "any-asset": "Any asset", + "no-asset-types-matching": "No asset types matching '{{entitySubtype}}' were found.", + "asset-type-list-empty": "No asset types selected.", + "asset-types": "Asset types", + "name": "Name", + "name-required": "Name is required.", + "description": "Description", + "type": "Type", + "type-required": "Type is required.", + "details": "Details", + "events": "Events", + "add-asset-text": "Add new asset", + "asset-details": "Asset details", + "assign-assets": "Assign assets", + "assign-assets-text": "Assign { count, plural, 1 {1 asset} other {# assets} } to customer", + "delete-assets": "Delete assets", + "unassign-assets": "Unassign assets", + "unassign-assets-action-title": "Unassign { count, plural, 1 {1 asset} other {# assets} } from customer", + "assign-new-asset": "Assign new asset", + "delete-asset-title": "Are you sure you want to delete the asset '{{assetName}}'?", + "delete-asset-text": "Be careful, after the confirmation the asset and all related data will become unrecoverable.", + "delete-assets-title": "Are you sure you want to delete { count, plural, 1 {1 asset} other {# assets} }?", + "delete-assets-action-title": "Delete { count, plural, 1 {1 asset} other {# assets} }", + "delete-assets-text": "Be careful, after the confirmation all selected assets will be removed and all related data will become unrecoverable.", + "make-public-asset-title": "Are you sure you want to make the asset '{{assetName}}' public?", + "make-public-asset-text": "After the confirmation the asset and all its data will be made public and accessible by others.", + "make-private-asset-title": "Are you sure you want to make the asset '{{assetName}}' private?", + "make-private-asset-text": "After the confirmation the asset and all its data will be made private and won't be accessible by others.", + "unassign-asset-title": "Are you sure you want to unassign the asset '{{assetName}}'?", + "unassign-asset-text": "After the confirmation the asset will be unassigned and won't be accessible by the customer.", + "unassign-asset": "Unassign asset", + "unassign-assets-title": "Are you sure you want to unassign { count, plural, 1 {1 asset} other {# assets} }?", + "unassign-assets-text": "After the confirmation all selected assets will be unassigned and won't be accessible by the customer.", + "copyId": "Copy asset Id", + "idCopiedMessage": "Asset Id has been copied to clipboard", + "select-asset": "Select asset", + "no-assets-matching": "No assets matching '{{entity}}' were found.", + "asset-required": "Asset is required", + "name-starts-with": "Asset name starts with", + "import": "Import assets", + "asset-file": "Asset file" + }, + "attribute": { + "attributes": "Attributes", + "latest-telemetry": "Latest telemetry", + "attributes-scope": "Entity attributes scope", + "scope-latest-telemetry": "Latest telemetry", + "scope-client": "Client attributes", + "scope-server": "Server attributes", + "scope-shared": "Shared attributes", + "add": "Add attribute", + "key": "Key", + "last-update-time": "Last update time", + "key-required": "Attribute key is required.", + "value": "Value", + "value-required": "Attribute value is required.", + "delete-attributes-title": "Are you sure you want to delete { count, plural, 1 {1 attribute} other {# attributes} }?", + "delete-attributes-text": "Be careful, after the confirmation all selected attributes will be removed.", + "delete-attributes": "Delete attributes", + "enter-attribute-value": "Enter attribute value", + "show-on-widget": "Show on widget", + "widget-mode": "Widget mode", + "next-widget": "Next widget", + "prev-widget": "Previous widget", + "add-to-dashboard": "Add to dashboard", + "add-widget-to-dashboard": "Add widget to dashboard", + "selected-attributes": "{ count, plural, 1 {1 attribute} other {# attributes} } selected", + "selected-telemetry": "{ count, plural, 1 {1 telemetry unit} other {# telemetry units} } selected" + }, + "audit-log": { + "audit": "Audit", + "audit-logs": "Audit Logs", + "timestamp": "Timestamp", + "entity-type": "Entity Type", + "entity-name": "Entity Name", + "user": "User", + "type": "Type", + "status": "Status", + "details": "Details", + "type-added": "Added", + "type-deleted": "Deleted", + "type-updated": "Updated", + "type-attributes-updated": "Attributes updated", + "type-attributes-deleted": "Attributes deleted", + "type-rpc-call": "RPC call", + "type-credentials-updated": "Credentials updated", + "type-assigned-to-customer": "Assigned to Customer", + "type-unassigned-from-customer": "Unassigned from Customer", + "type-activated": "Activated", + "type-suspended": "Suspended", + "type-credentials-read": "Credentials read", + "type-attributes-read": "Attributes read", + "type-relation-add-or-update": "Relation updated", + "type-relation-delete": "Relation deleted", + "type-relations-delete": "All relation deleted", + "type-alarm-ack": "Acknowledged", + "type-alarm-clear": "Cleared", + "type-login": "Login", + "type-logout": "Logout", + "status-success": "Success", + "status-failure": "Failure", + "audit-log-details": "Audit log details", + "no-audit-logs-prompt": "No logs found", + "action-data": "Action data", + "failure-details": "Failure details", + "search": "Search audit logs", + "clear-search": "Clear search" + }, + "confirm-on-exit": { + "message": "You have unsaved changes. Are you sure you want to leave this page?", + "html-message": "You have unsaved changes.
Are you sure you want to leave this page?", + "title": "Unsaved changes" + }, + "contact": { + "country": "Country", + "city": "City", + "state": "State / Province", + "postal-code": "Zip / Postal Code", + "postal-code-invalid": "Invalid Zip / Postal Code format.", + "address": "Address", + "address2": "Address 2", + "phone": "Phone", + "email": "Email", + "no-address": "No address" + }, + "common": { + "username": "Username", + "password": "Password", + "enter-username": "Enter username", + "enter-password": "Enter password", + "enter-search": "Enter search" + }, + "content-type": { + "json": "Json", + "text": "Text", + "binary": "Binary (Base64)" + }, + "customer": { + "customer": "Customer", + "customers": "Customers", + "management": "Customer management", + "dashboard": "Customer Dashboard", + "dashboards": "Customer Dashboards", + "devices": "Customer Devices", + "entity-views": "Customer Entity Views", + "assets": "Customer Assets", + "public-dashboards": "Public Dashboards", + "public-devices": "Public Devices", + "public-assets": "Public Assets", + "public-entity-views": "Public Entity Views", + "add": "Add Customer", + "delete": "Delete customer", + "manage-customer-users": "Manage customer users", + "manage-customer-devices": "Manage customer devices", + "manage-customer-dashboards": "Manage customer dashboards", + "manage-public-devices": "Manage public devices", + "manage-public-dashboards": "Manage public dashboards", + "manage-customer-assets": "Manage customer assets", + "manage-public-assets": "Manage public assets", + "add-customer-text": "Add new customer", + "no-customers-text": "No customers found", + "customer-details": "Customer details", + "delete-customer-title": "Are you sure you want to delete the customer '{{customerTitle}}'?", + "delete-customer-text": "Be careful, after the confirmation the customer and all related data will become unrecoverable.", + "delete-customers-title": "Are you sure you want to delete { count, plural, 1 {1 customer} other {# customers} }?", + "delete-customers-action-title": "Delete { count, plural, 1 {1 customer} other {# customers} }", + "delete-customers-text": "Be careful, after the confirmation all selected customers will be removed and all related data will become unrecoverable.", + "manage-users": "Manage users", + "manage-assets": "Manage assets", + "manage-devices": "Manage devices", + "manage-dashboards": "Manage dashboards", + "title": "Title", + "title-required": "Title is required.", + "description": "Description", + "details": "Details", + "events": "Events", + "copyId": "Copy customer Id", + "idCopiedMessage": "Customer Id has been copied to clipboard", + "select-customer": "Select customer", + "no-customers-matching": "No customers matching '{{entity}}' were found.", + "customer-required": "Customer is required", + "select-default-customer": "Select default customer", + "default-customer": "Default customer", + "default-customer-required": "Default customer is required in order to debug dashboard on Tenant level" + }, + "datetime": { + "date-from": "Date from", + "time-from": "Time from", + "date-to": "Date to", + "time-to": "Time to" + }, + "dashboard": { + "dashboard": "Dashboard", + "dashboards": "Dashboards", + "management": "Dashboard management", + "view-dashboards": "View Dashboards", + "add": "Add Dashboard", + "assign-dashboard-to-customer": "Assign Dashboard(s) To Customer", + "assign-dashboard-to-customer-text": "Please select the dashboards to assign to the customer", + "assign-to-customer-text": "Please select the customer to assign the dashboard(s)", + "assign-to-customer": "Assign to customer", + "unassign-from-customer": "Unassign from customer", + "make-public": "Make dashboard public", + "make-private": "Make dashboard private", + "manage-assigned-customers": "Manage assigned customers", + "assigned-customers": "Assigned customers", + "assign-to-customers": "Assign Dashboard(s) To Customers", + "assign-to-customers-text": "Please select the customers to assign the dashboard(s)", + "unassign-from-customers": "Unassign Dashboard(s) From Customers", + "unassign-from-customers-text": "Please select the customers to unassign from the dashboard(s)", + "no-dashboards-text": "No dashboards found", + "no-widgets": "No widgets configured", + "add-widget": "Add new widget", + "title": "Title", + "select-widget-title": "Select widget", + "select-widget-subtitle": "List of available widget types", + "delete": "Delete dashboard", + "title-required": "Title is required.", + "description": "Description", + "details": "Details", + "dashboard-details": "Dashboard details", + "add-dashboard-text": "Add new dashboard", + "assign-dashboards": "Assign dashboards", + "assign-new-dashboard": "Assign new dashboard", + "assign-dashboards-text": "Assign { count, plural, 1 {1 dashboard} other {# dashboards} } to customers", + "unassign-dashboards-action-text": "Unassign { count, plural, 1 {1 dashboard} other {# dashboards} } from customers", + "delete-dashboards": "Delete dashboards", + "unassign-dashboards": "Unassign dashboards", + "unassign-dashboards-action-title": "Unassign { count, plural, 1 {1 dashboard} other {# dashboards} } from customer", + "delete-dashboard-title": "Are you sure you want to delete the dashboard '{{dashboardTitle}}'?", + "delete-dashboard-text": "Be careful, after the confirmation the dashboard and all related data will become unrecoverable.", + "delete-dashboards-title": "Are you sure you want to delete { count, plural, 1 {1 dashboard} other {# dashboards} }?", + "delete-dashboards-action-title": "Delete { count, plural, 1 {1 dashboard} other {# dashboards} }", + "delete-dashboards-text": "Be careful, after the confirmation all selected dashboards will be removed and all related data will become unrecoverable.", + "unassign-dashboard-title": "Are you sure you want to unassign the dashboard '{{dashboardTitle}}'?", + "unassign-dashboard-text": "After the confirmation the dashboard will be unassigned and won't be accessible by the customer.", + "unassign-dashboard": "Unassign dashboard", + "unassign-dashboards-title": "Are you sure you want to unassign { count, plural, 1 {1 dashboard} other {# dashboards} }?", + "unassign-dashboards-text": "After the confirmation all selected dashboards will be unassigned and won't be accessible by the customer.", + "public-dashboard-title": "Dashboard is now public", + "public-dashboard-text": "Your dashboard {{dashboardTitle}} is now public and accessible via next public link:", + "public-dashboard-notice": "Note: Do not forget to make related devices public in order to access their data.", + "make-private-dashboard-title": "Are you sure you want to make the dashboard '{{dashboardTitle}}' private?", + "make-private-dashboard-text": "After the confirmation the dashboard will be made private and won't be accessible by others.", + "make-private-dashboard": "Make dashboard private", + "socialshare-text": "'{{dashboardTitle}}' powered by ThingsBoard", + "socialshare-title": "'{{dashboardTitle}}' powered by ThingsBoard", + "select-dashboard": "Select dashboard", + "no-dashboards-matching": "No dashboards matching '{{entity}}' were found.", + "dashboard-required": "Dashboard is required.", + "select-existing": "Select existing dashboard", + "create-new": "Create new dashboard", + "new-dashboard-title": "New dashboard title", + "open-dashboard": "Open dashboard", + "set-background": "Set background", + "background-color": "Background color", + "background-image": "Background image", + "background-size-mode": "Background size mode", + "no-image": "No image selected", + "drop-image": "Drop an image or click to select a file to upload.", + "settings": "Settings", + "columns-count": "Columns count", + "columns-count-required": "Columns count is required.", + "min-columns-count-message": "Only 10 minimum column count is allowed.", + "max-columns-count-message": "Only 1000 maximum column count is allowed.", + "widgets-margins": "Margin between widgets", + "horizontal-margin": "Horizontal margin", + "horizontal-margin-required": "Horizontal margin value is required.", + "min-horizontal-margin-message": "Only 0 is allowed as minimum horizontal margin value.", + "max-horizontal-margin-message": "Only 50 is allowed as maximum horizontal margin value.", + "vertical-margin": "Vertical margin", + "vertical-margin-required": "Vertical margin value is required.", + "min-vertical-margin-message": "Only 0 is allowed as minimum vertical margin value.", + "max-vertical-margin-message": "Only 50 is allowed as maximum vertical margin value.", + "autofill-height": "Auto fill layout height", + "mobile-layout": "Mobile layout settings", + "mobile-row-height": "Mobile row height, px", + "mobile-row-height-required": "Mobile row height value is required.", + "min-mobile-row-height-message": "Only 5 pixels is allowed as minimum mobile row height value.", + "max-mobile-row-height-message": "Only 200 pixels is allowed as maximum mobile row height value.", + "display-title": "Display dashboard title", + "toolbar-always-open": "Keep toolbar opened", + "title-color": "Title color", + "display-dashboards-selection": "Display dashboards selection", + "display-entities-selection": "Display entities selection", + "display-dashboard-timewindow": "Display timewindow", + "display-dashboard-export": "Display export", + "import": "Import dashboard", + "export": "Export dashboard", + "export-failed-error": "Unable to export dashboard: {{error}}", + "create-new-dashboard": "Create new dashboard", + "dashboard-file": "Dashboard file", + "invalid-dashboard-file-error": "Unable to import dashboard: Invalid dashboard data structure.", + "dashboard-import-missing-aliases-title": "Configure aliases used by imported dashboard", + "create-new-widget": "Create new widget", + "import-widget": "Import widget", + "widget-file": "Widget file", + "invalid-widget-file-error": "Unable to import widget: Invalid widget data structure.", + "widget-import-missing-aliases-title": "Configure aliases used by imported widget", + "open-toolbar": "Open dashboard toolbar", + "close-toolbar": "Close toolbar", + "configuration-error": "Configuration error", + "alias-resolution-error-title": "Dashboard aliases configuration error", + "invalid-aliases-config": "Unable to find any devices matching to some of the aliases filter.
Please contact your administrator in order to resolve this issue.", + "select-devices": "Select devices", + "assignedToCustomer": "Assigned to customer", + "assignedToCustomers": "Assigned to customers", + "public": "Public", + "public-link": "Public link", + "copy-public-link": "Copy public link", + "public-link-copied-message": "Dashboard public link has been copied to clipboard", + "manage-states": "Manage dashboard states", + "states": "Dashboard states", + "search-states": "Search dashboard states", + "selected-states": "{ count, plural, 1 {1 dashboard state} other {# dashboard states} } selected", + "edit-state": "Edit dashboard state", + "delete-state": "Delete dashboard state", + "add-state": "Add dashboard state", + "state": "Dashboard state", + "state-name": "Name", + "state-name-required": "Dashboard state name is required.", + "state-id": "State Id", + "state-id-required": "Dashboard state id is required.", + "state-id-exists": "Dashboard state with the same id is already exists.", + "is-root-state": "Root state", + "delete-state-title": "Delete dashboard state", + "delete-state-text": "Are you sure you want delete dashboard state with name '{{stateName}}'?", + "show-details": "Show details", + "hide-details": "Hide details", + "select-state": "Select target state", + "state-controller": "State controller" + }, + "datakey": { + "settings": "Settings", + "advanced": "Advanced", + "label": "Label", + "color": "Color", + "units": "Special symbol to show next to value", + "decimals": "Number of digits after floating point", + "data-generation-func": "Data generation function", + "use-data-post-processing-func": "Use data post-processing function", + "configuration": "Data key configuration", + "timeseries": "Timeseries", + "attributes": "Attributes", + "alarm": "Alarm fields", + "timeseries-required": "Entity timeseries are required.", + "timeseries-or-attributes-required": "Entity timeseries/attributes are required.", + "maximum-timeseries-or-attributes": "Maximum { count, plural, 1 {1 timeseries/attribute is allowed.} other {# timeseries/attributes are allowed} }", + "alarm-fields-required": "Alarm fields are required.", + "function-types": "Function types", + "function-types-required": "Function types are required.", + "maximum-function-types": "Maximum { count, plural, 1 {1 function type is allowed.} other {# function types are allowed} }", + "time-description": "timestamp of the current value;", + "value-description": "the current value;", + "prev-value-description": "result of the previous function call;", + "time-prev-description": "timestamp of the previous value;", + "prev-orig-value-description": "original previous value;" + }, + "datasource": { + "type": "Datasource type", + "name": "Name", + "add-datasource-prompt": "Please add datasource" + }, + "details": { + "edit-mode": "Edit mode", + "toggle-edit-mode": "Toggle edit mode" + }, + "device": { + "device": "Device", + "device-required": "Device is required.", + "devices": "Devices", + "management": "Device management", + "view-devices": "View Devices", + "device-alias": "Device alias", + "aliases": "Device aliases", + "no-alias-matching": "'{{alias}}' not found.", + "no-aliases-found": "No aliases found.", + "no-key-matching": "'{{key}}' not found.", + "no-keys-found": "No keys found.", + "create-new-alias": "Create a new one!", + "create-new-key": "Create a new one!", + "duplicate-alias-error": "Duplicate alias found '{{alias}}'.
Device aliases must be unique whithin the dashboard.", + "configure-alias": "Configure '{{alias}}' alias", + "no-devices-matching": "No devices matching '{{entity}}' were found.", + "alias": "Alias", + "alias-required": "Device alias is required.", + "remove-alias": "Remove device alias", + "add-alias": "Add device alias", + "name-starts-with": "Device name starts with", + "device-list": "Device list", + "use-device-name-filter": "Use filter", + "device-list-empty": "No devices selected.", + "device-name-filter-required": "Device name filter is required.", + "device-name-filter-no-device-matched": "No devices starting with '{{device}}' were found.", + "add": "Add Device", + "assign-to-customer": "Assign to customer", + "assign-device-to-customer": "Assign Device(s) To Customer", + "assign-device-to-customer-text": "Please select the devices to assign to the customer", + "make-public": "Make device public", + "make-private": "Make device private", + "no-devices-text": "No devices found", + "assign-to-customer-text": "Please select the customer to assign the device(s)", + "device-details": "Device details", + "add-device-text": "Add new device", + "credentials": "Credentials", + "manage-credentials": "Manage credentials", + "delete": "Delete device", + "assign-devices": "Assign devices", + "assign-devices-text": "Assign { count, plural, 1 {1 device} other {# devices} } to customer", + "delete-devices": "Delete devices", + "unassign-from-customer": "Unassign from customer", + "unassign-devices": "Unassign devices", + "unassign-devices-action-title": "Unassign { count, plural, 1 {1 device} other {# devices} } from customer", + "assign-new-device": "Assign new device", + "make-public-device-title": "Are you sure you want to make the device '{{deviceName}}' public?", + "make-public-device-text": "After the confirmation the device and all its data will be made public and accessible by others.", + "make-private-device-title": "Are you sure you want to make the device '{{deviceName}}' private?", + "make-private-device-text": "After the confirmation the device and all its data will be made private and won't be accessible by others.", + "view-credentials": "View credentials", + "delete-device-title": "Are you sure you want to delete the device '{{deviceName}}'?", + "delete-device-text": "Be careful, after the confirmation the device and all related data will become unrecoverable.", + "delete-devices-title": "Are you sure you want to delete { count, plural, 1 {1 device} other {# devices} }?", + "delete-devices-action-title": "Delete { count, plural, 1 {1 device} other {# devices} }", + "delete-devices-text": "Be careful, after the confirmation all selected devices will be removed and all related data will become unrecoverable.", + "unassign-device-title": "Are you sure you want to unassign the device '{{deviceName}}'?", + "unassign-device-text": "After the confirmation the device will be unassigned and won't be accessible by the customer.", + "unassign-device": "Unassign device", + "unassign-devices-title": "Are you sure you want to unassign { count, plural, 1 {1 device} other {# devices} }?", + "unassign-devices-text": "After the confirmation all selected devices will be unassigned and won't be accessible by the customer.", + "device-credentials": "Device Credentials", + "credentials-type": "Credentials type", + "access-token": "Access token", + "access-token-required": "Access token is required.", + "access-token-invalid": "Access token length must be from 1 to 20 characters.", + "rsa-key": "RSA public key", + "rsa-key-required": "RSA public key is required.", + "secret": "Secret", + "secret-required": "Secret is required.", + "device-type": "Device type", + "device-type-required": "Device type is required.", + "select-device-type": "Select device type", + "enter-device-type": "Enter device type", + "any-device": "Any device", + "no-device-types-matching": "No device types matching '{{entitySubtype}}' were found.", + "device-type-list-empty": "No device types selected.", + "device-types": "Device types", + "name": "Name", + "name-required": "Name is required.", + "description": "Description", + "label": "Label", + "events": "Events", + "details": "Details", + "copyId": "Copy device Id", + "copyAccessToken": "Copy access token", + "idCopiedMessage": "Device Id has been copied to clipboard", + "accessTokenCopiedMessage": "Device access token has been copied to clipboard", + "assignedToCustomer": "Assigned to customer", + "unable-delete-device-alias-title": "Unable to delete device alias", + "unable-delete-device-alias-text": "Device alias '{{deviceAlias}}' can't be deleted as it used by the following widget(s):
{{widgetsList}}", + "is-gateway": "Is gateway", + "public": "Public", + "device-public": "Device is public", + "select-device": "Select device", + "import": "Import device", + "device-file": "Device file" + }, + "dialog": { + "close": "Close dialog" + }, + "direction": { + "column": "Column", + "row": "Row" + }, + "error": { + "unable-to-connect": "Unable to connect to the server! Please check your internet connection.", + "unhandled-error-code": "Unhandled error code: {{errorCode}}", + "unknown-error": "Unknown error" + }, + "entity": { + "entity": "Entity", + "entities": "Entities", + "aliases": "Entity aliases", + "entity-alias": "Entity alias", + "unable-delete-entity-alias-title": "Unable to delete entity alias", + "unable-delete-entity-alias-text": "Entity alias '{{entityAlias}}' can't be deleted as it used by the following widget(s):
{{widgetsList}}", + "duplicate-alias-error": "Duplicate alias found '{{alias}}'.
Entity aliases must be unique whithin the dashboard.", + "missing-entity-filter-error": "Filter is missing for alias '{{alias}}'.", + "configure-alias": "Configure '{{alias}}' alias", + "alias": "Alias", + "alias-required": "Entity alias is required.", + "remove-alias": "Remove entity alias", + "add-alias": "Add entity alias", + "entity-list": "Entity list", + "entity-type": "Entity type", + "entity-types": "Entity types", + "entity-type-list": "Entity type list", + "any-entity": "Any entity", + "enter-entity-type": "Enter entity type", + "no-entities-matching": "No entities matching '{{entity}}' were found.", + "no-entity-types-matching": "No entity types matching '{{entityType}}' were found.", + "name-starts-with": "Name starts with", + "use-entity-name-filter": "Use filter", + "entity-list-empty": "No entities selected.", + "entity-type-list-empty": "No entity types selected.", + "entity-name-filter-required": "Entity name filter is required.", + "entity-name-filter-no-entity-matched": "No entities starting with '{{entity}}' were found.", + "all-subtypes": "All", + "select-entities": "Select entities", + "no-aliases-found": "No aliases found.", + "no-alias-matching": "'{{alias}}' not found.", + "create-new-alias": "Create a new one!", + "key": "Key", + "key-name": "Key name", + "no-keys-found": "No keys found.", + "no-key-matching": "'{{key}}' not found.", + "create-new-key": "Create a new one!", + "type": "Type", + "type-required": "Entity type is required.", + "type-device": "Device", + "type-devices": "Devices", + "list-of-devices": "{ count, plural, 1 {One device} other {List of # devices} }", + "device-name-starts-with": "Devices whose names start with '{{prefix}}'", + "type-asset": "Asset", + "type-assets": "Assets", + "list-of-assets": "{ count, plural, 1 {One asset} other {List of # assets} }", + "asset-name-starts-with": "Assets whose names start with '{{prefix}}'", + "type-entity-view": "Entity View", + "type-entity-views": "Entity Views", + "list-of-entity-views": "{ count, plural, 1 {One entity view} other {List of # entity views} }", + "entity-view-name-starts-with": "Entity Views whose names start with '{{prefix}}'", + "type-rule": "Rule", + "type-rules": "Rules", + "list-of-rules": "{ count, plural, 1 {One rule} other {List of # rules} }", + "rule-name-starts-with": "Rules whose names start with '{{prefix}}'", + "type-plugin": "Plugin", + "type-plugins": "Plugins", + "list-of-plugins": "{ count, plural, 1 {One plugin} other {List of # plugins} }", + "plugin-name-starts-with": "Plugins whose names start with '{{prefix}}'", + "type-tenant": "Tenant", + "type-tenants": "Tenants", + "list-of-tenants": "{ count, plural, 1 {One tenant} other {List of # tenants} }", + "tenant-name-starts-with": "Tenants whose names start with '{{prefix}}'", + "type-customer": "Customer", + "type-customers": "Customers", + "list-of-customers": "{ count, plural, 1 {One customer} other {List of # customers} }", + "customer-name-starts-with": "Customers whose names start with '{{prefix}}'", + "type-user": "User", + "type-users": "Users", + "list-of-users": "{ count, plural, 1 {One user} other {List of # users} }", + "user-name-starts-with": "Users whose names start with '{{prefix}}'", + "type-dashboard": "Dashboard", + "type-dashboards": "Dashboards", + "list-of-dashboards": "{ count, plural, 1 {One dashboard} other {List of # dashboards} }", + "dashboard-name-starts-with": "Dashboards whose names start with '{{prefix}}'", + "type-alarm": "Alarm", + "type-alarms": "Alarms", + "list-of-alarms": "{ count, plural, 1 {One alarms} other {List of # alarms} }", + "alarm-name-starts-with": "Alarms whose names start with '{{prefix}}'", + "type-rulechain": "Rule chain", + "type-rulechains": "Rule chains", + "list-of-rulechains": "{ count, plural, 1 {One rule chain} other {List of # rule chains} }", + "rulechain-name-starts-with": "Rule chains whose names start with '{{prefix}}'", + "type-rulenode": "Rule node", + "type-rulenodes": "Rule nodes", + "list-of-rulenodes": "{ count, plural, 1 {One rule node} other {List of # rule nodes} }", + "rulenode-name-starts-with": "Rule nodes whose names start with '{{prefix}}'", + "type-current-customer": "Current Customer", + "search": "Search entities", + "selected-entities": "{ count, plural, 1 {1 entity} other {# entities} } selected", + "entity-name": "Entity name", + "details": "Entity details", + "no-entities-prompt": "No entities found", + "no-data": "No data to display", + "columns-to-display": "Columns to Display" + }, + "entity-view": { + "entity-view": "Entity View", + "entity-view-required": "Entity view is required.", + "entity-views": "Entity Views", + "management": "Entity View management", + "view-entity-views": "View Entity Views", + "entity-view-alias": "Entity View alias", + "aliases": "Entity View aliases", + "no-alias-matching": "'{{alias}}' not found.", + "no-aliases-found": "No aliases found.", + "no-key-matching": "'{{key}}' not found.", + "no-keys-found": "No keys found.", + "create-new-alias": "Create a new one!", + "create-new-key": "Create a new one!", + "duplicate-alias-error": "Duplicate alias found '{{alias}}'.
Entity View aliases must be unique whithin the dashboard.", + "configure-alias": "Configure '{{alias}}' alias", + "no-entity-views-matching": "No entity views matching '{{entity}}' were found.", + "alias": "Alias", + "alias-required": "Entity View alias is required.", + "remove-alias": "Remove entity view alias", + "add-alias": "Add entity view alias", + "name-starts-with": "Entity View name starts with", + "entity-view-list": "Entity View list", + "use-entity-view-name-filter": "Use filter", + "entity-view-list-empty": "No entity views selected.", + "entity-view-name-filter-required": "Entity view name filter is required.", + "entity-view-name-filter-no-entity-view-matched": "No entity views starting with '{{entityView}}' were found.", + "add": "Add Entity View", + "assign-to-customer": "Assign to customer", + "assign-entity-view-to-customer": "Assign Entity View(s) To Customer", + "assign-entity-view-to-customer-text": "Please select the entity views to assign to the customer", + "no-entity-views-text": "No entity views found", + "assign-to-customer-text": "Please select the customer to assign the entity view(s)", + "entity-view-details": "Entity view details", + "add-entity-view-text": "Add new entity view", + "delete": "Delete entity view", + "assign-entity-views": "Assign entity views", + "assign-entity-views-text": "Assign { count, plural, 1 {1 entityView} other {# entityViews} } to customer", + "delete-entity-views": "Delete entity views", + "unassign-from-customer": "Unassign from customer", + "unassign-entity-views": "Unassign entity views", + "unassign-entity-views-action-title": "Unassign { count, plural, 1 {1 entityView} other {# entityViews} } from customer", + "assign-new-entity-view": "Assign new entity view", + "delete-entity-view-title": "Are you sure you want to delete the entity view '{{entityViewName}}'?", + "delete-entity-view-text": "Be careful, after the confirmation the entity view and all related data will become unrecoverable.", + "delete-entity-views-title": "Are you sure you want to entity view { count, plural, 1 {1 entityView} other {# entityViews} }?", + "delete-entity-views-action-title": "Delete { count, plural, 1 {1 entityView} other {# entityViews} }", + "delete-entity-views-text": "Be careful, after the confirmation all selected entity views will be removed and all related data will become unrecoverable.", + "unassign-entity-view-title": "Are you sure you want to unassign the entity view '{{entityViewName}}'?", + "unassign-entity-view-text": "After the confirmation the entity view will be unassigned and won't be accessible by the customer.", + "unassign-entity-view": "Unassign entity view", + "unassign-entity-views-title": "Are you sure you want to unassign { count, plural, 1 {1 entityView} other {# entityViews} }?", + "unassign-entity-views-text": "After the confirmation all selected entity views will be unassigned and won't be accessible by the customer.", + "entity-view-type": "Entity View type", + "entity-view-type-required": "Entity View type is required.", + "select-entity-view-type": "Select entity view type", + "enter-entity-view-type": "Enter entity view type", + "any-entity-view": "Any entity view", + "no-entity-view-types-matching": "No entity view types matching '{{entitySubtype}}' were found.", + "entity-view-type-list-empty": "No entity view types selected.", + "entity-view-types": "Entity View types", + "name": "Name", + "name-required": "Name is required.", + "description": "Description", + "events": "Events", + "details": "Details", + "copyId": "Copy entity view Id", + "assignedToCustomer": "Assigned to customer", + "unable-entity-view-device-alias-title": "Unable to delete entity view alias", + "unable-entity-view-device-alias-text": "Device alias '{{entityViewAlias}}' can't be deleted as it used by the following widget(s):
{{widgetsList}}", + "select-entity-view": "Select entity view", + "make-public": "Make entity view public", + "make-private": "Make entity view private", + "start-date": "Start date", + "start-ts": "Start time", + "end-date": "End date", + "end-ts": "End time", + "date-limits": "Date limits", + "client-attributes": "Client attributes", + "shared-attributes": "Shared attributes", + "server-attributes": "Server attributes", + "timeseries": "Timeseries", + "client-attributes-placeholder": "Client attributes", + "shared-attributes-placeholder": "Shared attributes", + "server-attributes-placeholder": "Server attributes", + "timeseries-placeholder": "Timeseries", + "target-entity": "Target entity", + "attributes-propagation": "Attributes propagation", + "attributes-propagation-hint": "Entity View will automatically copy specified attributes from Target Entity each time you save or update this entity view. For performance reasons target entity attributes are not propagated to entity view on each attribute change. You can enable automatic propagation by configuring \"copy to view\" rule node in your rule chain and linking \"Post attributes\" and \"Attributes Updated\" messages to the new rule node.", + "timeseries-data": "Timeseries data", + "timeseries-data-hint": "Configure timeseries data keys of the target entity that will be accessible to the entity view. This timeseries data is read-only.", + "make-public-entity-view-title": "Are you sure you want to make the entity view '{{entityViewName}}' public?", + "make-public-entity-view-text": "After the confirmation the entity view and all its data will be made public and accessible by others.", + "make-private-entity-view-title": "Are you sure you want to make the entity view '{{entityViewName}}' private?", + "make-private-entity-view-text": "After the confirmation the entity view and all its data will be made private and won't be accessible by others." + }, + "event": { + "event-type": "Event type", + "type-error": "Error", + "type-lc-event": "Lifecycle event", + "type-stats": "Statistics", + "type-debug-rule-node": "Debug", + "type-debug-rule-chain": "Debug", + "no-events-prompt": "No events found", + "error": "Error", + "alarm": "Alarm", + "event-time": "Event time", + "server": "Server", + "body": "Body", + "method": "Method", + "type": "Type", + "entity": "Entity", + "message-id": "Message Id", + "message-type": "Message Type", + "data-type": "Data Type", + "relation-type": "Relation Type", + "metadata": "Metadata", + "data": "Data", + "event": "Event", + "status": "Status", + "success": "Success", + "failed": "Failed", + "messages-processed": "Messages processed", + "errors-occurred": "Errors occurred" + }, + "extension": { + "extensions": "Extensions", + "selected-extensions": "{ count, plural, 1 {1 extension} other {# extensions} } selected", + "type": "Type", + "key": "Key", + "value": "Value", + "id": "Id", + "extension-id": "Extension id", + "extension-type": "Extension type", + "transformer-json": "JSON *", + "unique-id-required": "Current extension id already exists.", + "delete": "Delete extension", + "add": "Add extension", + "edit": "Edit extension", + "delete-extension-title": "Are you sure you want to delete the extension '{{extensionId}}'?", + "delete-extension-text": "Be careful, after the confirmation the extension and all related data will become unrecoverable.", + "delete-extensions-title": "Are you sure you want to delete { count, plural, 1 {1 extension} other {# extensions} }?", + "delete-extensions-text": "Be careful, after the confirmation all selected extensions will be removed.", + "converters": "Converters", + "converter-id": "Converter id", + "configuration": "Configuration", + "converter-configurations": "Converter configurations", + "token": "Security token", + "add-converter": "Add converter", + "add-config": "Add converter configuration", + "device-name-expression": "Device name expression", + "device-type-expression": "Device type expression", + "custom": "Custom", + "to-double": "To Double", + "transformer": "Transformer", + "json-required": "Transformer json is required.", + "json-parse": "Unable to parse transformer json.", + "attributes": "Attributes", + "add-attribute": "Add attribute", + "add-map": "Add mapping element", + "timeseries": "Timeseries", + "add-timeseries": "Add timeseries", + "field-required": "Field is required", + "brokers": "Brokers", + "add-broker": "Add broker", + "host": "Host", + "port": "Port", + "port-range": "Port should be in a range from 1 to 65535.", + "ssl": "Ssl", + "credentials": "Credentials", + "username": "Username", + "password": "Password", + "retry-interval": "Retry interval in milliseconds", + "anonymous": "Anonymous", + "basic": "Basic", + "pem": "PEM", + "ca-cert": "CA certificate file *", + "private-key": "Private key file *", + "cert": "Certificate file *", + "no-file": "No file selected.", + "drop-file": "Drop a file or click to select a file to upload.", + "mapping": "Mapping", + "topic-filter": "Topic filter", + "converter-type": "Converter type", + "converter-json": "Json", + "json-name-expression": "Device name json expression", + "topic-name-expression": "Device name topic expression", + "json-type-expression": "Device type json expression", + "topic-type-expression": "Device type topic expression", + "attribute-key-expression": "Attribute key expression", + "attr-json-key-expression": "Attribute key json expression", + "attr-topic-key-expression": "Attribute key topic expression", + "request-id-expression": "Request id expression", + "request-id-json-expression": "Request id json expression", + "request-id-topic-expression": "Request id topic expression", + "response-topic-expression": "Response topic expression", + "value-expression": "Value expression", + "topic": "Topic", + "timeout": "Timeout in milliseconds", + "converter-json-required": "Converter json is required.", + "converter-json-parse": "Unable to parse converter json.", + "filter-expression": "Filter expression", + "connect-requests": "Connect requests", + "add-connect-request": "Add connect request", + "disconnect-requests": "Disconnect requests", + "add-disconnect-request": "Add disconnect request", + "attribute-requests": "Attribute requests", + "add-attribute-request": "Add attribute request", + "attribute-updates": "Attribute updates", + "add-attribute-update": "Add attribute update", + "server-side-rpc": "Server side RPC", + "add-server-side-rpc-request": "Add server-side RPC request", + "device-name-filter": "Device name filter", + "attribute-filter": "Attribute filter", + "method-filter": "Method filter", + "request-topic-expression": "Request topic expression", + "response-timeout": "Response timeout in milliseconds", + "topic-expression": "Topic expression", + "client-scope": "Client scope", + "add-device": "Add device", + "opc-server": "Servers", + "opc-add-server": "Add server", + "opc-add-server-prompt": "Please add server", + "opc-application-name": "Application name", + "opc-application-uri": "Application uri", + "opc-scan-period-in-seconds": "Scan period in seconds", + "opc-security": "Security", + "opc-identity": "Identity", + "opc-keystore": "Keystore", + "opc-type": "Type", + "opc-keystore-type": "Type", + "opc-keystore-location": "Location *", + "opc-keystore-password": "Password", + "opc-keystore-alias": "Alias", + "opc-keystore-key-password": "Key password", + "opc-device-node-pattern": "Device node pattern", + "opc-device-name-pattern": "Device name pattern", + "modbus-server": "Servers/slaves", + "modbus-add-server": "Add server/slave", + "modbus-add-server-prompt": "Please add server/slave", + "modbus-transport": "Transport", + "modbus-tcp-reconnect": "Automatically reconnect", + "modbus-rtu-over-tcp": "RTU over TCP", + "modbus-port-name": "Serial port name", + "modbus-encoding": "Encoding", + "modbus-parity": "Parity", + "modbus-baudrate": "Baud rate", + "modbus-databits": "Data bits", + "modbus-stopbits": "Stop bits", + "modbus-databits-range": "Data bits should be in a range from 7 to 8.", + "modbus-stopbits-range": "Stop bits should be in a range from 1 to 2.", + "modbus-unit-id": "Unit ID", + "modbus-unit-id-range": "Unit ID should be in a range from 1 to 247.", + "modbus-device-name": "Device name", + "modbus-poll-period": "Poll period (ms)", + "modbus-attributes-poll-period": "Attributes poll period (ms)", + "modbus-timeseries-poll-period": "Timeseries poll period (ms)", + "modbus-poll-period-range": "Poll period should be positive value.", + "modbus-tag": "Tag", + "modbus-function": "Function", + "modbus-register-address": "Register address", + "modbus-register-address-range": "Register address should be in a range from 0 to 65535.", + "modbus-register-bit-index": "Bit index", + "modbus-register-bit-index-range": "Bit index should be in a range from 0 to 15.", + "modbus-register-count": "Register count", + "modbus-register-count-range": "Register count should be a positive value.", + "modbus-byte-order": "Byte order", + "sync": { + "status": "Status", + "sync": "Sync", + "not-sync": "Not sync", + "last-sync-time": "Last sync time", + "not-available": "Not available" + }, + "export-extensions-configuration": "Export extensions configuration", + "import-extensions-configuration": "Import extensions configuration", + "import-extensions": "Import extensions", + "import-extension": "Import extension", + "export-extension": "Export extension", + "file": "Extensions file", + "invalid-file-error": "Invalid extension file" + }, + "fullscreen": { + "expand": "Expand to fullscreen", + "exit": "Exit fullscreen", + "toggle": "Toggle fullscreen mode", + "fullscreen": "Fullscreen" + }, + "function": { + "function": "Function" + }, + "grid": { + "delete-item-title": "Are you sure you want to delete this item?", + "delete-item-text": "Be careful, after the confirmation this item and all related data will become unrecoverable.", + "delete-items-title": "Are you sure you want to delete { count, plural, 1 {1 item} other {# items} }?", + "delete-items-action-title": "Delete { count, plural, 1 {1 item} other {# items} }", + "delete-items-text": "Be careful, after the confirmation all selected items will be removed and all related data will become unrecoverable.", + "add-item-text": "Add new item", + "no-items-text": "No items found", + "item-details": "Item details", + "delete-item": "Delete Item", + "delete-items": "Delete Items", + "scroll-to-top": "Scroll to top" + }, + "help": { + "goto-help-page": "Go to help page" + }, + "home": { + "home": "Home", + "profile": "Profile", + "logout": "Logout", + "menu": "Menu", + "avatar": "Avatar", + "open-user-menu": "Open user menu" + }, + "import": { + "no-file": "No file selected", + "drop-file": "Drop a JSON file or click to select a file to upload.", + "drop-file-csv": "Drop a CSV file or click to select a file to upload.", + "column-value": "Value", + "column-title": "Title", + "column-example": "Example value data", + "column-key": "Attribute/telemetry key", + "csv-delimiter": "CSV delimiter", + "csv-first-line-header": "First line contains column names", + "csv-update-data": "Update attributes/telemetry", + "import-csv-number-columns-error": "A file should contain at least two columns", + "import-csv-invalid-format-error": "Invalid file format. Line: '{{line}}'", + "column-type": { + "name": "Name", + "type": "Type", + "column-type": "Column type", + "client-attribute": "Client attribute", + "shared-attribute": "Shared attribute", + "server-attribute": "Server attribute", + "timeseries": "Timeseries", + "entity-field": "Entity field", + "access-token": "Access token" + }, + "stepper-text":{ + "select-file": "Select a file", + "configuration": "Import configuration", + "column-type": "Select columns type", + "creat-entities": "Creating new entities", + "done": "Done" + }, + "message": { + "create-entities": "{{count}} new entities were successfully created.", + "update-entities": "{{count}} entities were successfully updated.", + "error-entities": "There was an error creating {{count}} entities." + } + }, + "item": { + "selected": "Selected" + }, + "js-func": { + "no-return-error": "Function must return value!", + "return-type-mismatch": "Function must return value of '{{type}}' type!", + "tidy": "Tidy" + }, + "key-val": { + "key": "Key", + "value": "Value", + "remove-entry": "Remove entry", + "add-entry": "Add entry", + "no-data": "No entries" + }, + "layout": { + "layout": "Layout", + "manage": "Manage layouts", + "settings": "Layout settings", + "color": "Color", + "main": "Main", + "right": "Right", + "select": "Select target layout" + }, + "legend": { + "direction": "Legend direction", + "position": "Legend position", + "show-max": "Show max value", + "show-min": "Show min value", + "show-avg": "Show average value", + "show-total": "Show total value", + "settings": "Legend settings", + "min": "min", + "max": "max", + "avg": "avg", + "total": "total" + }, + "login": { + "login": "Login", + "request-password-reset": "Request Password Reset", + "reset-password": "Reset Password", + "create-password": "Create Password", + "passwords-mismatch-error": "Entered passwords must be same!", + "password-again": "Password again", + "sign-in": "Please sign in", + "username": "Username (email)", + "remember-me": "Remember me", + "forgot-password": "Forgot Password?", + "password-reset": "Password reset", + "expired-password-reset-message": "Your credentials has been expired! Please create new password.", + "new-password": "New password", + "new-password-again": "New password again", + "password-link-sent-message": "Password reset link was successfully sent!", + "email": "Email" + }, + "position": { + "top": "Top", + "bottom": "Bottom", + "left": "Left", + "right": "Right" + }, + "profile": { + "profile": "Profile", + "change-password": "Change Password", + "current-password": "Current password" + }, + "relation": { + "relations": "Relations", + "direction": "Direction", + "search-direction": { + "FROM": "From", + "TO": "To" + }, + "direction-type": { + "FROM": "from", + "TO": "to" + }, + "from-relations": "Outbound relations", + "to-relations": "Inbound relations", + "selected-relations": "{ count, plural, 1 {1 relation} other {# relations} } selected", + "type": "Type", + "to-entity-type": "To entity type", + "to-entity-name": "To entity name", + "from-entity-type": "From entity type", + "from-entity-name": "From entity name", + "to-entity": "To entity", + "from-entity": "From entity", + "delete": "Delete relation", + "relation-type": "Relation type", + "relation-type-required": "Relation type is required.", + "any-relation-type": "Any type", + "add": "Add relation", + "edit": "Edit relation", + "delete-to-relation-title": "Are you sure you want to delete relation to the entity '{{entityName}}'?", + "delete-to-relation-text": "Be careful, after the confirmation the entity '{{entityName}}' will be unrelated from the current entity.", + "delete-to-relations-title": "Are you sure you want to delete { count, plural, 1 {1 relation} other {# relations} }?", + "delete-to-relations-text": "Be careful, after the confirmation all selected relations will be removed and corresponding entities will be unrelated from the current entity.", + "delete-from-relation-title": "Are you sure you want to delete relation from the entity '{{entityName}}'?", + "delete-from-relation-text": "Be careful, after the confirmation current entity will be unrelated from the entity '{{entityName}}'.", + "delete-from-relations-title": "Are you sure you want to delete { count, plural, 1 {1 relation} other {# relations} }?", + "delete-from-relations-text": "Be careful, after the confirmation all selected relations will be removed and current entity will be unrelated from the corresponding entities.", + "remove-relation-filter": "Remove relation filter", + "add-relation-filter": "Add relation filter", + "any-relation": "Any relation", + "relation-filters": "Relation filters", + "additional-info": "Additional info (JSON)", + "invalid-additional-info": "Unable to parse additional info json." + }, + "rulechain": { + "rulechain": "Rule chain", + "rulechains": "Rule chains", + "root": "Root", + "delete": "Delete rule chain", + "name": "Name", + "name-required": "Name is required.", + "description": "Description", + "add": "Add Rule Chain", + "set-root": "Make rule chain root", + "set-root-rulechain-title": "Are you sure you want to make the rule chain '{{ruleChainName}}' root?", + "set-root-rulechain-text": "After the confirmation the rule chain will become root and will handle all incoming transport messages.", + "delete-rulechain-title": "Are you sure you want to delete the rule chain '{{ruleChainName}}'?", + "delete-rulechain-text": "Be careful, after the confirmation the rule chain and all related data will become unrecoverable.", + "delete-rulechains-title": "Are you sure you want to delete { count, plural, 1 {1 rule chain} other {# rule chains} }?", + "delete-rulechains-action-title": "Delete { count, plural, 1 {1 rule chain} other {# rule chains} }", + "delete-rulechains-text": "Be careful, after the confirmation all selected rule chains will be removed and all related data will become unrecoverable.", + "add-rulechain-text": "Add new rule chain", + "no-rulechains-text": "No rule chains found", + "rulechain-details": "Rule chain details", + "details": "Details", + "events": "Events", + "system": "System", + "import": "Import rule chain", + "export": "Export rule chain", + "export-failed-error": "Unable to export rule chain: {{error}}", + "create-new-rulechain": "Create new rule chain", + "rulechain-file": "Rule chain file", + "invalid-rulechain-file-error": "Unable to import rule chain: Invalid rule chain data structure.", + "copyId": "Copy rule chain Id", + "idCopiedMessage": "Rule chain Id has been copied to clipboard", + "select-rulechain": "Select rule chain", + "no-rulechains-matching": "No rule chains matching '{{entity}}' were found.", + "rulechain-required": "Rule chain is required", + "management": "Rules management", + "debug-mode": "Debug mode" + }, + "rulenode": { + "details": "Details", + "events": "Events", + "search": "Search nodes", + "open-node-library": "Open node library", + "add": "Add rule node", + "name": "Name", + "name-required": "Name is required.", + "type": "Type", + "description": "Description", + "delete": "Delete rule node", + "select-all-objects": "Select all nodes and connections", + "deselect-all-objects": "Deselect all nodes and connections", + "delete-selected-objects": "Delete selected nodes and connections", + "delete-selected": "Delete selected", + "select-all": "Select all", + "copy-selected": "Copy selected", + "deselect-all": "Deselect all", + "rulenode-details": "Rule node details", + "debug-mode": "Debug mode", + "configuration": "Configuration", + "link": "Link", + "link-details": "Rule node link details", + "add-link": "Add link", + "link-label": "Link label", + "link-label-required": "Link label is required.", + "custom-link-label": "Custom link label", + "custom-link-label-required": "Custom link label is required.", + "link-labels": "Link labels", + "link-labels-required": "Link labels is required.", + "no-link-labels-found": "No link labels found", + "no-link-label-matching": "'{{label}}' not found.", + "create-new-link-label": "Create a new one!", + "type-filter": "Filter", + "type-filter-details": "Filter incoming messages with configured conditions", + "type-enrichment": "Enrichment", + "type-enrichment-details": "Add additional information into Message Metadata", + "type-transformation": "Transformation", + "type-transformation-details": "Change Message payload and Metadata", + "type-action": "Action", + "type-action-details": "Perform special action", + "type-external": "External", + "type-external-details": "Interacts with external system", + "type-rule-chain": "Rule Chain", + "type-rule-chain-details": "Forwards incoming messages to specified Rule Chain", + "type-input": "Input", + "type-input-details": "Logical input of Rule Chain, forwards incoming messages to next related Rule Node", + "type-unknown": "Unknown", + "type-unknown-details": "Unresolved Rule Node", + "directive-is-not-loaded": "Defined configuration directive '{{directiveName}}' is not available.", + "ui-resources-load-error": "Failed to load configuration ui resources.", + "invalid-target-rulechain": "Unable to resolve target rule chain!", + "test-script-function": "Test script function", + "message": "Message", + "message-type": "Message type", + "select-message-type": "Select message type", + "message-type-required": "Message type is required", + "metadata": "Metadata", + "metadata-required": "Metadata entries can't be empty.", + "output": "Output", + "test": "Test", + "help": "Help", + "reset-debug-mode": "Reset debug mode in all nodes" + }, + "tenant": { + "tenant": "Tenant", + "tenants": "Tenants", + "management": "Tenant management", + "add": "Add Tenant", + "admins": "Admins", + "manage-tenant-admins": "Manage tenant admins", + "delete": "Delete tenant", + "add-tenant-text": "Add new tenant", + "no-tenants-text": "No tenants found", + "tenant-details": "Tenant details", + "delete-tenant-title": "Are you sure you want to delete the tenant '{{tenantTitle}}'?", + "delete-tenant-text": "Be careful, after the confirmation the tenant and all related data will become unrecoverable.", + "delete-tenants-title": "Are you sure you want to delete { count, plural, 1 {1 tenant} other {# tenants} }?", + "delete-tenants-action-title": "Delete { count, plural, 1 {1 tenant} other {# tenants} }", + "delete-tenants-text": "Be careful, after the confirmation all selected tenants will be removed and all related data will become unrecoverable.", + "title": "Title", + "title-required": "Title is required.", + "description": "Description", + "details": "Details", + "events": "Events", + "copyId": "Copy tenant Id", + "idCopiedMessage": "Tenant Id has been copied to clipboard", + "select-tenant": "Select tenant", + "no-tenants-matching": "No tenants matching '{{entity}}' were found.", + "tenant-required": "Tenant is required" + }, + "timeinterval": { + "seconds-interval": "{ seconds, plural, 1 {1 second} other {# seconds} }", + "minutes-interval": "{ minutes, plural, 1 {1 minute} other {# minutes} }", + "hours-interval": "{ hours, plural, 1 {1 hour} other {# hours} }", + "days-interval": "{ days, plural, 1 {1 day} other {# days} }", + "days": "Days", + "hours": "Hours", + "minutes": "Minutes", + "seconds": "Seconds", + "advanced": "Advanced" + }, + "timewindow": { + "days": "{ days, plural, 1 { day } other {# days } }", + "hours": "{ hours, plural, 0 { hour } 1 {1 hour } other {# hours } }", + "minutes": "{ minutes, plural, 0 { minute } 1 {1 minute } other {# minutes } }", + "seconds": "{ seconds, plural, 0 { second } 1 {1 second } other {# seconds } }", + "realtime": "Realtime", + "history": "History", + "last-prefix": "last", + "period": "from {{ startTime }} to {{ endTime }}", + "edit": "Edit timewindow", + "date-range": "Date range", + "last": "Last", + "time-period": "Time period" + }, + "user": { + "user": "User", + "users": "Users", + "customer-users": "Customer Users", + "tenant-admins": "Tenant Admins", + "sys-admin": "System administrator", + "tenant-admin": "Tenant administrator", + "customer": "Customer", + "anonymous": "Anonymous", + "add": "Add User", + "delete": "Delete user", + "add-user-text": "Add new user", + "no-users-text": "No users found", + "user-details": "User details", + "delete-user-title": "Are you sure you want to delete the user '{{userEmail}}'?", + "delete-user-text": "Be careful, after the confirmation the user and all related data will become unrecoverable.", + "delete-users-title": "Are you sure you want to delete { count, plural, 1 {1 user} other {# users} }?", + "delete-users-action-title": "Delete { count, plural, 1 {1 user} other {# users} }", + "delete-users-text": "Be careful, after the confirmation all selected users will be removed and all related data will become unrecoverable.", + "activation-email-sent-message": "Activation email was successfully sent!", + "resend-activation": "Resend activation", + "email": "Email", + "email-required": "Email is required.", + "invalid-email-format": "Invalid email format.", + "first-name": "First Name", + "last-name": "Last Name", + "description": "Description", + "default-dashboard": "Default dashboard", + "always-fullscreen": "Always fullscreen", + "select-user": "Select user", + "no-users-matching": "No users matching '{{entity}}' were found.", + "user-required": "User is required", + "activation-method": "Activation method", + "display-activation-link": "Display activation link", + "send-activation-mail": "Send activation mail", + "activation-link": "User activation link", + "activation-link-text": "In order to activate user use the following activation link :", + "copy-activation-link": "Copy activation link", + "activation-link-copied-message": "User activation link has been copied to clipboard", + "details": "Details", + "login-as-tenant-admin": "Login as Tenant Admin", + "login-as-customer-user": "Login as Customer User" + }, + "value": { + "type": "Value type", + "string": "String", + "string-value": "String value", + "integer": "Integer", + "integer-value": "Integer value", + "invalid-integer-value": "Invalid integer value", + "double": "Double", + "double-value": "Double value", + "boolean": "Boolean", + "boolean-value": "Boolean value", + "false": "False", + "true": "True", + "long": "Long" + }, + "widget": { + "widget-library": "Widgets Library", + "widget-bundle": "Widgets Bundle", + "select-widgets-bundle": "Select widgets bundle", + "management": "Widget management", + "editor": "Widget Editor", + "widget-type-not-found": "Problem loading widget configuration.
Probably associated\n widget type was removed.", + "widget-type-load-error": "Widget wasn't loaded due to the following errors:", + "remove": "Remove widget", + "edit": "Edit widget", + "remove-widget-title": "Are you sure you want to remove the widget '{{widgetTitle}}'?", + "remove-widget-text": "After the confirmation the widget and all related data will become unrecoverable.", + "timeseries": "Time series", + "search-data": "Search data", + "no-data-found": "No data found", + "latest-values": "Latest values", + "rpc": "Control widget", + "alarm": "Alarm widget", + "static": "Static widget", + "select-widget-type": "Select widget type", + "missing-widget-title-error": "Widget title must be specified!", + "widget-saved": "Widget saved", + "unable-to-save-widget-error": "Unable to save widget! Widget has errors!", + "save": "Save widget", + "saveAs": "Save widget as", + "save-widget-type-as": "Save widget type as", + "save-widget-type-as-text": "Please enter new widget title and/or select target widgets bundle", + "toggle-fullscreen": "Toggle fullscreen", + "run": "Run widget", + "title": "Widget title", + "title-required": "Widget title is required.", + "type": "Widget type", + "resources": "Resources", + "resource-url": "JavaScript/CSS URL", + "remove-resource": "Remove resource", + "add-resource": "Add resource", + "html": "HTML", + "tidy": "Tidy", + "css": "CSS", + "settings-schema": "Settings schema", + "datakey-settings-schema": "Data key settings schema", + "javascript": "Javascript", + "js": "JS", + "remove-widget-type-title": "Are you sure you want to remove the widget type '{{widgetName}}'?", + "remove-widget-type-text": "After the confirmation the widget type and all related data will become unrecoverable.", + "remove-widget-type": "Remove widget type", + "add-widget-type": "Add new widget type", + "widget-type-load-failed-error": "Failed to load widget type!", + "widget-template-load-failed-error": "Failed to load widget template!", + "add": "Add Widget", + "undo": "Undo widget changes", + "export": "Export widget" + }, + "widget-action": { + "header-button": "Widget header button", + "open-dashboard-state": "Navigate to new dashboard state", + "update-dashboard-state": "Update current dashboard state", + "open-dashboard": "Navigate to other dashboard", + "custom": "Custom action", + "custom-pretty": "Custom action (with HTML template)", + "target-dashboard-state": "Target dashboard state", + "target-dashboard-state-required": "Target dashboard state is required", + "set-entity-from-widget": "Set entity from widget", + "target-dashboard": "Target dashboard", + "open-right-layout": "Open right dashboard layout (mobile view)" + }, + "widgets-bundle": { + "current": "Current bundle", + "widgets-bundles": "Widgets Bundles", + "add": "Add Widgets Bundle", + "delete": "Delete widgets bundle", + "title": "Title", + "title-required": "Title is required.", + "add-widgets-bundle-text": "Add new widgets bundle", + "no-widgets-bundles-text": "No widgets bundles found", + "empty": "Widgets bundle is empty", + "details": "Details", + "widgets-bundle-details": "Widgets bundle details", + "delete-widgets-bundle-title": "Are you sure you want to delete the widgets bundle '{{widgetsBundleTitle}}'?", + "delete-widgets-bundle-text": "Be careful, after the confirmation the widgets bundle and all related data will become unrecoverable.", + "delete-widgets-bundles-title": "Are you sure you want to delete { count, plural, 1 {1 widgets bundle} other {# widgets bundles} }?", + "delete-widgets-bundles-action-title": "Delete { count, plural, 1 {1 widgets bundle} other {# widgets bundles} }", + "delete-widgets-bundles-text": "Be careful, after the confirmation all selected widgets bundles will be removed and all related data will become unrecoverable.", + "no-widgets-bundles-matching": "No widgets bundles matching '{{widgetsBundle}}' were found.", + "widgets-bundle-required": "Widgets bundle is required.", + "system": "System", + "import": "Import widgets bundle", + "export": "Export widgets bundle", + "export-failed-error": "Unable to export widgets bundle: {{error}}", + "create-new-widgets-bundle": "Create new widgets bundle", + "widgets-bundle-file": "Widgets bundle file", + "invalid-widgets-bundle-file-error": "Unable to import widgets bundle: Invalid widgets bundle data structure." + }, + "widget-config": { + "data": "Data", + "settings": "Settings", + "advanced": "Advanced", + "title": "Title", + "general-settings": "General settings", + "display-title": "Display title", + "drop-shadow": "Drop shadow", + "enable-fullscreen": "Enable fullscreen", + "background-color": "Background color", + "text-color": "Text color", + "padding": "Padding", + "margin": "Margin", + "widget-style": "Widget style", + "title-style": "Title style", + "mobile-mode-settings": "Mobile mode settings", + "order": "Order", + "height": "Height", + "units": "Special symbol to show next to value", + "decimals": "Number of digits after floating point", + "timewindow": "Timewindow", + "use-dashboard-timewindow": "Use dashboard timewindow", + "display-timewindow": "Display timewindow", + "display-legend": "Display legend", + "datasources": "Datasources", + "maximum-datasources": "Maximum { count, plural, 1 {1 datasource is allowed.} other {# datasources are allowed} }", + "datasource-type": "Type", + "datasource-parameters": "Parameters", + "remove-datasource": "Remove datasource", + "add-datasource": "Add datasource", + "target-device": "Target device", + "alarm-source": "Alarm source", + "actions": "Actions", + "action": "Action", + "add-action": "Add action", + "search-actions": "Search actions", + "action-source": "Action source", + "action-source-required": "Action source is required.", + "action-name": "Name", + "action-name-required": "Action name is required.", + "action-name-not-unique": "Another action with the same name already exists.
Action name should be unique within the same action source.", + "action-icon": "Icon", + "action-type": "Type", + "action-type-required": "Action type is required.", + "edit-action": "Edit action", + "delete-action": "Delete action", + "delete-action-title": "Delete widget action", + "delete-action-text": "Are you sure you want delete widget action with name '{{actionName}}'?", + "display-icon": "Display title icon", + "icon-color": "Icon color", + "icon-size": "Icon size" + }, + "widget-type": { + "import": "Import widget type", + "export": "Export widget type", + "export-failed-error": "Unable to export widget type: {{error}}", + "create-new-widget-type": "Create new widget type", + "widget-type-file": "Widget type file", + "invalid-widget-type-file-error": "Unable to import widget type: Invalid widget type data structure." + }, + "widgets": { + "date-range-navigator": { + "localizationMap": { + "Sun": "Sun", + "Mon": "Mon", + "Tue": "Tue", + "Wed": "Wed", + "Thu": "Thu", + "Fri": "Fri", + "Sat": "Sat", + "Jan": "Jan", + "Feb": "Feb", + "Mar": "Mar", + "Apr": "Apr", + "May": "May", + "Jun": "Jun", + "Jul": "Jul", + "Aug": "Aug", + "Sep": "Sep", + "Oct": "Oct", + "Nov": "Nov", + "Dec": "Dec", + "January": "January", + "February": "February", + "March": "March", + "April": "April", + "June": "June", + "July": "July", + "August": "August", + "September": "September", + "October": "October", + "November": "November", + "December": "December", + "Custom Date Range": "Custom Date Range", + "Date Range Template": "Date Range Template", + "Today": "Today", + "Yesterday": "Yesterday", + "This Week": "This Week", + "Last Week": "Last Week", + "This Month": "This Month", + "Last Month": "Last Month", + "Year": "Year", + "This Year": "This Year", + "Last Year": "Last Year", + "Date picker": "Date picker", + "Hour": "Hour", + "Day": "Day", + "Week": "Week", + "2 weeks": "2 Weeks", + "Month": "Month", + "3 months": "3 Months", + "6 months": "6 Months", + "Custom interval": "Custom interval", + "Interval": "Interval", + "Step size": "Step size", + "Ok": "Ok" + } + } + }, + "icon": { + "icon": "Icon", + "select-icon": "Select icon", + "material-icons": "Material icons", + "show-all": "Show all icons" + }, + "custom": { + "widget-action": { + "action-cell-button": "Action cell button", + "row-click": "On row click", + "polygon-click": "On polygon click", + "marker-click": "On marker click", + "tooltip-tag-action": "Tooltip tag action", + "node-selected": "On node selected", + "element-click": "On HTML element click" + } + }, + "language": { + "language": "Language", + "locales": { + "de_DE": "German", + "fr_FR": "French", + "zh_CN": "Chinese", + "en_US": "English", + "it_IT": "Italian", + "ko_KR": "Korean", + "ru_RU": "Russian", + "es_ES": "Spanish", + "ja_JA": "Japanese", + "tr_TR": "Turkish", + "fa_IR": "Persian", + "uk_UA": "Ukrainian", + "cs_CZ": "Czech" + } + } +} diff --git a/ui-ngx/src/assets/locale/locale.constant-es_ES.json b/ui-ngx/src/assets/locale/locale.constant-es_ES.json new file mode 100644 index 0000000000..4fd2be2efb --- /dev/null +++ b/ui-ngx/src/assets/locale/locale.constant-es_ES.json @@ -0,0 +1,1720 @@ +{ + "access": { + "unauthorized": "No autorizado", + "unauthorized-access": "Acceso no autorizado", + "unauthorized-access-text": "¡Debe registrarse para tener acceso a este recurso!", + "access-forbidden": "Acceso Prohibido", + "access-forbidden-text": "No tiene permisos para acceder a esta ubicación!
Intente registrarse con otro usuario si aún desea acceder a esta ubicación.", + "refresh-token-expired": "La sesión ha expirado", + "refresh-token-failed": "No se puede actualizar la sesión" + }, + "action": { + "activate": "Activar", + "suspend": "Suspender", + "save": "Guardar", + "saveAs": "Guardar como", + "cancel": "Cancelar", + "ok": "Aceptar", + "delete": "Eliminar", + "add": "Agregar", + "yes": "Si", + "no": "No", + "update": "Actualizar", + "remove": "Eliminar", + "search": "Buscar", + "clear-search": "Borrar búsqueda", + "assign": "Asignar", + "unassign": "Anular asignación", + "share": "Compartir", + "make-private": "Hacer privado", + "apply": "Aplicar", + "apply-changes": "Aplicar cambios", + "edit-mode": "Modo edición", + "enter-edit-mode": "Entrar en modo edición", + "decline-changes": "Descartar cambios", + "close": "Cerrar", + "back": "Atrás", + "run": "Ejecutar", + "sign-in": "¡Registrarse!", + "edit": "Editar", + "view": "Ver", + "create": "Crear", + "drag": "Arrastrar", + "refresh": "Actualizar", + "undo": "Deshacer", + "copy": "Copiar", + "paste": "Pegar", + "copy-reference": "Copiar referencia", + "paste-reference": "Pegar referencia", + "import": "Importar", + "export": "Exportar", + "share-via": "Compartir vía {{provider}}", + "continue": "Continuar" + }, + "aggregation": { + "aggregation": "Agregación", + "function": "Función de agregación de datos", + "limit": "Valores máximos", + "group-interval": "Intervalo de agrupamiento", + "min": "Min", + "max": "Max", + "avg": "Promedio", + "sum": "Suma", + "count": "Contar", + "none": "Ninguno" + }, + "admin": { + "general": "General", + "general-settings": "Configuración general", + "outgoing-mail": "Servidor de correo", + "outgoing-mail-settings": "Configuración del servidor de correo de salida", + "system-settings": "Configuración del sistema", + "test-mail-sent": "¡El correo de prueba fue enviado correctamente!", + "base-url": "URL base", + "base-url-required": "URL base es requerida.", + "mail-from": "Correo desde", + "mail-from-required": "Correo desde es requerido.", + "smtp-protocol": "Protocolo SMTP", + "smtp-host": "Host SMTP", + "smtp-host-required": "Host SMTP es requerido.", + "smtp-port": "Puerto SMTP", + "smtp-port-required": "Debe suministrar un puerto SMTP", + "smtp-port-invalid": "Eso no parece un puerto SMTP válido.", + "timeout-msec": "Tiempo de espera (ms)", + "timeout-required": "Tiempo de espera es requerido.", + "timeout-invalid": "Eso no parece un tiempo de espera válido.", + "enable-tls": "Habilitar TLS", + "send-test-mail": "Enviar correo de prueba", + "password-policy": "Política de contraseñas", + "security-settings": "Configuraciones de seguridad", + "minimum-password-length": "Longitud mínima de contraseña", + "minimum-password-length-required": "Se requiere una longitud mínima de contraseña", + "minimum-password-length-range": "La longitud mínima de la contraseña debe estar en un rango de 5 a 50", + "minimum-uppercase-letters": "Número mínimo de letras mayúsculas", + "minimum-uppercase-letters-range": "El número mínimo de letras mayúsculas no puede ser negativo", + "minimum-lowercase-letters": "Número mínimo de letras minúsculas", + "minimum-lowercase-letters-range": "El número mínimo de letras minúsculas no puede ser negativo", + "minimum-digits": "Número mínimo de dígitos", + "minimum-digits-range": "El número mínimo de dígitos no puede ser negativo", + "minimum-special-characters": "Número mínimo de caracteres especiales.", + "minimum-special-characters-range": "El número mínimo de caracteres especiales no puede ser negativo.", + "password-expiration-period-days": "Periodo de caducidad de contraseña en días", + "password-expiration-period-days-range": "El período de caducidad de la contraseña en días no puede ser negativo" + }, + "alarm": { + "alarm": "Alarma", + "alarms": "Alarmas", + "select-alarm": "Seleccionar alarma", + "no-alarms-matching": "Alarmas que coincidan con '{{entity}}' no fueron encontradas.", + "alarm-required": "Alarma es requerida", + "alarm-status": "Estado de la alarma", + "search-status": { + "ANY": "Todas", + "ACTIVE": "Activas", + "CLEARED": "Borradas", + "ACK": "Reconocidas", + "UNACK": "Ignoradas" + }, + "display-status": { + "ACTIVE_UNACK": "Activa ignorada", + "ACTIVE_ACK": "Activa reconocida", + "CLEARED_UNACK": "Borrada ignorada", + "CLEARED_ACK": "Borrada reconocida" + }, + "no-alarms-prompt": "No se encontraron alarmas", + "created-time": "Tiempo de creación", + "type": "Tipo", + "severity": "Criticidad", + "originator": "Origen", + "originator-type": "Tipo de origen", + "details": "Detalles", + "status": "Estado", + "alarm-details": "Detalles de la alarma", + "start-time": "Tiempo de inicio", + "end-time": "Tiempo de finalización", + "ack-time": "Tiempo de reconocimiento", + "clear-time": "Tiempo de borrado", + "severity-critical": "Crítica", + "severity-major": "Alta", + "severity-minor": "Baja", + "severity-warning": "Alerta", + "severity-indeterminate": "Indeterminada", + "acknowledge": "Reconocer", + "clear": "Borrar", + "search": "buscar alarmas", + "selected-alarms": "{ count, plural, 1 {1 alarma} other {# alarmas} } seleccionadas", + "no-data": "No hay datos para mostrar", + "polling-interval": "Intervalo de sondeo de alarmas (seg)", + "polling-interval-required": "Intervalo de sondeo de alarmas es requerido.", + "min-polling-interval-message": "Se permite al menos 1 segundo de intervalo de sondeo.", + "aknowledge-alarms-title": "Reconocer { count, plural, 1 {1 alarma} other {# alarmas} }", + "aknowledge-alarms-text": "¿Está seguro de que desea reconocer { count, plural, 1 {1 alarma} other {# alarmas} }?", + "aknowledge-alarm-title": "Reconocer alarma", + "aknowledge-alarm-text": "¿Está seguro que quiere reconocer la alarma?", + "clear-alarms-title": "Quitar { count, plural, 1 {1 alarma} other {# alarmas} }", + "clear-alarms-text": "¿Está seguro de que desea quitar { count, plural, 1 {1 alarma} other {# alarmas}?", + "clear-alarm-title": "Quitar alarma", + "clear-alarm-text": "¿Está seguro que quiere quitar la alarma?", + "alarm-status-filter": "Filtro de estado de alarma" + }, + "alias": { + "add": "Agregar alias", + "edit": "Editar alias", + "name": "Nombre de alias", + "name-required": "Nombre de alias es requerido", + "duplicate-alias": "Ya existe un alias con el mismo nombre.", + "filter-type-single-entity": "Entidad única", + "filter-type-entity-list": "Lista de entidades", + "filter-type-entity-name": "Nombre de entidad", + "filter-type-state-entity": "Entidad del panel de estados", + "filter-type-state-entity-description": "Entidad tomada desde los parámetros del panel de estados", + "filter-type-asset-type": "Tipo de activo", + "filter-type-asset-type-description": "Activos de tipo '{{assetType}}'", + "filter-type-asset-type-and-name-description": "Activos del tipo '{{assetType}}' y su nombre empieza con '{{prefix}}'", + "filter-type-device-type": "Tipo de dispositivo", + "filter-type-device-type-description": "Dispositivos de tipo '{{deviceType}}'", + "filter-type-device-type-and-name-description": "Dispositivos del tipo '{{deviceType}}' y su nombre empieza con '{{prefix}}'", + "filter-type-entity-view-type": "Tipo de vista de entidad", + "filter-type-entity-view-type-description": "Vista de entidad del tipo '{{entityView}}'", + "filter-type-entity-view-type-and-name-description": "Las vista de entidad del tipo '{{entityView}}' y cuyo nombre comienza con '{{prefix}}'", + "filter-type-relations-query": "Consulta de relaciones", + "filter-type-relations-query-description": "{{entities}} que tienen {{relationType}} relación {{direction}} {{rootEntity}}", + "filter-type-asset-search-query": "Consultar búsqueda de activos", + "filter-type-asset-search-query-description": "Activos con tipos {{assetTypes}} que tienen {{relationType}} relación {{direction}} {{rootEntity}}", + "filter-type-device-search-query": "Consultar búqueda de dispositivos", + "filter-type-device-search-query-description": "Dispositivos con tipos {{deviceTypes}} que tienen {{relationType}} relación {{direction}} {{rootEntity}}", + "filter-type-entity-view-search-query": "Consultar vista de entidad", + "filter-type-entity-view-search-query-description": "Las vista de entidad de tipo {{entityViewTypes}} que tienen {{relationType}} relación {{direction}} {{rootEntity}}", + "entity-filter": "Filtro de entidad", + "resolve-multiple": "Resolver como entidades múltiples", + "filter-type": "Tipo de filtro", + "filter-type-required": "Tipo de filtro es requerido.", + "entity-filter-no-entity-matched": "No se encontraron entidades que coincidan con el filtro especificado.", + "no-entity-filter-specified": "No se especificó el filtro de entidad", + "root-state-entity": "Utilizar la entidad del panel de estados como raíz", + "root-entity": "Entidad raíz", + "state-entity-parameter-name": "Nombre de parámetro de entidad de estado", + "default-state-entity": "Entidad de estado predeterminada", + "default-entity-parameter-name": "Por defecto", + "max-relation-level": "Nivel máximo de relación", + "unlimited-level": "Nivel ilimitado", + "state-entity": "Entidad del panel de estados", + "all-entities": "Todas las entidades", + "any-relation": "alguna" + }, + "asset": { + "asset": "Activo", + "assets": "Activos", + "management": "Gestión de activos", + "view-assets": "Ver activos", + "add": "Agregar activo", + "assign-to-customer": "Asignar al cliente", + "assign-asset-to-customer": "Asignar activo(s) al cliente", + "assign-asset-to-customer-text": "Por favor, seleccione los activos para asignar al cliente", + "no-assets-text": "No se encontraron activos", + "assign-to-customer-text": "Por favor, seleccione el cliente para asignar el(los) activo(s)", + "public": "Público", + "assignedToCustomer": "Asignado al cliente", + "make-public": "Hacer público el activo", + "make-private": "Hacer privado el activo", + "unassign-from-customer": "Anular asignación del cliente", + "delete": "Eliminar activo", + "asset-public": "El activo es público", + "asset-type": "Tipo de activo", + "asset-type-required": "El tipo de activo es requerido.", + "select-asset-type": "Seleccionar tipo de activo", + "enter-asset-type": "Introduzca el tipo de activo", + "any-asset": "Algún activo", + "no-asset-types-matching": "No se encontraron tipos de activos que coincidan con '{{entitySubtype}}'.", + "asset-type-list-empty": "No se seleccionaron tipos de activos.", + "asset-types": "Tipos de activos", + "name": "Nombre", + "name-required": "El nombre es requerido.", + "description": "Descripción", + "type": "Tipo", + "type-required": "El tipo es requerido.", + "details": "Detalles", + "events": "Eventos", + "add-asset-text": "Agregar nuevo activo", + "asset-details": "Detalles del activo", + "assign-assets": "Asignar activos", + "assign-assets-text": "Asignar { count, plural, 1 {1 activo} other {# activos} } al cliente", + "delete-assets": "Eliminar activos", + "unassign-assets": "Anular asignación de activos", + "unassign-assets-action-title": "Anular asignación { count, plural, 1 {1 activo} other {# activos} } del cliente", + "assign-new-asset": "Asignar nuevo activo", + "delete-asset-title": "¿Está seguro de que desea eliminar el activo '{{assetName}}'?", + "delete-asset-text": "¡Cuidado! Después de la confirmación, el activo y todos los datos relacionados serán irrecuperables.", + "delete-assets-title": "¿Está seguro de que desea eliminar { count, plural, 1 {1 activos} other {# activos} }?", + "delete-assets-action-title": "Eliminar { count, plural, 1 {1 activo} other {# activos} }", + "delete-assets-text": "¡Cuidado! Después de la confirmación se eliminarán todos los activos seleccionados y todos los datos relacionados serán irrecuperables.", + "make-public-asset-title": "¿Está seguro de que desea que el activo '{{assetName}}' sea público?", + "make-public-asset-text": "Después de la confirmación, el activo y todos sus datos se harán públicos y accesibles por otros.", + "make-private-asset-title": "¿Está seguro de que desea que el activo '{{assetName}}' sea privado?", + "make-private-asset-text": "Después de la confirmación, el activo y todos sus datos se harán privados y no serán accesibles para otros", + "unassign-asset-title": "¿Está seguro de que desea anular asignación del activo '{{assetName}}'?", + "unassign-asset-text": "Después de la confirmación, se anulará asignación del activo y no será accesible por el cliente.", + "unassign-asset": "Anular asignación activo", + "unassign-assets-title": "¿Está seguro de que desea anular asignación { count, plural, 1 {1 activo} other {# activos} }?", + "unassign-assets-text": "Después de la confirmación, se anulará asignación de todos los activos seleccionados y no serán accesibles por el cliente", + "copyId": "Copiar ID del activo", + "idCopiedMessage": "ID del activo has sido copiada al portapapeles", + "select-asset": "Seleccionar activo", + "no-assets-matching": "No se encontraron activos que coincidan con '{{entity}}'.", + "asset-required": "El activo es requerido", + "name-starts-with": "El nombre del activo comienza con", + "import": "Importar activos", + "asset-file": "Archivo del activo" + }, + "attribute": { + "attributes": "Atributos", + "latest-telemetry": "Última telemetría", + "attributes-scope": "Alcance de los atributos de la entidad", + "scope-latest-telemetry": "Última telemetría", + "scope-client": "Atributos del cliente", + "scope-server": "Atributos del servidor", + "scope-shared": "Atributos compartidos", + "add": "Agregar atributos", + "key": "Clave", + "last-update-time": "Hora de la última actualización", + "key-required": "La clave del aributo es requerida.", + "value": "Valor", + "value-required": "Valor del atributo es requerido.", + "delete-attributes-title": "¿Está seguro de que desea eliminar { count, plural, 1 {1 atributo} other {# atributos} }?", + "delete-attributes-text": "¡Cuidado! Después de la confirmación, se eliminarán todos los atributos seleccionados.", + "delete-attributes": "Eliminar atributos", + "enter-attribute-value": "Introduzca el valor del atributo", + "show-on-widget": "Mostrar en widget", + "widget-mode": "Modo widget", + "next-widget": "Widget siguiente", + "prev-widget": "Widget previo", + "add-to-dashboard": "Agregar al panel", + "add-widget-to-dashboard": "Agregar widget al panel", + "selected-attributes": "{ count, plural, 1 {1 atributo} other {# atributos} } seleccionados", + "selected-telemetry": "{ count, plural, 1 {1 telemetry unit} other {# telemetry units} } seleccionadas" + }, + "audit-log": { + "audit": "Auditoría", + "audit-logs": "Registros de auditoría", + "timestamp": "Marca de tiempo", + "entity-type": "Tipo de entidad", + "entity-name": "Nombre de entidad", + "user": "Usuario", + "type": "Tipo", + "status": "Estado", + "details": "Detalles", + "type-added": "Agregado", + "type-deleted": "Eliminado", + "type-updated": "Actualizado", + "type-attributes-updated": "Atributos actualizados", + "type-attributes-deleted": "Atributos eliminados", + "type-rpc-call": "Llamada RPC", + "type-credentials-updated": "Credenciales actualizadas", + "type-assigned-to-customer": "Asignado al cliente", + "type-unassigned-from-customer": "Asignación anulada del cliente", + "type-activated": "Activado", + "type-suspended": "Suspendido", + "type-credentials-read": "Credenciales leídas", + "type-attributes-read": "Atributos leídos", + "type-relation-add-or-update": "Relación actualizada", + "type-relation-delete": "Relación eliminada", + "type-relations-delete": "Toda relación eliminada", + "type-alarm-ack": "Reconocida", + "type-alarm-clear": "Borrada", + "type-login": "Inicio de sesión", + "type-logout": "Cierre de sesión", + "status-success": "Correcto", + "status-failure": "Erróneo", + "audit-log-details": "Detalle del registro de auditoría", + "no-audit-logs-prompt": "No se encontraron registros", + "action-data": "Datos de acción", + "failure-details": "Detalles del error", + "search": "Buscar registros de auditoría", + "clear-search": "Borrar búsqueda" + }, + "confirm-on-exit": { + "message": "Tiene cambios sin guardar. ¿Está seguro de que desea salir de esta página?", + "html-message": "Tiene cambios sin guardar.
¿Está seguro de que desea salir de esta página?", + "title": "Cambios sin guardar" + }, + "contact": { + "country": "País", + "city": "Ciudad", + "state": "Estado / Provincia", + "postal-code": "Código Postal", + "postal-code-invalid": "Formato de código postal inválido.", + "address": "Dirección", + "address2": "Dirección 2", + "phone": "Teléfono", + "email": "Correo Electrónico", + "no-address": "Sin dirección" + }, + "common": { + "username": "Nombre de usuario", + "password": "Contraseña", + "enter-username": "Introduzca nombre de usuario", + "enter-password": "Introduzca contraseña", + "enter-search": "Introduzca búsqueda" + }, + "content-type": { + "json": "Json", + "text": "Texto", + "binary": "Binario (Base64)" + }, + "customer": { + "customer": "Cliente", + "customers": "Clientes", + "management": "Gestión del cliente", + "dashboard": "Panel del cliente", + "dashboards": "Paneles del cliente", + "devices": "Dispositivos del cliente", + "entity-views": "Vistas de entidad del cliente", + "assets": "Activos del Cliente", + "public-dashboards": "Paneles públicos", + "public-devices": "Dispositivos públicos", + "public-assets": "Activos públicos", + "public-entity-views": "Vista de entidad públicas", + "add": "Agregar cliente", + "delete": "Eliminar cliente", + "manage-customer-users": "Gestionar usuarios del cliente", + "manage-customer-devices": "Gestionar dispositivos del cliente", + "manage-customer-dashboards": "Gestionar paneles del cliente", + "manage-public-devices": "Gestionar dispositivos públicos", + "manage-public-dashboards": "Gestionar paneles públicos", + "manage-customer-assets": "Gestionar activos del cliente", + "manage-public-assets": "Gestionar activos públicos", + "add-customer-text": "Agregar nuevo cliente", + "no-customers-text": "No se encontraron clientes", + "customer-details": "Detalles del cliente", + "delete-customer-title": "¿Está seguro de que desea eliminar al cliente '{{customerTitle}}'?", + "delete-customer-text": "¡Cuidado! Después de la confirmación, el cliente y todos los datos relacionados serán irrecuperables.", + "delete-customers-title": "¿Está seguro de que desea eliminar { count, plural, 1 {1 cliente} other {# clientes} }?", + "delete-customers-action-title": "Eliminar { count, plural, 1 {1 cliente} other {# clientes} }", + "delete-customers-text": "¡Cuidado! Después de la confirmación, todos los clientes seleccionados serán eliminados y todos los datos relacionados serán irrecuperables.", + "manage-users": "Gestionar usuarios", + "manage-assets": "Gestionar activos", + "manage-devices": "Gestionar dispositivos", + "manage-dashboards": "Gestionar paneles", + "title": "Título", + "title-required": "El título es requerido.", + "description": "Descripción", + "details": "Detalles", + "events": "Eventos", + "copyId": "Copiar ID del cliente", + "idCopiedMessage": "ID del cliente ha sido copiada al portapapeles", + "select-customer": "Seleccionar cliente", + "no-customers-matching": "No se encontraron clientes que coincidan con '{{entity}}'.", + "customer-required": "El cliente es requerido", + "select-default-customer": "Seleccionar cliente predeterminado", + "default-customer": "Cliente predeterminado", + "default-customer-required": "Cliente predeterminado es requerido para depurar el panel en el nivel Organización" + }, + "datetime": { + "date-from": "Fecha desde", + "time-from": "Tiempo desde", + "date-to": "Fecha hasta", + "time-to": "Tiempo hasta" + }, + "dashboard": { + "dashboard": "Panel", + "dashboards": "Paneles", + "management": "Gestión del panel", + "view-dashboards": "Ver panel", + "add": "Agregar paneles", + "assign-dashboard-to-customer": "Asignar panel(es) al cliente", + "assign-dashboard-to-customer-text": "Por favor selecciona los paneles para asignar al cliente", + "assign-to-customer-text": "Por favor selecciona el cliente para asignar el(los) panel(es)", + "assign-to-customer": "Asignar al cliente", + "unassign-from-customer": "Anular asignación del cliente", + "make-public": "Hacer panel público", + "make-private": "Hacer panel privado", + "manage-assigned-customers": "Gestionar clientes asignados", + "assigned-customers": "Clientes asignados", + "assign-to-customers": "Asignar panel(es) al(los) cliente(s)", + "assign-to-customers-text": "Por favor seleccionar los clientes para asignar el(los) panel(es)", + "unassign-from-customers": "Anular asignación del(de los) panel(es) de los clientes", + "unassign-from-customers-text": "Por favor selecciona los clientes para anular asignación del(de los) panel(es)", + "no-dashboards-text": "No se encontraron paneles", + "no-widgets": "Sin widgets configurados", + "add-widget": "Agregar nuevo widget", + "title": "Título", + "select-widget-title": "Seleccionar widget", + "select-widget-subtitle": "Lista de tipos de widget disponibles", + "delete": "Eliminar panel", + "title-required": "El título es requerido.", + "description": "Descripción", + "details": "Detalles", + "dashboard-details": "Detalles del panel", + "add-dashboard-text": "Agregar nuevo panel", + "assign-dashboards": "Asignar paneles", + "assign-new-dashboard": "Asignar nuevo panel", + "assign-dashboards-text": "Asignar { count, plural, 1 {1 panel} other {# paneles} } a los clientes", + "unassign-dashboards-action-text": "Anular asignación { count, plural, 1 {1 dashboard} other {# dashboards} } de los clientes", + "delete-dashboards": "Eliminar paneles", + "unassign-dashboards": "Anular asignación de paneles", + "unassign-dashboards-action-title": "Anular asignación { count, plural, 1 {1 panel} other {# paneles} } del cliente", + "delete-dashboard-title": "¿Está seguro de que desea eliminar el panel '{{dashboardTitle}}'?", + "delete-dashboard-text": "¡Cuidado! Después de la confirmación, el panel y todos los datos relacionados serán irrecuperables.", + "delete-dashboards-title": "¿Está seguro de que desea eliminar { count, plural, 1 {1 dashboard} other {# dashboards} }?", + "delete-dashboards-action-title": "Eliminar { count, plural, 1 {1 dashboard} other {# dashboards} }", + "delete-dashboards-text": "¡Cuidado! Después de la confirmación, todos los paneles seleccionados serán eliminados y todos los datos relacionados serán irrecuperables.", + "unassign-dashboard-title": "¿Está seguro de que desea anular la asignación del panel '{{dashboardTitle}}'?", + "unassign-dashboard-text": "Después de la confirmación, se anulará la asignación del panel y no será accesible por el cliente.", + "unassign-dashboard": "Anular asignación del panel", + "unassign-dashboards-title": "¿Está seguro de que desea anular asignación { count, plural, 1 {1 panel} other {# paneles} }?", + "unassign-dashboards-text": "Después de la confirmación, se anulará la asignación de todos los paneles seleccionados y no serán accesibles por el cliente.", + "public-dashboard-title": "El panel es ahora público", + "public-dashboard-text": "Su panel {{dashboardTitle}} es ahora público y es accesible a través del siguiente enlace público :", + "public-dashboard-notice": "Nota No olvide hacer públicos los dispositivos relacionados para acceder a sus datos.", + "make-private-dashboard-title": "¿Está seguro de que desea hacer el panel '{{dashboardTitle}}' privado?", + "make-private-dashboard-text": "Después de la confirmación el panel se hará privado y no será accesible por otros.", + "make-private-dashboard": "Hacer el panel privado", + "socialshare-text": "'{{dashboardTitle}}' desarrollado por ThingsBoard.", + "socialshare-title": "'{{dashboardTitle}}' desarrollado por ThingsBoard", + "select-dashboard": "Seleccionar panel", + "no-dashboards-matching": "Paneles que coincidan con '{{entity}}' no fueron encontrados.", + "dashboard-required": "Panel es requerido.", + "select-existing": "Seleccionar panel existente", + "create-new": "Crear nuevo panel", + "new-dashboard-title": "Nuevo título de panel", + "open-dashboard": "Abrir panel", + "set-background": "Definir fondo", + "background-color": "Color de fondo", + "background-image": "Imagen de fondo", + "background-size-mode": "Modo de tamaño de fondo", + "no-image": "Ninguna imagen seleccionada", + "drop-image": "Colocar una imagen o hacer clic para seleccionar un archivo para cargar.", + "settings": "Configuración", + "columns-count": "Número de columnas", + "columns-count-required": "Número de columnas es requerido.", + "min-columns-count-message": "Solo está permitido 10 columnas como mínimo.", + "max-columns-count-message": "Sólo está permitido 1000 columnas como máximo.", + "widgets-margins": "Margen entre widgets", + "horizontal-margin": "Margen horizontal", + "horizontal-margin-required": "El valor del margen horizontal es requerido.", + "min-horizontal-margin-message": "Sólo está permitido el 0 como valor mínimo para el margen horizontal", + "max-horizontal-margin-message": "Sólo está permitido el 50 como valor máximo para el margen horizontal", + "vertical-margin": "Margen vertical", + "vertical-margin-required": "El valor del margen vertical es requerido.", + "min-vertical-margin-message": "Sólo está permitido el 0 como valor mínimo para el margen vertical.", + "max-vertical-margin-message": "Solo está permitido el 50 como valor máximo para el margen vertical", + "autofill-height": "Llenado automático de altura de diseño", + "mobile-layout": "Configuración de diseño para móvil", + "mobile-row-height": "Altura de fila para móvil, píxel", + "mobile-row-height-required": "Altura de fila para móvil es requerida.", + "min-mobile-row-height-message": "Sólo está permitido 5 píxeles como valor mínimo en la altura de fila para móvil.", + "max-mobile-row-height-message": "Sólo está permitido 200 píxeles como valor máximo en la altura de fila para móvil.", + "display-title": "Mostrar título del panel", + "toolbar-always-open": "Mantener la barra de herramientas abierta", + "title-color": "Color del título", + "display-dashboards-selection": "Mostrar selección del panel", + "display-entities-selection": "Mostrar selección de entidades", + "display-dashboard-timewindow": "Mostrar ventana de tiempo", + "display-dashboard-export": "Mostrar exportar", + "import": "Importar panel", + "export": "Exportar panel", + "export-failed-error": "No se puede exportar el panel: {{error}}", + "create-new-dashboard": "Crear nuevo panel", + "dashboard-file": "Archivo del panel", + "invalid-dashboard-file-error": "No se puede importar el panel: estructura de datos del panel no es válida.", + "dashboard-import-missing-aliases-title": "Configurar los alias utilizados por el panel importado", + "create-new-widget": "Crear nuevo widget", + "import-widget": "Importar widget", + "widget-file": "Archivo del widget", + "invalid-widget-file-error": "No se puede importar el widget: estructura de datos del widger no es válida.", + "widget-import-missing-aliases-title": "Configurar los alias utilizados por el widget importado", + "open-toolbar": "Abrir barra de herramientas del panel", + "close-toolbar": "Cerrar barra de herramientas", + "configuration-error": "Error de configuración", + "alias-resolution-error-title": "Error de configuración de los alias del panel", + "invalid-aliases-config": "No se puede encontrar algún dispositivo que coincida con algunos alias del filtro.
Por favor, contacte a su administrador para resolver este problema.", + "select-devices": "Seleccionar dispositivos", + "assignedToCustomer": "Asignado al cliente", + "assignedToCustomers": "Asignado a los clientes", + "public": "Público", + "public-link": "Enlace público", + "copy-public-link": "Copiar enlace público", + "public-link-copied-message": "El enlace público del panel ha sido copiado al portapapeles", + "manage-states": "Gestionar estados del panel", + "states": "Estados del panel", + "search-states": "Buscar estados del panel", + "selected-states": "{ count, plural, 1 {1 estado del panel } other {# estados del panel } } seleccionados", + "edit-state": "Editar estado del panel", + "delete-state": "Eliminar estado del panel", + "add-state": "Agregar estado del panel", + "state": "Estado del panel", + "state-name": "Nombre", + "state-name-required": "El nombre del estado del panel es requerido.", + "state-id": "ID del estado", + "state-id-required": "ID del estado del panel es requerida.", + "state-id-exists": "Ya existe el estado del panel con el mismo ID.", + "is-root-state": "Estado raíz", + "delete-state-title": "Eliminar estado del panel", + "delete-state-text": "¿Está seguro de que desea eliminar el estado del panel con el nombre '{{stateName}}'?", + "show-details": "Mostrar detalles", + "hide-details": "Ocultar detalles", + "select-state": "Seleccionar estado objetivo", + "state-controller": "Estado del controlador" + }, + "datakey": { + "settings": "Configuración", + "advanced": "Avanzado", + "label": "Etiqueta", + "color": "Color", + "units": "Símbolo especial para mostrar al lado del valor", + "decimals": "Número de dígitos después del punto flotante", + "data-generation-func": "Función de generación de datos", + "use-data-post-processing-func": "Usar la función de post-procesamiento de datos", + "configuration": "Configuración de clave de datos", + "timeseries": "Series temporales", + "attributes": "Atributos", + "alarm": "Campos de alarma", + "timeseries-required": "Series temporales de la entidad son requeridas", + "timeseries-or-attributes-required": "Series temporales/atributos de la entidad son requeridos.", + "maximum-timeseries-or-attributes": "Máximo { count, plural, 1 {1 serie temporal/atributo permitido.} other {# series temporales/atributos permitidos} }", + "alarm-fields-required": "Campos de alarma son requeridos.", + "function-types": "Tipos de funciones", + "function-types-required": "Tipos de funciones son requeridos.", + "maximum-function-types": "Máximo { count, plural, 1 {1 function type is allowed.} other {# function types are allowed} }", + "time-description": "marca de tiempo del valor actual;", + "value-description": "el valor actual;", + "prev-value-description": "resultado de la llamada a la función anterior;", + "time-prev-description": "marca de tiempo del valor anterior;", + "prev-orig-value-description": "valor anterior original;" + }, + "datasource": { + "type": "Tipo de fuente de datos", + "name": "Nombre", + "add-datasource-prompt": "Por favor agregue fuente de datos" + }, + "details": { + "edit-mode": "Modo de edición", + "toggle-edit-mode": "Cambiar modo de edición" + }, + "device": { + "device": "Dispositivo", + "device-required": "Dispositivo es requerido.", + "devices": "Dispositivos", + "management": "Gestión del dispositivo", + "view-devices": "Ver dispositivos", + "device-alias": "Alias del dispositivo", + "aliases": "Alias de los dispositivos", + "no-alias-matching": "'{{alias}}' no encontrado.", + "no-aliases-found": "No se encontraron alias.", + "no-key-matching": "'{{key}}' no encontrado.", + "no-keys-found": "Claves no encontradas.", + "create-new-alias": "¡Crear uno nuevo!", + "create-new-key": "¡Crear una nueva!", + "duplicate-alias-error": "Alias duplicado encontrado '{{alias}}'.
Los alias del dispositivo deben ser únicos dentro del panel.", + "configure-alias": "Configurar '{{alias}}' alias", + "no-devices-matching": "Dispositivos que coincidan con '{{entity}}' no fueron encontrados.", + "alias": "Alias", + "alias-required": "Alias del dispositivo es requerido.", + "remove-alias": "Eliminar alias del dispositivo", + "add-alias": "Agregar alias del dispositivo", + "name-starts-with": "El nombre del dispositivo comienza con", + "device-list": "Lista de dispositivos", + "use-device-name-filter": "Utilizar filtro", + "device-list-empty": "Ningún dispositivo seleccionado.", + "device-name-filter-required": "Filtro de nombre de dispositivo es requerido.", + "device-name-filter-no-device-matched": "Dispositivos que comienzan con '{{device}}' no fueron encontrados.", + "add": "Agregar Dispositivo", + "assign-to-customer": "Asignar al cliente", + "assign-device-to-customer": "Asignar Dispositivo(s) Al Cliente", + "assign-device-to-customer-text": "Por favor selecciona los dispositivos para asignar al cliente", + "make-public": "Hacer público el dispositivo", + "make-private": "Hacer privado el dispositivo", + "no-devices-text": "No se encontraron dispositivos", + "assign-to-customer-text": "Por favor seleccionar el cliente para asignar el(los) dispositivo(s)", + "device-details": "Detalles del dispositivo", + "add-device-text": "Agregar nuevo dispositivo", + "credentials": "Credenciales", + "manage-credentials": "Gestionar credenciales", + "delete": "Eliminar dispositivo", + "assign-devices": "Asignar dispositivos", + "assign-devices-text": "Asignar { count, plural, 1 {1 dispositivo} other {# dispositivos} } al cliente", + "delete-devices": "Eliminar dispositivos", + "unassign-from-customer": "Anular asignación del cliente", + "unassign-devices": "Desasignar dispositivos", + "unassign-devices-action-title": "Anular asignación { count, plural, 1 {1 dispositivo} other {# dispositivos} } del cliente", + "assign-new-device": "Asignar nuevo dispositivo", + "make-public-device-title": "¿Está seguro de que desea hacer el dispositivo '{{deviceName}}' público?", + "make-public-device-text": "Después de la confirmación, el dispositivo y todos sus datos se harán públicos y accesibles por otros.", + "make-private-device-title": "¿Está seguro de que desea hacer el dispositivo '{{deviceName}}' privado?", + "make-private-device-text": "Después de la confirmación, el dispositivo y todos sus datos se harán privados y no serán accesibles para otros.", + "view-credentials": "Ver credenciales", + "delete-device-title": "¿Está seguro de que desea hacer el dispositivo '{{deviceName}}'?", + "delete-device-text": "¡Cuidado! Después de la confirmación, el dispositivo y todos sus datos relacionados serán irrecuperables.", + "delete-devices-title": "¿Está seguro de que desea eliminar { count, plural, 1 {1 dispositivo} other {# dispositivos} }?", + "delete-devices-action-title": "Eliminar { count, plural, 1 {1 dispositivo} other {# dispositivos} }", + "delete-devices-text": "¡Cuidado! Después de la confirmación, todos los dispositivos seleccionados serán eliminados y todos los datos relacionados serán irrecuperables.", + "unassign-device-title": "¿Está seguro de que desea anular la asignación del dispositivo '{{deviceName}}'?", + "unassign-device-text": "Después de la confirmación, se anulará asignación del dispositivo y no será accesible por el cliente.", + "unassign-device": "Desasignar dispositivo", + "unassign-devices-title": "¿Está seguro de que desea desasignar { count, plural, 1 {1 dispositivo } other {# dispositivos} }?", + "unassign-devices-text": "Después de la confirmación, se anulará asignación de todos los dispositivos seleccionados y no serán accesibles por el cliente.", + "device-credentials": "Credenciales del dispositivo", + "credentials-type": "Tipo de credenciales", + "access-token": "Token de acceso", + "access-token-required": "Token de acceso es requerido.", + "access-token-invalid": "La longitud del token de acceso debe ser de 1 a 20 caracteres.", + "rsa-key": "Clave pública RSA", + "rsa-key-required": "Clave pública RSA es requerida.", + "secret": "Secreto", + "secret-required": "Secreto es requerido.", + "device-type": "Tipo de dispositivo", + "device-type-required": "Tipo de dispositivo es requerido.", + "select-device-type": "Seleccionar tipo de dispositivo", + "enter-device-type": "Teclee tipo de dispositivo", + "any-device": "Algún dispositivo", + "no-device-types-matching": "Tipos de dispositivos que coincidan con '{{entitySubtype}}' no fueron encontrados.", + "device-type-list-empty": "No se seleccionaron tipos de dispositivos.", + "device-types": "Tipo de dispositivos", + "name": "Nombre", + "name-required": "El nombre es requerido.", + "label": "Etiqueta", + "description": "Descripción", + "events": "Eventos", + "details": "Detalles", + "copyId": "Copiar ID del dispositivo", + "copyAccessToken": "Copiar token de acceso", + "idCopiedMessage": "ID del dispositivo ha sido copiada al portapapeles", + "accessTokenCopiedMessage": "Token de acceso al dispositivo ha sido copiado al portapapeles", + "assignedToCustomer": "Asignado al cliente", + "unable-delete-device-alias-title": "No se puede eliminar el alias del dispositivo", + "unable-delete-device-alias-text": "El alias del dispositivo '{{deviceAlias}}' no puede ser eliminado porque es usado por los siguientes widget(s):
{{widgetsList}}", + "is-gateway": "Es puerta de entrada", + "public": "Público", + "device-public": "El dispositivo es público", + "select-device": "Seleccionar dispositivo", + "device-file": "Archivo de dispositivo", + "import": "Importar dispositivo" + }, + "dialog": { + "close": "Cerrar diálogo" + }, + "direction": { + "column": "Columna", + "row": "Fila" + }, + "error": { + "unable-to-connect": "¡No se puede conectar al servidor! Por favor, revise su conexión a Internet.", + "unhandled-error-code": "Código de error no controlado: {{errorCode}}", + "unknown-error": "Error desconocido" + }, + "entity": { + "entity": "Entidad", + "entities": "Entidades", + "aliases": "Alias de las entidades", + "entity-alias": "Alias de la entidad", + "unable-delete-entity-alias-title": "No se puede borrar alias de la entidad", + "unable-delete-entity-alias-text": "Alias de la entidad '{{entityAlias}}' no se puede eliminar porque es usado por los siguientes widget(s):
{{widgetsList}}", + "duplicate-alias-error": "Alias duplicado fue encontrado '{{alias}}'.
Alias de las entidades deben ser únicos dentro del panel.", + "missing-entity-filter-error": "Falta filtro para el alias '{{alias}}'.", + "configure-alias": "Configurar '{{alias}}' alias", + "alias": "Alias", + "alias-required": "Alias de la entidad es requerida.", + "remove-alias": "Eliminar alias de la entidad", + "add-alias": "Agregar alias de la entidad", + "entity-list": "Lista de entidades", + "entity-type": "Tipo de entidad", + "entity-types": "Tipos de entidades", + "entity-type-list": "Lista de tipos de entidades", + "any-entity": "Alguna entidad", + "enter-entity-type": "Teclee tipo de entidad", + "no-entities-matching": "No se encontraron entidades que coincidan con '{{entity}}'.", + "no-entity-types-matching": "No se encontraron tipos de entidades que coincidan con '{{entityType}}'.", + "name-starts-with": "El nombre comienza con", + "use-entity-name-filter": "Utilizar filtro", + "entity-list-empty": "Entidades no seleccionadas.", + "entity-type-list-empty": "Tipos de entidades no seleccionados.", + "entity-name-filter-required": "Filtro del nombre de la entidad es requerido.", + "entity-name-filter-no-entity-matched": "No se encontraron entidades que comienzan con '{{entity}}'.", + "all-subtypes": "Todas", + "select-entities": "Seleccionar entidades", + "no-aliases-found": "No se encontraron alias.", + "no-alias-matching": "'{{alias}}' no encontrado.", + "create-new-alias": "¡Crear uno nuevo!", + "key": "Clave", + "key-name": "Nombre de clave", + "no-keys-found": "No se encontraron claves.", + "no-key-matching": "'{{key}}' no encontrada.", + "create-new-key": "¡Crear una nueva!", + "type": "Tipo", + "type-required": "Tipo de entidad es requerido.", + "type-device": "Dispositivo", + "type-devices": "Dispositivos", + "list-of-devices": "{ count, plural, 1 {Un dispositivo} other {Lista de # dispositivos} }", + "device-name-starts-with": "Dispositivos cuyos nombres comienzan con '{{prefix}}'", + "type-asset": "Activo", + "type-assets": "Activos", + "list-of-assets": "{ count, plural, 1 {Un activo} other {Lista de # activos} }", + "asset-name-starts-with": "Activos cuyos nombres comienzan con '{{prefix}}'", + "type-entity-view": "Vista de entidad", + "type-entity-views": "Vistas de entidad", + "list-of-entity-views": "{ count, plural, 1 {Una vista de entidad} other {Lista de # vistas de entidad} }", + "entity-view-name-starts-with": "Vistas de entidad cuyos nombres que comienzan con '{{prefix}}'", + "type-rule": "Regla", + "type-rules": "Reglas", + "list-of-rules": "{ count, plural, 1 {Una regla} other {Lista de # reglas} }", + "rule-name-starts-with": "Reglas cuyos nombres comienzan con '{{prefix}}'", + "type-plugin": "Complemento", + "type-plugins": "Complementos", + "list-of-plugins": "{ count, plural, 1 {Un complemento} other {Lista de # complementos} }", + "plugin-name-starts-with": "Complementos cuyos nombres comienzan con '{{prefix}}'", + "type-tenant": "Organización", + "type-tenants": "Organizaciones", + "list-of-tenants": "{ count, plural, 1 {Una organización} other {Lista de # organizaciones} }", + "tenant-name-starts-with": "Organizaciones cuyos nombres comienzan con '{{prefix}}'", + "type-customer": "Cliente", + "type-customers": "Clientes", + "list-of-customers": "{ count, plural, 1 {Un cliente} other {Lista de # clientes} }", + "customer-name-starts-with": "Clientes cuyos nombres comienzan con '{{prefix}}'", + "type-user": "Usuario", + "type-users": "Usuarios", + "list-of-users": "{ count, plural, 1 {Un usuario} other {Lista de # usuarios} }", + "user-name-starts-with": "Usuarios cuyos nombres comienzan con '{{prefix}}'", + "type-dashboard": "Panel", + "type-dashboards": "Paneles", + "list-of-dashboards": "{ count, plural, 1 {One dashboard} other {List of # dashboards} }", + "dashboard-name-starts-with": "Paneles cuyos nombres comienzan con '{{prefix}}'", + "type-alarm": "Alarma", + "type-alarms": "Alarmas", + "list-of-alarms": "{ count, plural, 1 {Una alarma} other {Lista de # alarmas} }", + "alarm-name-starts-with": "Alarmas cuyos nombres comienzan con '{{prefix}}'", + "type-rulechain": "Cadena de reglas", + "type-rulechains": "Cadenas de reglas", + "list-of-rulechains": "{ count, plural, 1 {Una cadena de reglas} other {Lista de # cadenas de reglas} }", + "rulechain-name-starts-with": "Cadenas de reglas cuyos nombres comienzan con '{{prefix}}'", + "type-rulenode": "Nodo de reglas", + "type-rulenodes": "Nodos de reglas", + "list-of-rulenodes": "{ count, plural, 1 {Un nodo de reglas} other {Lista de # nodos de reglas} }", + "rulenode-name-starts-with": "Nodos de reglas cuyos nombres comienzan con '{{prefix}}'", + "type-current-customer": "Cliente Actual", + "search": "Buscar entidades", + "selected-entities": "{ count, plural, 1 {1 entidad} other {# entidades} } seleccionadas", + "entity-name": "Nombre de la entidad", + "details": "Detalles de la entidad", + "no-entities-prompt": "Entidades no encontradas", + "no-data": "No hay datos para mostrar", + "columns-to-display": "Columnas a mostrar" + }, + "entity-view": { + "entity-view": "Vista de entidad", + "entity-view-required": "Vista de entidad es requerido.", + "entity-views": "Vistas de entidad", + "management": "Gestión de vistas de entidad", + "view-entity-views": "Ver vista de entidad", + "entity-view-alias": "Alias de vista de entidad", + "aliases": "Alias de vista de entidad", + "no-alias-matching": "'{{alias}}' no encontrado.", + "no-aliases-found": "No se encontraron alias.", + "no-key-matching": "'{{key}}' no encontrado.", + "no-keys-found": "No se encontraron claves.", + "create-new-alias": "¡Crear un nuevo!", + "create-new-key": "¡Crear una nueva!", + "duplicate-alias-error": "Alias duplicado'{{alias}}'.
Los alias de Entity View deben ser únicos en el panel.", + "configure-alias": "Configurar alias '{{alias}}'", + "no-entity-views-matching": "No se encontraron vistas que coincidan con '{{entity}}'.", + "alias": "Alias", + "alias-required": "Alias de vista de entidad es requerido.", + "remove-alias": "Borrar alias de la vista de entidad", + "add-alias": "Añadir alias a la vista de entidad", + "name-starts-with": "Nombre de vista de entidad comienza con", + "entity-view-list": "Lista de vistas de entidad", + "use-entity-view-name-filter": "Usar el filtro", + "entity-view-list-empty": "No hay vistas de entidad seleccionadas.", + "entity-view-name-filter-required": "Nombre del filtro de vista de entidad es requerido.", + "entity-view-name-filter-no-entity-view-matched": "No se encontraron vistas de entidad que comiencen con '{{entityView}}'.", + "add": "Añadir vista de entidad", + "assign-to-customer": "Asignar a cliente", + "assign-entity-view-to-customer": "Asignar vista de entidad a cliente", + "assign-entity-view-to-customer-text": "Por favor, seleccione las vistas de entidad para asignar al cliente", + "no-entity-views-text": "No se encontraron vistas de entidad", + "assign-to-customer-text": "Por favor, seleccione el cliente para asignar la vista de entidad", + "entity-view-details": "Detalles de la vista de entidad", + "add-entity-view-text": "Añadir nueva vista de entidad", + "delete": "Borrar vista de entidad", + "assign-entity-views": "Asignar vistas de entidad", + "assign-entity-views-text": "Asignar { count, plural, 1 {1 vista de entidad} other {# vistas de entidad} } a cliente", + "delete-entity-views": "Borrar vistas de entidad", + "unassign-from-customer": "Anular asignación a cliente", + "unassign-entity-views": "Anular asignación de vistas de entidad", + "unassign-entity-views-action-title": "Anular asignación { count, plural, 1 {1 vista de entidad} other {# vistas de entidad} } al cliente", + "assign-new-entity-view": "Asignar nueva vista de entidad", + "delete-entity-view-title": "¿Está seguro que quiere borrar la vista de entidad '{{entityViewName}}'?", + "delete-entity-view-text": "¡Cuidado! Después de la confirmación, la vista de la entidad y todos los datos relacionados serán irrecuperables.", + "delete-entity-views-title": "¿Está seguro que quiere borrar las vistas de entidad { count, plural, 1 {1 entityView} other {# entityViews} }?", + "delete-entity-views-action-title": "Borrar { count, plural, 1 {1 vista de entidad} other {# vistas de entidad} }", + "delete-entity-views-text": "¡Cuidado! Después de la confirmación, todas las vistas de entidades seleccionadas se eliminarán y todos los datos relacionados serán irrecuperables.", + "unassign-entity-view-title": "¿Está seguro que quiere anular la asignación de la vista de entidad '{{entityViewName}}'?", + "unassign-entity-view-text": "Después de la confirmación, la vista de la entidad quedará sin asignar y el cliente no podrá acceder a ella.", + "unassign-entity-view": "Anular asignación de la vista de entidad", + "unassign-entity-views-title": "¿Está seguro que quiere anular la asignación de { count, plural, 1 {1 vista de entidad} other {# vistas de entidad} }?", + "unassign-entity-views-text": "Después de la confirmación, todas las vistas de entidades seleccionadas quedarán sin asignar y el cliente no podrá acceder a ellas.", + "entity-view-type": "Tipo de vista de entidad", + "entity-view-type-required": "Tipo de vista de entidad es requerido.", + "select-entity-view-type": "Seleccione el tipo de vista de entidad", + "enter-entity-view-type": "Teclee el tipo de vista de entidad", + "any-entity-view": "Cualquier vista de entidad", + "no-entity-view-types-matching": "No se encontraron tipos de vista de entidad que coincidan con '{{entitySubtype}}'.", + "entity-view-type-list-empty": "No hay tipos de vista de entidad seleccionados.", + "entity-view-types": "Tipos de vista de entidad", + "name": "Nombre", + "name-required": "Nombre es requerido.", + "description": "Descripción", + "events": "Eventos", + "details": "Detalles", + "copyId": "Copiar el Id de la vista de entidad", + "assignedToCustomer": "Asignado a cliente", + "unable-entity-view-device-alias-title": "No se puede eliminar el alias de vista de entidad", + "unable-entity-view-device-alias-text": "El alias del dispositivo '{{entityViewAlias}}' no se puede borrar porque está siendo usado por el widget(s):
{{widgetsList}}", + "select-entity-view": "Seleccionar vista de entidad", + "make-public": "Hacer pública la vista de entidad", + "make-private": "Hacer que la vista de entidad sea privada", + "start-date": "Fecha de inicio", + "start-ts": "Tiempo de inicio", + "end-date": "Fecha de finalización", + "end-ts": "Tiempo de finalización", + "date-limits": "Limites de fecha", + "client-attributes": "Atributos de cliente", + "shared-attributes": "Atributos compartidos", + "server-attributes": "Atributos de servidor", + "timeseries": "Series temporales", + "client-attributes-placeholder": "Atributos de cliente", + "shared-attributes-placeholder": "Atributos compartidos", + "server-attributes-placeholder": "Atributos de servidor", + "timeseries-placeholder": "Series temporales", + "target-entity": "Entidad objetivo", + "attributes-propagation": "Propagación de atributos", + "attributes-propagation-hint": "La vista de entidad copiará automáticamente los atributos especificados de la entidad de destino cada vez que guarde o actualice esta vista de entidad. Por razones de rendimiento, los atributos de entidad objetivo no se propagan a la vista de entidad en cada cambio de atributo. Puede habilitar la propagación automática configurando el nodo de la regla \"copiar a la vista\" en su cadena de reglas y vincular los mensajes \"Atributos de la publicación\" y \"Atributos actualizados\" al nuevo nodo de la regla.", + "timeseries-data": "Datos de series temporales", + "timeseries-data-hint": "Configure las claves de los datos de las series temporales de la entidad de destino que serán accesibles para la vista de la entidad. Los datos de esta serie temporal son de solo lectura.", + "make-public-entity-view-title": "¿Está seguro de que desea que la vista de entidad '{{entityViewName}}' sea pública?", + "make-public-entity-view-text": "Después de la confirmación, la vista de la entidad y todos sus datos se harán públicos y accesibles para otros.", + "make-private-entity-view-title": "¿Está seguro de que desea que la vista de entidad '{{entityViewName}}' sea privada?", + "make-private-entity-view-text": "Después de la confirmación, la vista de la entidad y todos sus datos se harán privados y no serán accesibles para otros." + }, + "event": { + "event-type": "Tipo de evento", + "type-error": "Error", + "type-lc-event": "Ciclo de vida del evento", + "type-stats": "Estadísticas", + "type-debug-rule-node": "Depurar", + "type-debug-rule-chain": "Depurar", + "no-events-prompt": "No se encontraron eventos", + "error": "Error", + "alarm": "Alarma", + "event-time": "Tiempo del evento", + "server": "Servidor", + "body": "Cuerpo", + "method": "Método", + "type": "Tipo", + "entity": "Entidad", + "message-id": "ID del mensaje", + "message-type": "Tipo de mensaje", + "data-type": "Tipo de datos", + "relation-type": "Tipo de relación", + "metadata": "Metadatos", + "data": "Datos", + "event": "Evento", + "status": "Estado", + "success": "Correcto", + "failed": "Erróneo", + "messages-processed": "Mensajes procesados", + "errors-occurred": "Errores ocurridos" + }, + "extension": { + "extensions": "Extensiones", + "selected-extensions": "{ count, plural, 1 {1 extensión} other {# extensiones} } seleccionadas", + "type": "Tipo", + "key": "Clave", + "value": "Valor", + "id": "ID", + "extension-id": "ID de extensión", + "extension-type": "Tipo de extensión", + "transformer-json": "JSON *", + "unique-id-required": "Ya existe ID de extensión actual.", + "delete": "Eliminar extensión", + "add": "Agregar extensión", + "edit": "Editar extensión", + "delete-extension-title": "¿Está seguro de que desea eliminar la extensión '{{extensionId}}'?", + "delete-extension-text": "¡Cuidado! Después de la confirmación, la extensión y todos los datos relacionados serán irrecuperables.", + "delete-extensions-title": "¿Está seguro de que desea eliminar { count, plural, 1 {1 extensión} other {# extensiones} }?", + "delete-extensions-text": "¡Cuidado! Después de la confirmación, se eliminarán todas las extensiones seleccionadas.", + "converters": "Conversores", + "converter-id": "ID del conversor", + "configuration": "Configuración", + "converter-configurations": "Configuraciones del conversor", + "token": "Token de seguridad", + "add-converter": "Agregar conversor", + "add-config": "Agregar configuración del conversor", + "device-name-expression": "Expresión del nombre del dispositivo", + "device-type-expression": "Expresión del tipo del dispositivo", + "custom": "Personalizado", + "to-double": "Para duplicar", + "transformer": "Transformador", + "json-required": "Transformador json es requerido.", + "json-parse": "No se puede analizar el transformador json.", + "attributes": "Atributos", + "add-attribute": "Agregar atributos", + "add-map": "Agregar elemento de mapeo", + "timeseries": "Series temporales", + "add-timeseries": "Agregar series temporales", + "field-required": "Campo es requerido", + "brokers": "Agentes", + "add-broker": "Agregar agente", + "host": "Host", + "port": "Puerto", + "port-range": "El puerto debe estar en un rango desde 1 hasta 65535.", + "ssl": "SSL", + "credentials": "Credenciales", + "username": "Nombre de usuario", + "password": "Contraseña", + "retry-interval": "Intervalo de reintento en milisegundos", + "anonymous": "Anónimo", + "basic": "Básico", + "pem": "PEM", + "ca-cert": "Archivo de certificado CA *", + "private-key": "Archivo de clave privado *", + "cert": "Archivo de certificado *", + "no-file": "Ningún archivo seleccionado.", + "drop-file": "Colocar un archivo o hacer clic para seleccionar un archivo para cargar.", + "mapping": "Mapeo", + "topic-filter": "Filtro de tema", + "converter-type": "Tipo de conversor", + "converter-json": "Json", + "json-name-expression": "Expresión json para nombre del dispositivo", + "topic-name-expression": "Expresión temática para nombre del dispositivo", + "json-type-expression": "Expresión json para tipo de dispositivo", + "topic-type-expression": "Expresión temática para tipo de dispositivo", + "attribute-key-expression": "Expresión para clave de atributo", + "attr-json-key-expression": "Expresión json para clave de atributo", + "attr-topic-key-expression": "Expresión temática para clave de atributo", + "request-id-expression": "Expresión para solicitud de ID", + "request-id-json-expression": "Expresión json para solicitud de ID", + "request-id-topic-expression": "Expresión temática para solicitud de ID", + "response-topic-expression": "Expresión temática para respuesta", + "value-expression": "Expresión para valor", + "topic": "Tema", + "timeout": "Tiempo de espera en milisegundos", + "converter-json-required": "Conversor json es requerido.", + "converter-json-parse": "No se puede analizar el conversor json.", + "filter-expression": "Expresión para filtro", + "connect-requests": "Solicitudes de conexión", + "add-connect-request": "Agregar solicitudes de conexión", + "disconnect-requests": "Solicitudes de desconexión", + "add-disconnect-request": "Agregar solicitud de desconexión", + "attribute-requests": "Solicitudes de atributo", + "add-attribute-request": "Agregar solicitudes de atributo", + "attribute-updates": "Actualizaciones de atributo", + "add-attribute-update": "Agregar actualizaciones de atributo", + "server-side-rpc": "RPC lado servidor", + "add-server-side-rpc-request": "Agregar solicitud RPC lado servidor", + "device-name-filter": "Filtro de nombre de dispositivo", + "attribute-filter": "Filtro de atributo", + "method-filter": "Filtro de método", + "request-topic-expression": "Expresión temática para solicitud", + "response-timeout": "Tiempo de espera de respuesta en milisegundos", + "topic-expression": "Expresión temática", + "client-scope": "Alcance del cliente", + "add-device": "Agregar dispositivo", + "opc-server": "Servidores", + "opc-add-server": "Agregar servidor", + "opc-add-server-prompt": "Por favor agregar servidor", + "opc-application-name": "Nombre de aplicación", + "opc-application-uri": "Aplicación URI", + "opc-scan-period-in-seconds": "Período de exploración en segundos", + "opc-security": "Seguridad", + "opc-identity": "Identidad", + "opc-keystore": "Repositorio", + "opc-type": "Tipo", + "opc-keystore-type": "Tipo", + "opc-keystore-location": "Ubicación *", + "opc-keystore-password": "Contraseña", + "opc-keystore-alias": "Alias", + "opc-keystore-key-password": "Clave de contraseña", + "opc-device-node-pattern": "Patrón de nodo de dispositivo", + "opc-device-name-pattern": "Patrón de nombre de dispositivo", + "modbus-server": "Servidores/esclavos", + "modbus-add-server": "Agregar servidor/esclavo", + "modbus-add-server-prompt": "Por favor agregar servidor/esclavo", + "modbus-transport": "Transporte", + "modbus-tcp-reconnect": "Reconexión automática", + "modbus-rtu-over-tcp": "RTU sobre TCP", + "modbus-port-name": "Nombre del puerto serial", + "modbus-encoding": "Codificación", + "modbus-parity": "Paridad", + "modbus-baudrate": "Tasa de baudios", + "modbus-databits": "Bits de datos", + "modbus-stopbits": "Bits de parada", + "modbus-databits-range": "Bits de datos deben estar en un rango entre 7 y 8.", + "modbus-stopbits-range": "Bits de parada deben estar en un rango entre 1 a 2.", + "modbus-unit-id": "ID de unidad", + "modbus-unit-id-range": "ID de unidad debe estar en un rango entre 1 a 247.", + "modbus-device-name": "Nombre del dispositivo", + "modbus-poll-period": "Período de sondeo (ms)", + "modbus-attributes-poll-period": "Atributos del período de sondeo (ms)", + "modbus-timeseries-poll-period": "Período de sondeo de las series temporales (ms)", + "modbus-poll-period-range": "El período de sondeo debe ser una valor positivo.", + "modbus-tag": "Etiqueta", + "modbus-function": "Función", + "modbus-register-address": "Dirección del registro", + "modbus-register-address-range": "Dirección del registro debe estar en un rango entre 0 y 65535.", + "modbus-register-bit-index": "Índice de bit", + "modbus-register-bit-index-range": "Índice de bit debe estar en un rango entre 0 y 15.", + "modbus-register-count": "Contador del registro", + "modbus-register-count-range": "Contador del registro debe ser un valor positivo.", + "modbus-byte-order": "Orden del byte", + "sync": { + "status": "Estado", + "sync": "Sincronización", + "not-sync": "No sincronización", + "last-sync-time": "Ultima hora de sincronización", + "not-available": "No disponible" + }, + "export-extensions-configuration": "Exportar configuración de extensiones", + "import-extensions-configuration": "Importar configuración de extensiones", + "import-extensions": "Importar extensiones", + "import-extension": "Importar extensión", + "export-extension": "Exportar extensión", + "file": "Archivo de extensiones", + "invalid-file-error": "Archivo de extensión no válido" + }, + "fullscreen": { + "expand": "Expandir a pantalla completa", + "exit": "Salir de pantalla completa", + "toggle": "Alternar el modo de pantalla completa", + "fullscreen": "Pantalla completa" + }, + "function": { + "function": "Función" + }, + "grid": { + "delete-item-title": "¿Está seguro de que desea eliminar este ítem?", + "delete-item-text": "¡Cuidado! Después de la confirmación, este ítem y todos los datos relacionados serán irrecuperables.", + "delete-items-title": "¿Está seguro de que desea eliminar { count, plural, 1 {1 ítem} other {# ítems} }?", + "delete-items-action-title": "Eliminar { count, plural, 1 {1 ítem} other {# ítems} }", + "delete-items-text": "¡Cuidado! Después de la confirmación se eliminarán todos los ítems seleccionados y todos los datos relacionados serán irrecuperables.", + "add-item-text": "Agregar nuevo ítem", + "no-items-text": "No se encontraron ítems", + "item-details": "Detalles del ítem", + "delete-item": "Eliminar ítem", + "delete-items": "Eliminar ítems", + "scroll-to-top": "Desplazar al inicio" + }, + "help": { + "goto-help-page": "Ir a la página de ayuda" + }, + "home": { + "home": "Página principal", + "profile": "Perfil", + "logout": "Cerrar sesión", + "menu": "Menú", + "avatar": "Avatar", + "open-user-menu": "Abrir menú de usuario" + }, + "import": { + "no-file": "Ningún archivo seleccionado", + "drop-file": "Suelte un archivo JSON o haga clic para seleccionar un archivo para cargar.", + "column-value": "Valor", + "column-title": "Título", + "column-example": "Datos de ejemplo", + "drop-file-csv": "Suelte un archivo CSV o haga clic para seleccionar un archivo para cargar.", + "column-key": "Clave de atributo/telemetría", + "csv-delimiter": "Delimitador CSV", + "csv-first-line-header": "La primera línea contiene nombres de columna.", + "csv-update-data": "Actualizar atributos/telemetría", + "import-csv-number-columns-error": "Un archivo debe contener al menos dos columnas", + "import-csv-invalid-format-error": "Formato de archivo inválido. Línea: '{{line}}'", + "column-type": { + "access-token": "Token de acceso", + "client-attribute": "Atributo de cliente", + "column-type": "Tipo de columna", + "entity-field": "Campo de entidad", + "name": "Nombre", + "server-attribute": "Atributo de servidor", + "shared-attribute": "Atributo compartido", + "timeseries": "Series temporales", + "type": "Tipo" + }, + "stepper-text": { + "select-file": "Seleccione un archivo", + "configuration": "Importar configuración", + "column-type": "Seleccionar tipo de columnas", + "creat-entities": "Creando nuevas entidades", + "done": "Hecho" + }, + "message": { + "create-entities": "Se crearon{{count}} nuevas entidades correctamente.", + "update-entities": "{{count}} entidades se actualizaron correctamente.", + "error-entities": "Se produjo un error al crear {{count}} entidades." + } + }, + "item": { + "selected": "Seleccionado" + }, + "js-func": { + "no-return-error": "La función debe devolver el valor!", + "return-type-mismatch": "La función debe devolver el valor de '{{type}}' type!", + "tidy": "Formatear" + }, + "key-val": { + "key": "Clave", + "value": "Valor", + "remove-entry": "Eliminar entrada", + "add-entry": "Agregar entrada", + "no-data": "Ninguna entrada" + }, + "layout": { + "layout": "Diseño", + "manage": "Gestionar diseños", + "settings": "Configuración de diseño", + "color": "Color", + "main": "Principal", + "right": "Derecha", + "select": "Seleccionar diseño objetivo" + }, + "legend": { + "direction": "", + "position": "Posición de la leyenda", + "show-max": "Mostrar valor máximo", + "show-min": "Mostrar valor mínimo", + "show-avg": "Mostrar valor promedio", + "show-total": "Mostrar valor total", + "settings": "Configuración de la leyenda", + "min": "mínimo", + "max": "máximo", + "avg": "promedio", + "total": "total" + }, + "login": { + "login": "Iniciar sesión", + "request-password-reset": "Restablecer contraseña", + "reset-password": "Restablecer contraseña", + "create-password": "Crear contraseña", + "passwords-mismatch-error": "¡Las contraseñas introducidas deben ser iguales!", + "password-again": "Repita la contraseña de nuevo", + "sign-in": "Por favor, inicie sesión", + "username": "Nombre de usuario (correo electrónico)", + "remember-me": "Recordarme", + "forgot-password": "¿Olvidó la contraseña?", + "password-reset": "Restablecer contraseña", + "expired-password-reset-message": "", + "new-password": "Nueva contraseña", + "new-password-again": "Repita la nueva contraseña", + "password-link-sent-message": "¡El enlace para el restablecer la contraseña fue enviado correctamente!", + "email": "Correo electrónico" + }, + "position": { + "top": "Superior", + "bottom": "Inferior", + "left": "Izquierda", + "right": "Derecha" + }, + "profile": { + "profile": "Perfil", + "change-password": "Cambiar contraseña", + "current-password": "Contraseña actual" + }, + "relation": { + "relations": "Relaciones", + "direction": "Dirección", + "search-direction": { + "FROM": "Desde", + "TO": "Hacia" + }, + "direction-type": { + "FROM": "desde", + "TO": "hacia" + }, + "from-relations": "Relaciones salientes", + "to-relations": "Relaciones entrantes", + "selected-relations": "{ count, plural, 1 {1 relación} other {# relaciones} } selecciondas", + "type": "Tipo", + "to-entity-type": "Hacia tipo de entidad", + "to-entity-name": "Hacia nombre de entidad", + "from-entity-type": "Desde tipo de entidad", + "from-entity-name": "Desde nombre de entidad", + "to-entity": "Hacia entidad", + "from-entity": "Desde entidad", + "delete": "Eliminar relación", + "relation-type": "Tipo de relación", + "relation-type-required": "Tipo de relación es requerido.", + "any-relation-type": "Algún tipo", + "add": "Agregar relación", + "edit": "Editar relación", + "delete-to-relation-title": "¿Está seguro de que desea eliminar la relación hacia la entidad '{{entityName}}'?", + "delete-to-relation-text": "¡Cuidado! Después de la confirmación, la entidad '{{entityName}}' no estará relacionada desde la entidad actual.", + "delete-to-relations-title": "¿Está seguro de que desea eliminar { count, plural, 1 {1 relación} other {# relaciones} }?", + "delete-to-relations-text": "¡Cuidado! Después de la confirmación, se eliminarán todas las relaciones seleccionadas y las entidades correspondientes no estarán relacionadas desde la entidad actual.", + "delete-from-relation-title": "¿Está seguro de que desea eliminar la relación desde la entidad '{{entityName}}'?", + "delete-from-relation-text": "¡Cuidado! Después de la confirmación, la entidad actual no será relacionada desde la entidad '{{entityName}}'.", + "delete-from-relations-title": "¿Está seguro de que desea eliminar { count, plural, 1 {1 relación} other {# relaciones} }?", + "delete-from-relations-text": "¡Cuidado! Después de la confirmación, se eliminarán todas las relaciones seleccionadas y la entidad actual no será relacionada desde las correspondientes entidades.", + "remove-relation-filter": "Eliminar filtro de relación", + "add-relation-filter": "Agregar filtro de relación", + "any-relation": "Alguna relación", + "relation-filters": "Filtros de relación", + "additional-info": "Información adicional (JSON)", + "invalid-additional-info": "No se puede analizar información adicional json." + }, + "rulechain": { + "rulechain": "Cadena de reglas", + "rulechains": "Cadenas de reglas", + "root": "Raíz", + "delete": "Eliminar cadena de reglas", + "name": "Nombre", + "name-required": "El nombre es requerido.", + "description": "Descripción", + "add": "Agregar cadena de reglas", + "set-root": "Hacer la cadena de reglas raíz", + "set-root-rulechain-title": "¿Está seguro de que desea hacer la cadena de reglas '{{ruleChainName}}' root?", + "set-root-rulechain-text": "Después de la confirmación, la cadena de reglas se volverá raíz y manejará todos los mensajes de transporte entrantes.", + "delete-rulechain-title": "¿Está seguro de que desea eliminar la cadena de reglas '{{ruleChainName}}'?", + "delete-rulechain-text": "¡Cuidado! Después de la confirmación, la cadena de reglas y todos los datos relacionados serán irrecuperables.", + "delete-rulechains-title": "¿Está seguro de que desea eliminar { count, plural, 1 {1 cadena de reglas} other {# cadenas de reglas} }?", + "delete-rulechains-action-title": "Eliminar { count, plural, 1 {1 cadena de reglas} other {# cadenas de reglas} }", + "delete-rulechains-text": "¡Cuidado! Después de la confirmación se eliminarán todas las cadenas de reglas seleccionadas y todos los datos relacionados serán irrecuperables.", + "add-rulechain-text": "Agregar nueva cadena de reglas", + "no-rulechains-text": "Cadenas de reglas no encontradas", + "rulechain-details": "Detalles de la cadena de reglas", + "details": "Detalles", + "events": "Eventos", + "system": "Sistema", + "import": "Importar cadena de reglas", + "export": "Exportar cadena de reglas", + "export-failed-error": "No se puede exportar la cadena de reglas: {{error}}", + "create-new-rulechain": "Crear nueva cadena de reglas", + "rulechain-file": "Archivo de la cadena de reglas", + "invalid-rulechain-file-error": "No se puede importar la cadena de reglas: Estructura de datos de la cadena de reglas inválida.", + "copyId": "Copiar ID de la cadena de reglas", + "idCopiedMessage": "ID de la cadena de reglas ha sido copiada al portapapeles", + "select-rulechain": "Seleccionar cadena de reglas", + "no-rulechains-matching": "Cadenas de reglas que coincidan con '{{entity}}' no fueron encontradas.", + "rulechain-required": "Cadena de reglas es requerida", + "management": "Gestión de reglas", + "debug-mode": "Mode de depuración" + }, + "rulenode": { + "details": "Detalles", + "events": "Eventos", + "search": "Nodos de búsqueda", + "open-node-library": "Abrir librería de nodos", + "add": "Agregar nodo de reglas", + "name": "Nombre", + "name-required": "El nombre es requerido.", + "type": "Tipo", + "description": "Descripción", + "delete": "Eliminar nodo de reglas", + "select-all-objects": "Seleccionar todos los nodos y conexiones", + "deselect-all-objects": "Deshacer selección de todos los nodos y conexiones", + "delete-selected-objects": "Eliminar nodos y conexiones seleccionados", + "delete-selected": "Eliminar seleccionado", + "select-all": "Seleccionar todos", + "copy-selected": "Copiar seleccionado", + "deselect-all": "Deshace selección de todos", + "rulenode-details": "Detalles del nodo de reglas", + "debug-mode": "Modo de depuración", + "configuration": "Configuración", + "link": "Enlace", + "link-details": "Detalles del enlace del nodo de reglas", + "add-link": "Agregar enlace", + "link-label": "Etiqueta del enlace", + "link-label-required": "Etiqueta del enlace es requerida.", + "custom-link-label": "Etiqueta del enlace personalizada", + "custom-link-label-required": "Etiqueta del enlace personalizado es requerida.", + "link-labels": "Etiquetas del enlace", + "link-labels-required": "Etiquetas del enlace son requeridas.", + "no-link-labels-found": "No se encontraron etiquetas de enlaces", + "no-link-label-matching": "'{{label}}' no encontrada.", + "create-new-link-label": "Crear una nueva!", + "type-filter": "Filtro", + "type-filter-details": "Filtrar mensajes entrantes con las condiciones configuradas", + "type-enrichment": "Enriquecimiento", + "type-enrichment-details": "Agregar información adicional en mensajes de metadatos", + "type-transformation": "Transformación", + "type-transformation-details": "Cambiar carga útil del Mensaje y Metadatos", + "type-action": "Acción", + "type-action-details": "Ejecutar acción especial", + "type-external": "Externo", + "type-external-details": "Interactuar con sistemas externos", + "type-rule-chain": "Cadena de reglas", + "type-rule-chain-details": "Reenvía los mensajes entrantes a la cadena de reglas especificada", + "type-input": "Entrada", + "type-input-details": "Entrada lógica de la Cadena de Reglas, reenvíar los mensajes entrantes al siguiente nodo de regla relacionado.", + "type-unknown": "Desconocido", + "type-unknown-details": "Regla de nodo no resuelta", + "directive-is-not-loaded": "La directiva de configuración definida '{{directiveName}}' no está disponible.", + "ui-resources-load-error": "Error al cargar los recursos de configuración ui.", + "invalid-target-rulechain": "¡No se puede resolver la cadena de reglas objetivo!", + "test-script-function": "Probar función script", + "message": "Mensaje", + "message-type": "Tipo de mensaje", + "select-message-type": "Seleccionar tipo de mensaje", + "message-type-required": "Tipo de mensaje es requerido", + "metadata": "Metadatos", + "metadata-required": "La entradas de matadatos no pueden estar vacías.", + "output": "Salida", + "test": "Prueba", + "help": "Ayuda", + "reset-debug-mode": "Restablecer el modo de depuración en todos los nodos" + }, + "tenant": { + "tenant": "Organización", + "tenants": "Organizaciones", + "management": "Gestión de la organización", + "add": "Agregar organización", + "admins": "Administradores", + "manage-tenant-admins": "Gestionar administradores de la organización", + "delete": "Eliminar organización", + "add-tenant-text": "Agregar nueva organización", + "no-tenants-text": "Organizaciones no encontradas", + "tenant-details": "Detalles de la organización", + "delete-tenant-title": "¿Está seguro de que desea eliminar la organización '{{tenantTitle}}'?", + "delete-tenant-text": "¡Cuidado! Después de la confirmación, la organización y todos los datos relacionados serán irrecuperables.", + "delete-tenants-title": "¿Está seguro de que desea eliminar { count, plural, 1 {1 organización} other {# organizaciones} }?", + "delete-tenants-action-title": "Eliminar { count, plural, 1 {1 organización} other {# organizaciones} }", + "delete-tenants-text": "¡Cuidado! Después de la confirmación se eliminarán todas las organizaciones seleccionadas y todos los datos relacionados serán irrecuperables.", + "title": "Título", + "title-required": "Título es requerido.", + "description": "Descripción", + "details": "Detalles", + "events": "Eventos", + "copyId": "Copiar ID de la organización", + "idCopiedMessage": "ID de la organización ha sido copiado al portapapeles", + "select-tenant": "Seleccionar organización", + "no-tenants-matching": "No se encontraron organizaciones que coincidan con '{{entity}}'.", + "tenant-required": "Organización es requerida" + }, + "timeinterval": { + "seconds-interval": "{ seconds, plural, 1 {1 segundo} other {# segundos} }", + "minutes-interval": "{ minutes, plural, 1 {1 minuto} other {# minutos} }", + "hours-interval": "{ hours, plural, 1 {1 hora} other {# horas} }", + "days-interval": "{ days, plural, 1 {1 día} other {# días} }", + "days": "Días", + "hours": "Horas", + "minutes": "Minutos", + "seconds": "Segundos", + "advanced": "Avanzado" + }, + "timewindow": { + "days": "{ days, plural, 1 { día } other {# días } }", + "hours": "{ hours, plural, 0 { horas } 1 {1 hora } other {# horas } }", + "minutes": "{ minutes, plural, 0 { minutos } 1 {1 minuto } other {# minutos } }", + "seconds": "{ seconds, plural, 0 { segundos } 1 {1 segundo } other {# segundos } }", + "realtime": "Tiempo real", + "history": "Historia", + "last-prefix": "último(s)", + "period": "desde {{ startTime }} hasta {{ endTime }}", + "edit": "Editar ventana de tiempo", + "date-range": "Rango de fecha", + "last": "Último(s)", + "time-period": "Período de tiempo" + }, + "user": { + "user": "Usuario", + "users": "Usuarios", + "customer-users": "Usuarios cliente", + "tenant-admins": "Administradores de la Organización", + "sys-admin": "Administrador del sistema", + "tenant-admin": "Administrador de la organización", + "customer": "Cliente", + "anonymous": "Anónimo", + "add": "Agregar Usuario", + "delete": "Eliminar usuario", + "add-user-text": "Agregar nuevo usuario", + "no-users-text": "No se encontraron usuarios", + "user-details": "Detalles de usuario", + "delete-user-title": "¿Está seguro de que desea eliminar el usuario '{{userEmail}}'?", + "delete-user-text": "¡Cuidado! Después de la confirmación, el usuario y todos los datos relacionados serán irrecuperables.", + "delete-users-title": "¿Está seguro de que desea eliminar { count, plural, 1 {1 usuario} other {# usuarios} }?", + "delete-users-action-title": "Borrar { count, plural, 1 {1 usuario} other {# usuarios} }", + "delete-users-text": "¡Cuidado! Después de la confirmación se eliminarán todas los usuarios seleccionados y todos los datos relacionados serán irrecuperables.", + "activation-email-sent-message": "¡Correo electrónico de activación fue enviado correctamente!", + "resend-activation": "Reenviar activación", + "email": "Correo electrónico", + "email-required": "Correo electrónico es requerido.", + "invalid-email-format": "Formato de correo electrónico inválido.", + "first-name": "Nombre", + "last-name": "Apellido", + "description": "Descripción", + "default-dashboard": "Panel predeterminado", + "always-fullscreen": "Siempre pantalla completa", + "select-user": "Seleccionar usuario", + "no-users-matching": "Usuarios que coincidan con '{{entity}}' no fueron encontrados.", + "user-required": "Usuario es requerido", + "activation-method": "Método de activación", + "display-activation-link": "Mostrar enlace de activación", + "send-activation-mail": "Enviar correo electrónico de activación", + "activation-link": "Enlace de activación de usuario", + "activation-link-text": "Para activar el usuario, utilice el siguiente enlace de activación :", + "copy-activation-link": "Copiar enlace de activación", + "activation-link-copied-message": "El enlace de activación de usuario ha sido copiado al portapapeles", + "details": "Detalles", + "login-as-tenant-admin": "Iniciar sesión como Administrador de la Organización", + "login-as-customer-user": "Iniciar sesión como Usuario cliente" + }, + "value": { + "type": "Tipo de valor", + "string": "Cadena de caracteres", + "string-value": "Valor de la cadena de caracteres", + "integer": "Entero", + "integer-value": "Valor entero", + "invalid-integer-value": "Valor de entero inválido", + "double": "Doble", + "double-value": "Valor doble", + "boolean": "Booleano", + "boolean-value": "Valor booleano", + "false": "Falso", + "true": "Verdadero", + "long": "Largo" + }, + "widget": { + "widget-library": "Librería de widgets", + "widget-bundle": "Paquete de widgets", + "select-widgets-bundle": "Seleccionar paquete de widgets", + "management": "Gestión de widget", + "editor": "Editor de Widget", + "widget-type-not-found": "Problema cargando configuración de widget.
Probablemente el tipo de widget asociado fue eliminado.", + "widget-type-load-error": "El widget no fue cargado debido a los siguientes errores:", + "remove": "Eliminar widget", + "edit": "Editar widget", + "remove-widget-title": "¿Está seguro de que desea eliminar el widget '{{widgetTitle}}'?", + "remove-widget-text": "Después de la confirmación, el widget y todos los datos relacionados serán irrecuperables.", + "timeseries": "Series temporales", + "search-data": "Buscar datos", + "no-data-found": "No se encontraron datos", + "latest-values": "Últimos valores", + "rpc": "Widget de control", + "alarm": "Widget de alarma", + "static": "Widget estático", + "select-widget-type": "Seleccionar tipo de widget", + "missing-widget-title-error": "¡Título del widget debe ser especificado!", + "widget-saved": "Widget guardado", + "unable-to-save-widget-error": "¡No se puede guardar widget! ¡El widget tiene errores!", + "save": "Guardar widget", + "saveAs": "Guardar widget como", + "save-widget-type-as": "Guardar tipo de widget como", + "save-widget-type-as-text": "Por favor escriba el nuevo título del widget y/o seleccionar paquete de widgets objetivo", + "toggle-fullscreen": "Alternar pantalla completa", + "run": "Ejecutar widget", + "title": "Título del widget", + "title-required": "Título del widget es requerido.", + "type": "Tipo de widget", + "resources": "Recursos", + "resource-url": "URL JavaScript/CSS", + "remove-resource": "Eliminar recurso", + "add-resource": "Agregar recurso", + "html": "HTML", + "tidy": "Formatear", + "css": "CSS", + "settings-schema": "Esquema de configuración", + "datakey-settings-schema": "Esquema de configuración de clave de datos", + "javascript": "Javascript", + "js": "JS", + "remove-widget-type-title": "¿Está seguro de que desea eliminar el tipo de widget '{{widgetName}}'?", + "remove-widget-type-text": "Después de la confirmación, el tipo de widget y todos los datos relacionados serán irrecuperables.", + "remove-widget-type": "Eliminar tipo de widget", + "add-widget-type": "Agregar nuevo tipo de widget", + "widget-type-load-failed-error": "¡Error al cargar el tipo de widget!", + "widget-template-load-failed-error": "¡Error al cargar la plantilla del widget!", + "add": "Agregar widget", + "undo": "Deshacer cambios en el widget", + "export": "Exportar widget" + }, + "widget-action": { + "custom-pretty": "", + "header-button": "Botón del encabezado del widget", + "open-dashboard-state": "Navegar a nuevo estado del panel", + "update-dashboard-state": "Actualizar estado vigente del panel", + "open-dashboard": "Navegar a otro panel", + "custom": "Acción personalizada", + "target-dashboard-state": "Estado del panel objetivo", + "target-dashboard-state-required": "Estado del panel objetivo es requerido", + "set-entity-from-widget": "Asignar entidad desde widget", + "target-dashboard": "Panel objetivo", + "open-right-layout": "Abrir diseño del panel derecho (vista móvil)" + }, + "widgets-bundle": { + "current": "Paquete actual", + "widgets-bundles": "Paquetes de widgets", + "add": "Agregar paquete de widgets", + "delete": "Eliminar paquete de widgets", + "title": "Título", + "title-required": "Título es requerido.", + "add-widgets-bundle-text": "Agregar nuevo paquete de widgets", + "no-widgets-bundles-text": "No se encontraron paquetes de widgets", + "empty": "Paquete de widgets está vacío", + "details": "Detalles", + "widgets-bundle-details": "Detalles del paquete de widgets", + "delete-widgets-bundle-title": "¿Está seguro de que desea eliminar el paquete de widgets '{{widgetsBundleTitle}}'?", + "delete-widgets-bundle-text": "¡Cuidado! Después de la confirmación, el paquete de widgets y todos los datos relacionados serán irrecuperables.", + "delete-widgets-bundles-title": "¿Está seguro de que desea eliminar { count, plural, 1 {1 paquete de widgets} other {# paquetes de widgets} }?", + "delete-widgets-bundles-action-title": "Eliminar { count, plural, 1 {1 paquete de widgets} other {# paquetes de widgets} }", + "delete-widgets-bundles-text": "¡Cuidado! Después de la confirmación se eliminarán todas los paquetes de widgets seleccionados y todos los datos relacionados serán irrecuperables.", + "no-widgets-bundles-matching": "Paquetes de widgets que coincidan con '{{widgetsBundle}}' no fueron encontrados.", + "widgets-bundle-required": "Paquete de widgets es requerido.", + "system": "Sistema", + "import": "Importar paquete de widgets", + "export": "Exportar paquete de widgets", + "export-failed-error": "No se puede exportar paquete de widgets: {{error}}", + "create-new-widgets-bundle": "Crear nuevo paquete de widgets", + "widgets-bundle-file": "Archivo de paquete de widgets", + "invalid-widgets-bundle-file-error": "No se puede importar paquete de widgets: Estructura de datos del paquete de widgets inválida." + }, + "widget-config": { + "data": "Datos", + "settings": "Configuración", + "advanced": "Avanzado", + "title": "Título", + "general-settings": "Configuración general", + "display-title": "Mostrar título", + "drop-shadow": "Colocar sombra", + "enable-fullscreen": "Habilitar pantalla completa", + "background-color": "Color de fondo", + "text-color": "Color del texto", + "padding": "Relleno", + "margin": "Margen", + "widget-style": "Estilo del widget", + "title-style": "Estilo del título", + "mobile-mode-settings": "Configuración del modo móvil", + "order": "Orden", + "height": "Altura", + "units": "Símbolo especial para mostrar junto al valor.", + "decimals": "Número de dígitos después del punto flotante", + "timewindow": "Ventana de tiempo", + "use-dashboard-timewindow": "Utilizar ventana de tiempo del panel", + "display-timewindow": "Mostrar ventana de tiempo", + "display-legend": "Mostrar leyenda", + "datasources": "Orígenes de datos", + "maximum-datasources": "Máximo { count, plural, 1 {1 origen de datos permitido.} other {# orígenes de datos permitidos} }", + "datasource-type": "Tipo", + "datasource-parameters": "Parámetros", + "remove-datasource": "Eliminar origen de datos", + "add-datasource": "Agregar origen de datos", + "target-device": "Dispositivo objetivo", + "alarm-source": "Origen de la alarma", + "actions": "Acciones", + "action": "Acción", + "add-action": "Agregar acción", + "search-actions": "Buscar acciones", + "action-source": "Origen de acción", + "action-source-required": "Fuente de acción es requerida.", + "action-name": "Nombre", + "action-name-required": "Nombre de acción es requerido.", + "action-name-not-unique": "Ya existe otra acción con el mismo nombre.
El nombre de la acción debe ser único dentro del mismo orígen de acción.", + "action-icon": "Icono", + "action-type": "Tipo", + "action-type-required": "Tipo de acción es requerido.", + "edit-action": "Editar acción", + "delete-action": "Eliminar acción", + "delete-action-title": "Eliminar acción del widget", + "delete-action-text": "¿Está seguro de que desea eliminar la acción del widget con nombre '{{actionName}}'?", + "display-icon": "Mostrar icono del título", + "icon-color": "Color del icono", + "icon-size": "Tamaño del icono" + }, + "widget-type": { + "import": "Importar tipo de widget", + "export": "Exportar tipo de widget", + "export-failed-error": "No se puede exportar tipo de widget: {{error}}", + "create-new-widget-type": "Crear nuevo tipo de widget", + "widget-type-file": "Archivo de tipo de widget", + "invalid-widget-type-file-error": "No se puede importar tipo de widget: Estructura de datos del tipo de widget es inválida." + }, + "widgets": { + "date-range-navigator": { + "localizationMap": { + "Sun": "Dom.", + "Mon": "Lun.", + "Tue": "Mar.", + "Wed": "Mié", + "Thu": "Jue.", + "Fri": "Vie.", + "Sat": "Sáb.", + "Jan": "Ene.", + "Feb": "Feb.", + "Mar": "Mar.", + "Apr": "Abr.", + "May": "May.", + "Jun": "Jun.", + "Jul": "Jul.", + "Aug": "Ago.", + "Sep": "Sept.", + "Oct": "Oct.", + "Nov": "Nov.", + "Dec": "Dic.", + "January": "Enero", + "February": "Febrero", + "March": "Marzo", + "April": "Abril", + "June": "Junio", + "July": "Julio", + "August": "Agosto", + "September": "Septiembre", + "October": "Octubre", + "November": "Noviembre", + "December": "Diciembre", + "Custom Date Range": "Intervalo de fechas personalizado", + "Date Range Template": "Plantilla de rango de fechas", + "Today": "Hoy", + "Yesterday": "Ayer", + "This Week": "Esta semana", + "Last Week": "La semana pasada", + "This Month": "Este mes", + "Last Month": "El mes pasado", + "Year": "Año", + "This Year": "Este año", + "Last Year": "Último", + "Date picker": "Date picker", + "Hour": "Hora", + "Day": "Día", + "Week": "Semana", + "2 weeks": "2 Semanas", + "Month": "Mes", + "3 months": "3 Meses", + "6 months": "6 Meses", + "Custom interval": "Intervalo personalizado", + "Interval": "Intervalo", + "Step size": "Numero de pie", + "Ok": "De acuerdo" + } + } + }, + "icon": { + "icon": "Icono", + "select-icon": "Seleccionar icono", + "material-icons": "Iconos de material design", + "show-all": "Mostrar todos los iconos" + }, + "custom": { + "widget-action": { + "action-cell-button": "Botón de acción de celda", + "row-click": "Clic en la fila", + "polygon-click": "Clic en la fila", + "marker-click": "Clic en el polígono", + "tooltip-tag-action": "Acción de etiqueta para globo de ayuda", + "node-selected": "Clic en el nodo seleccionado", + "element-click": "Clic en el elemento HTML" + } + }, + "language": { + "language": "Lenguaje", + "locales": { + "de_DE": "Alemán", + "fr_FR": "Francés", + "zh_CN": "Chino", + "en_US": "Inglés", + "it_IT": "Italiano", + "ko_KR": "Coreano", + "ru_RU": "Ruso", + "es_ES": "Español", + "ja_JA": "Japonés", + "tr_TR": "Turco", + "fa_IR": "Persa", + "uk_UA": "Ucraniano", + "cs_CZ": "Checo" + } + } +} diff --git a/ui-ngx/src/assets/locale/locale.constant-fa_IR.json b/ui-ngx/src/assets/locale/locale.constant-fa_IR.json new file mode 100644 index 0000000000..ee9c011073 --- /dev/null +++ b/ui-ngx/src/assets/locale/locale.constant-fa_IR.json @@ -0,0 +1,1643 @@ +{ + "access": { + "unauthorized": "غير مجاز", + "unauthorized-access": "دسترسي غير مجاز", + "unauthorized-access-text": "!شما بايد وارد شويد تا به اين منبع دسترسي پيدا کنيد", + "access-forbidden": "دسترسي ممنوع", + "access-forbidden-text": "!اگر هنوز تمايل داريد به اينجا دسترسي پيدا کنيد، تلاش کنيد با نام کاربري ديگري وارد شويد
.شما حق دسترسي به اينجا را نداريد", + "refresh-token-expired": "اين بخش، منقضي شده است", + "refresh-token-failed": "بازيابي اين بخش ممکن نيست" + }, + "action": { + "activate": "فعال سازي", + "suspend": "معلّق", + "save": "ذخيره سازي", + "saveAs": "ذخيره سازي در", + "cancel": "لغو", + "ok": "قبول", + "delete": "حذف", + "add": "اضافه", + "yes": "بله", + "no": "خير", + "update": "به روز کردن", + "remove": "حذف", + "search": "جستجو", + "clear-search": "پاک کردن جستجو", + "assign": "تخصيص", + "unassign": "لغو تخصيص", + "share": "به اشتراک گذاري", + "make-private": "شخصي سازي", + "apply": "اعمال", + "apply-changes": "اعمال تغييرات", + "edit-mode": "حالت ويرايش", + "enter-edit-mode": "ورود به حالت ويرايش", + "decline-changes": "عدم پذيرش تغييرات", + "close": "بستن", + "back": "بازگشت", + "run": "اجرا", + "sign-in": "!ورود", + "edit": "ويرايش", + "view": "نمايش", + "create": "ايجاد", + "drag": "کشيدن", + "refresh": "بازيابي", + "undo": "برگرداندن آخرين عمل", + "copy": "رونوشت", + "paste": "الصاق رونوشت", + "copy-reference": "رونوشت مرجع", + "paste-reference": "رونوشت مرجع", + "import": "وارد کردن", + "export": "صدور", + "share-via": "{{provider}} اشتراک گذاري از طريق" + }, + "aggregation": { + "aggregation": "تجميع", + "function": "تابع تجميع داده ها", + "limit": "بيشترين مقادير", + "group-interval": "فاصله گروه بندي", + "min": "کمترين", + "max": "بيشترين", + "avg": "ميانگين", + "sum": "جمع", + "count": "شمارش", + "none": "هيچکدام" + }, + "admin": { + "general": "عمومي", + "general-settings": "تنظيمات عمومي", + "outgoing-mail": "پيام خروجي", + "outgoing-mail-settings": "تنظيمات پيام خروجي", + "system-settings": "تنظيمات سيستم", + "test-mail-sent": "!ارسال پيام آزمايشي موفقيت آميز بود", + "base-url": "مبنا URL", + "base-url-required": ".مبنا مورد نياز است URL", + "mail-from": "... پيام از", + "mail-from-required": ".پيام از ... مورد نياز است", + "smtp-protocol": "SMTP قرارداد", + "smtp-host": "SMTP ميزبان", + "smtp-host-required": ".مورد نياز است SMTP ميزبان", + "smtp-port": "SMTP درگاه", + "smtp-port-required": ".فراهم کنيد SMTP شما بايد يک درگاه", + "smtp-port-invalid": ".معتبر باشد SMTP به نظر نمي آيد يک درگاه", + "timeout-msec": "مهلت (msec)", + "timeout-required": ".مهلت مورد نياز است", + "timeout-invalid": ".مهلت، به نظر نمي آيد معتبر باشد", + "enable-tls": "TLS فعال سازي", + "send-test-mail": "ارسال پيام آزمايشي" + }, + "alarm": { + "alarm": "هشدار", + "alarms": "هشدارها", + "select-alarm": "انتخاب هشدار", + "no-alarms-matching": ".يافت نشد '{{entity}}' هيچ هشداري مطابق", + "alarm-required": "هشدار مورد نياز است", + "alarm-status": "وضعيت هشدار", + "search-status": { + "ANY": "هر", + "ACTIVE": "فعال", + "CLEARED": "پاک شده", + "ACK": "تصديق شده", + "UNACK": "تصديق نشده" + }, + "display-status": { + "ACTIVE_UNACK": "تصديق نشده فعال", + "ACTIVE_ACK": "تصديق شده فعال", + "CLEARED_UNACK": "تصديق نشده پاک شده", + "CLEARED_ACK": "تصديق شده پاک شده" + }, + "no-alarms-prompt": "هيچ هشداري يافت نشد", + "created-time": "زمان ايجاد", + "type": "نوع", + "severity": "شدت", + "originator": "مبدأ", + "originator-type": "نوع مبدأ", + "details": "جزئيات", + "status": "وضعيت", + "alarm-details": "جزئيات هشدار", + "start-time": "زمان شروع", + "end-time": "زمان پايان", + "ack-time": "زمان تصديق", + "clear-time": "زمان پاک شدن", + "severity-critical": "بحراني", + "severity-major": "مهم", + "severity-minor": "جزئي", + "severity-warning": "اخطار", + "severity-indeterminate": "نامشخص", + "acknowledge": "تصديق", + "clear": "پاک کردن", + "search": "جستجوي هشدارها", + "selected-alarms": "اننخاب شده { count, plural, 1 {1 هشدار} other {# هشدارها} }", + "no-data": "هيچ داده اي براي نمايش نيست", + "polling-interval": "هشدار دهنده فاصله نمونه برداري (sec)", + "polling-interval-required": ".هشدار دهنده فاصله نمونه برداري مورد نياز است", + "min-polling-interval-message": ".حداقل فاصله مجاز نمونه برداري، 1 ثانيه است", + "aknowledge-alarms-title": "{ count, plural, 1 {1 هشدار} other {# هشدارها} } تصديق", + "aknowledge-alarms-text": "اطمينان داريد؟ { count, plural, 1 {1 هشدار} other {# هشدارها} } آيا شما از تصديق", + "aknowledge-alarm-title": "تصديق هشدار", + "aknowledge-alarm-text": "آيا شما از تصديق هشدار اطمينان داريد؟", + "clear-alarms-title": "{ count, plural, 1 {1 هشدار} other {# هشدارها} } پاک کردن", + "clear-alarms-text": "اطمينان داريد؟ { count, plural, 1 {1 هشدار} other {# هشدارها} } آيا شما از پاک کردن", + "clear-alarm-title": "پاک کردن هشدار", + "clear-alarm-text": "آيا شما از پاک کردن هشدار اطمينان داريد؟", + "alarm-status-filter": "فيلتر وضعيت هشدار" + }, + "alias": { + "add": "افزودن نام مستعار", + "edit": "ويرايش نام مستعار", + "name": "نام مستعار", + "name-required": "نام مستعار مورد نياز است", + "duplicate-alias": ".در حال حاضر نام مستعار مشابهي وجود دارد", + "filter-type-single-entity": "موجودي تکي", + "filter-type-entity-list": "ليست موجودي", + "filter-type-entity-name": "نام موجودي", + "filter-type-state-entity": "موجودي از وضعيت داشبورد", + "filter-type-state-entity-description": "پارامترهاي موجودي گرفته شده از وضعيت داشبورد", + "filter-type-asset-type": "نوع دارايي", + "filter-type-asset-type-description": "'{{assetType}}' دارايي هاي نوع", + "filter-type-asset-type-and-name-description": ".شروع مي شود '{{prefix}}' که نامشان با '{{assetType}}' دارايي هاي نوع", + "filter-type-device-type": "نوع دستگاه", + "filter-type-device-type-description": "'{{deviceType}}' دستگاه هاي نوع", + "filter-type-device-type-and-name-description": ".شروع مي شود '{{prefix}}' که نامشان با '{{deviceType}}' دستگاه هاي نوع", + "filter-type-entity-view-type": "نوع نمايش موجودي", + "filter-type-entity-view-type-description": "'{{entityView}}' نمايش هاي موجودي نوع ", + "filter-type-entity-view-type-and-name-description": ".شروع مي شود '{{prefix}}' که نامشان با '{{entityView}}' نمايش هاي موجودي نوع", + "filter-type-relations-query": "پرس و جو درمورد ارتباطات", + "filter-type-relations-query-description": ". دارند {{direction}} {{rootEntity}} را {{relationType}} که ارتباط {{entities}}", + "filter-type-asset-search-query": "پرس و جو درمورد جستجوي دارايي", + "filter-type-asset-search-query-description": ".دارند {{direction}} {{rootEntity}} را {{relationType}} که ارتباط{{assetTypes}} دارايي ها از انواع", + "filter-type-device-search-query": "پرس و چو درمورد جستجوي دستگاه", + "filter-type-device-search-query-description": ".دارند {{direction}} {{rootEntity}} را {{relationType}} که ارتباط{{deviceTypes}} دستگاه ها از انواع", + "filter-type-entity-view-search-query": "پرس و جو درمورد جستجوي نمايش موجودي", + "filter-type-entity-view-search-query-description": ".دارند {{direction}} {{rootEntity}} را {{relationType}} که ارتباط{{entityViewTypes}} نمايش هاي موجودي از انواع", + "entity-filter": "فيلتر موجودي", + "resolve-multiple": "تصميم با توجه به موجودي هاي متعدد", + "filter-type": "نوع فيلتر", + "filter-type-required": ".نوع فيلتر مورد نياز است", + "entity-filter-no-entity-matched": ".هيچ موجودي منطبق بر فيلتر مشخص شده يافت نشد", + "no-entity-filter-specified": ".هيچ فيلتر موجودي اي تعيين نشده است", + "root-state-entity": "موجودي وضعيت داشبورد به عنوان پايه استفاده شود", + "root-entity": "موجودي پايه", + "state-entity-parameter-name": "نام پارامتر موجودي وضعيت", + "default-state-entity": "موجودي وضعيت پيش فرض", + "default-entity-parameter-name": "به صورت پيش فرض", + "max-relation-level": "بالاترين سطح ارتباط", + "unlimited-level": "سطح نامحدود", + "state-entity": "موجودي وضعيت داشبورد", + "all-entities": "تمام موجودي ها", + "any-relation": "هر" + }, + "asset": { + "asset": "دارايي", + "assets": "دارايي ها", + "management": "مديريت دارايي", + "view-assets": "نمايش دارايي ها", + "add": "افزودن دارايي", + "assign-to-customer": "تخصيص به مشتري", + "assign-asset-to-customer": "تخصيص دارايي(ها) به مشتري", + "assign-asset-to-customer-text": "لطفا دارايي ها را انتخاب کنيد تا به مشتري تخصيص يابد", + "no-assets-text": "هيچ دارايي اي يافت نشد", + "assign-to-customer-text": "لطفا مشتري را انتخاب کنيد تا دارايي(ها) تخصيص يابد", + "public": "عمومي", + "assignedToCustomer": "تخصيص يافته به مشتري", + "make-public": "عمومي سازي دارايي", + "make-private": "شخصي سازي دارايي", + "unassign-from-customer": "لغو تخصيص از مشتري", + "delete": "حذف دارايي", + "asset-public": "دارايي عمومي است", + "asset-type": "نوع دارايي", + "asset-type-required": ".نوع دارايي مورد نياز است", + "select-asset-type": "انتخاب کردن نوع دارايي", + "enter-asset-type": "وارد کردن نوع دارايي", + "any-asset": "هر دارايي", + "no-asset-types-matching": ".يافت نشد '{{entitySubtype}}' هيچ دارايي منطبق بر", + "asset-type-list-empty": ".هيچيک از انواع دارايي انتخاب نشد", + "asset-types": "انواع دارايي", + "name": "نام", + "name-required": ".نام مورد نياز است", + "description": "توصيف", + "type": "نوع", + "type-required": ".نوع مورد نياز است", + "details": "جزئيات", + "events": "رويدادها", + "add-asset-text": "افزودن دارايي جديد", + "asset-details": "جزئيات دارايي", + "assign-assets": "تخصيص دارايي ها", + "assign-assets-text": "به مشتري { count, plural, 1 {1 دارايي} other {# دارايي} } تخصيص", + "delete-assets": "حذف دارايي ها", + "unassign-assets": "لغو تخصيص دارايي ها", + "unassign-assets-action-title": "از مشتري { count, plural, 1 {1 دارايي} other {# دارايي} } لغو تخصيص", + "assign-new-asset": "تخصيص دارايي جديد", + "delete-asset-title": "مطمئنيد؟ '{{assetName}}' آيا از حذف دارايي", + "delete-asset-text": ".مراقب باشيد، پس از تأييد، دارايي و تمام داده هاي مربوطه غير قابل بازيابي مي شوند", + "delete-assets-title": "مطمئنيد؟ { count, plural, 1 {1 دارايي} other {# دارايي} } آيا از حذف", + "delete-assets-action-title": "{ count, plural, 1 {1 دارايي} other {# دارايي} } حذف", + "delete-assets-text": ".مراقب باشيد، پس از تأييد، تمام دارايي هاي انتخاب شده حذف، و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند", + "make-public-asset-title": "مطمئنيد؟ '{{assetName}}' آيا از عمومي سازي", + "make-public-asset-text": ".پس از تأييد، دارايي و تمامي داده هايش عمومي و قابل دسترسي براي ديگران مي شود", + "make-private-asset-title": "مطمئنيد؟ '{{assetName}}' آيا از شخصي سازي دارايي", + "make-private-asset-text": ".پس از تأييد، دارايي و تمامي داده هايش شخصي و خارج از دسترس ديگران مي شوند", + "unassign-asset-title": "مطمئنيد؟ '{{assetName}}' آيا از لغو تخصيص دارايي", + "unassign-asset-text": ".پس از تأييد، دارايي، لغو تخصيص و خارج از دسترس مشتري مي شود", + "unassign-asset": "لغو تخصيص دارايي", + "unassign-assets-title": "مطمئنيد؟ { count, plural, 1 {1 دارايي} other {# دارايي} } آيا از لغو تخصيص", + "unassign-assets-text": ".پس از تأييد، تمام دارايي هاي انتخاب شده، لغو تخصيص و خارج از دسترس مشتري مي شوند", + "copyId": "دارايي ID رونوشت از", + "idCopiedMessage": "دارايي در حافظه موقت رونوشت شد ID", + "select-asset": "انتخاب دارايي", + "no-assets-matching": ".يافت نشد '{{entity}}' هيچ دارايي منطبق بر", + "asset-required": "دارايي مود نياز است", + "name-starts-with": "نام دارايي شروع مي شود با" + }, + "attribute": { + "attributes": "ويژگي ها", + "latest-telemetry": "آخرين سنجش", + "attributes-scope": "حوزه ويژگي هاي موجودي", + "scope-latest-telemetry": "آخرين سنجش", + "scope-client": "ويژگي هاي مشتري", + "scope-server": "ويژگي هاي سِروِر", + "scope-shared": "ويژگي هاي مشترک", + "add": "افزودن ويژگي ها", + "key": "کليد", + "last-update-time": "آخرين زمان به روز رساني", + "key-required": ".کليد ويژگي مورد نياز است", + "value": "مقدار", + "value-required": ".مقدار ويژگي مورد نياز است", + "delete-attributes-title": "مطمئنيد؟ { count, plural, 1 {1 ويژگي} other {# ويژگي} } آيا از حذف", + "delete-attributes-text": ".مراقب باشيد، پس از تأييد، تمام ويژگي هاي انتخاب شده حذف مي گردند", + "delete-attributes": "حذف ويژگي ها", + "enter-attribute-value": "وارد کردن مقدار ويژگي", + "show-on-widget": "نمايش بر ويجت", + "widget-mode": "حالت ويجت", + "next-widget": "ويجت بعد", + "prev-widget": "ويجت قبل", + "add-to-dashboard": "افزودن به داشبورد", + "add-widget-to-dashboard": "افزودن ويجت به داشبورد", + "selected-attributes": "انتخاب شدند { count, plural, 1 {1 ويژگي} other {# ويژگي} }", + "selected-telemetry": "انتخاب شد { count, plural, 1 {1 واحد سنجش} other {# واحد سنجش} }" + }, + "audit-log": { + "audit": "بازبيني", + "audit-logs": "داده هاي ثبت شده از بازبيني", + "timestamp": "برچسب زمان", + "entity-type": "نوع موحودي", + "entity-name": "نام موجودي", + "user": "کاربر", + "type": "نوع", + "status": "وضعيت", + "details": "جزئيات", + "type-added": "اضافه شده", + "type-deleted": "حذف شده", + "type-updated": "به روز", + "type-attributes-updated": "ويژگي ها به روز شد", + "type-attributes-deleted": "ويژگي ها حذف شد", + "type-rpc-call": "RPC فراخواني", + "type-credentials-updated": "اعتبارنامه ها به روز شد", + "type-assigned-to-customer": "به مشتري تخصيص يافت", + "type-unassigned-from-customer": "از مشتري لغو تخصيص شد", + "type-activated": "فعال شد", + "type-suspended": "معلق", + "type-credentials-read": "اعتبارنامه ها خوانده شد", + "type-attributes-read": "ويژگي ها خوانده شد", + "type-relation-add-or-update": "ارتباط به روز شد", + "type-relation-delete": "ارتباط حذف شد", + "type-relations-delete": "تمام ارتباطات حذف شد", + "type-alarm-ack": "تصديق شده", + "type-alarm-clear": "پاک شده", + "status-success": "موفقيت", + "status-failure": "عدم موفقيت", + "audit-log-details": "بازبيني جزئيات ثبت داده ها", + "no-audit-logs-prompt": "هيچ داده ثبت شده اي يافت نشد", + "action-data": "داده هاي اقدام", + "failure-details": "جزئيات عدم موفقيت", + "search": "جستجوي داده هاي ثبت شده از بازبيني", + "clear-search": "پاک کردن جستجو" + }, + "confirm-on-exit": { + "message": "شما تغييراتي ذخيره نشده داريد. از ترک اين صفحه مطمئنيد؟", + "html-message": "از ترک اين صفحه مطمئنيد؟
.شما تغييراتي ذخيره نشده داريد", + "title": "تغييرات ذخيره نشده " + }, + "contact": { + "country": "کشور", + "city": "شهر", + "state": "استان / ايالت", + "postal-code": "کد پستي", + "postal-code-invalid": ".قالب کد پستي نامعتبر است", + "address": "نشاني", + "address2": "2 نشاني", + "phone": "تلفن", + "email": "پست الکترونيک", + "no-address": "بدون آدرس" + }, + "common": { + "username": "نام کاربري", + "password": "رمز عبور", + "enter-username": "وارد کردن نام کاربري", + "enter-password": "وارد کردن رمز عبور", + "enter-search": "وارد کردن جستجو" + }, + "content-type": { + "json": "JSON", + "text": "Text", + "binary": "Binary (Base64)" + }, + "customer": { + "customer": "مشتري", + "customers": "مشتريان", + "management": "مديريت مشتري", + "dashboard": "داشبورد مشتري", + "dashboards": "داشبوردهاي مشتري", + "devices": "دستگاه هاي مشتري", + "entity-views": "نمايش موجودي مشتري", + "assets": "دارايي هاي مشتري", + "public-dashboards": "داشبوردهاي عمومي", + "public-devices": "دستگاه هاي عمومي", + "public-assets": "دارايي هاي عمومي", + "public-entity-views": "نمايش موجودي عمومي", + "add": "افزودن مشتري", + "delete": "حذف مشتري", + "manage-customer-users": "مديريت کاربرهاي مشتري", + "manage-customer-devices": "مديريت دستگاه هاي مشتري", + "manage-customer-dashboards": "مديريت داشبوردهاي مشتري", + "manage-public-devices": "مديريت دستگاه هاي عمومي", + "manage-public-dashboards": "مديريت داشبوردهاي عمومي", + "manage-customer-assets": "مديريت دارايي هاي مشتري", + "manage-public-assets": "مديريت دارايي هاي عمومي", + "add-customer-text": "افزودن مشتري جديد", + "no-customers-text": "هيچ مشتري اي يافت نشد", + "customer-details": "جزئيات اطلاعات مشتري", + "delete-customer-title": "مطمئنيد؟ '{{customerTitle}}' از حذف مشتري", + "delete-customer-text": ".مراقب باشيد، پس از تأييد، مشتري و تمامي داده هاي مربوطه، غير قابل بازيابي مي شوند", + "delete-customers-title": "مطمئنيد؟ { count, plural, 1 {1 مشتري} other {# مشتري} } از حذف", + "delete-customers-action-title": "{ count, plural, 1 {1 مشتري} other {# مشتري} } حذف", + "delete-customers-text": ".مراقب باشيد، پس از تأييد، تمام مشتريانِ انتخاب شده حذف، و تمامي داده هاي مربوطه غير قابل دسترسي مي شوند", + "manage-users": "مديريت کاربرها", + "manage-assets": "مديريت دارايي ها", + "manage-devices": "مديريت دستگاه ها", + "manage-dashboards": "مديريت داشبوردها", + "title": "عنوان", + "title-required": ".عنوان مورد نياز است", + "description": "توصيف", + "details": "جزئيات", + "events": "رويدادها", + "copyId": "مشتري ID رونوشت از", + "idCopiedMessage": "مشتري در حافظه موقت رونوشت شد ID", + "select-customer": "انتخاب مشتري", + "no-customers-matching": ".يافت نشد '{{entity}}' هيچ مشتري منطبق بر", + "customer-required": "مشتري مورد نياز است", + "select-default-customer": "انتخاب مشتري پيش فرض", + "default-customer": "مشتري پيش فرض", + "default-customer-required": "جهت عيب يابي داشبورد در سطح کاربر مياني، مشتري پيش فرض مورد نياز است" + }, + "datetime": { + "date-from": "تاريخ از", + "time-from": "زمان از", + "date-to": "تاريخ تا", + "time-to": "زمان تا" + }, + "dashboard": { + "dashboard": "داشبورد", + "dashboards": "داشبوردها", + "management": "مديريت داشبورد", + "view-dashboards": "نمايش داشبوردها", + "add": "افزودن داشبورد", + "assign-dashboard-to-customer": "تخصيص داشبورد(ها) به مشتري", + "assign-dashboard-to-customer-text": "لطفا داشبوردها را، جهت تخصيص به مشتري، انتخاب کنيد", + "assign-to-customer-text": "لطفا مشتري را، جهت تخصيص داشبورد(ها)، انتخاب کنيد", + "assign-to-customer": "تخصيص به مشتري", + "unassign-from-customer": "لغو تخصيص از مشتري", + "make-public": "عمومي سازي مشتري", + "make-private": "شخصي سازي داشبورد", + "manage-assigned-customers": "مديريت مشتريان تخصيص داده شده", + "assigned-customers": "مشتريان تخصيص داده شده", + "assign-to-customers": "تخصيص داشبورد(ها) به مشتريان", + "assign-to-customers-text": "لطفا مشتريان را، جهت تخصيص داشبورد(ها)، انتخاب کنيد", + "unassign-from-customers": "لغو تخصيص داشبوردها از مشتريان", + "unassign-from-customers-text": "لطفا مشتريان را، جهت لغو تخصيص از داشبورد(ها)، انتخاب کنيد", + "no-dashboards-text": "هيچ داشبوردي يافت نشد", + "no-widgets": "هيچ ويجتي پيکربندي نشده است", + "add-widget": "افزودن ويجت جديد", + "title": "عنوان", + "select-widget-title": "انتخاب ويجت", + "select-widget-subtitle": "ليست انواع ويجت هاي در دسترس", + "delete": "حذف داشبورد", + "title-required": ".عنوان مورد نياز است", + "description": "توصيف", + "details": "جزئيات", + "dashboard-details": "جزئيات داشبورد", + "add-dashboard-text": "افزودن داشبورد جديد", + "assign-dashboards": "تخصيص داشبوردها", + "assign-new-dashboard": "تخصيص داشبورد جديد", + "assign-dashboards-text": "به مشتريان { count, plural, 1 {1 داشبورد} other {# داشبورد} } تخصيص", + "unassign-dashboards-action-text": "از مشتريان { count, plural, 1 {1 داشبورد} other {# داشبورد} } لغو تخصيص", + "delete-dashboards": "حذف داشبوردها", + "unassign-dashboards": "لغو تخصيص داشبوردها", + "unassign-dashboards-action-title": "از مشتري { count, plural, 1 {1 داشبورد} other {# داشبورد} } لغو تخصيص", + "delete-dashboard-title": "مطمئنيد؟ '{{dashboardTitle}}' از حذف", + "delete-dashboard-text": ".مراقب باشيد، پس از تأييد، داشبورد و تمامي داده هاي مربوطه، غير قابل بازيابي مي شوند", + "delete-dashboards-title": "مطمئنيد؟ { count, plural, 1 {1 داشبورد} other {# داشبورد} } از حذف", + "delete-dashboards-action-title": "{ count, plural, 1 {1 داشبورد} other {# داشبورد} } حذف", + "delete-dashboards-text": ".مراقب باشيد، پس از تأييد، تمام داشبوردهاي انتخاب شده حذف، و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند ", + "unassign-dashboard-title": "مطمئنيد؟ '{{dashboardTitle}}' از لغو تخصيص داشبورد", + "unassign-dashboard-text": ".پس از تأييد، داشبورد، لغو تخصيص و خارج از دسترس مشتري مي شود", + "unassign-dashboard": "لغو تخصيص داشبورد", + "unassign-dashboards-title": "مطمئنيد؟ { count, plural, 1 {1 داشبورد} other {# داشبورد} } از لغو تخصيص", + "unassign-dashboards-text": ".پس از تأييد، تمام داشبوردهاي انتخاب شده، لغو تخصيص و خارج از دسترس مشتري مي شوند", + "public-dashboard-title": "داشبورد اکنون عمومي است", + "public-dashboard-text": ":قابل دسترسي است اکنون عمومي بوده و از طريق پيوند عمومي ديگر ، {{dashboardTitle}} ،داشبورد شما", + "public-dashboard-notice": ".فراموش نکنيد براي دسترسي به داده هاي دستگاه هاي مربوطه، آنها را عمومي نماييد :توجه", + "make-private-dashboard-title": "مطمئنيد؟ '{{dashboardTitle}}' از شخصي سازي داشبورد", + "make-private-dashboard-text": ".پس از تأييد، داشبورد، شخصي و خارج از دسترس ديگران مي شود", + "make-private-dashboard": "شخصي سازي داشبورد", + "socialshare-text": "ThingsBoard طراحي شده توسط '{{dashboardTitle}}'", + "socialshare-title": "ThingsBoard طراحي شده توسط '{{dashboardTitle}}'", + "select-dashboard": "انتخاب داشبورد", + "no-dashboards-matching": ".يافت نشد '{{entity}}' هيچ داشبوردي منطبق بر", + "dashboard-required": ".داشبورد مورد نياز است", + "select-existing": "انتخاب داشبورد موجود", + "create-new": "ايجاد داشبورد جديد", + "new-dashboard-title": "عنوان داشبورد جديد", + "open-dashboard": "باز کردن داشبورد", + "set-background": "تنظيم پس زمينه", + "background-color": "رنگ پس زمينه", + "background-image": "تصوير پس زمينه", + "background-size-mode": "حالت اندازه پس زمينه", + "no-image": "هيچ تصويري انتخاب نشد", + "drop-image": ".جهت بارگذاري يک تصوير، آن را با موس کِشيده و رها کنيد، و يا روي آن کليک نماييد", + "settings": "تنظيمات", + "columns-count": "شمارش ستون ها", + "columns-count-required": ".شمارش ستون ها مورد نياز است", + "min-columns-count-message": ".کمترين تعداد مجاز ستون ها 10 عدد است", + "max-columns-count-message": ".بيشترين تعداد مجاز ستون ها 1000 عدد است", + "widgets-margins": "حاشيه بين ويجت ها", + "horizontal-margin": "حاشيه افقي", + "horizontal-margin-required": ".مقدار حاشيه افقي مورد نياز است", + "min-horizontal-margin-message": ".کمترين مقدار مجاز حاشيه افقي 0 است", + "max-horizontal-margin-message": ".بيشترين مقدار مجاز حاشيه افقي 50 است", + "vertical-margin": "حاشيه عمودي", + "vertical-margin-required": ".حاشيه عمودي مورد نياز است", + "min-vertical-margin-message": ".کمترين مقدار مجاز حاشيه عمودي 0 است", + "max-vertical-margin-message": ".بيشترين مقدار مجاز حاشيه افقي 50 است", + "autofill-height": "تنظيم خودکار ارتفاع چيدمان طرح", + "mobile-layout": "تنظيمات چيدمان طرح در تلفن همراه", + "mobile-row-height": "(px) ارتفاع رديف در تلفن همراه", + "mobile-row-height-required": ".مقدار ارتفاع ردبف در تلفن همراه مورد نياز است", + "min-mobile-row-height-message": ".کمترين مقدار مجاز ارتفاع رديف در تلفن همراه 5 پيکسل است", + "max-mobile-row-height-message": ".بيشترين مقدار مجاز ارتفاع رديف در تلفن همراه 200 پيکسل است", + "display-title": "نمايش عنوان داشبورد", + "toolbar-always-open": "باز نگه داشتن نوار ابزار", + "title-color": "رنگ عنوان", + "display-dashboards-selection": "نمايش انتخاب داشبوردها", + "display-entities-selection": "نمايش انتخاب موجودي ها", + "display-dashboard-timewindow": "نمايش پنجره زمان", + "display-dashboard-export": "نمايش صدور", + "import": "وارد کردن داشبورد", + "export": "صادر کردن داشبورد", + "export-failed-error": "{{error}} :صدور داشبورد ممکن نيست", + "create-new-dashboard": "ايجاد داشبورد جديد", + "dashboard-file": "پرونده داشبورد", + "invalid-dashboard-file-error": ".وارد کردن داشبورد ممکن نيست: ساختار داده داشبورد نامعتبر است", + "dashboard-import-missing-aliases-title": "پيکربندي نامهاي مستعار استفاده شده توسط داشبوردِ وارده", + "create-new-widget": "ايجاد ويجت جديد", + "import-widget": "وارد کردن ويجت", + "widget-file": "پرونده ويجت", + "invalid-widget-file-error": ".وارد کردن ويجت ممکن نيست: ساختار داده ويجت نامعتبر است", + "widget-import-missing-aliases-title": "پيکربندي نامهاي مستعار استفاده شده توسط ويجتِ وارده", + "open-toolbar": "باز کردن نوار ابزار داشبورد", + "close-toolbar": "بستن نوار ابزار", + "configuration-error": "خطاي پيکربندي", + "alias-resolution-error-title": "خطاي پيکربندي نامهاي مستعار داشبورد", + "invalid-aliases-config": ".لطفا جهت حل اين موضوع با مسئول مربوط به خود تماس بگيريد
.يافتن دستگاهي منطبق بر فبلتر بعضي نامهاي مستعار ممکن نيست", + "select-devices": "انتخاب دستگاه ها", + "assignedToCustomer": "تخصيص يافته به مشتري", + "assignedToCustomers": "تخصيص يافته به مشتريان", + "public": "عمومي", + "public-link": "پيوند عمومي", + "copy-public-link": "رونوشت از پيوند عمومي", + "public-link-copied-message": "پيوند عمومي داشبورد در حافظه موقت رونوشت شد", + "manage-states": "مديريت وضعيت هاي داشبورد", + "states": "وضعيت هاي داشبورد", + "search-states": "جستجوي وضعيت هاي داشبورد", + "selected-states": "انتخاب شدند { count, plural, 1 {1 وضعيت داشبورد} other {# وضعيت داشبورد} }", + "edit-state": "ويرايش وضعيت داشبورد", + "delete-state": "حذف وضعيت داشبورد", + "add-state": "افزودن وضعيت داشبورد", + "state": "وضعيت داشبورد", + "state-name": "نام", + "state-name-required": ".نام وضعيت داشبورد مورد نياز است", + "state-id": "وضعيت ID", + "state-id-required": ".وضعيت داشبورد مورد نياز است ID", + "state-id-exists": ".مشابه موجود است ID در حال حاضر وضعيت داشبوردي با", + "is-root-state": "وضعيت پايه", + "delete-state-title": "حذف وضعيت داشبورد", + "delete-state-text": "مطمئنيد؟ '{{stateName}}' از حذف وضعيت داشبورد با نام", + "show-details": "نمايش جزئيات", + "hide-details": "پنهان کردن جزئيات", + "select-state": "انتخاب وضعيت هدف", + "state-controller": "کنترل کننده وضعيت" + }, + "datakey": { + "settings": "تنظيمات", + "advanced": "پيشرفته", + "label": "برچسب", + "color": "رنگ", + "units": "کارکتر خاص براي نمايش بعد از مقدار تعين شده", + "decimals": "تعداد ارقام بعد از مميّز شناور", + "data-generation-func": "تابع توليد داده", + "use-data-post-processing-func": "استفاده از تابع پس پردازش داده", + "configuration": "پيکربندي کليد داده", + "timeseries": "سري هاي زماني", + "attributes": "ويژگي ها", + "alarm": "حوزه هاي هشدار", + "timeseries-required": ".سري هاي زماني موجودي مورد نياز است", + "timeseries-or-attributes-required": ".سري هاي زماني / ويژگي هاي موجودي مورد نياز است", + "maximum-timeseries-or-attributes": "{ count, plural, 1 {.1 سري زماني / ويژگي مجاز است} other {# سري زماني / ويژگي مجازند} } بيشترين", + "alarm-fields-required": ".حوزه هاي هشدار مورد نياز است", + "function-types": "نوع توابع", + "function-types-required": ".نوع تابع مورد نياز است", + "maximum-function-types": "{ count, plural, 1 {.1 نوع تابع مجاز است} other {# نوع تابع مجازند} } بيشترين", + "time-description": "برچسب زماني مقدار فعلي؛", + "value-description": "مقدار فعلي؛", + "prev-value-description": "نتيجه ي فراخوانيِ تابع قبلي؛", + "time-prev-description": "برچسب زماني مقدار قبلي؛", + "prev-orig-value-description": "ممقدار اصلي قبلي" + }, + "datasource": { + "type": "نوع منبع داده", + "name": "نام", + "add-datasource-prompt": "لطفا منبع داده را اضافه کنيد" + }, + "details": { + "edit-mode": "حالت ويرايش", + "toggle-edit-mode": "حالت ويرايش را تغيير دهيد" + }, + "device": { + "device": "دستگاه", + "device-required": ".دستگاه مورد نياز است", + "devices": "دستگاه ها", + "management": "مديريت دستگاه", + "view-devices": "نمايش دستگاه ها", + "device-alias": "نام مستعار دستگاه", + "aliases": "نامهاي مستعار دستگاه", + "no-alias-matching": ".يافت نشد'{{alias}}'", + "no-aliases-found": ".هيچ نام مستعاري يافت نشد", + "no-key-matching": ".يافت نشد'{{key}}'", + "no-keys-found": ".هيچ کليدي يافت نشد", + "create-new-alias": "!ايجاد يک نام مستعار جديد", + "create-new-key": "!ايجاد يک کليد جديد", + "duplicate-alias-error": ".نام مستعار در داشبورد بايد يکتا باشد
'{{alias}}'نام مستعار مشابه يافت شد", + "configure-alias": "نام مستعار '{{alias}}' پيکربندي", + "no-devices-matching": "مطابقت داشته باشد وجود ندارد '{{entity}}' هيچ دستگاهي که با ", + "alias": "نام مستعار", + "alias-required": ".نام مستعار مورد نياز است", + "remove-alias": "حذف نام مستعار دستگاه", + "add-alias": "افزودن نام مستعار دستگاه", + "name-starts-with": "اسم دستگاه شروع مي شود با", + "device-list": "ليست دستگاه ها", + "use-device-name-filter": "از فيلتر استفاده کنيد", + "device-list-empty": ".هيچ دستگاهي انتخاب نشده است", + "device-name-filter-required": ".فيلتر نام دستگاه مورد نياز است", + "device-name-filter-no-device-matched": ".شروع شود يافت نشد '{{device}}' هيچ دستگاهي که با", + "add": "افزودن دستگاه", + "assign-to-customer": "تخصيص به مشتري", + "assign-device-to-customer": "تخصيص دستگاه (ها) به مشتري", + "assign-device-to-customer-text": "لطفا دستگاه ها را انتخاب کنيد تا به مشتري تخصيص يابد", + "make-public": "عمومي سازي دستگاه", + "make-private": "شخصي سازي دستگاه", + "no-devices-text": "هيچ دستگاهي يافت نشد", + "assign-to-customer-text": "لطفا مشتري را انتخاب کنيد تا دستگاه(ها) تخصيص يابد", + "device-details": "جزئيات دستگاه", + "add-device-text": "افزودن دستگاه جديد", + "credentials": "اعتبارنامه ها", + "manage-credentials": "مديريت اعتبارنامه ها", + "delete": "حذف دستگاه", + "assign-devices": "تخصيص دستگاه ها", + "assign-devices-text": "به مشتري { count, plural, 1 {1 دستگاه} other {# دستگاه} } تخصيص", + "delete-devices": "حذف دستگاه ها", + "unassign-from-customer": "لغو تخصيص از مشتري", + "unassign-devices": "لغو تخصيص دستگاه ها", + "unassign-devices-action-title": "از مشتري { count, plural, 1 {1 دستگاه} other {# دستگاه} } لغو تخصيص", + "assign-new-device": "تخصيص دستگاه جديد", + "make-public-device-title": "مطمئنيد؟ '{{deviceName}}' از عمومي سازي دستگاه", + "make-public-device-text": ".پس از تأييد، دستگاه و تمامي داده هايش عمومي و قابل دسترسي براي ديگران مي شود", + "make-private-device-title": "مطمئنيد؟ '{{deviceName}}' از شخصي سازي دستگاه", + "make-private-device-text": ".پس از تأييد، دستگاه و تمامي داده هايش شخصي و خارج از دسترس ديگران مي شوند", + "view-credentials": "نمايش اعتبارنامه ها", + "delete-device-title": "مطمئنيد؟ '{{deviceName}}' از حذف", + "delete-device-text": ".مراقب باشيد، پس از تأييد، دستگاه و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند", + "delete-devices-title": "مطمئنيد؟ { count, plural, 1 {1 دستگاه} other {# دستگاه} } از حذف", + "delete-devices-action-title": "{ count, plural, 1 {1 دستگاه} other {# دستگاه} } حذف", + "delete-devices-text": ".مراقب باشيد، پس از تأييد، تمام دستگاه هاي انتخاب شده، حذف، و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند", + "unassign-device-title": "مطمئنيد؟ '{{deviceName}}' از لغو تخصيص", + "unassign-device-text": ".پس از تأييد، دستگاه، لغو تخصيص و خارج از دسترس مشتري مي شود", + "unassign-device": "لغو تخصيص دستگاه", + "unassign-devices-title": "مطمئنيد؟ { count, plural, 1 {1 دستگاه} other {# دستگاه} } از لغو تخصيص", + "unassign-devices-text": ".پس از تأييد، تمام دستگاه هاي انتخاب شده، لغو تخصيص و خارج از دسترس مشتري مي شوند", + "device-credentials": "اعتبارنامه هاي دستگاه", + "credentials-type": "نوع اعتبارنامه ها", + "access-token": "شناسه دسترسي", + "access-token-required": ".شناسه دسترسي مورد نياز است", + "access-token-invalid": ".طول شناسه دسترسي بايد از 1 تا 20 حرف باشد", + "rsa-key": "RSA کليد عمومي", + "rsa-key-required": ".مورد نياز است RSA کليد عمومي", + "secret": "محرمانه", + "secret-required": ".شناسه محرمانه مورد نياز است", + "device-type": "نوع دستگاه", + "device-type-required": ".نوع دستگاه مورد نياز است", + "select-device-type": "انتخاب نوع دستگاه", + "enter-device-type": "وارد کردن نوع دستگاه", + "any-device": "هر دستگاهي", + "no-device-types-matching": ".يافت نشد '{{entitySubtype}}' هيچ نوع دستگاهي منطبق بر", + "device-type-list-empty": ".هيچ نوع دستگاهي انتخاب نشد", + "device-types": "انواع دستگاه", + "name": "نام", + "name-required": ".نام مورد نياز است", + "description": "توصيف", + "events": "رويدادها", + "details": "جزئيات", + "copyId": "دستگاه ID رونوشت از", + "copyAccessToken": "رونوشت از شناسه دسترسي", + "idCopiedMessage": ".دستگاه در حافظه موقت رونوشت شد ID", + "accessTokenCopiedMessage": ".شناسه دسترسي دستگاه در حافظه موقت رونوشت شد", + "assignedToCustomer": "تخصيص يافته به مشتري", + "unable-delete-device-alias-title": "حذف نام مستعار دستگاه ممکن نيست", + "unable-delete-device-alias-text": "
{{widgetsList}} :را تا زمان استفاده توسط ويجت(هاي) زير نمي توان حذف کرد ، '{{deviceAlias}}' ،نام مستعار دستگاه", + "is-gateway": "درگاه است", + "public": "عمومي", + "device-public": "دستگاه عمومي است", + "select-device": "انتخاب دستگاه" + }, + "dialog": { + "close": "بستن گفتگو" + }, + "error": { + "unable-to-connect": ".اتصال به سِروِر ممکن نيست! لطفا اتصال اينترنت خود را بررسي کنيد", + "unhandled-error-code": "{{errorCode}} :کد خطاي رسيدگي نشده", + "unknown-error": "خطاي ناشناخته" + }, + "entity": { + "entity": "موجودي", + "entities": "موجودي ها", + "aliases": "نامهاي مستعار موجودي", + "entity-alias": "نام مستعار موجودي", + "unable-delete-entity-alias-title": "حذف نام مستعار موجودي ممکن نيست", + "unable-delete-entity-alias-text": "
{{widgetsList}} :را تا زمان استفاده توسط ويجت(هاي) زير نمي توان حذف کرد ، '{{entityAlias}}' ،نام مستعار موجودي", + "duplicate-alias-error": ".نامهاي مستعار موجودي بايد در داشبورد، منحصر بفرد باشند
.يافت شد '{{alias}}' نام مستعار تکراري", + "missing-entity-filter-error": ".مفقود است '{{alias}}' فيلتر براي نام مستعار", + "configure-alias": "'{{alias}}' پيکربندي نام مستعار", + "alias": "نام مستعار", + "alias-required": ".نام مستعار موجودي مورد نياز است", + "remove-alias": "حذف نام مستعار موجودي", + "add-alias": "افزودن نام مستعار موجودي", + "entity-list": "ليست موجودي", + "entity-type": "نوع موجودي", + "entity-types": "انواع موجودي", + "entity-type-list": "ليست نوع موجودي", + "any-entity": "هر موجودي", + "enter-entity-type": "وارد کردن نوع موجودي", + "no-entities-matching": ".يافت نشد '{{entity}}' هيچ موجودي منطبق بر", + "no-entity-types-matching": ".يافت نشد '{{entityType}}' هيچ نوع موجودي منطبق بر", + "name-starts-with": "نام شروع مي شود با", + "use-entity-name-filter": "استفاده از فيلتر", + "entity-list-empty": ".هيچ موجودي اي انتخاب نشده است", + "entity-type-list-empty": ".هيچ نوع موجودي انتخاب نشده است", + "entity-name-filter-required": ".فيلتر نام موجودي مورد نياز است", + "entity-name-filter-no-entity-matched": ".شروع شود يافت نشد '{{entity}}' هيچ موجودي که با", + "all-subtypes": "همه", + "select-entities": "انتخاب موجودي ها", + "no-aliases-found": ".هيچ نام مستعاري يافت نشد", + "no-alias-matching": ".يافت نشد '{{alias}}'", + "create-new-alias": "!ايجاد يک نام مستعار جديد", + "key": "کليد", + "key-name": "نام کليد", + "no-keys-found": ".هيچ کليدي يافت نشد", + "no-key-matching": "'.يافت نشد {{key}}'", + "create-new-key": "!ايجاد يک کليد جديد", + "type": "نوع", + "type-required": ".نوع موجودي مورد نياز است", + "type-device": "دستگاه", + "type-devices": "دستگاه ها", + "list-of-devices": "{ count, plural, 1 {يک دستگاه} other {ليست # دستگاه} }", + "device-name-starts-with": "شروع مي شود '{{prefix}}' دستگاه هايي که نامشان با", + "type-asset": "دارايي", + "type-assets": "دارايي ها", + "list-of-assets": "{ count, plural, 1 {يک دارايي} other {ليست # دارايي} }", + "asset-name-starts-with": "شروع مي شود '{{prefix}}' دارايي هايي که نامشان با", + "type-entity-view": "نمايش موجودي", + "type-entity-views": "نمايش هاي موجودي", + "list-of-entity-views": "{ count, plural, 1 {يک نمايش موجودي} other {ليست # نمايش موجودي} }", + "entity-view-name-starts-with": "شروع مي شود '{{prefix}}' نمايش هاي موجودي که نامشان با", + "type-rule": "قاعده", + "type-rules": "قواعد", + "list-of-rules": "{ count, plural, 1 {يک قاعده} other {ليست # قاعده} }", + "rule-name-starts-with": "شروع مي شود '{{prefix}}' قواعدي که نامشان با", + "type-plugin": "ابزار جانبي", + "type-plugins": "ابزارهاي جانبي", + "list-of-plugins": "{ count, plural, 1 {يک ابزار جانبي} other {ليست # ابزار جانبي} }", + "plugin-name-starts-with": "شروع مي شود '{{prefix}}' ابزارهاي جانبي که نامشان با", + "type-tenant": "کاربر", + "type-tenants": "کاربران", + "list-of-tenants": "{ count, plural, 1 {يک کاربر} other {ليست # کاربر} }", + "tenant-name-starts-with": "شروع مي شود '{{prefix}}' کاربرهايي که نامشان با", + "type-customer": "مشتري", + "type-customers": "مشتريان", + "list-of-customers": "{ count, plural, 1 {يک مشتري} other {ليست # مشتري} }", + "customer-name-starts-with": "شروع مي شود '{{prefix}}' مشترياني که نامشان با", + "type-user": "کاربر", + "type-users": "کاربران", + "list-of-users": "{ count, plural, 1 {يک کاربر} other {ليست # کاربر} }", + "user-name-starts-with": "شروع مي شود '{{prefix}}' کاربرهايي که نامشان با", + "type-dashboard": "داشبورد", + "type-dashboards": "داشبوردها", + "list-of-dashboards": "{ count, plural, 1 {يک داشبورد} other {ليست # داشبورد} }", + "dashboard-name-starts-with": "شروع مي شود '{{prefix}}' داشبوردهايي که نامشان با", + "type-alarm": "هشدار", + "type-alarms": "هشدارها", + "list-of-alarms": "{ count, plural, 1 {يک هشدار} other {ليست # هشدار} }", + "alarm-name-starts-with": "شروع مي شود '{{prefix}}' هشدارهايي که نامشان با", + "type-rulechain": "زنجيره قواعد", + "type-rulechains": "زنجيره هاي قواعد", + "list-of-rulechains": "{ count, plural, 1 {يک زنجيره قواعد} other {ليست # زنجيره قواعد} }", + "rulechain-name-starts-with": "شروع مي شود '{{prefix}}' زنجيره هاي قواعدي که نامشان با", + "type-rulenode": "گره قواعد", + "type-rulenodes": "گره هاي قواعد", + "list-of-rulenodes": "{ count, plural, 1 {يک گره قواعد} other {ليست # گره قواعد} }", + "rulenode-name-starts-with": "شروع مي شود '{{prefix}}' گره هاي قواعدي که نامشان با", + "type-current-customer": "مشتري فعلي", + "search": "جستجوي موجودي ها", + "selected-entities": "انتخاب شدند { count, plural, 1 {1 موجودي} other {# موجودي} }", + "entity-name": "نام موجودي", + "details": "جزئيات موجودي", + "no-entities-prompt": "هيچ موجودي اي يافت نشد", + "no-data": "هيچ داده اي براي نمايش نيست", + "columns-to-display": "ستون ها براي نمايش" + }, + "entity-view": { + "entity-view": "نمايش موجودي", + "entity-view-required": ".نمايش موجودي مورد نياز است", + "entity-views": "نمايش هاي موجودي", + "management": "مديريت نمايش موجودي", + "view-entity-views": "نمايش نمايش هاي موجودي", + "entity-view-alias": "نام مستعار نمايش موجودي", + "aliases": "نامهاي مستعار نمايش موجودي", + "no-alias-matching": ".يافت نشد '{{alias}}'", + "no-aliases-found": ".هيچ نام مستعاري يافت نشد", + "no-key-matching": ".يافت نشد '{{key}}'", + "no-keys-found": ".هيچ کليدي يافت نشد", + "create-new-alias": "!ايجاد نام مستعار جديد", + "create-new-key": "!ايجاد کليد جديد", + "duplicate-alias-error": ".نامهاي مستعار نمايش موجودي بايد در داشبورد، منحصر بفرد باشند
.يافت شد '{{alias}}' نام مستعار تکراري", + "configure-alias": "'{{alias}}' پيکربندي نام مستعار", + "no-entity-views-matching": ".يافت نشد '{{entity}}' هيچ موجودي منطبق بر", + "alias": "نام مستعار", + "alias-required": ".نام مستعار نمايش موجودي مورد نياز است", + "remove-alias": "حذف نام مستعار نمايش موجودي", + "add-alias": "افزودن نام مستعار نمايش موجودي", + "name-starts-with": "نام نمايش موجودي شروع مي شود با", + "entity-view-list": "ليست نمايش موجودي", + "use-entity-view-name-filter": "استفاده از فيلتر", + "entity-view-list-empty": ".هيچ نمايش موجودي انتخاب نشد", + "entity-view-name-filter-required": ".فيلتر نام نمايش موجودي مورد نياز است", + "entity-view-name-filter-no-entity-view-matched": ".شروع شود يافت نشد '{{entityView}}' هيچ نمايش موجودي که با", + "add": "افزودن نمايش موجودي", + "assign-to-customer": "تخصيص به مشتري", + "assign-entity-view-to-customer": "تخصيص نمايش(هاي) موجودي به مشتري", + "assign-entity-view-to-customer-text": ".لطفا نمايش هاي موجودي را انتخاب کنيد تا به مشتري تخصيص يابند", + "no-entity-views-text": "هيچ نمايش موجودي يافت نشد", + "assign-to-customer-text": ".لطفا مشتري را انتخاب کنيد تا نمايش(هاي) موجودي تخصيص يابد", + "entity-view-details": "جزئيات نمايش موجودي", + "add-entity-view-text": "افزودن نمايش موجودي جديد", + "delete": "حذف نمايش موجودي", + "assign-entity-views": "تخصيص نمايش هاي موجودي", + "assign-entity-views-text": "به مشتري { count, plural, 1 {1 نمايش موجودي} other {# نمايش موجودي} } تخصيص", + "delete-entity-views": "حذف نمايش هاي موجودي", + "unassign-from-customer": "لغو تخصيص از مشتري", + "unassign-entity-views": "لغو تخصيص نمايش هاي موجودي", + "unassign-entity-views-action-title": "از مشتري { count, plural, 1 {1 نمايش موجودي} other {# نمايش موجودي} } لغو تخصيص", + "assign-new-entity-view": "تخصيص نمايش موجودي جديد", + "delete-entity-view-title": "مطمئنيد؟ '{{entityViewName}}' از حذف نمايش موجودي", + "delete-entity-view-text": ".مراقب باشيد، پس از تأييد، نمايش موجودي و تمامي داده هاي مربوطه، غير قابل بازيابي مي شوند", + "delete-entity-views-title": "مطمئنيد؟ { count, plural, 1 {1 نمايش موجودي} other {# نمايش موجودي} } از حذف نمايش موجودي", + "delete-entity-views-action-title": "{ count, plural, 1 {1 نمايش موجودي} other {# نمايش موجودي} } حذف", + "delete-entity-views-text": ".مراقب باشيد، پس از تأييد، تمام نمايش هاي موجوديِ انتخاب شده حذف، و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند", + "unassign-entity-view-title": "مطمئنيد؟ '{{entityViewName}}' از لغو تخصيص نمايش موجودي", + "unassign-entity-view-text": ".پس از تأييد، نمايش موجودي، لغو تخصيص و خارج از دسترس مشتري مي شود", + "unassign-entity-view": "لغو تخصيص نمايش موجودي", + "unassign-entity-views-title": "مطمئنيد؟ { count, plural, 1 {1 نمايش موجودي} other {# نمايش موجودي} } از لغو تخصيص", + "unassign-entity-views-text": ".پس از تأييد، تمام نمايش هاي موجوديِ انتخاب شده، لغو تخصيص و خارج از دسترس مشتري مي شوند", + "entity-view-type": "نوع نمايش موجودي", + "entity-view-type-required": ".نوع نمايش موجودي مورد نياز است", + "select-entity-view-type": "انتخاب نوع نمايش موجودي", + "enter-entity-view-type": "وارد کردن نوع نمايش موجودي", + "any-entity-view": "هر نمايش موجودي", + "no-entity-view-types-matching": ".يافت نشد '{{entitySubtype}}' هيچ نوع نمايش موجودي منطبق بر", + "entity-view-type-list-empty": ".هيچ نوع نمايش موجودي انتخاب نشد", + "entity-view-types": "انواع نمايش موجودي", + "name": "نام", + "name-required": ".نام مورد نياز است", + "description": "توصيف", + "events": "رويدادها", + "details": "جزئيات", + "copyId": "نمايش موجودي ID رونوشت از", + "assignedToCustomer": "تخصيص يافته به مشتري", + "unable-entity-view-device-alias-title": ".حذف نام مستعار نمايش موجودي ممکن نيست", + "unable-entity-view-device-alias-text": "
{{widgetsList}} :را تا زمان استفاده توسط ويجت(هاي) زير نمي توان حذف کرد ، '{{entityViewAlias}}' ،نام مستعار دستگاه", + "select-entity-view": "انتخاب نمايش موجودي", + "make-public": "عمومي سازي نمايش موجودي", + "start-date": "تاريخ شروع", + "start-ts": "زمان شروع", + "end-date": "تاريخ پايان", + "end-ts": "زمان پايان", + "date-limits": "محدوده تاريخ", + "client-attributes": "ويژگي هاي مشتري", + "shared-attributes": "ويژگي هاي مشترک", + "server-attributes": "ويژگي هاي سِروِر", + "timeseries": "سري هاي زماني", + "client-attributes-placeholder": "ويژگي هاي مشتري", + "shared-attributes-placeholder": "ويژگي هاي مشترک", + "server-attributes-placeholder": "ويژگي هاي سِروِر", + "timeseries-placeholder": "سري هاي زماني", + "target-entity": "موجودي هدف", + "attributes-propagation": "انتشار ويژگي ها", + "attributes-propagation-hint": "هر بار که شما نمايش موجودي را بروز رساني يا ذخيره مي کنيد، نمايش موجودي بصور خودکار ويژگي هاي تعيين شده را از موجودي هدف کپي مي کند و به دلايل عملکردي، ويژگي هاي موجودي هدف، با هر بار تغيير ويژگي، در نمايش موجودي انتشار نمي يابند. مي توانيد با پيکربندي گره قواعد در زنجيره قواعد خود و پيوند دهي \"Post attributes\" و \"Attributes Updated\" آن به گره قواعد جديد، انتشار خودکار \"copy to view\" را ممکن سازيد ." , + "timeseries-data": "داده ي سري هاي زماني", + "timeseries-data-hint": "کليدهاي داده ي سري هاي زمانيِ موجوديِ هدف را پيکربندي کنيد تا در دسترسِ نمايش موجودي باشند. اين سري هاي زماني، فقط خواندني است" + }, + "event": { + "event-type": "نوع رويداد", + "type-error": "خطا", + "type-lc-event": "رويداد چرخه عمر", + "type-stats": "آمار", + "type-debug-rule-node": "اشکال زدايي", + "type-debug-rule-chain": "اشکال زدايي", + "no-events-prompt": "هيچ رويدادي يافت نشد", + "error": "خطا", + "alarm": "هشدار", + "event-time": "زمان رويداد", + "server": "سِروِر", + "body": "بدنه", + "method": "روش", + "type": "نوع", + "entity": "موجودي", + "message-id": "پيام ID", + "message-type": "نوع پيام", + "data-type": "نوع داده", + "relation-type": "نوع ارتباط", + "metadata": "فرا داده", + "data": "داده", + "event": "رويداد", + "status": "وضعيت", + "success": "موفقيت", + "failed": "عدم موفقيت", + "messages-processed": "پيام پردازش شد", + "errors-occurred": "خطاها رخ دادند" + }, + "extension": { + "extensions": "دنباله ها", + "selected-extensions": "انتخاب شدند { count, plural, 1 {1 افزونه} other {افزونه ها #} }", + "type": "نوع", + "key": "کليد", + "value": "مقدار", + "id": "ID", + "extension-id": " افزونه ID", + "extension-type": "نوع افزونه", + "transformer-json": "JSON *", + "unique-id-required": ".افزونه فعلي موجود است ID در حال حاضر", + "delete": "حذف دنباله", + "add": "افزودن دنباله", + "edit": "ويرايش دنباله", + "delete-extension-title": "مطمئنيد؟ '{{extensionId}}' از حذف افزونه", + "delete-extension-text": ".مراقب باشيد، پس از تأييد، افزونه و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند", + "delete-extensions-title": "مطمئنيد؟ { count, plural, 1 {1 افزونه} other {# افزونه} } از حذف", + "delete-extensions-text": ".مراقب باشيد، پس از تأييد، تمام افزونه هاي انتخاب شده حذف مي گردند", + "converters": "مبدّل ها", + "converter-id": "مبدّل ID", + "configuration": "پيکربندي", + "converter-configurations": "پيکربندي هاي مبدّل", + "token": "نشانه امنيت", + "add-converter": "افزودن مبدّل", + "add-config": "افزودن پيکربندي مبدّل", + "device-name-expression": "عبارت نام دستگاه", + "device-type-expression": "عبارت نوع دستگاه", + "custom": "متداول", + "to-double": "دو برابر شدن", + "transformer": "مبدّل", + "json-required": ".مبدّل مورد نياز است JSON", + "json-parse": ".مبدّل ممکن نيست JSON تجزيه", + "attributes": "ويژگي ها", + "add-attribute": "افزودن ويژگي", + "add-map": "افزودن جزء نگاشت", + "timeseries": "سري هاي زماني", + "add-timeseries": "افزودن سري هاي زماني", + "field-required": "دامنه مورد نياز است", + "brokers": "واسطه ها", + "add-broker": "افزودن واسطه", + "host": "ميزبان", + "port": "درگاه", + "port-range": ".درگاه بايد در بازه اي بين 1 تا 65535 باشد", + "ssl": "Ssl", + "credentials": "اعتبارنامه ها", + "username": "نام کاربري", + "password": "رمز عبور", + "retry-interval": "بازخواني فاصله در ميلي ثانيه", + "anonymous": "بي نام", + "basic": "پايه", + "pem": "PEM", + "ca-cert": "CA پرونده گواهينامه *", + "private-key": "پرونده کليد شخصي *", + "cert": "پرونده گواهينامه *", + "no-file": ".هيچ پرونده اي انتخاب نشد", + "drop-file": ".جهت بارگذاري يک پرونده، آن را با موس کِشيده و رها کنيد، و يا روي آن کليک نماييد", + "mapping": "نگاشت", + "topic-filter": "فيلتر عنوان", + "converter-type": "نوع مبدّل", + "converter-json": "JSON", + "json-name-expression": "نام دستگاه JSON عبارت", + "topic-name-expression": "عبارت عنوان نام دستگاه", + "json-type-expression": "نوع دستگاه JSON عبارت", + "topic-type-expression": "عبارت عنوان نوع دستگاه", + "attribute-key-expression": "عبارت کليد ويژگي", + "attr-json-key-expression": "کليد ويژگي JSON عبارت", + "attr-topic-key-expression": "عبارت عنوان کليد ويژگي", + "request-id-expression": "ID درخواست عبارت", + "request-id-json-expression": "ID JSON درخواست عبارت", + "request-id-topic-expression": "ID درخواست عبارت عنوان", + "response-topic-expression": "عبارت عنوان پاسخ", + "value-expression": "عبارت مقدار", + "topic": "عنوان", + "timeout": "وقفه در ميلي ثانيه", + "converter-json-required": ".مبدّل مورد نياز است JSON", + "converter-json-parse": ".مبدّل ممکن نيست JSON تجزيه", + "filter-expression": "عبارت فيلتر", + "connect-requests": "درخواست هاي اتصال", + "add-connect-request": "افزودن درخواست اتصال", + "disconnect-requests": "درخواست هاي قطع اتصال", + "add-disconnect-request": "افزودن درخواست قطع اتصال", + "attribute-requests": "درخواست هاي ويژگي", + "add-attribute-request": "افزودن درخواست ويژگي", + "attribute-updates": "به روز رساني هاي ويژگي ", + "add-attribute-update": "افزودن به روز رساني ويژگي ", + "server-side-rpc": "سَمت سِروِر RPC", + "add-server-side-rpc-request": "سَمت سِروِر RPC افزودن درخواست", + "device-name-filter": "فيلتر نام دستگاه", + "attribute-filter": "فيلتر ويژگي ", + "method-filter": "فيلتر روش", + "request-topic-expression": "عبارت عنوان درخواست", + "response-timeout": "وقفه پاسخ در ميلي ثانيه", + "topic-expression": "بيان موضوع", + "client-scope": "حوزه مشتري", + "add-device": "افزودن دستگاه", + "opc-server": "سِروِرها", + "opc-add-server": "افزودن سِروِر", + "opc-add-server-prompt": "لطفا سِروِر را اضافه کنيد", + "opc-application-name": "نام برنامه کاربردي", + "opc-application-uri": "برنامه کاربردي URI", + "opc-scan-period-in-seconds": "دوره پويش در ثانيه", + "opc-security": "امنيت", + "opc-identity": "هويت", + "opc-keystore": "کي استور", + "opc-type": "نوع", + "opc-keystore-type": "نوع", + "opc-keystore-location": "* موقعيت مکاني", + "opc-keystore-password": "رمز عبور", + "opc-keystore-alias": "نام مستعار", + "opc-keystore-key-password": "کليد رمز عبور", + "opc-device-node-pattern": "الگوي گره دستگاه", + "opc-device-name-pattern": "الگوي نام دستگاه", + "modbus-server": "سِروِرها/جايگزين آماده به کار", + "modbus-add-server": "افزودن سِروِر/ جايگزين آماده به کار ", + "modbus-add-server-prompt": "لطفا سِروِرها/جايگزين آماده به کار را اضافه کنيد", + "modbus-transport": "انتقال", + "modbus-port-name": "نام در گاه سريال", + "modbus-encoding": "رمز گذاري", + "modbus-parity": "توازن", + "modbus-baudrate": "نرخ علامت در ثانيه", + "modbus-databits": "بيت هاي داده", + "modbus-stopbits": "بيت هاي توقف", + "modbus-databits-range": ".بيت هاي داده بايد در بازه اي بين 7 تا 8 باشند", + "modbus-stopbits-range": ".بيت هاي توقف بايد در بازه اي بين 1 تا 2 باشند", + "modbus-unit-id": "واحد ID", + "modbus-unit-id-range": ".واحد بايد در بازه اي بين 1 تا 247 باشد ID", + "modbus-device-name": "نام دستگاه", + "modbus-poll-period": "(ms) دوره نمونه برداري", + "modbus-attributes-poll-period": "(ms) دوره نمونه برداري ويژگي ها", + "modbus-timeseries-poll-period": "(ms) دوره نمونه برداري سري هاي زماني", + "modbus-poll-period-range": ".دوره نمونه برداري بايد مقداري مثبت باشد", + "modbus-tag": "برچسب", + "modbus-function": "تابع", + "modbus-register-address": "ثبت نام نشاني", + "modbus-register-address-range": ".نشاني ثبت بايد در بازه اي بين 0 تا 65535 باشد", + "modbus-register-bit-index": "شاخص بيت", + "modbus-register-bit-index-range": ".شاخص بيت بايد در بازه اي بين 0 تا 15 باشد", + "modbus-register-count": "شمارش ثبت", + "modbus-register-count-range": ".شمارش ثبت بايد مقداري مثبت باشد", + "modbus-byte-order": "ترتيب بايت", + + "sync": { + "status": "وضعيت", + "sync": "همگام", + "not-sync": "غير همگام", + "last-sync-time": "آخرين زمان همگام سازي", + "not-available": "خارج از دسترس" + }, + + "export-extensions-configuration": "صدور پيکربندي افزونه ها", + "import-extensions-configuration": "وارد کردن پيکربندي افزونه ها", + "import-extensions": "وارد کردن افزونه ها", + "import-extension": "وارد کردن افزونه", + "export-extension": "صدور افزونه", + "file": "پرونده افزونه ها", + "invalid-file-error": "پرونده افزونه نامعتبر است" + }, + "fullscreen": { + "expand": "بسط به حالت تمام صفحه", + "exit": "خروج از حالت تمام صفحه", + "toggle": "تغيير حالت تمام صفحه", + "fullscreen": "حالت تمام صفحه" + }, + "function": { + "function": "تابع" + }, + "grid": { + "delete-item-title": "از حذف اين مورد مطمئنيد؟", + "delete-item-text": ".مراقب باشيد، پس از تأييد، اين مورد و تمامي داده هاي مربوطه غيرقابل بازيابي مي شوند", + "delete-items-title": "مطمئنيد؟ { count, plural, 1 {1 مورد} other {# مورد} } از حذف", + "delete-items-action-title": "{ count, plural, 1 {1 مورد} other {# مورد} } حذف", + "delete-items-text": ".مراقب باشيد، پس از تأييد، تمام مواردِ انتخاب شده حذف، و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند", + "add-item-text": "افزودن مورد جديد", + "no-items-text": "هيچ موردي يافت نشد", + "item-details": "جزئيات مورد", + "delete-item": "حذف مورد", + "delete-items": "حذف موارد", + "scroll-to-top": "پيمايش به بالا" + }, + "help": { + "goto-help-page": "رفتن به صفحه کمک" + }, + "home": { + "home": "خانه", + "profile": "پرونده شخصي", + "logout": "خروج", + "menu": "فهرست انتخاب", + "avatar": "آواتار", + "open-user-menu": "بازکردن فهرست انتخاب کاربر" + }, + "import": { + "no-file": "هيچ پرونده اي انتخاب نشد", + "drop-file": ".آن را با موس کِشيده و رها کنيد، و يا روي آن کليک نماييد ،JSON جهت بارگذاري يک پرونده" + }, + "item": { + "selected": "انتخاب شده" + }, + "js-func": { + "no-return-error": "!تابع بايد مقدار را برگرداند", + "return-type-mismatch": "!را برگرداند '{{type}}' تابع بايد مقدار نوع", + "tidy": "مرتب" + }, + "key-val": { + "key": "کليد", + "value": "مقدار", + "remove-entry": "حذف ورودي", + "add-entry": "افزودن ورودي", + "no-data": "هيچ ورودي وجود ندارد" + }, + "layout": { + "layout": "طرح بندي", + "manage": "مديريت طرح بندي ها", + "settings": "تنظيمات طرح بندي", + "color": "رنگ", + "main": "اصلي", + "right": "راست", + "select": "انتخاب طرح بندي هدف" + }, + "legend": { + "position": "محل فهرست علائم", + "show-max": "نمايش بيشترين مقدار", + "show-min": "نمايش کمترين مقدار", + "show-avg": "نمايش مقدار ميانگين", + "show-total": "نمايش مقدار مجموع", + "settings": "تنظيمات فهرست علائم", + "min": "کمترين", + "max": "بيشترين", + "avg": "ميانگين", + "total": "مجموع" + }, + "login": { + "login": "ورود", + "request-password-reset": "درخواست بازنشاني رمز عبور", + "reset-password": "بازنشاني رمز عبور", + "create-password": "ايجاد رمز عبور", + "passwords-mismatch-error": "!رمزهاي عبور وارد شده بايد مشابه باشند", + "password-again": "رمز عبور دوباره", + "sign-in": "لطفا وارد شويد", + "username": "(نام کاربري (پست الکترونيک", + "remember-me": "مرا به خاطر داشته باش", + "forgot-password": "رمز عبور را فراموش کرده ايد؟", + "password-reset": "بازنشاني رمز عبور", + "new-password": "رمز عبور جديد", + "new-password-again": "رمز عبور جديد دوباره", + "password-link-sent-message": "!پيوند بازنشاني رمز عبور با موفقيت ارسال شد", + "email": "پست الکترونيک" + }, + "position": { + "top": "بالا", + "bottom": "پايين", + "left": "چپ", + "right": "راست" + }, + "profile": { + "profile": "پرونده شخصي", + "change-password": "تغيير رمز عبور", + "current-password": "رمز عبور فعلي" + }, + "relation": { + "relations": "ارتباطات", + "direction": "جهت", + "search-direction": { + "FROM": "از", + "TO": "به" + }, + "direction-type": { + "FROM": "از", + "TO": "به" + }, + "from-relations": "ارتباطات خارج از محدوده", + "to-relations": "ارتباطات داخل محدوده", + "selected-relations": "انتخاب شدند { count, plural, 1 {1 ارتباط} other {ارتباط #} }", + "type": "نوع", + "to-entity-type": "به نوع موجودي", + "to-entity-name": "به نام موجودي", + "from-entity-type": "از نوع موجودي", + "from-entity-name": "از نام موجودي", + "to-entity": "به موجودي", + "from-entity": "از موجودي", + "delete": "حذف ارتباط", + "relation-type": "نوع ارتباط", + "relation-type-required": ".نوع ارتباط مورد نياز است", + "any-relation-type": "هر نوع", + "add": "افزودن ارتباط", + "edit": "ويرايش ارتباط", + "delete-to-relation-title": "مطمئنيد؟ '{{entityName}}' از حذف ارتباط با موجودي", + "delete-to-relation-text": ".غيرمرتبط با موجودي فعلي مي شود '{{entityName}}' مراقب باشيد، پس از تأييد، موجودي", + "delete-to-relations-title": "مطمئنيد؟ { count, plural, 1 {1 ارتباط} other {# ارتباط} } از حذف", + "delete-to-relations-text": ".مراقب باشيد، پس از تأييد، تمام روابطِ انتخاب شده حذف، و موجودي هاي مربوطه، غيرمرتبط با موجودي فعلي مي شوند", + "delete-from-relation-title": "مطمئنيد؟ '{{entityName}}' از حذف ارتباط از موجودي", + "delete-from-relation-text": ".مي شود '{{entityName}}' مراقب باشيد، پس از تأييد، موجودي فعلي، غيرمرتبط از جانب موجودي", + "delete-from-relations-title": "مطمئنيد؟ { count, plural, 1 {1 ارتباط} other {# ارتباط} } از حذف", + "delete-from-relations-text": ".مراقب باشيد، پس از تأييد، تمام روابطِ انتخاب شده حذف، و موجودي فعلي، غيرمرتبط از جانب موجودي هاي مربوطه مي شود", + "remove-relation-filter": "حذف فيلتر ارتباط", + "add-relation-filter": "افزودن فيلتر ارتباط", + "any-relation": "هر ارتباط", + "relation-filters": "فيلترهاي ارتباط", + "additional-info": "(JSON) اطلاعات تکميلي", + "invalid-additional-info": "اطلاعات تکميلي ممکن نيست JSON تجزيه" + }, + "rulechain": { + "rulechain": "زنجيره قواعد", + "rulechains": "زنجيره هاي قواعد", + "root": "پايه", + "delete": "حذف زنجيره قواعد", + "name": "نام", + "name-required": ".نام مورد نياز است", + "description": "توصيف", + "add": "افزودن زنجيره قواعد", + "set-root": "ريشه زنجيره قواعد را ايجاد کنيد", + "set-root-rulechain-title": "به عنوان ريشه مطمئنيد؟ '{{ruleChainName}}' از قرار دادن زنجيره قواعد", + "set-root-rulechain-text": ".پس از تأييد، زنجيره قواعد، به عنوان ريشه تعيين شده و به تمام پيامهاي انتقالي رسيدگي مي کند", + "delete-rulechain-title": "مطمئنيد؟ '{{ruleChainName}}' از حذف زنجيره قواعد", + "delete-rulechain-text": ".مراقب باشيد، پس از تأييد، زنجيره قواعد و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند", + "delete-rulechains-title": "مطمئنيد؟ { count, plural, 1 {1 زنجيره قواعد} other {# زنجيره قواعد} } از حذف", + "delete-rulechains-action-title": "{ count, plural, 1 {1 زنجيره قواعد} other {# زنجيره قواعد} } حذف", + "delete-rulechains-text": ".مراقب باشيد، پس از تأييد، تمام زنجيره هاي قواعدِ انتخاب شده حذف، و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند", + "add-rulechain-text": "افزودن زنجيره قواعد جديد", + "no-rulechains-text": "هيچ زنجيره قواعدي يافت نشد", + "rulechain-details": "جزئيات زنجيره قواعد", + "details": "جزئيات", + "events": "رويدادها", + "system": "سيستم", + "import": "وارد کردن زنجيره قواعد", + "export": "صدور زنجيره قواعد", + "export-failed-error": "{{error}} :صدور زنجيره قواعد ممکن نيست", + "create-new-rulechain": "ايجاد زنجيره قواعد جديد", + "rulechain-file": "پرونده زنجيره قواعد", + "invalid-rulechain-file-error": ".وارد کردن زنجيره قواعد ممکن نيست: ساختار داده زنجيره قواعد نامعتبر است", + "copyId": "زنجيره قواعد ID رونوشت", + "idCopiedMessage": "زنجيره قواعد در حافظه موقت رونوشت شد ID", + "select-rulechain": "انتخاب زنجيره قواعد", + "no-rulechains-matching": ".يافت نشد '{{entity}}' هيچ زنجيره قواعدي منطبق بر", + "rulechain-required": "زنجيره قواعد مورد نياز است", + "management": "مديريت قواعد", + "debug-mode": "حالت اشکال زدايي" + }, + "rulenode": { + "details": "جزئيات", + "events": "رويدادها", + "search": "جستجوي گره ها", + "open-node-library": "باز کردن کتابخانه گره ها", + "add": "افزودن گره قواعد", + "name": "نام", + "name-required": ".نام مورد نياز است", + "type": "نوع", + "description": "توصيف", + "delete": "حذف گره قواعد", + "select-all-objects": "انتخاب تمام گره ها و اتصالات", + "deselect-all-objects": "لغو انتخاب تمام گره ها و اتصالات", + "delete-selected-objects": "حذف گره ها و اتصالاتِ انتخاب شده", + "delete-selected": "حذفِ انتخاب شده", + "select-all": "انتخاب همه", + "copy-selected": "رونوشتِ انتخاب شده", + "deselect-all": "لغو انتخاب همه", + "rulenode-details": "جزئيات گره قواعد", + "debug-mode": "حالت اشکال زدايي", + "configuration": "پيکربندي", + "link": "پيوند", + "link-details": "جزئيات پيوند گره قواعد", + "add-link": "افزودن پيوند", + "link-label": "برچسب پيوند", + "link-label-required": ".برچسب پيوند مورد نياز است", + "custom-link-label": "برچسب پيوند متداول", + "custom-link-label-required": ".برچسب پيوند متداول مورد نياز است", + "link-labels": "برچسب هاي پيوند", + "link-labels-required": ".برچسب هاي پيوند مورد نيازند", + "no-link-labels-found": "هيچ برچسب پيوندي يافت نشد", + "no-link-label-matching": ".پيدا نشد'{{label}}'", + "create-new-link-label": "!برچسب پيوند ايجاد کنيد", + "type-filter": "فيلتر", + "type-filter-details": "پيام هاي ورودي را با شرايط پيکربندي فيلتر کنيد", + "type-enrichment": "افزودن", + "type-enrichment-details": "افزودن اطلاعات تکميلي به فرا داده ي پيام", + "type-transformation": "تبديل", + "type-transformation-details": "تغيير بازده و فرا داده ي پيام", + "type-action": "اقدام", + "type-action-details": "انجام اقدام ويژه", + "type-external": "خارجي", + "type-external-details": "ارتباط متقابل با سيستم خارجي", + "type-rule-chain": "زنجيره قواعد", + "type-rule-chain-details": "ارسال پيامهاي وارده به زنجيره قواعدي مشخص", + "type-input": "ورودي", + "type-input-details": "ورودي منطقي زنجيره قواعد، پيامهاي ورودي را به گره قواعد مرتبط بعدي ارسال مي کند", + "type-unknown": "ناشناخته", + "type-unknown-details": "گره قواعدِ حل نشده", + "directive-is-not-loaded": ".در دسترس نيست '{{directiveName}}' دستورالعمل پيکربنديِ مشخص شده", + "ui-resources-load-error": ".پيکربندي UI عدم موفقيت در بارگذاري منابع", + "invalid-target-rulechain": "!حلّ زنجيره قواعد هدف ممکن نيست", + "test-script-function": "آزمايش تابع اسکريپت", + "message": "پيام", + "message-type": "نوع پيام", + "select-message-type": "انتخاب نوع پيام", + "message-type-required": "نوع پيام مورد نياز است", + "metadata": "فوق داده", + "metadata-required": ".ورودي هاي فرا داده نمي تواند خالي باشد", + "output": "خروجي", + "test": "آزمايش", + "help": "کمک" + }, + "tenant": { + "tenant": "کاربر", + "tenants": "کاربران", + "management": "مديريت کاربران", + "add": "افزودن کاربر", + "admins": "سرپرستان", + "manage-tenant-admins": "مديريت مديران کاربر", + "delete": "حذف کاربر", + "add-tenant-text": "افزودن کاربر جديد", + "no-tenants-text": "هيچ کاربري يافت نشد", + "tenant-details": "جزئيات کاربر", + "delete-tenant-title": "مطمئنيد؟ '{{tenantTitle}}' از حذف کاربر", + "delete-tenant-text": ".مراقب باشيد، پس از تأييد، کاربر و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند", + "delete-tenants-title": "مطمئنيد؟ { count, plural, 1 {1 کاربر} other {# کاربر} } از حذف", + "delete-tenants-action-title": "{ count, plural, 1 {1 کاربر} other {# کاربر} } حذف", + "delete-tenants-text": ".مراقب باشيد، پس از تأييد، تمام کاربران حذف، و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند", + "title": "عنوان", + "title-required": ".عنوان مورد نياز است", + "description": "توصيف", + "details": "جزئيات", + "events": "رويدادها", + "copyId": "متصرّف ID رونوشت", + "idCopiedMessage": "کاربر در حافظه موقت رونوشت شد ID", + "select-tenant": "انتخاب کاربر", + "no-tenants-matching": ".يافت نشد '{{entity}}' هيچ کاربري منطبق بر", + "tenant-required": "کاربر مورد نياز است" + }, + "timeinterval": { + "seconds-interval": "{ seconds, plural, 1 {1 ثانيه} other {ثانيه #} }", + "minutes-interval": "{ minutes, plural, 1 {1 دقيقه} other {دقيقه #} }", + "hours-interval": "{ hours, plural, 1 {1 ساعت} other {ساعت #} }", + "days-interval": "{ days, plural, 1 {1 روز} other {روز #} }", + "days": "روزها", + "hours": "ساعات", + "minutes": "دقايق", + "seconds": "ثانيه ها", + "advanced": "پيشرفته" + }, + "timewindow": { + "days": "{ days, plural, 1 { روز } other {روز #} }", + "hours": "{ hours, plural, 0 { 1ساعت } 1 { ساعت } other {# ساعت } }", + "minutes": "{ minutes, plural, 0 { 1دقيقه } 1 { دقيقه } other {دقيقه # } }", + "seconds": "{ seconds, plural, 0 { 1ثانيه } 1 { ثانيه } other {ثانيه # } }", + "realtime": "بي درنگ", + "history": "تاريخچه", + "last-prefix": "آخرين", + "period": "{{ endTime }} تا {{ startTime }} از", + "edit": "ويرايش پنجره زماني", + "date-range": "بازه داده", + "last": "آخرين", + "time-period": "دوره زماني" + }, + "user": { + "user": "کاربر", + "users": "کاربرها", + "customer-users": "کاربرهاي مشتري", + "tenant-admins": "مديران کاربر", + "sys-admin": "مدير سيستم", + "tenant-admin": "کاربر مدير", + "customer": "مشتري", + "anonymous": "بي نام", + "add": "افزودن کاربر", + "delete": "حذف کاربر", + "add-user-text": "افزودن کاربر جديد", + "no-users-text": "هيچ کاربري يافت نشد", + "user-details": "جزئيات کاربر", + "delete-user-title": "مطمئنيد؟ '{{userEmail}}' از حذف کاربر", + "delete-user-text": ".مراقب باشيد، پس از تأييد، کاربر و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند", + "delete-users-title": "مطمئنيد؟ { count, plural, 1 {1 کاربر} other {# کاربر} } از حذف", + "delete-users-action-title": "{ count, plural, 1 {1 کاربر} other {کاربر #} } حذف", + "delete-users-text": ".مراقب باشيد، پس از تأييد، تمام کاربرهاي انتخاب شده حذف، و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند", + "activation-email-sent-message": "!پست الکترونيک فعال سازي با موفقيت ارسال شد", + "resend-activation": "ارسال مجدد فعال سازي", + "email": "پست الکترونيک", + "email-required": ".پست الکترونيک مورد نياز است", + "invalid-email-format": ".قالب نامعتبر پست الکترونيک", + "first-name": "نام", + "last-name": "نام خانوادگي", + "description": "توصيف", + "default-dashboard": "داشبورد پيش فرض", + "always-fullscreen": "همواره در حالت تمام صفحه", + "select-user": "انتخاب کاربر", + "no-users-matching": ".يافت نشد '{{entity}}' هيچ کاربري منطبق بر", + "user-required": "کاربر مورد نياز است", + "activation-method": "روش فعال سازي", + "display-activation-link": "نمايش پيوند فعال سازي", + "send-activation-mail": "ارسال پست الکترونيک فعال سازي", + "activation-link": "پيوند فعال سازي کاربر", + "activation-link-text": ":
استفاده کنيد جهت فعال سازي کاربر، از پيوند فعال سازي زير", + "copy-activation-link": "رونوشت پيوند فعال سازي", + "activation-link-copied-message": "پيوند فعال سازي کاربر در حافظه موقت رونوشت شد", + "details": "جزئيات", + "login-as-tenant-admin": "ورود به عنوان کاربر مدير", + "login-as-customer-user": "ورود به عنوان کاربر مشتري" + }, + "value": { + "type": "نوع مقدار", + "string": "رشته", + "string-value": "مقدار رشته", + "integer": "عدد صحيح", + "integer-value": "مقدار عدد صحيح", + "invalid-integer-value": "عدد صحيح نامعتبر", + "double": "دو برابر", + "double-value": "مقدار دو برابر", + "boolean": "بولين", + "boolean-value": "مقدار بولين", + "false": "نادرست", + "true": "صحيح", + "long": "بلند" + }, + "widget": { + "widget-library": "کتابخانه ويجت ها", + "widget-bundle": "بسته ويجت", + "select-widgets-bundle": "انتخاب بسته ويجت", + "management": "مديريت ويجت", + "editor": "ويرايشگر ويجت", + "widget-type-not-found": ".نوع ويجت حذف شد \n احتمالا مرتبط است
.مشکل بارگذاري پيکربندي ويجت", + "widget-type-load-error": ":ويجت، به علت خطاهاي زير، بارگذاري نشد", + "remove": "حذف ويجت", + "edit": "ويرايش ويجت", + "remove-widget-title": "مطمئنيد؟ '{{widgetTitle}}' از حذف ويجت", + "remove-widget-text": ".پس از تأييد، ويجت و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند", + "timeseries": "سري هاي زماني", + "search-data": "جستجوي داده", + "no-data-found": "هيچ داده اي يافت نشد", + "latest-values": "آخرين مقادير", + "rpc": "ويجت کنترل", + "alarm": "ويجت هشدار", + "static": "ويجت ايستا", + "select-widget-type": "انتخاب نوع ويجت", + "missing-widget-title-error": "!عنوان ويجت بايد مشخص شود", + "widget-saved": "ويجت ذخيره شد", + "unable-to-save-widget-error": "!ذخيره سازي ويجت ممکن نيست! ويجت خطاهايي دارد", + "save": "ذخيره سازي ويجت", + "saveAs": "ذخيره سازي ويجت به عنوان", + "save-widget-type-as": "ذخيره سازي نوع ويجت به عنوان", + "save-widget-type-as-text": "لطفا عنوان ويجت جديد را وارد کنيد و/يا بسته ويجت هدف را انتخاب نماييد", + "toggle-fullscreen": "حالت تمام صفحه را تغيير دهيد", + "run": "اجراي ويجت", + "title": "عنوان ويجت", + "title-required": ".عنوان ويجت مورد نياز است", + "type": "نوع ويجت", + "resources": "منابع", + "resource-url": "JavaScript/CSS URL", + "remove-resource": "حذف منبع", + "add-resource": "افزودن منبع", + "html": "HTML", + "tidy": "مرتب", + "css": "CSS", + "settings-schema": "طرح تنظيمات", + "datakey-settings-schema": "طرح تنظيمات کليد داده", + "javascript": "Javascript", + "remove-widget-type-title": "مطمئنيد؟ '{{widgetName}}' از حذف ويجت نوع", + "remove-widget-type-text": ".پس از تأييد، نوع ويجت و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند", + "remove-widget-type": "حذف نوع ويجت", + "add-widget-type": "افزودن نوع ويجت جديد", + "widget-type-load-failed-error": "!عدم موفقيت در بارگذاري نوع ويجت", + "widget-template-load-failed-error": "!عدم موفقيت در بارگذاري قالب ويجت", + "add": "افزودن ويجت", + "undo": "برگرداندن تغييرات ويجت", + "export": "صدور ويجت" + }, + "widget-action": { + "header-button": "دکمه هدر ويجت", + "open-dashboard-state": "هدايت به وضعيت داشبورد جديد", + "update-dashboard-state": "به روز رساني وضعيت داشبورد فعلي", + "open-dashboard": "هدايت به داشبورد ديگر", + "custom": "اقدام متداول", + "target-dashboard-state": "وضعيت داشبورد هدف", + "target-dashboard-state-required": "وضعيت داشبورد هدف مورد نياز است", + "set-entity-from-widget": "تنظيم موجودي از ويجت", + "target-dashboard": "داشبورد هدف", + "open-right-layout": "(طرح داشبورد سمت راست را باز کنيد (نماي تلفن همراه" + }, + "widgets-bundle": { + "current": "بسته فعلي", + "widgets-bundles": "بسته هاي ويجت", + "add": "افزودن بسته ويجت", + "delete": "حذف بسته ويجت", + "title": "عنوان", + "title-required": ".عنوان مورد نياز است", + "add-widgets-bundle-text": "افزودن بسته ويجت جديد", + "no-widgets-bundles-text": "هيچ بسته ويجتي يافت نشد", + "empty": "بسته ويجت خالي است", + "details": "جزئيات", + "widgets-bundle-details": "جزئيات بسته ويجت", + "delete-widgets-bundle-title": "مطمئنيد؟ '{{widgetsBundleTitle}}' از حذف بسته ويجت", + "delete-widgets-bundle-text": ".مراقب باشيد، پس از تأييد، بسته ويجت و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند", + "delete-widgets-bundles-title": "مطمئنيد؟ { count, plural, 1 {1 بسته ويجت} other {# بسته ويجت} } از حذف", + "delete-widgets-bundles-action-title": "{ count, plural, 1 {1 بسته ويجت} other {# بسته ويجت} } حذف", + "delete-widgets-bundles-text": ".مراقب باشيد، پس از تأييد، تمام بسته هاي ويجتِ انتخاب شده حذف، و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند", + "no-widgets-bundles-matching": "يافت نشد '{{widgetsBundle}}' هيچ بسته ويجتي منطبق بر", + "widgets-bundle-required": ".بسته ويجت مورد نياز است", + "system": "سيستم", + "import": "وارد کردن بسته ويجت", + "export": "صدور بسته ويجت", + "export-failed-error": "{{error}} :صدور بسته ويجت ممکن نيست", + "create-new-widgets-bundle": "ايجاد بسته ويجت جديد", + "widgets-bundle-file": "پرونده بسته ويجت", + "invalid-widgets-bundle-file-error": ".وارد کردن بسته ويجت ممکن نيست: ساختار داده بسته ويجت نامعتبر است" + }, + "widget-config": { + "data": "داده", + "settings": "تنظيمات", + "advanced": "پيشرفته", + "title": "عنوان", + "general-settings": "تنظيمات عمومي", + "display-title": "نمايش عنوان", + "drop-shadow": "سايه افتادن", + "enable-fullscreen": "فعال سازي حالت تمام صفحه", + "background-color": "رنگ پس زمينه", + "text-color": "رنگ متن", + "padding": "حاشيه داخلي", + "margin": "حاشيه", + "widget-style": "سبک ويجت", + "title-style": "سبک عنوان", + "mobile-mode-settings": "تنظيمات حالت تلفن همراه", + "order": "ترتيب", + "height": "ارتفاع", + "units": "کارکتر خاص براي نمايش بعد از مقدار تعين شده", + "decimals": "تعداد ارقام بعد از مميّز شناور", + "timewindow": "پنجره زمان", + "use-dashboard-timewindow": "استفاده از پنجره زمان داشبورد", + "display-legend": "نمايش فهرست علائم", + "datasources": "منابع داده", + "maximum-datasources": "{ count, plural, 1 {.1 منبع داده مجاز است} other {# منبع داده مجازند.} } بيشترين", + "datasource-type": "نوع", + "datasource-parameters": "پارامترها", + "remove-datasource": "حذف منبع داده", + "add-datasource": "افزودن منبع داده", + "target-device": "دستگاه هدف", + "alarm-source": "منشأ هشدار", + "actions": "اقدامات", + "action": "اقدام", + "add-action": "افزودن اقدام", + "search-actions": "جستجوي اقدامات", + "action-source": "منشأ اقدام", + "action-source-required": ".منشأ اقدام مورد نياز است", + "action-name": "نام", + "action-name-required": ".نام اقدام مورد نياز است", + "action-name-not-unique": ".در حيطه يک منشأ اقدام، نام اقدام بايد منحصر بفرد باشد
.در حال حاضر اقدامي ديگر با نام مشابه موجود است", + "action-icon": "شمايل", + "action-type": "نوع", + "action-type-required": ".نوع اقدام مورد نياز است", + "edit-action": "ويرايش اقدام", + "delete-action": "حذف اقدام", + "delete-action-title": "حذف اقدام ويجت", + "delete-action-text": "مطمئنيد؟ '{{actionName}}' از حذف اقدام ويجت با نام" + }, + "widget-type": { + "import": "وارد کردن نوع ويجت", + "export": "صدور نوع ويجت", + "export-failed-error": "{{error}} :صدور نوع ويجت ممکن نيست", + "create-new-widget-type": "ايجاد نوع جديد ويجت", + "widget-type-file": "پرونده نوع ويجت", + "invalid-widget-type-file-error": ".وارد کردن نوع ويجت ممکن نيست: ساختار داده نوع ويجت نامعتبر است" + }, + "widgets": { + "date-range-navigator": { + "localizationMap": { + "Sun": "یکشنبه", + "Mon": "دوشنبه", + "Tue": "سه‌شنبه", + "Wed": "چهارشنبه", + "Thu": "پنجشنبه", + "Fri": "جمعه", + "Sat": "شنبه", + "Jan": "ژانویهٔ", + "Feb": "فوریهٔ", + "Mar": "مارس", + "Apr": "آوریل", + "May": "مهٔ", + "Jun": "ژوئن", + "Jul": "ژوئیهٔ", + "Aug": "اوت", + "Sep": "سپتامبر", + "Oct": "اکتبر", + "Nov": "نوامبر", + "Dec": "دسامبر", + "January": "January", + "February": "February", + "March": "March", + "April": "April", + "June": "June", + "July": "July", + "August": "August", + "September": "September", + "October": "October", + "November": "November", + "December": "December", + "Custom Date Range": "Custom Date Range", + "Date Range Template": "Date Range Template", + "Today": "Today", + "Yesterday": "Yesterday", + "This Week": "This Week", + "Last Week": "Last Week", + "This Month": "This Month", + "Last Month": "Last Month", + "Year": "Year", + "This Year": "This Year", + "Last Year": "Last Year", + "Date picker": "Date picker", + "Hour": "Hour", + "Day": "Day", + "Week": "Week", + "2 weeks": "2 weeks", + "Month": "Month", + "3 months": "3 months", + "6 months": "6 months", + "Custom interval": "Custom interval", + "Interval": "Interval", + "Step size": "Step size", + "Ok": "Ok" + } + } + }, + "icon": { + "icon": "آيکون", + "select-icon": "انتخاب آيکون", + "material-icons": "آيکونهاي اجسام", + "show-all": "نمايش تمام آيکونها" + }, + "custom": { + "widget-action": { + "action-cell-button": "دکمه سلول عملياتي", + "row-click": "در رديف کليک کنيد", + "marker-click": "روي نشانگر کليک کنيد", + "tooltip-tag-action": "اقدام برچسب راهنماي ابزار" + } + }, + "language": { + "language": "زبان", + "locales": { + "de_DE": "آلمانی", + "fr_FR": "فرانسوي", + "zh_CN": "چيني", + "en_US": "انگليسي", + "it_IT": "ايتاليايي", + "ko_KR": "کره اي", + "ru_RU": "روسي", + "es_ES": "اسپانيولي", + "ja_JA": "ژاپني", + "tr_TR": "ترکي", + "fa_IR": "فارسي", + "uk_UA": "اوکراین", + "cs_CZ": "در چک " + } + } +} diff --git a/ui-ngx/src/assets/locale/locale.constant-fr_FR.json b/ui-ngx/src/assets/locale/locale.constant-fr_FR.json new file mode 100644 index 0000000000..1fdec0d6d5 --- /dev/null +++ b/ui-ngx/src/assets/locale/locale.constant-fr_FR.json @@ -0,0 +1,1533 @@ +{ + "access": { + "access-forbidden": "Accès interdit", + "access-forbidden-text": "Vous n'avez pas accès à cet emplacement!
Essayez de vous connecter avec un autre utilisateur si vous souhaitez toujours accéder à cet emplacement.", + "refresh-token-expired": "La session a expiré", + "refresh-token-failed": "Impossible de rafraîchir la session", + "unauthorized": "non autorisé", + "unauthorized-access": "accès non autorisé", + "unauthorized-access-text": "Vous devez vous connecter pour avoir accès à cette ressource!" + }, + "action": { + "activate": "Activer", + "add": "Ajouter", + "apply": "Appliquer", + "apply-changes": "Appliquer les modifications", + "assign": "Attribuer", + "back": "retour", + "cancel": "Annuler", + "clear-search": "Effacer la recherche", + "close": "Fermer", + "copy": "Copier", + "copy-reference": "Copier la référence", + "create": "Créer", + "decline-changes": "Refuser les modifications", + "delete": "Supprimer", + "drag": "Drag", + "edit": "Modifier", + "edit-mode": "Mode édition", + "enter-edit-mode": "Entrer en mode édition", + "export": "Exporter", + "import": "Importer", + "make-private": "Rendre privé", + "no": "Non", + "ok": "OK", + "paste": "coller", + "paste-reference": "Coller référence", + "refresh": "Rafraîchir", + "remove": "Supprimer", + "run": "Exécuter", + "save": "Enregistrer", + "saveAs": "Enregistrer sous", + "search": "Rechercher", + "share": "Partager", + "share-via": "Partager via {{provider}}", + "sign-in": "Connectez-vous!", + "suspend": "Suspendre", + "unassign": "Retirer", + "undo": "Annuler", + "update": "mise à jour", + "view": "Afficher", + "yes": "Oui" + }, + "admin": { + "base-url": "URL de base", + "base-url-required": "L'URL de base est requise.", + "enable-tls": "Activer TLS", + "general": "Général", + "general-settings": "Paramètres généraux", + "mail-from": "Mail de", + "mail-from-required": "Mail de est requis.", + "outgoing-mail": "courrier sortant", + "outgoing-mail-settings": "Paramètres de courrier sortant", + "send-test-mail": "Envoyer un mail de test", + "smtp-host": "Hôte SMTP", + "smtp-host-required": "L'hôte SMTP est requis.", + "smtp-port": "Port SMTP", + "smtp-port-invalid": "Cela ne ressemble pas à un port smtp valide.", + "smtp-port-required": "Vous devez fournir un port smtp.", + "smtp-protocol": "Protocole SMTP", + "system-settings": "Paramètres système", + "test-mail-sent": "Le courrier de test a été envoyé avec succès!", + "timeout-invalid": "Cela ne ressemble pas à un délai d'expiration valide.", + "timeout-msec": "Délai (msec)", + "timeout-required": "Le délai est requis." + }, + "aggregation": { + "aggregation": "agrégation", + "avg": "Moyenne", + "count": "Compte", + "function": "Fonction d'agrégation de données", + "group-interval": "Intervalle de regroupement", + "limit": "Valeurs maximales", + "max": "Max", + "min": "Min", + "none": "Aucune", + "sum": "Somme" + }, + "alarm": { + "ack-time": "Heure d'acquittement", + "acknowledge": "Acquitter", + "aknowledge-alarms-text": "Etes-vous sûr de vouloir acquitter {count, plural, 1 {1 alarme} other {# alarmes}}?", + "aknowledge-alarms-title": "Acquitter {count, plural, 1 {1 alarme} other {# alarmes}}", + "alarm": "Alarme", + "alarm-details": "Détails de l'alarme", + "alarm-required": "Une alarme est requise", + "alarm-status": "Etat d'alarme", + "alarms": "Alarmes", + "clear": "Effacer", + "clear-alarms-text": "Êtes-vous sûr de vouloir effacer {count, plural, 1 {1 alarme} other {# alarmes}}?", + "clear-alarms-title": "Effacer {count, plural, 1 {1 alarme} other {# alarmes}}", + "clear-time": "Heure d'éffacement", + "created-time": "Heure de création", + "details": "Détails", + "display-status": { + "ACTIVE_ACK": "Active acquittée", + "ACTIVE_UNACK": "Active non acquittée", + "CLEARED_ACK": "effacée acquittée", + "CLEARED_UNACK": "effacée non acquittée" + }, + "end-time": "Heure de fin", + "min-polling-interval-message": "Un intervalle d'interrogation d'au moins 1 seconde est autorisé.", + "no-alarms-matching": "Aucune alarme correspondant à {{entity}} n'a été trouvée. ", + "no-alarms-prompt": "Aucune alarme trouvée", + "no-data": "Aucune donnée à afficher", + "originator": "Source", + "originator-type": "Type de Source", + "polling-interval": "Intervalle d'interrogation des alarmes (sec)", + "polling-interval-required": "L'intervalle d'interrogation des alarmes est requis.", + "search": "Rechercher des alarmes", + "search-status": { + "ACK": "acquitté", + "ACTIVE": "active", + "ANY": "Toutes", + "CLEARED": "effacée", + "UNACK": "non acquittée" + }, + "select-alarm": "Sélectionnez une alarme", + "selected-alarms": "{count, plural, 1 {1 alarme} other {# alarmes}} sélectionnées", + "severity": "Gravitée", + "severity-critical": "Critique", + "severity-indeterminate": "indéterminée", + "severity-major": "Majeure", + "severity-minor": "mineure", + "severity-warning": "Avertissement", + "start-time": "Heure de début", + "status": "Etat", + "type": "Type" + }, + "alias": { + "add": "Ajouter un alias", + "all-entities": "Toutes les entités", + "any-relation": "toutes", + "default-entity-parameter-name": "Par défaut", + "default-state-entity": "Entité d'état par défaut", + "duplicate-alias": "Un alias portant le même nom existe déjà.", + "edit": "Modifier l'alias", + "entity-filter": "Filtre d'entité", + "entity-filter-no-entity-matched": "Aucune entité correspondant au filtre spécifié n'a été trouvée.", + "filter-type": "Type de filtre", + "filter-type-asset-search-query": "requête de recherche d'Assets", + "filter-type-asset-search-query-description": "Assets de types {{assetTypes}} ayant {{relationType}} relation {{direction}} {{rootEntity}}", + "filter-type-asset-type": "type d'Asset", + "filter-type-asset-type-and-name-description": "Assets de type '{{assetType}}' et dont le nom commence par '{{prefix}}'", + "filter-type-asset-type-description": "Assets de type '{{assetType}}'", + "filter-type-device-search-query": "Requête de recherche de dispositif", + "filter-type-device-search-query-description": "Dispositifs de types {{deviceTypes}} ayant {{relationType}} relation {{direction}} {{rootEntity}}", + "filter-type-device-type": "Type de dispositif", + "filter-type-device-type-and-name-description": "Dispositifs de type '{{deviceType}}' et dont le nom commence par '{{prefix}}'", + "filter-type-device-type-description": "Dispositifs de type '{{deviceType}}'", + "filter-type-entity-list": "Liste d'entités", + "filter-type-entity-name": "Nom d'entité", + "filter-type-relations-query": "Interrogation des relations", + "filter-type-relations-query-description": "{{entities}} ayant {{relationType}} relation {{direction}} {{rootEntity}}", + "filter-type-required": "Le type de filtre est requis.", + "filter-type-single-entity": "Entité unique", + "filter-type-state-entity": "Entité de l'état du tableau de bord", + "filter-type-state-entity-description": "Entité extraite des paramètres d'état du tableau de bord", + "max-relation-level": "Niveau de relation maximum", + "name": "Nom de l'alias", + "name-required": "Le nom d'alias est requis", + "no-entity-filter-specified": "Aucun filtre d'entité spécifié", + "resolve-multiple": "Résoudre en plusieurs entités", + "root-entity": "Entité racine", + "root-state-entity": "Utiliser l'entité d'état du tableau de bord en tant que racine", + "state-entity": "Entité d'état du tableau de bord", + "state-entity-parameter-name": "Nom du paramètre d'entité d'état", + "unlimited-level": "niveau illimité" + }, + "asset": { + "add": "Ajouter un Asset", + "add-asset-text": "Ajouter un nouvel Asset", + "any-asset": "Tout Asset", + "asset": "Asset", + "asset-details": "Détails de l'Asset", + "asset-public": "L'Asset est public", + "asset-required": "Asset requis", + "asset-type": "Type d'Asset", + "asset-type-list-empty": "Aucun type d'Asset sélectionné.", + "asset-type-required": "Le type d'Asset est requis.", + "asset-types": "Types d'Asset", + "assets": "Assets", + "assign-asset-to-customer": "Attribuer des Assets au client", + "assign-asset-to-customer-text": "Veuillez sélectionner les Assets à attribuer au client", + "assign-assets": "Attribuer des Assets", + "assign-assets-text": "Attribuer {count, plural, 1 {1 asset} other {# assets}} au client", + "assign-new-asset": "Attribuer un nouvel Asset", + "assign-to-customer": "Attribuer au client", + "assign-to-customer-text": "Veuillez sélectionner le client pour attribuer le ou les Assets", + "assignedToCustomer": "attribué au client", + "copyId": "Copier l'Id de l'Asset", + "delete": "Supprimer un Asset", + "delete-asset-text": "Faites attention, après la confirmation, l'Asset et toutes les données associées deviendront irrécupérables.", + "delete-asset-title": "Êtes-vous sûr de vouloir supprimer l'Asset '{{assetName}}'?", + "delete-assets": "Supprimer des Assets", + "delete-assets-action-title": "Supprimer {count, plural, 1 {1 asset} other {# assets}}", + "delete-assets-text": "Attention, après la confirmation, tous les Assets sélectionnés seront supprimés et toutes les données associées deviendront irrécupérables.", + "delete-assets-title": "Etes-vous sûr de vouloir supprimer {count, plural, 1 {1 asset} other {# assets}}?", + "description": "Description", + "details": "Détails", + "enter-asset-type": "Entrez le type d'Asset", + "events": "Evènements", + "idCopiedMessage": "L'Id d'asset a été copié dans le presse-papier", + "make-private": "Rendre l'Asset privé", + "make-private-asset-text": "Après la confirmation, l'Asset et toutes ses données seront rendus privés et ne seront pas accessibles par d'autres.", + "make-private-asset-title": "Etes-vous sûr de vouloir rendre l'Asset '{{assetName}}' privé '?", + "make-public": "Rendre l'Asset public", + "make-public-asset-text": "Après la confirmation, l'asset et toutes ses données seront rendus publics et accessibles aux autres.", + "make-public-asset-title": "Êtes-vous sûr de vouloir rendre l'Asset '{{assetName}}' public '?", + "management": "Gestion d'Assets", + "name": "Nom", + "name-required": "Nom est requis.", + "name-starts-with": "Le nom de l'Asset commence par", + "no-asset-types-matching": "Aucun type d'Asset correspondant à {{entitySubtype}} n'a été trouvé. ", + "no-assets-matching": "Aucun Asset correspondant à {{entity}} n'a été trouvé. ", + "no-assets-text": "Aucun Asset trouvé", + "public": "Public", + "select-asset": "Sélectionner un Asset", + "select-asset-type": "Sélectionner le type d'Asset", + "type": "Type", + "type-required": "Le type est requis.", + "unassign-asset": "Retirer l'Asset", + "unassign-asset-text": "Après la confirmation, l'Asset sera non attribué et ne sera pas accessible au client.", + "unassign-asset-title": "Êtes-vous sûr de vouloir retirer l'attribution de l'Asset '{{assetName}}'?", + "unassign-assets": "Retirer les Assets", + "unassign-assets-action-title": "Retirer {count, plural, 1 {1 asset} other {# assets}} du client", + "unassign-assets-text": "Après la confirmation, tous les Assets sélectionnés ne seront pas attribués et ne seront pas accessibles au client.", + "unassign-assets-title": "Êtes-vous sûr de vouloir retirer l'attribution de {count, plural, 1 {1 asset} other {# assets}}?", + "unassign-from-customer": "Retirer du client", + "view-assets": "Afficher les Assets" + }, + "attribute": { + "add": "Ajouter un attribut", + "add-to-dashboard": "Ajouter au tableau de bord", + "add-widget-to-dashboard": "Ajouter un widget au tableau de bord", + "attributes": "Attributs", + "attributes-scope": "Etendue des attributs d'entité", + "delete-attributes": "Supprimer les attributs", + "delete-attributes-text": "Attention, après la confirmation, tous les attributs sélectionnés seront supprimés.", + "delete-attributes-title": "Êtes-vous sûr de vouloir supprimer {count, plural, 1 {1 attribut} other {# attributs}}?", + "enter-attribute-value": "Entrez la valeur de l'attribut", + "key": "Clé", + "key-required": "La Clé d'attribut est requise.", + "last-update-time": "Dernière mise à jour", + "latest-telemetry": "Dernière télémétrie", + "next-widget": "Widget suivant", + "prev-widget": "Widget précédent", + "scope-client": "Attributs du client", + "scope-latest-telemetry": "Dernière télémétrie", + "scope-server": "Attributs du serveur", + "scope-shared": "Attributs partagés", + "selected-attributes": "{count, plural, 1 {1 attribut} other {# attributs}} sélectionnés", + "selected-telemetry": "{count, plural, 1 {1 unité de télémétrie} other {# unités de télémétrie}} sélectionnées", + "show-on-widget": "Afficher sur le widget", + "value": "Valeur", + "value-required": "La valeur d'attribut est obligatoire.", + "widget-mode": "Mode du widget" + }, + "audit-log": { + "action-data": "Action data", + "audit": "Audit", + "audit-log-details": "Détails du journal d'audit", + "audit-logs": "Journaux d'audit", + "clear-search": "Effacer la recherche", + "details": "Détails", + "entity-name": "Nom de l'entité", + "entity-type": "Type d'entité", + "failure-details": "Détails de l'échec", + "no-audit-logs-prompt": "Aucun journal trouvé", + "search": "Rechercher les journaux d'audit", + "status": "Etat", + "status-failure": "Échec", + "status-success": "Succès", + "timestamp": "Horodatage", + "type": "Type", + "type-activated": "Activé", + "type-added": "Ajouté", + "type-alarm-ack": "Acquitté", + "type-alarm-clear": "Effacé", + "type-assigned-to-customer": "Attribué au client", + "type-attributes-deleted": "Attributs supprimés", + "type-attributes-read": "Attributs lus", + "type-attributes-updated": "Attributs mis à jour", + "type-credentials-read": "Lecture des informations d'identification", + "type-credentials-updated": "Informations d'identification actualisées", + "type-deleted": "Supprimé", + "type-relation-add-or-update": "Relation mise à jour", + "type-relation-delete": "Relation supprimée", + "type-relations-delete": "Toutes les relations ont été supprimées", + "type-rpc-call": "Appel RPC", + "type-suspended": "Suspendu", + "type-unassigned-from-customer": "Non attribué du client", + "type-updated": "Mise à jour", + "user": "Utilisateur" + }, + "common": { + "enter-password": "Entrez le mot de passe", + "enter-search": "Entrez la recherche", + "enter-username": "Entrez le nom d'utilisateur", + "password": "Mot de passe", + "username": "Nom d'utilisateur" + }, + "confirm-on-exit": { + "html-message": "Vous avez des modifications non enregistrées.
Êtes-vous sûr de vouloir quitter cette page?", + "message": "Vous avez des modifications non enregistrées. Êtes-vous sûr de vouloir quitter cette page?", + "title": "Modifications non enregistrées" + }, + "contact": { + "address": "Adresse", + "address2": "adresse 2", + "city": "Ville", + "country": "Pays", + "email": "Email", + "no-address": "Pas d'adresse", + "phone": "Téléphone", + "postal-code": "Code postal", + "postal-code-invalid": "Format de code postal / code postal invalide", + "state": "Etat / Province" + }, + "content-type": { + "binary": "Binaire (Base64)", + "json": "Json", + "text": "Texte" + }, + "custom": { + "widget-action": { + "action-cell-button": "Action cell button", + "marker-click": "On marker click", + "row-click": "On row click", + "polygon-click": "On polygon click", + "tooltip-tag-action": "Tooltip tag action", + "node-selected": "On node selected", + "element-click": "On HTML element click" + } + }, + "customer": { + "add": "Ajouter un client", + "add-customer-text": "Ajouter un nouveau client", + "assets": "Assets du client", + "copyId": "Copier l'id du client", + "customer": "Client", + "customer-details": "Détails du client", + "customer-required": "Le client est requis", + "customers": "Clients", + "dashboard": "Tableau de bord du client", + "dashboards": "tableaux de bord du client", + "default-customer": "Client par défaut", + "default-customer-required": "Le client par défaut est requis pour déboguer le tableau de bord au niveau du Tenant", + "delete": "Supprimer le client", + "delete-customer-text": "Faites attention, après la confirmation, le client et toutes les données associées deviendront irrécupérables.", + "delete-customer-title": "Êtes-vous sûr de vouloir supprimer le client '{{customerTitle}}'?", + "delete-customers-action-title": "Supprimer {count, plural, 1 {1 client} other {# clients}}", + "delete-customers-text": "Faites attention, après la confirmation, tous les clients sélectionnés seront supprimés et toutes les données associées deviendront irrécupérables.", + "delete-customers-title": "Êtes-vous sûr de vouloir supprimer {count, plural, 1 {1 client} other {# clients}}?", + "description": "Description", + "details": "Détails", + "devices": "Dispositifs du client", + "events": "Événements", + "idCopiedMessage": "L'Id du client a été copié dans le presse-papier", + "manage-assets": "Gérer les Assets", + "manage-customer-assets": "Gérer les Assets du client", + "manage-customer-dashboards": "Gérer les tableaux de bord du client", + "manage-customer-devices": "Gérer les dispositifs du client", + "manage-customer-users": "Gérer les utilisateurs du client", + "manage-dashboards": "Gérer les tableaux de bord", + "manage-devices": "Gérer les dispositifs", + "manage-public-assets": "Gérer les Assets publics", + "manage-public-dashboards": "Gérer les tableaux de bord publics", + "manage-public-devices": "Gérer les dispositifs publics", + "manage-users": "Gérer les utilisateurs", + "management": "Gestion des clients", + "no-customers-matching": "Aucun client correspondant à '{{entity}} n'a été trouvé.", + "no-customers-text": "Aucun client trouvé", + "public-assets": "Assets publics", + "public-dashboards": "Tableaux de bord publics", + "public-devices": "Dispositifs publics", + "select-customer": "Sélectionner un client", + "select-default-customer": "Sélectionnez le client par défaut", + "title": "Titre", + "title-required": "Le titre est requis." + }, + "dashboard": { + "add": "Ajouter un tableau de bord", + "add-dashboard-text": "Ajouter un nouveau tableau de bord", + "add-state": "Ajouter un état du tableau de bord", + "add-widget": "Ajouter un nouveau widget", + "alias-resolution-error-title": "Erreur de configuration des alias de tableau de bord", + "assign-dashboard-to-customer": "Attribuer des tableaux de bord au client", + "assign-dashboard-to-customer-text": "Veuillez sélectionner les tableaux de bord à affecter au client", + "assign-dashboards": "Attribuer des tableaux de bord", + "assign-dashboards-text": "Attribuer {count, plural, 1 {1 tableau de bord} other {# tableaux de bord}} aux clients", + "assign-new-dashboard": "Attribuer un nouveau tableau de bord", + "assign-to-customer": "Attribuer au client", + "assign-to-customer-text": "Veuillez sélectionner le client pour attribuer le ou les tableaux de bord", + "assign-to-customers": "Attribuer des tableaux de bord aux clients", + "assign-to-customers-text": "Veuillez sélectionner les clients pour attribuer les tableaux de bord", + "assigned-customers": "clients affectés", + "assignedToCustomer": "Attribué au client", + "assignedToCustomers": "attribué aux clients", + "autofill-height": "Hauteur de remplissage automatique", + "background-color": "Couleur de fond", + "background-image": "Image d'arrière-plan", + "background-size-mode": "Mode de taille d'arrière-plan", + "close-toolbar": "Fermer la barre d'outils", + "columns-count": "Nombre de colonnes", + "columns-count-required": "Le nombre de colonnes est requis.", + "configuration-error": "Erreur de configuration", + "copy-public-link": "Copier le lien public", + "create-new": "Créer un nouveau tableau de bord", + "create-new-dashboard": "Créer un nouveau tableau de bord", + "create-new-widget": "Créer un nouveau widget", + "dashboard": "Tableau de bord", + "dashboard-details": "Détails du tableau de bord", + "dashboard-file": "Fichier du tableau de bord", + "dashboard-import-missing-aliases-title": "Configurer les alias utilisés par le tableau de bord importé", + "dashboard-required": "Le tableau de bord est requis.", + "dashboards": "Tableaux de bord", + "delete": "Supprimer le tableau de bord", + "delete-dashboard-text": "Faites attention, après la confirmation, le tableau de bord et toutes les données associées deviendront irrécupérables.", + "delete-dashboard-title": "Êtes-vous sûr de vouloir supprimer le tableau de bord '{{dashboardTitle}}'?", + "delete-dashboards": "Supprimer les tableaux de bord", + "delete-dashboards-action-title": "Supprimer {count, plural, 1 {1 tableau de bord} other {# tableaux de bord}}", + "delete-dashboards-text": "Attention, après la confirmation, tous les tableaux de bord sélectionnés seront supprimés et toutes les données associées deviendront irrécupérables.", + "delete-dashboards-title": "Voulez-vous vraiment supprimer {count, plural, 1 {1 tableau de bord} other {# tableaux de bord}}?", + "delete-state": "Supprimer l'état du tableau de bord", + "delete-state-text": "Etes-vous sûr de vouloir supprimer l'état du tableau de bord avec le nom '{{stateName}}'?", + "delete-state-title": "Supprimer l'état du tableau de bord", + "description": "Description", + "details": "Détails", + "display-dashboard-export": "Afficher l'exportation", + "display-dashboard-timewindow": "Afficher fenêtre de temps", + "display-dashboards-selection": "Afficher la sélection des tableaux de bord", + "display-entities-selection": "Afficher la sélection des entités", + "display-title": "Afficher le titre du tableau de bord", + "drop-image": "Déposer une image ou cliquez pour sélectionner un fichier à télécharger.", + "edit-state": "Modifier l'état du tableau de bord", + "export": "Exporter le tableau de bord", + "export-failed-error": "Impossible d'exporter le tableau de bord: {{error}}", + "hide-details": "Masquer les détails", + "horizontal-margin": "Marge horizontale", + "horizontal-margin-required": "Une valeur de marge horizontale est requise.", + "import": "Importer le tableau de bord", + "import-widget": "Importer un widget", + "invalid-aliases-config": "Impossible de trouver des dispositifs correspondant à certains filtres d'alias.
Veuillez contacter votre administrateur pour résoudre ce problème.", + "invalid-dashboard-file-error": "Impossible d'importer le tableau de bord: structure de données du tableau de bord non valide", + "invalid-widget-file-error": "Impossible d'importer le widget: structure de données de widget invalide.", + "is-root-state": "Etat racine", + "make-private": "Rendre privé le tableau de bord", + "make-private-dashboard": "Rendre privé le tableau de bord", + "make-private-dashboard-text": "Après la confirmation, le tableau de bord sera rendu privé et ne sera plus accessible aux autres.", + "make-private-dashboard-title": "Etes-vous sûr de vouloir rendre le tableau de bord '{{dashboardTitle}}' privé?", + "make-public": "Rendre public le tableau de bord", + "manage-assigned-customers": "Gérer les clients affectés", + "manage-states": "Gérer les états du tableau de bord", + "management": "Gestion du tableau de bord", + "max-columns-count-message": "Seulement 1000 colonnes maximum sont autorisées.", + "max-horizontal-margin-message": "Seulement 50 sont autorisés en tant que valeur de marge horizontale maximale.", + "max-mobile-row-height-message": "Seuls 200 pixels sont autorisés en tant que valeur maximale de hauteur de ligne mobile.", + "max-vertical-margin-message": "Seulement 50 sont autorisés en tant que valeur de marge verticale maximale.", + "min-columns-count-message": "Seul un nombre minimum de 10 colonnes est autorisé.", + "min-horizontal-margin-message": "Seul 0 est autorisé comme valeur de marge horizontale minimale.", + "min-mobile-row-height-message": "Seuls 5 pixels sont autorisés en tant que valeur minimale de hauteur de ligne mobile.", + "min-vertical-margin-message": "Seul 0 est autorisé comme valeur de marge verticale minimale.", + "mobile-layout": "Paramètres de mise en page mobiles", + "mobile-row-height": "Hauteur de ligne mobile, px", + "mobile-row-height-required": "Une valeur de hauteur de ligne mobile est requise.", + "new-dashboard-title": "Nouveau titre du tableau de bord", + "no-dashboards-matching": "Aucun tableau de bord correspondant à {{entity}} n'a été trouvé. ", + "no-dashboards-text": "Aucun tableau de bord trouvé", + "no-image": "Aucune image sélectionnée", + "no-widgets": "Aucun widget configuré", + "open-dashboard": "Ouvrir le tableau de bord", + "open-toolbar": "Ouvrir la barre d'outils du tableau de bord", + "public": "Public", + "public-dashboard-notice": " Remarque: N'oubliez pas de rendre publics les dispositifs associés pour accéder à leurs données.", + "public-dashboard-text": "Votre tableau de bord {{dashboardTitle}} est maintenant public et accessible via le lien public
: ", + "public-dashboard-title": "Le tableau de bord est maintenant public", + "public-link": "Lien public", + "public-link-copied-message": "Le lien public du tableau de bord a été copié dans le presse-papier", + "search-states": "Recherche des états du tableau de bord", + "select-dashboard": "Sélectionner le tableau de bord", + "select-devices": "Selectionner les dispositifs", + "select-existing": "Sélectionnez un tableau de bord existant", + "select-state": "Sélectionnez l'état cible", + "select-widget-subtitle": "Liste des types de widgets disponibles", + "select-widget-title": "Sélectionner un widget", + "selected-states": "{count, plural, 1 {1 état du tableau de bord} other {# états du tableau de bord}} sélectionnés", + "set-background": "Définir l'arrière-plan", + "settings": "Paramètres", + "show-details": "Afficher les détails", + "socialshare-text": "'{{dashboardTitle}}' powered by ThingsBoard", + "socialshare-title": "'{{dashboardTitle}}' powered by ThingsBoard", + "state": "Etat du tableau de bord", + "state-controller": "Contrôleur d'état", + "state-id": "ID d'état", + "state-id-exists": "L'état du tableau de bord avec le même Id existe déjà.", + "state-id-required": "L'Id d'état du tableau de bord est requis.", + "state-name": "Nom", + "state-name-required": "Le nom de l'état du tableau de bord est requis", + "states": "Etats du tableau de bord", + "title": "Titre", + "title-color": "Couleur du titre", + "title-required": "Le titre est requis.", + "toolbar-always-open": "Garder la barre d'outils ouverte", + "unassign-dashboard": "Retirer le tableau de bord", + "unassign-dashboard-text": "Après la confirmation, le tableau de bord ne sera pas attribué et ne sera pas accessible au client.", + "unassign-dashboard-title": "Êtes-vous sûr de vouloir annuler l'affectation du tableau de bord '{{dashboardTitle}}'?", + "unassign-dashboards": "Retirer les tableaux de bord", + "unassign-dashboards-action-text": "Annuler l'affectation {count, plural, 1 {1 tableau de bord} other {# tableaux de bord}} des clients", + "unassign-dashboards-action-title": "Annuler l'affectation {count, plural, 1 {1 tableau de bord} other {# tableaux de bord}} du client", + "unassign-dashboards-text": "Après la confirmation, tous les tableaux de bord sélectionnés ne seront pas attribués et ne seront pas accessibles au client.", + "unassign-dashboards-title": "Etes-vous sûr de vouloir annuler l'affectation {count, plural, 1 {1 tableau de bord} other {# tableaux de bord}}?", + "unassign-from-customer": "Retirer du client", + "unassign-from-customers": "Retirer les tableaux de bord des clients", + "unassign-from-customers-text": "Veuillez sélectionner les clients à annuler l'affectation du ou des tableaux de bord", + "vertical-margin": "Marge verticale", + "vertical-margin-required": "Une valeur de marge verticale est requise", + "view-dashboards": "Afficher les tableaux de bord", + "widget-file": "Fichier du Widget", + "widget-import-missing-aliases-title": "Configurer les alias utilisés par le widget importé", + "widgets-margins": "Marge entre les widgets" + }, + "datakey": { + "advanced": "Avancé", + "alarm": "Champs d'alarme", + "alarm-fields-required": "Les champs d'alarme sont obligatoires.", + "attributes": "Attributs", + "color": "Couleur", + "configuration": "Configuration de la clé de données", + "data-generation-func": "Fonction de génération de données", + "decimals": "Nombre de chiffres après virgule flottante", + "function-types": "Types de fonctions", + "function-types-required": "Les types de fonctions sont obligatoires", + "label": "Label", + "maximum-function-types": "Maximum {count, plural, 1 {1 type de fonction est autorisé.} other {# types de fonctions sont autorisés}}", + "maximum-timeseries-or-attributes": "Maximum {count, plural, 1 {1 timeseries / attribut est autorisé.} other {# timeseries / attributs sont autorisés}}", + "settings": "Paramètres", + "timeseries": "Timeseries", + "timeseries-or-attributes-required": "Les timeseries / attributs d'entité sont obligatoires.", + "timeseries-required": "Les Timeseries de l'entité sont obligatoires.", + "units": "Symbole spécial à afficher à côté de la valeur", + "use-data-post-processing-func": "Utiliser la fonction de post-traitement des données" + }, + "datasource": { + "add-datasource-prompt": "Veuillez ajouter une source de données", + "name": "Nom", + "type": "Type de source de données" + }, + "datetime": { + "date-from": "Date de", + "date-to": "Date à", + "time-from": "Heure de", + "time-to": "Heure à" + }, + "details": { + "edit-mode": "Mode édition", + "toggle-edit-mode": "Activer le mode édition" + }, + "device": { + "access-token": "Jeton d'accès", + "access-token-invalid": "La longueur du jeton d'accès doit être comprise entre 1 et 20 caractères.", + "access-token-required": "Le jeton d'accès est requis.", + "accessTokenCopiedMessage": "Le jeton d'accès au dispositif a été copié dans le presse-papier", + "add": "Ajouter un dispositif", + "add-alias": "Ajouter un alias de dispositif", + "add-device-text": "Ajouter un nouveau dispositif", + "alias": "Alias", + "alias-required": "Un alias du dispositif est requis.", + "aliases": "Alias ​​du dispositif", + "any-device": "N'importe quel dispositif", + "assign-device-to-customer": "Affecter des dispositifs au client", + "assign-device-to-customer-text": "Veuillez sélectionner les dispositif à affecter au client", + "assign-devices": "Attribuer des dispositifs", + "assign-devices-text": "Attribuer {count, plural, 1 {1 dispositif} other {# dispositifs}} au client", + "assign-new-device": "Attribuer un nouveau dispositif", + "assign-to-customer": "Attribuer au client", + "assign-to-customer-text": "Veuillez sélectionner le client pour attribuer le ou les dispositifs", + "assignedToCustomer": "Attribué au client", + "configure-alias": "Configurer '{{alias}}' alias", + "copyAccessToken": "Copier le jeton d'accès", + "copyId": "Copier l'Id du dispositif", + "create-new-alias": "Créez un nouveau!", + "create-new-key": "Créez un nouveau!", + "credentials": "Informations d'identification", + "credentials-type": "Type d'identification", + "delete": "Supprimer le dispositif", + "delete-device-text": "Faites attention, après la confirmation, le dispositif et toutes les données associées deviendront irrécupérables.", + "delete-device-title": "Êtes-vous sûr de vouloir supprimer le dispositif '{{deviceName}}'?", + "delete-devices": "Supprimer les dispositifs", + "delete-devices-action-title": "Supprimer {count, plural, 1 {1 dispositif} other {# dispositifs}}", + "delete-devices-text": "Faites attention, après la confirmation, tous les dispositifs sélectionnés seront supprimés et toutes les données associées deviendront irrécupérables.", + "delete-devices-title": "Êtes-vous sûr de vouloir supprimer {count, plural, 1 {1 dispositif} other {# dispositifs}}?", + "description": "Description", + "details": "Détails", + "device": "Dispositif", + "device-alias": "Alias ​​du dispositif", + "device-credentials": "Informations d'identification du dispositif", + "device-details": "Détails du dispositif", + "device-list": "Liste des dispositifs", + "device-list-empty": "Aucun dispositif sélectionné.", + "device-name-filter-no-device-matched": "Aucun dispositif commençant par '{{device}} n'a été trouvé.", + "device-name-filter-required": "Le filtre de nom de dispositif est requis.", + "device-public": "Le dispositif est public", + "device-required": "Le dispositif est requis.", + "device-type": "Type de dispositif", + "device-type-list-empty": "Aucun type de dispositif sélectionné.", + "device-type-required": "Le type de dispositif est requis.", + "device-types": "Types de dispositif", + "devices": "Dispositifs", + "duplicate-alias-error": "Alias ​​en double trouvé '{{alias}}'.
Les alias de dispositifs doivent être uniques dans le tableau de bord.", + "enter-device-type": "Entrez le type de dispositif", + "events": "Événements", + "idCopiedMessage": "l'Id du dispositif a été copié dans le presse-papiers", + "is-gateway": "Est une passerelle", + "make-private": "Rendre le dispositif privé", + "make-private-device-text": "Après la confirmation, le dispositif et toutes ses données seront rendues privées et ne seront pas accessibles par d'autres.", + "make-private-device-title": "Etes-vous sûr de vouloir rendre le dispositif {{deviceName}} privé?", + "make-public": "Rendre le dispositif public", + "make-public-device-text": "Après la confirmation, le dispositif et toutes ses données seront rendus publics et accessibles par d'autres.", + "make-public-device-title": "Êtes-vous sûr de vouloir rendre le dispositif {{deviceName}} 'public?", + "manage-credentials": "Gérer les informations d'identification", + "management": "Gestion des dispositifs", + "name": "Nom", + "name-required": "Le nom est requis.", + "name-starts-with": "Le nom du dispositif commence par", + "no-alias-matching": "'{{alias}}' introuvable.", + "no-aliases-found": "Aucun alias trouvé.", + "no-device-types-matching": "Aucun type de dispositif correspondant à {{entitySubtype}} n'a été trouvé.", + "no-devices-matching": "Aucun dispositif correspondant à '{{entity}} n'a été trouvé.", + "no-devices-text": "Aucun dispositif trouvé", + "no-key-matching": "'{{key}}' introuvable.", + "no-keys-found": "Aucune clé trouvée", + "public": "Public", + "remove-alias": "Supprimer l'alias du dispositif", + "rsa-key": "Clé publique RSA", + "rsa-key-required": "La clé publique RSA est requise.", + "secret": "Secret", + "secret-required": "Code secret est requis.", + "select-device": "Selectionner un dispositif", + "select-device-type": "Sélectionner le type d'appareil", + "unable-delete-device-alias-text": "L'alias du dispositif '{{deviceAlias}}' ne peut pas être supprimé car il est utilisé par les widgets suivants:
{{widgetsList}}", + "unable-delete-device-alias-title": "Impossible de supprimer l'alias du dispositif", + "unassign-device": "Annuler l'affectation du dispositif", + "unassign-device-text": "Après la confirmation, le dispositif ne sera pas attribué et ne sera pas accessible au client.", + "unassign-device-title": "Êtes-vous sûr de vouloir annuler l'affection du dispositif {{deviceName}} '?", + "unassign-devices": "Annuler l'affectation des dispositifs", + "unassign-devices-action-title": "Annuler l'affectation de {count, plural, 1 {1 dispositif} other {#dispositifs}} du client", + "unassign-devices-text": "Après la confirmation, tous les dispositifs sélectionnés ne seront pas attribues et ne seront pas accessibles par le client.", + "unassign-devices-title": "Voulez-vous vraiment annuler l'affectation de {count, plural, 1 {1 dispositif} other {# dispositifs}}?", + "unassign-from-customer": "Retirer du client", + "use-device-name-filter": "Utiliser le filtre", + "view-credentials": "Afficher les informations d'identification", + "view-devices": "Afficher les dispositifs" + }, + "dialog": { + "close": "Fermer le dialogue" + }, + "entity": { + "add-alias": "Ajouter un alias d'entité", + "alarm-name-starts-with": "Les alarmes dont le nom commence par '{{prefix}}'", + "alias": "Alias", + "alias-required": "Un alias d'entité est requis.", + "aliases": "alias d'entité", + "all-subtypes": "Tout", + "any-entity": "Toute entité", + "asset-name-starts-with": "Les Assets dont le nom commence par '{{prefix}}'", + "configure-alias": "Configurer '{{alias}}' alias", + "create-new-alias": "Créez un nouveau!", + "create-new-key": "Créez un nouveau!", + "customer-name-starts-with": "Les clients dont les noms commencent par '{{prefix}}'", + "dashboard-name-starts-with": "Les tableaux de bord dont les noms commencent par '{{prefix}}'", + "details": "Détails de l'entité", + "device-name-starts-with": "Dispositifs dont le nom commence par '{{prefix}}'", + "duplicate-alias-error": "Alias ​​en double trouvé '{{alias}}'.
Les alias d'entité doivent être uniques dans le tableau de bord.", + "enter-entity-type": "Entrez le type d'entité", + "entities": "Entités", + "entity": "Entité", + "entity-alias": "Alias de l'entité", + "entity-list": "Liste d'entités", + "entity-list-empty": "Aucune entité sélectionnée.", + "entity-name": "Nom de l'entité", + "entity-name-filter-no-entity-matched": "Aucune entité commençant par '{{entity}}' n'a été trouvée.", + "entity-name-filter-required": "Le filtre de nom d'entité est requis.", + "entity-type": "Type d'entité", + "entity-type-list": "Liste de types d'entités", + "entity-type-list-empty": "Aucun type d'entité sélectionné.", + "entity-types": "Types d'entité", + "key": "Clé", + "key-name": "Nom de la clé", + "list-of-alarms": "{count, plural, 1 {Une alarme} other {Liste de # alarmes}}", + "list-of-assets": "{count, plural, 1 {Un Asset} other {Liste de # Assets}}", + "list-of-customers": "{count, plural, 1 {Un client} other {Liste de # clients}}", + "list-of-dashboards": "{count, plural, 1 {Un tableau de bord} other {Liste de # tableaux de bord}}", + "list-of-devices": "{count, plural, 1 {Un dispositif} other {Liste de # dispositifs}}", + "list-of-plugins": "{count, plural, 1 {Un plugin} other {Liste de # plugins}}", + "list-of-rulechains": "{count, plural, 1 {Une chaîne de règles} other {Liste de # chaînes de règles}}", + "list-of-rulenodes": "{count, plural, 1 {Un noeud de règles} other {Liste de # noeuds de règles}}", + "list-of-rules": "{count, plural, 1 {Une règle} other {Liste de # règles}}", + "list-of-tenants": "{count, plural, 1 {Un tenant} other {Liste de # tenants}}", + "list-of-users": "{count, plural, 1 {Un utilisateur} other {Liste de # utilisateurs}}", + "missing-entity-filter-error": "Le filtre est manquant pour l'alias '{{alias}}'.", + "name-starts-with": "Nom commence par", + "no-alias-matching": "'{{alias}}' introuvable.", + "no-aliases-found": "Aucun alias trouvé.", + "no-data": "Aucune donnée à afficher", + "no-entities-matching": "Aucune entité correspondant à '{{entity}}' n'a été trouvée.", + "no-entities-prompt": "Aucune entité trouvée", + "no-entity-types-matching": "Aucun type d'entité correspondant à {{entityType}} n'a été trouvé. ", + "no-key-matching": "'{{key}}' introuvable.", + "no-keys-found": "Aucune clé trouvée", + "plugin-name-starts-with": "Plugins dont les noms commencent par '{{prefix}}'", + "remove-alias": "Supprimer l'alias d'entité", + "rule-name-starts-with": "Règles dont les noms commencent par '{{prefix}}'", + "rulechain-name-starts-with": "Chaînes de règles dont les noms commencent par '{{prefix}}'", + "rulenode-name-starts-with": "Les noeuds de règles dont le nom commence par '{{prefix}}'", + "search": "Recherche d'entités", + "select-entities": "Sélectionner des entités", + "selected-entities": "{count, plural, 1 {1 entité} other {# entités}} sélectionnées", + "tenant-name-starts-with": "Les Tenant dont le nom commence par '{{prefix}}'", + "type": "Type", + "type-alarm": "Alarme", + "type-alarms": "Alarmes", + "type-asset": "Asset", + "type-assets": "Assets", + "type-current-customer": "Client actuel", + "type-customer": "Client", + "type-customers": "Clients", + "type-dashboard": "Tableau de bord", + "type-dashboards": "Tableaux de bord", + "type-device": "Dispositif", + "type-devices": "Dispositifs", + "type-plugin": "Plugin", + "type-plugins": "Plugins", + "type-required": "Le type d'entité est obligatoire.", + "type-rule": "Règle", + "type-rulechain": "Chaîne de règles", + "type-rulechains": "Chaînes de règles", + "type-rulenode": "Noeud de règle", + "type-rulenodes": "Noeuds de règle", + "type-rules": "Règles", + "type-tenant": "Tenant", + "type-tenants": "Tenants", + "type-user": "Utilisateur", + "type-users": "Utilisateurs", + "unable-delete-entity-alias-text": "L'alias d'entité '{{entityAlias}}' ne peut pas être supprimé car il est utilisé par les widgets suivants:
{{widgetsList}}", + "unable-delete-entity-alias-title": "Impossible de supprimer l'alias d'entité", + "use-entity-name-filter": "Utiliser un filtre", + "user-name-starts-with": "Utilisateurs dont les noms commencent par '{{prefix}}'" + }, + "error": { + "unable-to-connect": "Impossible de se connecter au serveur! Veuillez vérifier votre connexion Internet.", + "unhandled-error-code": "Code d'erreur non géré: {{errorCode}}", + "unknown-error": "Erreur inconnue" + }, + "event": { + "alarm": "Alarme", + "body": "Corps", + "data": "Données", + "data-type": "Type de données", + "entity": "Entité", + "error": "erreur", + "errors-occurred": "Des erreurs sont survenues", + "event": "événement", + "event-time": "Heure de l'événement", + "event-type": "Type d'événement", + "failed": "Échec", + "message-id": "Message Id", + "message-type": "Type de message", + "messages-processed": "Messages traités", + "metadata": "Métadonnées", + "method": "Méthode", + "no-events-prompt": "Aucun événement trouvé", + "relation-type": "Type de relation", + "server": "Serveur", + "status": "Etat", + "success": "Succès", + "type": "Type", + "type-debug-rule-chain": "Debug", + "type-debug-rule-node": "Debug", + "type-error": "Erreur", + "type-lc-event": "Evénement du cycle de vie", + "type-stats": "Statistiques" + }, + "extension": { + "add": "Ajouter une extension", + "add-attribute": "Ajouter un attribut", + "add-attribute-request": "Ajouter une demande d'attribut", + "add-attribute-update": "Ajouter une mise à jour d'attribut", + "add-broker": "Ajouter un Broker", + "add-config": "Ajouter une configuration de convertisseur", + "add-connect-request": "Ajouter une demande de connexion", + "add-converter": "Ajouter un convertisseur", + "add-device": "Ajouter un dispositif", + "add-disconnect-request": "Ajouter une demande de déconnexion", + "add-map": "Ajouter un élément de mappage", + "add-server-side-rpc-request": "Ajouter une requête RPC côté serveur", + "add-timeseries": "Ajouter des timeseries", + "anonymous": "Anonyme", + "attr-json-key-expression": "Expression json de la clé d'attribut", + "attr-topic-key-expression": "Expression du topic de la clé d'attribut", + "attribute-filter": "Filtre d'attribut", + "attribute-key-expression": "Expression de clé d'attribut", + "attribute-requests": "Demandes d'attributs", + "attribute-updates": "Mises à jour des attributs", + "attributes": "Attributs", + "basic": "Basic", + "brokers": "Brokers", + "ca-cert": "Fichier de certificat CA", + "cert": "Fichier de certificat *", + "client-scope": "Portée client", + "configuration": "Configuration", + "connect-requests": "Demandes de connexion", + "converter-configurations": "Configurations du convertisseur", + "converter-id": "ID du convertisseur", + "converter-json": "Json", + "converter-json-parse": "Impossible d'analyser le convertisseur json.", + "converter-json-required": "Le convertisseur json est requis.", + "converter-type": "Type de convertisseur", + "converters": "Convertisseurs", + "credentials": "Informations d'identification", + "custom": "Custom", + "delete": "Supprimer l'extension", + "delete-extension-text": "Attention, après la confirmation, l'extension et toutes les données associées deviendront irrécupérables.", + "delete-extension-title": "Êtes-vous sûr de vouloir supprimer l'extension '{{extensionId}}'?", + "delete-extensions-text": "Attention, après la confirmation, toutes les extensions sélectionnées seront supprimées.", + "delete-extensions-title": "Êtes-vous sûr de vouloir supprimer {count, plural, 1 {1 extension} other {# extensions}}?", + "device-name-expression": "expression du nom du dispositif", + "device-name-filter": "Filtre de nom de dispositif", + "device-type-expression": "expression de type de dispositif", + "disconnect-requests": "Demandes de déconnection", + "drop-file": "Déposez un fichier ou cliquez pour sélectionner un fichier à télécharger.", + "edit": "Modifier l'extension", + "export-extension": "Exporter l'extension", + "export-extensions-configuration": "Exporter la configuration des extensions", + "extension-id": "Id de l'extension", + "extension-type": "Type d'extension", + "extensions": "Extensions", + "field-required": "Le champ est obligatoire", + "file": "Fichier d'extensions", + "filter-expression": "Expression du filtre", + "host": "Hôte", + "id": "Id", + "import-extension": "Importer une extension", + "import-extensions": "Importer des extensions", + "import-extensions-configuration": "Importer la configuration des extensions", + "invalid-file-error": "Fichier d'extension non valide", + "json-name-expression": "Expression json du nom du dispositif", + "json-parse": "Impossible d'analyser json transformer.", + "json-required": "Transformer json est requis.", + "json-type-expression": "Expression json du type de dispositif", + "key": "Clé", + "mapping": "Mappage", + "method-filter": "Filtre de méthode", + "modbus-add-server": "Ajouter serveur/esclave", + "modbus-add-server-prompt": "Veuillez ajouter serveur/esclave", + "modbus-attributes-poll-period": "Période d'interrogation des attributs (ms)", + "modbus-baudrate": "Débit en bauds", + "modbus-byte-order": "Ordre des octets", + "modbus-databits": "Bits de données", + "modbus-databits-range": "Les bits de données doivent être compris entre 7 et 8.", + "modbus-device-name": "Nom du dispositif", + "modbus-encoding": "Encodage", + "modbus-function": "Fonction", + "modbus-parity": "parité", + "modbus-poll-period": "Période d'interrogation (ms)", + "modbus-poll-period-range": "La période d'interrogation doit être une valeur positive.", + "modbus-port-name": "Nom du port série", + "modbus-register-address": "Adresse du registre", + "modbus-register-address-range": "L'adresse du registre doit être comprise entre 0 et 65535.", + "modbus-register-bit-index": "Bit index", + "modbus-register-bit-index-range": "L'index de bit doit être compris entre 0 et 15.", + "modbus-register-count": "Nombre de registre", + "modbus-register-count-range": "Le nombre de registres doit être une valeur positive.", + "modbus-server": "Serveurs / esclaves", + "modbus-stopbits": "Bits d'arrêt", + "modbus-stopbits-range": "Les bits d'arrêt doivent être compris entre 1 et 2.", + "modbus-tag": "Tag", + "modbus-timeseries-poll-period": "Période d'interrogation des Timeseries (ms)", + "modbus-transport": "Transport", + "modbus-unit-id": "Id de l'unité", + "modbus-unit-id-range": "L'ID de l'unité doit être compris entre 1 et 247.", + "no-file": "Aucun fichier sélectionné.", + "opc-add-server": "Ajouter un serveur", + "opc-add-server-prompt": "Veuillez ajouter un serveur", + "opc-application-name": "Nom de l'application", + "opc-application-uri": "Uri de l'application", + "opc-device-name-pattern": "modèle de nom du dispositif", + "opc-device-node-pattern": "modèle de noeud de dispositif", + "opc-identity": "Identité", + "opc-keystore": "Magasin de clés", + "opc-keystore-alias": "Alias", + "opc-keystore-key-password": "Mot de passe de la clé", + "opc-keystore-location": "Emplacement *", + "opc-keystore-password": "Mot de passe", + "opc-keystore-type": "Type", + "opc-scan-period-in-seconds": "Période d'analyse en secondes", + "opc-security": "Sécurité", + "opc-server": "Serveurs", + "opc-type": "Type", + "password": "Mot de passe", + "pem": "PEM", + "port": "Port", + "port-range": "Le port doit être compris entre 1 et 65535.", + "private-key": "Fichier de clé privée *", + "request-id-expression": "Expression de demande d'id", + "request-id-json-expression": "Expression json de la demande d'id", + "request-id-topic-expression": "Expression de la demande d'id du topic", + "request-topic-expression": "Expression de la demande du topic", + "response-timeout": "Délai de réponse en millisecondes", + "response-topic-expression": "Expression du topic de la réponse", + "retry-interval": "Intervalle de nouvelle tentative en millisecondes", + "selected-extensions": "{count, plural, 1 {1 extension} other {# extensions}} sélectionné", + "server-side-rpc": "RPC côté serveur", + "ssl": "Ssl", + "sync": { + "last-sync-time": "Dernière heure de synchronisation", + "not-available": "Non disponible", + "not-sync": "Non sync", + "status": "Status", + "sync": "Sync" + }, + "timeout": "Délai d'attente en millisecondes", + "timeseries": "Timeseries", + "to-double": "To Double", + "token": "Jeton de sécurité", + "topic": "Topic", + "topic-expression": "Expression du topic", + "topic-filter": "Filtre du topic", + "topic-name-expression": "Expression du nom du dispositif (topic)", + "topic-type-expression": "Expression de type de dispositif (topic)", + "transformer": "Transformer", + "transformer-json": "JSON *", + "type": "Type", + "unique-id-required": "L'identifiant d'extension actuel existe déjà.", + "username": "Nom d'utilisateur", + "value": "Valeur", + "value-expression": "Expression de la valeur" + }, + "fullscreen": { + "exit": "Quitter le plein écran", + "expand": "Afficher en plein écran", + "fullscreen": "Plein écran", + "toggle": "Activer le mode plein écran" + }, + "function": { + "function": "Fonction" + }, + "grid": { + "add-item-text": "Ajouter un nouvel élément", + "delete-item": "Supprimer l'élément", + "delete-item-text": "Faites attention, après la confirmation, cet élément et toutes les données associées deviendront irrécupérables.", + "delete-item-title": "Êtes-vous sûr de vouloir supprimer cet élément?", + "delete-items": "Supprimer les éléments", + "delete-items-action-title": "Supprimer {count, plural, 1 {1 élément} other {# éléments}}", + "delete-items-text": "Attention, après la confirmation, tous les éléments sélectionnés seront supprimés et toutes les données associées deviendront irrécupérables.", + "delete-items-title": "Etes-vous sûr de vouloir supprimer {count, plural, 1 {1 élément} other {# éléments}}?", + "item-details": "Détails de l'élément", + "no-items-text": "Aucun élément trouvé", + "scroll-to-top": "Défiler vers le haut" + }, + "help": { + "goto-help-page": "Aller à la page d'aide" + }, + "home": { + "avatar": "Avatar", + "home": "Accueil", + "logout": "Déconnexion", + "menu": "Menu", + "open-user-menu": "Ouvrir le menu utilisateur", + "profile": "Profile" + }, + "icon": { + "icon": "Icône", + "material-icons": "Material icons", + "select-icon": "Sélectionner l'icône", + "show-all": "Afficher toutes les icônes" + }, + "import": { + "drop-file": "Déposez un fichier JSON ou cliquez pour sélectionner un fichier à télécharger.", + "no-file": "Aucun fichier sélectionné" + }, + "item": { + "selected": "Sélectionné" + }, + "js-func": { + "no-return-error": "La fonction doit renvoyer une valeur!", + "return-type-mismatch": "La fonction doit renvoyer une valeur de type '{{type}}' !", + "tidy": "Tidy" + }, + "key-val": { + "add-entry": "Ajouter une entrée", + "key": "Clé", + "no-data": "Aucune entrée", + "remove-entry": "Supprimer l'entrée", + "value": "Valeur" + }, + "language": { + "language": "Language", + "locales": { + "de_DE": "Allemand", + "en_US": "Anglais", + "fr_FR": "Français", + "es_ES": "Espagnol", + "it_IT": "Italien", + "ko_KR": "Coréen", + "ru_RU": "Russe", + "zh_CN": "Chinois", + "ja_JA": "Japonaise", + "tr_TR": "Turc", + "fa_IR": "Persane", + "uk_UA": "Ukrainien", + "cs_CZ": "Tchèque" + } + }, + "layout": { + "color": "Couleur", + "layout": "Mise en page", + "main": "Principal", + "manage": "Gérer les mises en page", + "right": "Droite", + "select": "Sélectionner la mise en page cible", + "settings": "Paramètres de mise en page" + }, + "legend": { + "avg": "moy", + "max": "max", + "min": "min", + "position": "Position de la légende", + "settings": "Paramètres de la légende", + "show-avg": "Afficher la valeur moyenne", + "show-max": "Afficher la valeur maximale", + "show-min": "Afficher la valeur min", + "show-total": "Afficher la valeur totale", + "total": "total" + }, + "login": { + "create-password": "Créer un mot de passe", + "email": "Email", + "forgot-password": "Mot de passe oublié?", + "login": "Login", + "new-password": "Nouveau mot de passe", + "new-password-again": "nouveau mot de passe", + "password-again": "Mot de passe à nouveau", + "password-link-sent-message": "Le lien de réinitialisation du mot de passe a été envoyé avec succès!", + "password-reset": "Mot de passe réinitialisé", + "passwords-mismatch-error": "Les mots de passe saisis doivent être identiques!", + "remember-me": "Se souvenir de moi", + "request-password-reset": "Demander la réinitialisation du mot de passe", + "reset-password": "Réinitialiser le mot de passe", + "sign-in": "Veuillez vous connecter", + "username": "Nom d'utilisateur (email)" + }, + "position": { + "bottom": "Bas", + "left": "Gauche", + "right": "Droite", + "top": "Haut" + }, + "profile": { + "change-password": "Modifier le mot de passe", + "current-password": "Mot de passe actuel", + "profile": "Profile" + }, + "relation": { + "add": "Ajouter une relation", + "add-relation-filter": "Ajouter un filtre de relation", + "additional-info": "Informations supplémentaires (JSON)", + "any-relation": "toute relation", + "any-relation-type": "N'importe quel type", + "delete": "Supprimer la relation", + "delete-from-relation-text": "Attention, après la confirmation, l'entité actuelle ne sera pas liée à l'entité '{{entityName}}'.", + "delete-from-relation-title": "Etes-vous sûr de vouloir supprimer la relation de l'entité '{{entityName}}'?", + "delete-from-relations-text": "Attention, après la confirmation, toutes les relations sélectionnées seront supprimées et l'entité actuelle ne sera pas liée aux entités correspondantes.", + "delete-from-relations-title": "Êtes-vous sûr de vouloir supprimer {count, plural, 1 {1 relation} other {# relations}}?", + "delete-to-relation-text": "Attention, après la confirmation, l'entité '{{entityName}} ne sera plus liée à l'entité actuelle.", + "delete-to-relation-title": "Êtes-vous sûr de vouloir supprimer la relation avec l'entité '{{entityName}}'?", + "delete-to-relations-text": "Attention, après la confirmation, toutes les relations sélectionnées seront supprimées et les entités correspondantes ne seront pas liées à l'entité en cours.", + "delete-to-relations-title": "Etes-vous sûr de vouloir supprimer {count, plural, 1 {1 relation} other {# relations}}?", + "direction": "Sens", + "direction-type": { + "FROM": "de", + "TO": "à" + }, + "edit": "Modifier la relation", + "from-entity": "De l'entité", + "from-entity-name": "Du nom d'entité", + "from-entity-type": "Du type d'entité", + "from-relations": "Relations sortantes", + "invalid-additional-info": "Impossible d'analyser les informations supplémentaires json.", + "relation-filters": "Filtres de relation", + "relation-type": "Type de relation", + "relation-type-required": "Le type de relation est requis.", + "relations": "Relations", + "remove-relation-filter": "Supprimer le filtre de relation", + "search-direction": { + "FROM": "De", + "TO": "À" + }, + "selected-relations": "{count, plural, 1 {1 relation} other {# relations}} sélectionné", + "to-entity": "À l'entité", + "to-entity-name": "vers le nom de l'entité", + "to-entity-type": "Vers le type d'entité", + "to-relations": "Relations entrantes", + "type": "Type" + }, + "rulechain": { + "add": "Ajouter une chaîne de règles", + "add-rulechain-text": "Ajouter une nouvelle chaîne de règles", + "copyId": "Copier l'identifiant de la chaîne de règles", + "create-new-rulechain": "Créer une nouvelle chaîne de règles", + "debug-mode": "Mode de débogage", + "delete": "Supprimer la chaîne de règles", + "delete-rulechain-text": "Attention, après la confirmation, la chaîne de règles et toutes les données associées deviendront irrécupérables.", + "delete-rulechain-title": "Voulez-vous vraiment supprimer la chaîne de règles '{{ruleChainName}}'?", + "delete-rulechains-action-title": "Supprimer {count, plural, 1 {1 chaîne de règles} other {# chaînes de règles}}", + "delete-rulechains-text": "Attention, après la confirmation, toutes les chaînes de règles sélectionnées seront supprimées et toutes les données associées deviendront irrécupérables.", + "delete-rulechains-title": "Êtes-vous sûr de vouloir supprimer {count, plural, 1 {1 chaîne de règles} other {# chaînes de règles}}?", + "description": "Description", + "details": "Détails", + "events": "Evénements", + "export": "Exporter la chaîne de règles", + "export-failed-error": "Impossible d'exporter la chaîne de règles: {{error}}", + "idCopiedMessage": "L'ID de la chaîne de règles a été copié dans le presse-papier", + "import": "Importer la chaîne de règles", + "invalid-rulechain-file-error": "Impossible d'importer la chaîne de règles: structure de données de la chaîne de règles non valide", + "management": "Gestion des règles", + "name": "Nom", + "name-required": "Le nom est requis.", + "no-rulechains-matching": "Aucune chaîne de règles correspondant à {{entity}} n'a été trouvée.", + "no-rulechains-text": "Aucune chaîne de règles trouvée", + "root": "Racine", + "rulechain": "Chaîne de règles", + "rulechain-details": "Détails de la chaîne de règles", + "rulechain-file": "Fichier de chaîne de règles", + "rulechain-required": "Chaîne de règles requise", + "rulechains": "Chaînes de règles", + "select-rulechain": "Sélectionner la chaîne de règles", + "set-root": "Rend la chaîne de règles racine (root) ", + "set-root-rulechain-text": "Après la confirmation, la chaîne de règles deviendra racine (root) et gérera tous les messages de transport entrants.", + "set-root-rulechain-title": "Voulez-vous vraiment que la chaîne de règles '{{ruleChainName}} soit racine (root) ?", + "system": "Système" + }, + "rulenode": { + "add": "Ajouter un noeud de règle", + "add-link": "Ajouter un lien", + "configuration": "Configuration", + "copy-selected": "Copier les éléments sélectionnés", + "create-new-link-label": "Créez un nouveau!", + "custom-link-label": "Etiquette de lien personnalisée", + "custom-link-label-required": "Une étiquette de lien personnalisée est requise", + "debug-mode": "Mode de débogage", + "delete": "Supprimer le noeud de règle", + "delete-selected": "Supprimer les éléments sélectionnés", + "delete-selected-objects": "Supprimer les nœuds et les connexions sélectionnés", + "description": "Description", + "deselect-all": "Désélectionner tout", + "deselect-all-objects": "Désélectionnez tous les nœuds et toutes les connexions", + "details": "Détails", + "directive-is-not-loaded": "La directive de configuration définie '{{directiveName}} n'est pas disponible.", + "events": "Événements", + "help": "Aide", + "invalid-target-rulechain": "Impossible de résoudre la chaîne de règles cible!", + "link": "Lien", + "link-details": "Détails du lien du noeud de la règle", + "link-label": "Étiquette du lien", + "link-label-required": "L'étiquette du lien est obligatoire", + "link-labels": "Étiquettes de lien", + "link-labels-required": "Les étiquettes de lien sont obligatoires", + "message": "Message", + "message-type": "Type de message", + "message-type-required": "Le type de message est obligatoire", + "metadata": "Métadonnées", + "metadata-required": "Les entrées de métadonnées ne peuvent pas être vides.", + "name": "Nom", + "name-required": "Le nom est requis.", + "no-link-label-matching": "'{{label}}' introuvable.", + "no-link-labels-found": "Aucune étiquette de lien trouvée", + "open-node-library": "Ouvrir la bibliothèque de noeud", + "output": "Output", + "rulenode-details": "Détails du noeud de la règle", + "search": "Recherche de noeuds", + "select-all": "Tout sélectionner", + "select-all-objects": "Sélectionnez tous les noeuds et connexions", + "select-message-type": "Sélectionner le type de message", + "test": "Test", + "test-script-function": "Tester le script", + "type": "Type", + "type-action": "Action", + "type-action-details": "Effectuer une action spéciale", + "type-enrichment": "Enrichissement", + "type-enrichment-details": "Ajouter des informations supplémentaires dans les métadonnées de message", + "type-external": "Externe", + "type-external-details": "Interagit avec le système externe", + "type-filter": "Filtre", + "type-filter-details": "Filtrer les messages entrants avec des conditions configurées", + "type-input": "Input", + "type-input-details": "Entrée logique de la chaîne de règles, transmet les messages entrants au prochain nœud de règle associé", + "type-rule-chain": "Chaîne de règles", + "type-rule-chain-details": "Transmet les messages entrants à la chaîne de règles spécifiée", + "type-transformation": "Transformation", + "type-transformation-details": "Modifier le payload du message et les métadonnées ", + "type-unknown": "Inconnu", + "type-unknown-details": "Noeud de règle non résolu", + "ui-resources-load-error": "Impossible de charger les ressources de configuration de l'interface utilisateur." + }, + "tenant": { + "add": "Ajouter un Tenant", + "add-tenant-text": "Ajouter un nouveau Tenant", + "admins": "Admins", + "copyId": "Copier l'Id du Tenant", + "delete": "Supprimer le Tenant", + "delete-tenant-text": "Attention, après la confirmation, le Tenant et toutes les données associées deviendront irrécupérables.", + "delete-tenant-title": "Etes-vous sûr de vouloir supprimer le tenant '{{tenantTitle}}'?", + "delete-tenants-action-title": "Supprimer {count, plural, 1 {1 tenant} other {# tenants}}", + "delete-tenants-text": "Attention, après la confirmation, tous les Tenants sélectionnés seront supprimés et toutes les données associées deviendront irrécupérables.", + "delete-tenants-title": "Etes-vous sûr de vouloir supprimer {count, plural, 1 {1 tenant} other {# tenants}}?", + "description": "Description", + "details": "Détails", + "events": "Événements", + "idCopiedMessage": "L'Id du Tenant a été copié dans le Presse-papiers", + "manage-tenant-admins": "Gérer les administrateurs du Tenant", + "management": "Gestion des Tenants", + "no-tenants-matching": "Aucun Tenant correspondant à {{entity}} n'a été trouvé. ", + "no-tenants-text": "Aucun Tenant trouvé", + "select-tenant": "Sélectionner un Tenant", + "tenant": "Tenant", + "tenant-details": "Détails du Tenant", + "tenant-required": "Tenant requis", + "tenants": "Tenants", + "title": "Titre", + "title-required": "Le titre est requis." + }, + "timeinterval": { + "advanced": "Avancé", + "days": "Jours", + "days-interval": "{days, plural, 1 {1 jour} other {# jours}}", + "hours": "Heures", + "hours-interval": "{hours, plural, 1 {1 heure} other {# heures}}", + "minutes": "Minutes", + "minutes-interval": "{minutes, plural, 1 {1 minute} other {# minutes}}", + "seconds": "Secondes", + "seconds-interval": "{seconds, plural, 1 {1 seconde} other {# secondes}}" + }, + "timewindow": { + "date-range": "Plage de dates", + "days": "{days, plural, 1 {jour} other {# jours}}", + "edit": "Modifier timewindow", + "history": "Historique", + "hours": "{hours, plural, 0 {heure} 1 {1 heure} other {# heures}}", + "last": "Dernier", + "last-prefix": "dernier", + "minutes": "{minutes, plural, 0 {minute} 1 {1 minute} other {# minutes}}", + "period": "de {{startTime}} à {{endTime}}", + "realtime": "Temps réel", + "seconds": "{seconds, plural, 0 {second} 1 {1 second} other {# seconds}}", + "time-period": "Période" + }, + "user": { + "activation-email-sent-message": "L'e-mail d'activation a été envoyé avec succès!", + "activation-link": "Lien d'activation utilisateur", + "activation-link-copied-message": "le lien d'activation de l'utilisateur a été copié dans le presse-papier", + "activation-link-text": "Pour activer l'utilisateur, utilisez le lien d'activation suivant: ", + "activation-method": "Méthode d'activation", + "add": "Ajouter un utilisateur", + "add-user-text": "Ajouter un nouvel utilisateur", + "always-fullscreen": "Toujours en plein écran", + "anonymous": "Anonyme", + "copy-activation-link": "Copier le lien d'activation", + "customer": "Client", + "customer-users": "Utilisateurs du client", + "default-dashboard": "Tableau de bord par défaut", + "delete": "Supprimer l'utilisateur", + "delete-user-text": "Attention, après la confirmation, l'utilisateur et toutes les données associées deviendront irrécupérables.", + "delete-user-title": "Etes-vous sûr de vouloir supprimer l'utilisateur '{{userEmail}}'?", + "delete-users-action-title": "Supprimer {count, plural, 1 {1 utilisateur} other {# utilisateurs}}", + "delete-users-text": "Attention, après la confirmation, tous les utilisateurs sélectionnés seront supprimés et toutes les données associées deviendront irrécupérables.", + "delete-users-title": "Etes-vous sûr de vouloir supprimer {count, plural, 1 {1 utilisateur} other {# utilisateurs}}?", + "description": "Description", + "details": "Détails", + "display-activation-link": "Afficher le lien d'activation", + "email": "Email", + "email-required": "Email est requis.", + "first-name": "Prénom", + "invalid-email-format": "Format de courrier électronique non valide", + "last-name": "Nom de famille", + "no-users-matching": "Aucun utilisateur correspondant à '{{entity}}' n'a été trouvé.", + "no-users-text": "Aucun utilisateur trouvé", + "resend-activation": "Renvoyer l'activation", + "select-user": "Sélectionner l'utilisateur", + "send-activation-mail": "Envoyer un mail d'activation", + "sys-admin": "Administrateur du système", + "tenant-admin": "Administrateur du Tenant", + "tenant-admins": "administrateurs du Tenant", + "user": "utilisateur", + "user-details": "Détails de l'utilisateur", + "user-required": "L'utilisateur est requis", + "users": "Utilisateurs" + }, + "value": { + "boolean": "booléen", + "boolean-value": "Valeur booléenne", + "double": "Double", + "double-value": "Valeur double", + "false": "Faux", + "integer": "Entier", + "integer-value": "Valeur entière", + "invalid-integer-value": "Valeur entière invalide", + "long": "Long", + "string": "String", + "string-value": "Valeur String", + "true": "Vrai", + "type": "Type de valeur" + }, + "widget": { + "add": "Ajouter un widget", + "add-resource": "Ajouter une ressource", + "add-widget-type": "Ajouter un nouveau type de widget", + "alarm": "Widget d'alarme", + "css": "CSS", + "datakey-settings-schema": "Schéma des paramètres de Data key", + "edit": "Modifier le widget", + "editor": " Editeur de widget", + "export": "Exporter widget", + "html": "HTML", + "javascript": "Javascript", + "latest-values": "Dernières valeurs", + "management": "Gestion des widgets", + "missing-widget-title-error": "Le titre du widget doit être spécifié!", + "no-data-found": "Aucune donnée trouvée", + "remove": "Supprimer le widget", + "remove-resource": "Supprimer une ressource", + "remove-widget-text": "Après la confirmation, le widget et toutes les données associées deviendront irrécupérables.", + "remove-widget-title": "Êtes-vous sûr de vouloir supprimer le widget '{{widgetTitle}}'?", + "remove-widget-type": "Supprimer le type de widget", + "remove-widget-type-text": "Après la confirmation, le type de widget et toutes les données associées deviendront irrécupérables.", + "remove-widget-type-title": "Êtes-vous sûr de vouloir supprimer le type de widget '{{widgetName}}'?", + "resource-url": "URL JavaScript / CSS", + "resources": "Ressources", + "rpc": "Widget de contrôle", + "run": "Exécuter un widget", + "save": "Enregistrer le widget", + "save-widget-type-as": "Enregistrer le type de widget sous", + "save-widget-type-as-text": "Veuillez saisir un nouveau titre de widget et / ou sélectionner un ensemble de widgets cibles", + "saveAs": "Enregistrer le widget sous", + "search-data": "Rechercher des données", + "select-widget-type": "Sélectionnez le type de widget", + "select-widgets-bundle": "Sélectionner un ensemble de widgets", + "settings-schema": "Schéma des paramètres", + "static": "Widget statique", + "tidy": "Tidy", + "timeseries": "Séries chronologiques", + "title": "Titre du widget", + "title-required": "Le titre du widget est requis.", + "toggle-fullscreen": "Basculer le mode plein écran", + "type": "Type de widget", + "unable-to-save-widget-error": "Impossible de sauvegarder le widget! Le widget a des erreurs!", + "undo": "Annuler les modifications du widget", + "widget-bundle": "Ensemble de widget", + "widget-library": "Bibliothèque de widgets", + "widget-saved": "Widget enregistré", + "widget-template-load-failed-error": "Impossible de charger le modèle de widget!", + "widget-type-load-error": "Le widget n'a pas été chargé à cause des erreurs suivantes:", + "widget-type-load-failed-error": "Impossible de charger le type de widget!", + "widget-type-not-found": "Problème de chargement de la configuration du widget.
Le type de widget associé a probablement été supprimé." + }, + "widget-action": { + "custom": "Action personnalisée", + "header-button": "Bouton d'en-tête de widget", + "open-dashboard": "Naviguer vers un autre tableau de bord", + "open-dashboard-state": "Naviguer vers un nouvel état du tableau de bord", + "open-right-layout": "Ouvrir la disposition du tableau de bord droite (vue mobile)", + "set-entity-from-widget": "Définir l'entité à partir du widget", + "target-dashboard": "Tableau de bord cible", + "target-dashboard-state": "Etat du tableau de bord cible", + "target-dashboard-state-required": "L'état du tableau de bord cible est requis", + "update-dashboard-state": "Mettre à jour l'état actuel du tableau de bord" + }, + "widget-config": { + "action": "Action", + "action-icon": "Icône", + "action-name": "Nom", + "action-name-not-unique": "Une autre action portant le même nom existe déjà.
Le nom de l'action doit être unique dans la même source d'action.", + "action-name-required": "Le nom de l'action est requis", + "action-source": "Source de l'action", + "action-source-required": "Une source d'action est requise.", + "action-type": "Type", + "action-type-required": "Le type d'action est requis.", + "actions": "Actions", + "add-action": "Ajouter une action", + "add-datasource": "Ajouter une source de données", + "advanced": "Avancé", + "alarm-source": "Source d'alarme", + "background-color": "couleur de fond", + "data": "Données", + "datasource-parameters": "Paramètres", + "datasource-type": "Type", + "datasources": "Sources de données", + "decimals": "Nombre de chiffres après virgule flottante", + "delete-action": "Supprimer l'action", + "delete-action-text": "Etes-vous sûr de vouloir supprimer l'action du widget nommé '{{actionName}}'?", + "delete-action-title": "Supprimer l'action du widget", + "display-timewindow": "Afficher fenêtre de temps", + "display-legend": "Afficher la légende", + "display-title": "Afficher le titre", + "drop-shadow": "Ombre portée", + "edit-action": "Modifier l'action", + "enable-fullscreen": "Activer le plein écran", + "general-settings": "Paramètres généraux", + "height": "Hauteur", + "margin": "Marge", + "maximum-datasources": "Maximum {count, plural, 1 {1 datasource est autorisé.} other {# datasources sont autorisés}}", + "mobile-mode-settings": "Paramètres du mode mobile", + "order": "Ordre", + "padding": "Padding", + "remove-datasource": "Supprimer la source de données", + "search-actions": "Recherche d'actions", + "settings": "Paramètres", + "target-device": "Dispositif cible", + "text-color": "Couleur du texte", + "timewindow": "Fenêtre de temps", + "title": "Titre", + "title-style": "Style de titre", + "units": "Symbole spécial à afficher à côté de la valeur", + "use-dashboard-timewindow": "Utiliser la fenêtre de temps du tableau de bord", + "widget-style": "Style du widget", + "display-icon": "Afficher l'icône du titre", + "icon-color": "Couleur de l'icône", + "icon-size": "Taille de l'icône" + }, + "widget-type": { + "create-new-widget-type": "Créer un nouveau type de widget", + "export": "Exporter le type de widget", + "export-failed-error": "Impossible d'exporter le type de widget: {{error}}", + "import": "Importer le type de widget", + "invalid-widget-type-file-error": "Impossible d'importer le type de widget: structure de données de type widget invalide.", + "widget-type-file": "Fichier de type Widget" + }, + "widgets": { + "date-range-navigator": { + "localizationMap": { + "Sun": "Dim.", + "Mon": "Lun.", + "Tue": "Mar.", + "Wed": "Mer.", + "Thu": "Jeu.", + "Fri": "Ven.", + "Sat": "Sam.", + "Jan": "Janv.", + "Feb": "Févr.", + "Mar": "Mars", + "Apr": "Avr.", + "May": "Mai", + "Jun": "Juin", + "Jul": "Juil.", + "Aug": "Août", + "Sep": "Sept.", + "Oct": "Oct.", + "Nov": "Nov.", + "Dec": "Déc.", + "January": "Janvier", + "February": "Février", + "March": "Mars", + "April": "Avril", + "June": "Juin", + "July": "Juillet", + "August": "Août", + "September": "Septembre", + "October": "Octobre", + "November": "Novembre", + "December": "Décembre", + "Custom Date Range": "Plage de dates personnalisée", + "Date Range Template": "Modèle de plage de dates", + "Today": "Aujourd'hui", + "Yesterday": "Hier", + "This Week": "Cette semaine", + "Last Week": "La semaine dernière", + "This Month": "Ce mois-ci", + "Last Month": "Le mois dernier", + "Year": "Année", + "This Year": "Cette année", + "Last Year": "L'année dernière", + "Date picker": "Sélecteur de date", + "Hour": "Heure", + "Day": "Journée", + "Week": "La semaine", + "2 weeks": "2 Semaines", + "Month": "Mois", + "3 months": "3 Mois", + "6 months": "6 Mois", + "Custom interval": "Intervalle personnalisé", + "Interval": "Intervalle", + "Step size": "Taille de pas", + "Ok": "Ok" + } + } + }, + "widgets-bundle": { + "add": "Ajouter un groupe de widgets", + "add-widgets-bundle-text": "Ajouter un nouveau groupe de widgets", + "create-new-widgets-bundle": "Créer un nouveau groupe de widgets", + "current": "Groupe actuel", + "delete": "Supprimer le groupe de widgets", + "delete-widgets-bundle-text": "Attention, après la confirmation, le groupe de widgets et toutes les données associées deviendront irrécupérables.", + "delete-widgets-bundle-title": "Êtes-vous sûr de vouloir supprimer le groupe de widgets '{{widgetsBundleTitle}}'?", + "delete-widgets-bundles-action-title": "Supprimer {count, plural, 1 {1 groupe de widgets} other {# groupes de widgets}}", + "delete-widgets-bundles-text": "Attention, après la confirmation, tous les groupes de widgets sélectionnés seront supprimés et toutes les données associées deviendront irrécupérables.", + "delete-widgets-bundles-title": "Voulez-vous vraiment supprimer {count, plural, 1 {1 groupe de widgets} other {# groupes de widgets}}?", + "details": "Détails", + "empty": "Le groupe de widgets est vide", + "export": "Exporter le groupe de widgets", + "export-failed-error": "Impossible d'exporter le groupe de widgets: {{error}}", + "import": "Importer un groupe de widgets", + "invalid-widgets-bundle-file-error": "Impossible d'importer un groupe de widgets: structure de données du groupe de widgets non valides.", + "no-widgets-bundles-matching": "Aucun groupe de widgets correspondant à {{widgetsBundle}} n'a été trouvé.", + "no-widgets-bundles-text": "Aucun groupe de widgets trouvé", + "system": "Système", + "title": "Titre", + "title-required": "Le titre est requis.", + "widgets-bundle-details": "Détails des groupes de widgets", + "widgets-bundle-file": "Fichier de groupe de widgets", + "widgets-bundle-required": "Un groupe de widgets est requis.", + "widgets-bundles": "Groupes de widgets" + } +} diff --git a/ui-ngx/src/assets/locale/locale.constant-it_IT.json b/ui-ngx/src/assets/locale/locale.constant-it_IT.json new file mode 100644 index 0000000000..2f36ba5f52 --- /dev/null +++ b/ui-ngx/src/assets/locale/locale.constant-it_IT.json @@ -0,0 +1,1655 @@ +{ + "access": { + "unauthorized": "Non autorizzato", + "unauthorized-access": "Accesso non autorizzato", + "unauthorized-access-text": "Devi effettuare il login per accedere a questa risorsa!", + "access-forbidden": "Accesso Vietato", + "access-forbidden-text": "Non hai i diritti di accesso a questa posizione!
Prova ad effettuare il login con un diverso account.", + "refresh-token-expired": "Sessione scaduta", + "refresh-token-failed": "Impossibile aggiornare la sessione" + }, + "action": { + "activate": "Attiva", + "suspend": "Sospendi", + "save": "Salva", + "saveAs": "Salva come", + "cancel": "Cancella", + "ok": "OK", + "delete": "Elimina", + "add": "Aggiungi", + "yes": "Sì", + "no": "No", + "update": "Aggiorna", + "remove": "Rimuovi", + "search": "Cerca", + "clear-search": "Cancella ricerca", + "assign": "Assegna", + "unassign": "Annulla assegnazione", + "share": "Condividi", + "make-private": "Rendi privato", + "apply": "Applica", + "apply-changes": "Applica modifiche", + "edit-mode": "Modalità modifica", + "enter-edit-mode": "Attiva modalità di modifica", + "decline-changes": "Annulla modifiche", + "close": "Chiudi", + "back": "Indietro", + "run": "Esegui", + "sign-in": "Registrati!", + "edit": "Modifica", + "view": "Visualizza", + "create": "Crea", + "drag": "Trascina", + "refresh": "Aggiorna", + "undo": "Annulla", + "copy": "Copia", + "paste": "Incolla", + "copy-reference": "Copia riferimento", + "paste-reference": "Incolla riferimento", + "import": "Importa", + "export": "Esporta", + "share-via": "Condividi con {{provider}}" + }, + "aggregation": { + "aggregation": "Aggregazione", + "function": "Funzione di aggregazione dati", + "limit": "Valori max", + "group-interval": "Intervallo di raggruppamento", + "min": "Min", + "max": "Max", + "avg": "Media", + "sum": "Somma", + "count": "Conteggio", + "none": "Nessuna" + }, + "admin": { + "general": "Generale", + "general-settings": "Impostazioni Generali", + "outgoing-mail": "Posta in uscita", + "outgoing-mail-settings": "Impostazioni Posta in uscita", + "system-settings": "Impostazioni di sistema", + "test-mail-sent": "Mail di test inviata con successo!", + "base-url": "URL di base", + "base-url-required": "URL di base obbligatoria.", + "mail-from": "Mittente", + "mail-from-required": "Mittente obbligatorio.", + "smtp-protocol": "Protocollo SMTP", + "smtp-host": "Host SMTP", + "smtp-host-required": "Host SMTP obbligatorio.", + "smtp-port": "Porta SMTP", + "smtp-port-required": "Porta SMTP obbligatoria.", + "smtp-port-invalid": "Numero di porta SMTP non valido.", + "timeout-msec": "Timeout (msec)", + "timeout-required": "Timeout obbligatorio.", + "timeout-invalid": "Timeout non valido.", + "enable-tls": "Abilita TLS", + "send-test-mail": "Invia mail di test" + }, + "alarm": { + "alarm": "Allarme", + "alarms": "Allarmi", + "select-alarm": "Seleziona un allarme", + "no-alarms-matching": "Nessun allarme corrispondente a '{{entity}}' è stato trovato.", + "alarm-required": "Allarme richiesto", + "alarm-status": "Stato Allarme", + "search-status": { + "ANY": "Qualsiasi", + "ACTIVE": "Attivo", + "CLEARED": "Cancellato", + "ACK": "Riconosciuto", + "UNACK": "Non riconosciuto" + }, + "display-status": { + "ACTIVE_UNACK": "Active Unacknowledged", + "ACTIVE_ACK": "Active Acknowledged", + "CLEARED_UNACK": "Cleared Unacknowledged", + "CLEARED_ACK": "Cleared Acknowledged" + }, + "no-alarms-prompt": "Nessun allarme trovato", + "created-time": "Orario di creazione", + "type": "Tipo", + "severity": "Livello di gravità", + "originator": "Origine", + "originator-type": "Tipo origine", + "details": "Dettagli", + "status": "Stato", + "alarm-details": "Dettagli allarme", + "start-time": "Orario inizio", + "end-time": "Orario fine", + "ack-time": "Orario conferma", + "clear-time": "Orario cancellazione", + "severity-critical": "Critico", + "severity-major": "Maggiore", + "severity-minor": "Minore", + "severity-warning": "Avviso", + "severity-indeterminate": "Indeterminato", + "acknowledge": "Conferma", + "clear": "Cancella", + "search": "Cerca allarmi", + "selected-alarms": "{ count, plural, 1 {1 allarme selezionato} other {# allarmi selezionati} }", + "no-data": "Nessun dato da visualizzare", + "polling-interval": "Intervallo di polling (sec) Allarmi", + "polling-interval-required": "Intervallo di polling Allarmi richiesto.", + "min-polling-interval-message": "L'intervallo di polling deve essere di almeno 1 sec.", + "aknowledge-alarms-title": "Conferma { count, plural, 1 {1 allarme} other {# allarmi} }", + "aknowledge-alarms-text": "Sei sicuro di voler confermare { count, plural, 1 {1 allarme} other {# allarmi} }?", + "aknowledge-alarm-title": "Conferma allarme", + "aknowledge-alarm-text": "Sei sicuro di voler confermare l'allarme?", + "clear-alarms-title": "Elimina { count, plural, 1 {1 allarme} other {# allarmi} }", + "clear-alarms-text": "Sei sicuro di voler eliminare { count, plural, 1 {1 allarme} other {# allarmi} }?", + "clear-alarm-title": "Elimina allarme", + "clear-alarm-text": "Sei sicuro di voler eliminare l'allarme?", + "alarm-status-filter": "Filtro stato allarme" + }, + "alias": { + "add": "Aggiungi alias", + "edit": "Modifica alias", + "name": "Nome Alias", + "name-required": "Nome Alias obbligatorio", + "duplicate-alias": "Un Alias con lo stesso nome è già presente.", + "filter-type-single-entity": "Singola entità", + "filter-type-entity-list": "Lista Entità", + "filter-type-entity-name": "Nome Entità", + "filter-type-state-entity": "Entità dallo stato della dashboard", + "filter-type-state-entity-description": "Entità prelevata dai parametri di stato della dashboard", + "filter-type-asset-type": "Tipo di Asset", + "filter-type-asset-type-description": "Asset di tipo '{{assetType}}'", + "filter-type-asset-type-and-name-description": "Asset di tipo '{{assetType}}' e con un nome che inizia per '{{prefix}}'", + "filter-type-device-type": "Tipo di dispositivo", + "filter-type-device-type-description": "Dispositivi di tipo '{{deviceType}}'", + "filter-type-device-type-and-name-description": "Dispositivi di tipo '{{deviceType}}' e con un nome che inizia per '{{prefix}}'", + "filter-type-entity-view-type": "Tipo vista entità", + "filter-type-entity-view-type-description": "Viste entità di tipo '{{entityView}}'", + "filter-type-entity-view-type-and-name-description": "Viste entità di tipo '{{entityView}}' e con un nome che inizia per '{{prefix}}'", + "filter-type-relations-query": "Query relazioni", + "filter-type-relations-query-description": "{{entities}} che hanno una relazione {{relationType}} {{direction}} {{rootEntity}}", + "filter-type-asset-search-query": "Query ricerca asset", + "filter-type-asset-search-query-description": "Asset di tipo {{assetTypes}} che hanno una relazione {{relationType}} {{direction}} {{rootEntity}}", + "filter-type-device-search-query": "Query ricerca dispositivo", + "filter-type-device-search-query-description": "Dispositivi di tipo {{deviceTypes}} che hanno una relazione {{relationType}} {{direction}} {{rootEntity}}", + "filter-type-entity-view-search-query": "Query ricerca Vista entità", + "filter-type-entity-view-search-query-description": "Viste entità di tipo {{entityViewTypes}} che hanno una relazione {{relationType}} {{direction}} {{rootEntity}}", + "entity-filter": "Filtro entità", + "resolve-multiple": "Risolvi come entità multiple", + "filter-type": "Tipo di filtro", + "filter-type-required": "Tipo di filtro richiesto.", + "entity-filter-no-entity-matched": "Nessuna entità corrispondente al filtro specificato è stata trovata.", + "no-entity-filter-specified": "Nessun filtro di entità specificato", + "root-state-entity": "Usa l'entità di stato della dashboard come radice", + "root-entity": "Entità radice", + "state-entity-parameter-name": "Nome parametro entità di stato", + "default-state-entity": "Entità di stato predefinita", + "default-entity-parameter-name": "Predefinito", + "max-relation-level": "Massimo livello relazione", + "unlimited-level": "Illimitato", + "state-entity": "Entità di stato della dashboard", + "all-entities": "Tutte le entità", + "any-relation": "qualsiasi" + }, + "asset": { + "asset": "Asset", + "assets": "Asset", + "management": "Gestione Asset", + "view-assets": "Visualizza Asset", + "add": "Aggiungi Asset", + "assign-to-customer": "Assegna a cliente", + "assign-asset-to-customer": "Assegna Asset al Cliente", + "assign-asset-to-customer-text": "Seleziona gli asset da assegnare al cliente", + "no-assets-text": "Nessun asset trovato", + "assign-to-customer-text": "Seleziona il cliente a cui assegnare l'asset / gli asset", + "public": "Pubblico", + "assignedToCustomer": "Assegnato al cliente", + "make-public": "Rendi pubblico l'asset", + "make-private": "Rendi privato l'asset", + "unassign-from-customer": "Assegnazione annullata dal cliente", + "delete": "Cancella asset", + "asset-public": "L'Asset è pubblico", + "asset-type": "Tipo di Asset", + "asset-type-required": "Tipo di Asset richiesto.", + "select-asset-type": "Seleziona tipo di asset", + "enter-asset-type": "Inserisci tipo di asset", + "any-asset": "Qualsiasi asset", + "no-asset-types-matching": "Nessun asset corrispondente al tipo '{{entitySubtype}}' è stato trovato.", + "asset-type-list-empty": "Nessun tipo di asset selezionato.", + "asset-types": "Tipi di Asset", + "name": "Nome", + "name-required": "Nome obbligatorio.", + "description": "Descrizione", + "type": "Tipo", + "type-required": "Tipo obbligatorio.", + "details": "Dettagli", + "events": "Eventi", + "add-asset-text": "Aggiungi un nuovo asset", + "asset-details": "Dettagli Asset", + "assign-assets": "Assegna asset", + "assign-assets-text": "Assegna { count, plural, 1 {1 asset} other {# asset} } al cliente", + "delete-assets": "Cancella asset", + "unassign-assets": "Annulla assegnazione asset", + "unassign-assets-action-title": "Annulla assegnazione { count, plural, 1 {1 asset} other {# asset} } al cliente", + "assign-new-asset": "Assegna un nuovo asset", + "delete-asset-title": "Sei sicuro di voler cancellare l'asset '{{assetName}}'?", + "delete-asset-text": "Attenzione, dopo la conferma l'asset e tutti i relativi dati non saranno più recuperabili.", + "delete-assets-title": "Sei sicuro di voler eliminare { count, plural, 1 {1 asset} other {# asset} }?", + "delete-assets-action-title": "Elimina { count, plural, 1 {1 asset} other {# asset} }", + "delete-assets-text": "Attenzione, dopo la modifica tutti gli asset selezionati saranno rimossi e tutti i relativi dati non saranno più recuperabili.", + "make-public-asset-title": "Sei sicuro di voler rendere pubblico l'asset '{{assetName}}'?", + "make-public-asset-text": "Dopo la conferma l'asset e tutti i suoi dati saranno resi pubblici e accessibili dagli altri.", + "make-private-asset-title": "Sei sicuro di voler rendere privato l'asset '{{assetName}}'?", + "make-private-asset-text": "Dopo la conferma l'asset e tutti i suoi dati saranno resi privati e non accessibili dagli altri.", + "unassign-asset-title": "Sei sicuro di voler annullare l'assegnazione dell'asset '{{assetName}}'?", + "unassign-asset-text": "Dopo la conferma l'assegnazione dell'asset sarà annullata e l'asset non sarà più accessibile dal cliente.", + "unassign-asset": "Annulla assegnazione asset", + "unassign-assets-title": "Sei sicuro di voler annullare l'assegnazione di { count, plural, 1 {1 asset} other {# asset} }?", + "unassign-assets-text": "Dopo la conferma sarà annullata l'assegnazione di tutti gli asset selezionati e questi non saranno più accessibili dal cliente.", + "copyId": "Copia Id asset", + "idCopiedMessage": "Id Asset copiato negli Appunti", + "select-asset": "Seleziona asset", + "no-assets-matching": "Nessun asset corrispondente a '{{entity}}' è stato trovato.", + "asset-required": "Asset obbligatorio", + "name-starts-with": "Asset con nome che inizia per" + }, + "attribute": { + "attributes": "Attributi", + "latest-telemetry": "Ultima telemetria", + "attributes-scope": "Visibilità attributi entità", + "scope-latest-telemetry": "Ultima telemetria", + "scope-client": "Attributi client", + "scope-server": "Attributi server", + "scope-shared": "Attributi condivisi", + "add": "Aggiungi attributo", + "key": "Chiave", + "last-update-time": "Ultimo aggiornamento", + "key-required": "Attributo chiave richiesto.", + "value": "Valore", + "value-required": "Attributo valore richiesto.", + "delete-attributes-title": "Sei sicuro di voler eliminare { count, plural, 1 {1 attributo} other {# attributi} }?", + "delete-attributes-text": "Attenzione, dopo la conferma tutti gli attributi selezionati saranno rimossi.", + "delete-attributes": "Elimina attributi", + "enter-attribute-value": "Inserisci il valore dell'attributo", + "show-on-widget": "Mostra sul widget", + "widget-mode": "Modalità Widget", + "next-widget": "Widget successivo", + "prev-widget": "Widget precedente", + "add-to-dashboard": "Aggiungi alla dashboard", + "add-widget-to-dashboard": "Aggiungi widget alla dashboard", + "selected-attributes": "{ count, plural, 1 {1 attributo selezionato} other {# attributi selezionati} }", + "selected-telemetry": "{ count, plural, 1 {1 unità di telemetria selezionata} other {# unità di telemetria selezionate} }" + }, + "audit-log": { + "audit": "Audit", + "audit-logs": "Log Audit", + "timestamp": "Timestamp", + "entity-type": "Tipo Entità", + "entity-name": "Nome Entità", + "user": "Utente", + "type": "Tipo", + "status": "Stato", + "details": "Dettagli", + "type-added": "Aggiunto", + "type-deleted": "Eliminato", + "type-updated": "Aggiornato", + "type-attributes-updated": "Attributi aggiornati", + "type-attributes-deleted": "Attributi eliminati", + "type-rpc-call": "Chiamata RPC", + "type-credentials-updated": "Credenziali aggiornate", + "type-assigned-to-customer": "Assegnato al Cliente", + "type-unassigned-from-customer": "Assegnazione annullata dal Cliente", + "type-activated": "Attivato", + "type-suspended": "Sospeso", + "type-credentials-read": "Credenziali lette", + "type-attributes-read": "Attributi letti", + "type-relation-add-or-update": "Relazione aggiornata", + "type-relation-delete": "Relazione eliminata", + "type-relations-delete": "Eliminate tutte le relazioni", + "type-alarm-ack": "Confermato", + "type-alarm-clear": "Eliminato", + "status-success": "Successo", + "status-failure": "Fallito", + "audit-log-details": "Dettaglio log audit", + "no-audit-logs-prompt": "Log non trovati", + "action-data": "Action data", + "failure-details": "Dettagli fallimento", + "search": "Cerca log audit", + "clear-search": "Cancella ricerca" + }, + "confirm-on-exit": { + "message": "Alcune modifiche non sono state salvate. Sei sicuro di voler abbandonare questa pagina?", + "html-message": "Alcune modifiche non sono state salvate.
Sei sicuro di voler abbandonare questa pagina?", + "title": "Modifiche non salvate" + }, + "contact": { + "country": "Nazione", + "city": "Città", + "state": "Stato / Provincia", + "postal-code": "CAP", + "postal-code-invalid": "Formato CAP non valido.", + "address": "Indirizzo", + "address2": "Indirizzo 2", + "phone": "Telefono", + "email": "Email", + "no-address": "Nessun indirizzo" + }, + "common": { + "username": "Nome utente", + "password": "Password", + "enter-username": "Inserisci nome utente", + "enter-password": "Inserisci password", + "enter-search": "Cerca ..." + }, + "content-type": { + "json": "Json", + "text": "Testo", + "binary": "Binario (Base64)" + }, + "customer": { + "customer": "Cliente", + "customers": "Clienti", + "management": "Gestione cliente", + "dashboard": "Dashboard cliente", + "dashboards": "Dashboard cliente", + "devices": "Dispositivi cliente", + "entity-views": "Viste entità cliente", + "assets": "Asset cliente", + "public-dashboards": "Dashboard pubbliche", + "public-devices": "Dispositivi pubblici", + "public-assets": "Asset pubblici", + "public-entity-views": "Viste entità pubbliche", + "add": "Aggiungi cliente", + "delete": "Elimina cliente", + "manage-customer-users": "Gestisci utenti cliente", + "manage-customer-devices": "Gestisci dispositivi cliente", + "manage-customer-dashboards": "Gestisci dashboard cliente", + "manage-public-devices": "Gestisci dispositivi pubblici", + "manage-public-dashboards": "Gestisci dashboard pubbliche", + "manage-customer-assets": "Gestisci asset cliente", + "manage-public-assets": "Gestisci asset pubblici", + "add-customer-text": "Aggiungi nuovo cliente", + "no-customers-text": "Nessun cliente trovato", + "customer-details": "Dettagli cliente", + "delete-customer-title": "Sei sicuro di voler eliminare il cliente '{{customerTitle}}'?", + "delete-customer-text": "Attenzione, dopo la conferma il cliente e tutti i suoi dati non saranno più recuperabili.", + "delete-customers-title": "Sei sicuro di voler cancellare { count, plural, 1 {1 cliente} other {# clienti} }?", + "delete-customers-action-title": "Elimina { count, plural, 1 {1 cliente} other {# clienti} }", + "delete-customers-text": "Attenzione, dopo la conferma tutti i clienti selezionati saranno rimossi e i loro dati non saranno più recuperabili.", + "manage-users": "Gestisci utenti", + "manage-assets": "Gestisci asset", + "manage-devices": "Gestisci dispositivi", + "manage-dashboards": "Gestisci dashboard", + "title": "Titolo", + "title-required": "Titolo obbligatorio.", + "description": "Descrizione", + "details": "Dettagli", + "events": "Eventi", + "copyId": "Copia Id cliente", + "idCopiedMessage": "Id cliente copiato negli appunti", + "select-customer": "Seleziona cliente", + "no-customers-matching": "Nessun cliente corrispondente a '{{entity}}' è stato trovato.", + "customer-required": "Cliente obbligatorio", + "select-default-customer": "Seleziona cliente di default", + "default-customer": "Cliente di default", + "default-customer-required": "Il cliente di default è obbligatorio per il debug della dashboard a livello di Tenant" + }, + "datetime": { + "date-from": "Data da", + "time-from": "Orario da", + "date-to": "Data a", + "time-to": "Orario a" + }, + "dashboard": { + "dashboard": "Dashboard", + "dashboards": "Dashboard", + "management": "Gestione Dashboard", + "view-dashboards": "Mostra Dashboard", + "add": "Aggiungi Dashboard", + "assign-dashboard-to-customer": "Assegna Dashboard al cliente", + "assign-dashboard-to-customer-text": "Seleziona le dashboard da assegnare al client", + "assign-to-customer-text": "Seleziona il cliente a cui assegnare la/le dashboard", + "assign-to-customer": "Assegna al cliente", + "unassign-from-customer": "Annulla assegnazione al cliente", + "make-public": "Rendi pubblica la dashboard", + "make-private": "Rendi privata la dashboard", + "manage-assigned-customers": "Gestisci clienti assegnati", + "assigned-customers": "Clienti assegnati", + "assign-to-customers": "Assegna Dashboard ai Clienti", + "assign-to-customers-text": "Seleziona i clienti da assegnare alla/alle dashboard", + "unassign-from-customers": "Annulla assegnazione Dashboard ai Clienti", + "unassign-from-customers-text": "Seleziona i clienti di cui annullare l'assegnazione alla/alle dashboard", + "no-dashboards-text": "Nessuna dashboard trovata", + "no-widgets": "Nessun widget configurato", + "add-widget": "Aggiungi nuovo widget", + "title": "Titolo", + "select-widget-title": "Seleziona widget", + "select-widget-subtitle": "Elenco tipi di widget disponibili", + "delete": "Elimina dashboard", + "title-required": "Titolo obbligatorio.", + "description": "Descrizione", + "details": "Dettagli", + "dashboard-details": "Dettagli dashboard", + "add-dashboard-text": "Aggiungi nuova dashboard", + "assign-dashboards": "Assegna dashboard", + "assign-new-dashboard": "Assegna nuova dashboard", + "assign-dashboards-text": "Assegna { count, plural, 1 {1 dashboard} other {# dashboard} } ai clienti", + "unassign-dashboards-action-text": "Annulla assegnazione { count, plural, 1 {1 dashboard} other {# dashboard} } ai clienti", + "delete-dashboards": "Elimina dashboard", + "unassign-dashboards": "Annulla assegnazione dashboard", + "unassign-dashboards-action-title": "Annulla assegnazione { count, plural, 1 {1 dashboard} other {# dashboard} } al cliente", + "delete-dashboard-title": "Sei sicuro di voler cancellare la dashboard '{{dashboardTitle}}'?", + "delete-dashboard-text": "Attenzione, dopo la conferma la dashboard e tutti i suoi dati non saranno più recuperabili.", + "delete-dashboards-title": "Sei sicuro di voler eliminare { count, plural, 1 {1 dashboard} other {# dashboard} }?", + "delete-dashboards-action-title": "Cancella { count, plural, 1 {1 dashboard} other {# dashboard} }", + "delete-dashboards-text": "Attenzione, dopo la conferma tutte le dashboard selezionate saranno eliminate e tutti i loro dati non saranno più recuperabili.", + "unassign-dashboard-title": "Sei sicuro di voler annullare l'assegnazione della dashboard '{{dashboardTitle}}'?", + "unassign-dashboard-text": "Dopo la conferma sarà annullata l'assegnazione della dashboard e questa non sarà più accessibile dal cliente.", + "unassign-dashboard": "Annulla assegnazione dashboard", + "unassign-dashboards-title": "Sei sicuro di voler annullare l'assegnazione di { count, plural, 1 {1 dashboard} other {# dashboard} }?", + "unassign-dashboards-text": "Dopo la conferma sarà annullata l'assegnazione di tutte le dashboard selezionate e queste non saranno più accessibili dal cliente.", + "public-dashboard-title": "La Dashboard è ora pubblica", + "public-dashboard-text": "La dashboard {{dashboardTitle}} è ora pubblica e accessibile al link:", + "public-dashboard-notice": "Nota: Ricorda di rendere pubblici i relativi dispositivi per accedere ai loro dati.", + "make-private-dashboard-title": "Sei sicuro di voler rendere privata la dashboard '{{dashboardTitle}}'?", + "make-private-dashboard-text": "Dopo la conferma la dashboard sarà resa privata e non più accessibile dagli altri.", + "make-private-dashboard": "Rendi privata la dashboard", + "socialshare-text": "'{{dashboardTitle}}' powered by ThingsBoard", + "socialshare-title": "'{{dashboardTitle}}' powered by ThingsBoard", + "select-dashboard": "Seleziona dashboard", + "no-dashboards-matching": "Nessuna dashboard corrispondente a '{{entity}}' è stata trovata.", + "dashboard-required": "Dashboard obbligatoria.", + "select-existing": "Seleziona una dashboard esistente", + "create-new": "Crea nuova dashboard", + "new-dashboard-title": "Titolo nuova dashboard", + "open-dashboard": "Apri dashboard", + "set-background": "Imposta sfondo", + "background-color": "Colore sfondo", + "background-image": "Immagine sfondo", + "background-size-mode": "Modalità dimensione sfondo", + "no-image": "Nessuna immagine selezionata", + "drop-image": "Trascina un'immagine o fai clic per selezionare un file da caricare.", + "settings": "Impostazioni", + "columns-count": "Numero colonne", + "columns-count-required": "Numero colonne obbligatorio.", + "min-columns-count-message": "Ammesso un numero minimo di colonne pari a 10.", + "max-columns-count-message": "Ammesso un numero massimo di colonne pari a 1000.", + "widgets-margins": "Margine tra i widget", + "horizontal-margin": "Margine orizzontale", + "horizontal-margin-required": "Margine orizzontale obbligatorio.", + "min-horizontal-margin-message": "Ammesso un margine orizzontale minimo pari a 0.", + "max-horizontal-margin-message": "Ammesso un margine orizzontale massimo pari a 50.", + "vertical-margin": "Margine verticale", + "vertical-margin-required": "Margine verticale obbligatorio.", + "min-vertical-margin-message": "Ammesso un margine verticale minimo pari a 0.", + "max-vertical-margin-message": "Ammesso un margine verticale massimo pari a 50.", + "autofill-height": "Riempi automaticamente altezza layout", + "mobile-layout": "Impostazioni layout mobile", + "mobile-row-height": "Altezza riga mobile (px)", + "mobile-row-height-required": "Altezza riga mobile è richiesta.", + "min-mobile-row-height-message": "5 pixel è il minimo concesso al valore altezza riga mobile.", + "max-mobile-row-height-message": "200 pixel è il massimo concesso al valore altezza riga mobile.", + "display-title": "Mostra titolo dashboard", + "toolbar-always-open": "Mantieni aperta la barra degli strumenti", + "title-color": "Colore titolo", + "display-dashboards-selection": "Mostra selezione dashboard", + "display-entities-selection": "Mostra selezione entità", + "display-dashboard-timewindow": "Mostra intervallo temporale", + "display-dashboard-export": "Mostra esportazione", + "import": "Importa dashboard", + "export": "Esporta dashboard", + "export-failed-error": "Impossibile esportare la dashboard: {{error}}", + "create-new-dashboard": "Crea nuova dashboard", + "dashboard-file": "File dashboard", + "invalid-dashboard-file-error": "Impossibile importare la dashboard: struttura dati della dashboard non valida.", + "dashboard-import-missing-aliases-title": "Configura alias utilizzati dalla dashboard importata", + "create-new-widget": "Crea nuovo widget", + "import-widget": "Importa widget", + "widget-file": "Widget file", + "invalid-widget-file-error": "Impossibile importare il widget: struttura dati del widget non valida.", + "widget-import-missing-aliases-title": "Configura gli alias utilizzati dai widget importati", + "open-toolbar": "Apri barra degli strumenti", + "close-toolbar": "Chiudi barra degli strumenti", + "configuration-error": "Errore di configurazione", + "alias-resolution-error-title": "Errore di configurazione degli alias della dashboard", + "invalid-aliases-config": "Impossibile trovare un dispositivo corrispondente ad un qualche filtro degli alias.
Contatta l'amministratore per risolvere il problema.", + "select-devices": "Seleziona dispositivi", + "assignedToCustomer": "Assegnato al cliente", + "assignedToCustomers": "Assegnato ai clienti", + "public": "Pubblico", + "public-link": "Link pubblico", + "copy-public-link": "Copia link pubblico", + "public-link-copied-message": "Link pubblico della dashboard copiato negli appunti", + "manage-states": "Gestisci stati dashboard", + "states": "Stati dashboard", + "search-states": "Ricerca stati dashboard", + "selected-states": "{ count, plural, 1 {1 stato dashboard selezionato} other {# stati dashboard selezionati} }", + "edit-state": "Modifica stato dashboard", + "delete-state": "Elimina stato dashboard", + "add-state": "Aggiungi stato dashboard", + "state": "Stato dashboard", + "state-name": "Nome", + "state-name-required": "Nome stato dashboard obbligatorio.", + "state-id": "Id stato", + "state-id-required": "Id stato dashboard obbligatorio.", + "state-id-exists": "Uno stato della dashboard con lo stesso id è già presente.", + "is-root-state": "Stato radice", + "delete-state-title": "Elimina stato dashboard", + "delete-state-text": "Sei sicuro di voler eliminare lo stato della dashboard di nome '{{stateName}}'?", + "show-details": "Mostra dettagli", + "hide-details": "Nascondi dettagli", + "select-state": "Seleziona stato target", + "state-controller": "Stato controller" + }, + "datakey": { + "settings": "Impostazioni", + "advanced": "Avanzate", + "label": "Etichetta", + "color": "Colore", + "units": "Simbolo speciale da mostrare accanto al valore", + "decimals": "Numero cifre decimali", + "data-generation-func": "Funzione generazione dati", + "use-data-post-processing-func": "Usa funzione dopo il processamento dei dati", + "configuration": "Configurazione data key", + "timeseries": "Serie temporali", + "attributes": "Attributi", + "alarm": "Campi allarme", + "timeseries-required": "Le serie temporali dell'entità sono richieste.", + "timeseries-or-attributes-required": "Le serie temporali o gli attributi dell'entità sono richiesti.", + "maximum-timeseries-or-attributes": "Massimo { count, plural, 1 {1 serie temporale/attributo consentito.} other {# serie temporali/attributi consentiti.} }", + "alarm-fields-required": "Campi allarme obbligatori.", + "function-types": "Tipi funzione", + "function-types-required": "Tipi funzione obbligatorio.", + "maximum-function-types": "Massimo { count, plural, 1 {1 tipo di funzione consentito.} other {# tipi di funzione consentiti} }", + "time-description": "timestamp del valore corrente;", + "value-description": "il valore corrente;", + "prev-value-description": "risultato della precedente chiamata alla funzione;", + "time-prev-description": "timestamp del valore precedente;", + "prev-orig-value-description": "valore precedente originale;" + }, + "datasource": { + "type": "Tipo sorgente dati", + "name": "Nome", + "add-datasource-prompt": "Aggiungi una sorgente dati" + }, + "details": { + "edit-mode": "Modalità modifica", + "toggle-edit-mode": "Attiva/disattiva modalità di modifica" + }, + "device": { + "device": "Dispositivo", + "device-required": "Dispositivo richiesto.", + "devices": "Dispositivi", + "management": "Gestione dispositivo", + "view-devices": "Visualizza Dispositivi", + "device-alias": "Alias dispositivo", + "aliases": "Alias dispositivo", + "no-alias-matching": "'{{alias}}' non trovato.", + "no-aliases-found": "Nessun alias trovato.", + "no-key-matching": "'{{key}}' non trovata.", + "no-keys-found": "Nessuna chiave trovata.", + "create-new-alias": "Creane uno nuovo!", + "create-new-key": "Creane una nuova!", + "duplicate-alias-error": "Sono stati trovati dei duplicati dell'alias '{{alias}}'.
Gli alias di un dispositivo devono essere univoci all'interno della dashboard.", + "configure-alias": "Configura alias '{{alias}}'", + "no-devices-matching": "Nessun dispositivo corrispondente a '{{entity}}' è stato trovato.", + "alias": "Alias", + "alias-required": "Alias dispositivo richiesto.", + "remove-alias": "Rimuovi alias dispositivo", + "add-alias": "Aggiungi alias dispositivo", + "name-starts-with": "Dispositivo il cui nome inizia per", + "device-list": "Lista dispositivi", + "use-device-name-filter": "Usa filtro", + "device-list-empty": "Nessun dispositivo selezionato.", + "device-name-filter-required": "Filtro nome dispositivo obbligatorio.", + "device-name-filter-no-device-matched": "Nessun dispositivo il cui nome inizia per '{{device}}' è stato trovato.", + "add": "Aggiungi Dispositivo", + "assign-to-customer": "Assegna al cliente", + "assign-device-to-customer": "Assegna dispositivo/dispositivi al Cliente", + "assign-device-to-customer-text": "Seleziona i dispositivi da assegnare al cliente", + "make-public": "Rendi pubblico il dispositivo", + "make-private": "Rendi privato il dispositivo", + "no-devices-text": "Nessun dispositivo trovato", + "assign-to-customer-text": "Seleziona il cliente a cui assegnare il dispositivo/i dispositivi", + "device-details": "Dettagli dispositivo", + "add-device-text": "Aggiungi nuovo dispositivo", + "credentials": "Credenziali", + "manage-credentials": "Gestisci credenziali", + "delete": "Elimina dispositivo", + "assign-devices": "Assegna dispositivi", + "assign-devices-text": "Assegna { count, plural, 1 {1 dispositivo} other {# dispositivi} } al cliente", + "delete-devices": "Elimina dispositivi", + "unassign-from-customer": "Annulla assegnazione al cliente", + "unassign-devices": "Annulla assegnazione dispositivi", + "unassign-devices-action-title": "Annulla assegnazione { count, plural, 1 {1 dispositivo} other {# dispositivi} } al cliente", + "assign-new-device": "Assegna nuovo dispositivo", + "make-public-device-title": "Sei sicuro di voler rendere pubblico il dispositivo '{{deviceName}}'?", + "make-public-device-text": "Dopo la conferma il dispositivo e tutti i suoi dati saranno resi pubblici e accessibili dagli altri.", + "make-private-device-title": "Sei sicuro di voler rendere privato il dispositivo '{{deviceName}}'?", + "make-private-device-text": "Dopo la conferma il dispositivo e tutti i suoi dati saranno resi privati e non più accessibili da altri utenti.", + "view-credentials": "Visualizza credenziali", + "delete-device-title": "Sei sicuro di voler eliminare il dispositivo '{{deviceName}}'?", + "delete-device-text": "Attenzione, dopo la conferma il dispositivo e tutti i suoi dati non saranno più recuperabili.", + "delete-devices-title": "Sei sicuro di voler eliminare { count, plural, 1 {1 dispositivo} other {# dispositivi} }?", + "delete-devices-action-title": "Elimina { count, plural, 1 {1 dispositivo} other {# dispositivi} }", + "delete-devices-text": "Attenzione, dopo la conferma tutti i dispositivi selezionati saranno eliminati e i relativi dati non saranno più recuperabili.", + "unassign-device-title": "Sei sicuro di voler annullare l'assegnazione del dispositivo '{{deviceName}}'?", + "unassign-device-text": "Dopo la conferma sarà annullata l'assegnazione del dispositivo e questo non sarà più accessibile dal cliente.", + "unassign-device": "Annulla assegnazione dispositivo", + "unassign-devices-title": "Sei sicuro di voler annullare l'assegnazione di { count, plural, 1 {1 dispositivo} other {# dispositivi} }?", + "unassign-devices-text": "Dopo la conferma sarà annullata l'assegnazione di tutti i dispositivi selezionati e questi non saranno più accessibili dal cliente.", + "device-credentials": "Credenziali Dispositivo", + "credentials-type": "Tipo credenziali", + "access-token": "Token di accesso", + "access-token-required": "Token di accesso obbligatorio.", + "access-token-invalid": "Il token di accesso deve avere una lunghezza compresa tra 1 e 20 caratteri.", + "rsa-key": "Chiave pubblica RSA", + "rsa-key-required": "Chiave pubblica RSA obbligatoria.", + "secret": "Secret", + "secret-required": "Secret obbligatorio.", + "device-type": "Tipo dispositivo", + "device-type-required": "Tipo dispositivo obbligatorio.", + "select-device-type": "Seleziona tipo dispositivo", + "enter-device-type": "Inserisci typo dispositivo", + "any-device": "Qualsiasi dispositivo", + "no-device-types-matching": "Nessun dispositivo corrispondente a '{{entitySubtype}}' è stato trovato.", + "device-type-list-empty": "Nessun tipo di dispositivo selezionato.", + "device-types": "Tipi dispositivo", + "name": "Nome", + "name-required": "Nome obbligatorio.", + "description": "Descrizione", + "events": "Eventi", + "details": "Dettagli", + "copyId": "Copia Id dispositivo", + "copyAccessToken": "Copia token di accesso", + "idCopiedMessage": "Id dispositivo copiato negli Appunti", + "accessTokenCopiedMessage": "Token di accesso del dispositivo copiato negli Appunti", + "assignedToCustomer": "Assegnato al cliente", + "unable-delete-device-alias-title": "Impossibile rimuovere l'alias del dispositivo", + "unable-delete-device-alias-text": "L'alias del dispositivo '{{deviceAlias}}' non può essere eliminato perché utilizzato dai seguenti widget:
{{widgetsList}}", + "is-gateway": "È un gateway", + "public": "Pubblico", + "device-public": "Il dispositivo è pubblico", + "select-device": "Seleziona dispositivo" + }, + "dialog": { + "close": "Close dialog" + }, + "error": { + "unable-to-connect": "Impossibile connettersi al server! Controlla la connessione ad Internet.", + "unhandled-error-code": "Codice errore non gestito: {{errorCode}}", + "unknown-error": "Errore sconosciuto" + }, + "entity": { + "entity": "Entità", + "entities": "Entità", + "aliases": "Alias entità", + "entity-alias": "Alias entità", + "unable-delete-entity-alias-title": "Impossibile eliminare alias entità", + "unable-delete-entity-alias-text": "L'alias dell'entità '{{entityAlias}}' non può essere eliminato perché utilizzato dai seguenti widget:
{{widgetsList}}", + "duplicate-alias-error": "Trovato un duplicato dell'alias '{{alias}}'.
Gli alias dell'entità devono essere univoci all'interno della dashboard.", + "missing-entity-filter-error": "Manca il filtro per l'alias '{{alias}}'.", + "configure-alias": "Configura '{{alias}}' alias", + "alias": "Alias", + "alias-required": "Alias entità obbligatorio.", + "remove-alias": "Rimuovi alias entità", + "add-alias": "Aggiungi alias entità", + "entity-list": "Lista entità", + "entity-type": "Tipo entità", + "entity-types": "Tipi entità", + "entity-type-list": "Lista tipo entità", + "any-entity": "Qualsiasi entità", + "enter-entity-type": "Inserisci tipo entità", + "no-entities-matching": "Nessuna entità corrispondente a '{{entity}}' è stata trovata.", + "no-entity-types-matching": "Nessun tipo di entità corrispondente a '{{entityType}}' è stato trovato.", + "name-starts-with": "Nome inizia per", + "use-entity-name-filter": "Usa filtro", + "entity-list-empty": "Nessuna entità selezionata.", + "entity-type-list-empty": "Nessun tipo di entità selezionato.", + "entity-name-filter-required": "Filtro nome entità obbligatorio.", + "entity-name-filter-no-entity-matched": "Nessuna entità che inizia per '{{entity}}' è stata trovata.", + "all-subtypes": "Tutte", + "select-entities": "Seleziona entità", + "no-aliases-found": "Nessun alias trovato.", + "no-alias-matching": "'{{alias}}' non trovato.", + "create-new-alias": "Creane uno nuovo!", + "key": "Chiave", + "key-name": "Nome chiave", + "no-keys-found": "Nessuna chiave trovata.", + "no-key-matching": "'{{key}}' non trovata.", + "create-new-key": "Creane una nuova!", + "type": "Tipo", + "type-required": "Tipo entità obbligatorio.", + "type-device": "Dispositivo", + "type-devices": "Dispositivi", + "list-of-devices": "{ count, plural, 1 {Un dispositivo} other {Lista di # dispositivi} }", + "device-name-starts-with": "Dispositivi i cui nomi iniziano per '{{prefix}}'", + "type-asset": "Asset", + "type-assets": "Asset", + "list-of-assets": "{ count, plural, 1 {Un asset} other {Lista di # asset} }", + "asset-name-starts-with": "Asset i cui nomi iniziano per '{{prefix}}'", + "type-entity-view": "Vista entità", + "type-entity-views": "Viste entità", + "list-of-entity-views": "{ count, plural, 1 {Una vista entità} other {Lista di # viste entità} }", + "entity-view-name-starts-with": "Viste entità i cui nomi iniziano per '{{prefix}}'", + "type-rule": "Regola", + "type-rules": "Regole", + "list-of-rules": "{ count, plural, 1 {Una regola} other {Lista di # regole} }", + "rule-name-starts-with": "Regole i cui nomi iniziano per '{{prefix}}'", + "type-plugin": "Plugin", + "type-plugins": "Plugin", + "list-of-plugins": "{ count, plural, 1 {Un plugin} other {Lista di # plugin} }", + "plugin-name-starts-with": "Plugin i cui nomi iniziano per '{{prefix}}'", + "type-tenant": "Tenant", + "type-tenants": "Tenants", + "list-of-tenants": "{ count, plural, 1 {One tenant} other {Lista di # tenants} }", + "tenant-name-starts-with": "Tenants whose names start with '{{prefix}}'", + "type-customer": "Cliente", + "type-customers": "Clienti", + "list-of-customers": "{ count, plural, 1 {Un cliente} other {Lista di # clienti} }", + "customer-name-starts-with": "Clienti i cui nomi iniziano per '{{prefix}}'", + "type-user": "Utente", + "type-users": "Utenti", + "list-of-users": "{ count, plural, 1 {Un utente} other {Lista di # utenti} }", + "user-name-starts-with": "Utenti i cui nomi iniziano per '{{prefix}}'", + "type-dashboard": "Dashboard", + "type-dashboards": "Dashboard", + "list-of-dashboards": "{ count, plural, 1 {Una dashboard} other {Lista di # dashboard} }", + "dashboard-name-starts-with": "Dashboard i cui nomi iniziano per '{{prefix}}'", + "type-alarm": "Allarme", + "type-alarms": "Allarmi", + "list-of-alarms": "{ count, plural, 1 {Un allarme} other {Lista di # allarmi} }", + "alarm-name-starts-with": "Allarmi i cui nomi iniziano per '{{prefix}}'", + "type-rulechain": "Rule chain", + "type-rulechains": "Rule chain", + "list-of-rulechains": "{ count, plural, 1 {Una rule chain} other {Lista di # catene di regole} }", + "rulechain-name-starts-with": "Catene di regole i cui nomi iniziano per '{{prefix}}'", + "type-rulenode": "Nodo regola", + "type-rulenodes": "Nodi regola", + "list-of-rulenodes": "{ count, plural, 1 {Un nodo regola} other {Lista di # nodi regola} }", + "rulenode-name-starts-with": "Nodi regola i cui nomi iniziano per '{{prefix}}'", + "type-current-customer": "Cliente attuale", + "search": "Ricerca entità", + "selected-entities": "{ count, plural, 1 {1 entità selezionata} other {# entità selezionate} }", + "entity-name": "Nome entità", + "details": "Dettagli entità", + "no-entities-prompt": "Nessuna entità trovata", + "no-data": "Nessun dato da mostrare", + "columns-to-display": "Colonne da mostrare" + }, + "entity-view": { + "entity-view": "Vista entità", + "entity-view-required": "Vista entità richiesta.", + "entity-views": "Viste entità", + "management": "Gestione viste entità", + "view-entity-views": "Visualizza Viste entità", + "entity-view-alias": "Alias vista entità", + "aliases": "Alias vista entità", + "no-alias-matching": "'{{alias}}' non trovato.", + "no-aliases-found": "Nessun alias trovato.", + "no-key-matching": "'{{key}}' non trovata.", + "no-keys-found": "Nessuna chiave trovata.", + "create-new-alias": "Creane uno nuovo!", + "create-new-key": "Creane una nuova!", + "duplicate-alias-error": "Sono stati trovati dei duplicati dell'alias '{{alias}}'.
Gli alias di una vista entità devono essere univoci all'interno della dashboard.", + "configure-alias": "Configura alias '{{alias}}'", + "no-entity-views-matching": "Nessuna vista entità corrispondente a '{{entity}}' è stata trovato.", + "alias": "Alias", + "alias-required": "Alias vista entità richiesto.", + "remove-alias": "Rimuovi alias vista entità", + "add-alias": "Aggiungi alias vista entità", + "name-starts-with": "Vista entità il cui nome inizia per", + "entity-view-list": "Lista viste entità", + "use-entity-view-name-filter": "Usa filtro", + "entity-view-list-empty": "Nessuna vista entità selezionata.", + "entity-view-name-filter-required": "Filtro nome vista entità obbligatorio.", + "entity-view-name-filter-no-entity-view-matched": "Nessuna vista entità il cui nome inizia per '{{entity-view}}' è stata trovata.", + "add": "Aggiungi Vista entità", + "assign-to-customer": "Assegna al cliente", + "assign-entity-view-to-customer": "Assegna vista entità/viste entità al Cliente", + "assign-entity-view-to-customer-text": "Seleziona la vista entità da assegnare al cliente", + "no-entity-views-text": "Nessuna vista entità trovata", + "assign-to-customer-text": "Seleziona il cliente a cui assegnare la vista entità/le vista entità", + "entity-view-details": "Dettagli vista entità", + "add-entity-view-text": "Aggiungi nuova vista entità", + "delete": "Elimina vista entità", + "assign-entity-views": "Assegna viste entità", + "assign-entity-views-text": "Assegna { count, plural, 1 {1 vista entità} other {# viste entità} } al cliente", + "delete-entity-views": "Elimina viste entità", + "unassign-from-customer": "Annulla assegnazione al cliente", + "unassign-entity-views": "Annulla assegnazione viste entità", + "unassign-entity-views-action-title": "Annulla assegnazione { count, plural, 1 {1 vista entità} other {# viste entità} } al cliente", + "assign-new-entity-view": "Assegna nuova vista entità", + "delete-entity-view-title": "Sei sicuro di voler eliminare la vista entità '{{entity-viewName}}'?", + "delete-entity-view-text": "Attenzione, dopo la conferma la vista entità e tutti i suoi dati non saranno più recuperabili.", + "delete-entity-views-title": "Sei sicuro di voler eliminare { count, plural, 1 {1 vista entità} other {# viste entità} }?", + "delete-entity-views-action-title": "Elimina { count, plural, 1 {1 vista entità} other {# viste entità} }", + "delete-entity-views-text": "Attenzione, dopo la conferma tutte le vista entità selezionati saranno eliminate e i relativi dati non saranno più recuperabili.", + "unassign-entity-view-title": "Sei sicuro di voler annullare l'assegnazione della vista entità '{{entity-viewName}}'?", + "unassign-entity-view-text": "Dopo la conferma sarà annullata l'assegnazione della vista entità e questa non sarà più accessibile dal cliente.", + "unassign-entity-view": "Annulla assegnazione vista entità", + "unassign-entity-views-title": "Sei sicuro di voler annullare l'assegnazione di { count, plural, 1 {1 vista entità} other {# viste entità} }?", + "unassign-entity-views-text": "Dopo la conferma sarà annullata l'assegnazione di tutte le vista entità selezionate e queste non saranno più accessibili dal cliente.", + "entity-view-type": "Tipo vista entità", + "entity-view-type-required": "Tipo vista entità obbligatorio.", + "select-entity-view-type": "Seleziona tipo vista entità", + "enter-entity-view-type": "Inserisci tipo vista entità", + "any-entity-view": "Qualsiasi vista entità", + "no-entity-view-types-matching": "Nessuna vista entità corrispondente a '{{entitySubtype}}' è stata trovata.", + "entity-view-type-list-empty": "Nessun tipo di vista entità selezionato.", + "entity-view-types": "Tipi vista entità", + "name": "Nome", + "name-required": "Nome obbligatorio.", + "description": "Descrizione", + "events": "Eventi", + "details": "Dettagli", + "copyId": "Copia Id vista entità", + "assignedToCustomer": "Assegnata al cliente", + "unable-entity-view-device-alias-title": "Impossibile rimuovere l'alias del vista entità", + "unable-entity-view-device-alias-text": "L'alias del vista entità '{{entity-viewAlias}}' non può essere eliminato perché utilizzato dai seguenti widget:
{{widgetsList}}", + "select-entity-view": "Seleziona vista entità", + "make-public": "Rendi pubblica la vista entità", + "make-private": "Rendi privata la vista entità", + "start-date": "Data inizio", + "start-ts": "Ora inizio", + "end-date": "Data fine", + "end-ts": "Ora fine", + "date-limits": "Limiti temporali", + "client-attributes": "Attributi cliente", + "shared-attributes": "Attributi condivisi", + "server-attributes": "Attributi server", + "timeseries": "Serie temporali", + "client-attributes-placeholder": "Attributi cliente", + "shared-attributes-placeholder": "Attributi condivisi", + "server-attributes-placeholder": "Attributi server", + "timeseries-placeholder": "Serie temporali", + "target-entity": "Entità target", + "attributes-propagation": "Propagazione degli attributi", + "attributes-propagation-hint": "La vista entità copierà automaticamente gli attributi specificati dall'entità target ogni volta che questa vista entità sarà salvata e aggiornata. Per ragioni di performance, gli attributi dell'entità target non sono propagati alle viste entità ogni cambiamento di attributo. È possibile abilitare la propagazione automatica configurando il nodo regola \"Copia alla vista\" nella rule chain e collegando i messaggi \"Post attributes\" a \"Attributes Updated\" al nuovo nodo regola.", + "timeseries-data": "Dati delle serie temporali", + "timeseries-data-hint": "Imposta le chiavi delle serie temporali dell'entità target che saranno accessibili alla vista entità. Questi dati sono di sola lettura.", + "make-public-entity-view-title": "Sei sicuro di voler rendere pubblica la vista entità '{{entity-viewName}}'?", + "make-public-entity-view-text": "Dopo la conferma la vista entità e tutti i suoi dati saranno resi pubblici e accessibili dagli altri.", + "make-private-entity-view-title": "Sei sicuro di voler rendere privata la vista entità '{{entity-viewName}}'?", + "make-private-entity-view-text": "Dopo la conferma la vista entità e tutti i suoi dati saranno resi privati e non più accessibili da altri utenti." + }, + "event": { + "event-type": "Tipo evento", + "type-error": "Errore", + "type-lc-event": "Ciclo di vita evento", + "type-stats": "Statistiche", + "type-debug-rule-node": "Debug", + "type-debug-rule-chain": "Debug", + "no-events-prompt": "Nessun evento trovato", + "error": "Errore", + "alarm": "Allarme", + "event-time": "Orario evento", + "server": "Server", + "body": "Body", + "method": "Metodo", + "type": "Tipo", + "entity": "Entità", + "message-id": "Id Messaggio", + "message-type": "Tipo Messaggio", + "data-type": "Tipo di dato", + "relation-type": "Tipo di relazione", + "metadata": "Metadati", + "data": "Dati", + "event": "Evento", + "status": "Stato", + "success": "Success", + "failed": "Failed", + "messages-processed": "Messaggi elaborati", + "errors-occurred": "Si sono verificati degli errori" + }, + "extension": { + "extensions": "Estensioni", + "selected-extensions": "{ count, plural, 1 {1 estensione selezionata} other {# estensioni selezionate} }", + "type": "Tipo", + "key": "Chiave", + "value": "Valore", + "id": "Id", + "extension-id": "Id Estensione", + "extension-type": "Tipo Estensione", + "transformer-json": "JSON *", + "unique-id-required": "Id estensione corrente già esistente.", + "delete": "Elimina estensione", + "add": "Aggiungi estensione", + "edit": "Modifica estensione", + "delete-extension-title": "Sei sicuro di voler eliminare l'estensione '{{extensionId}}'?", + "delete-extension-text": "Attenzione, dopo la conferma l'estensione e tutti i suoi data non saranno più recuperabili.", + "delete-extensions-title": "Sei sicuro di voler eliminare { count, plural, 1 {1 estensione} other {# estensioni} }?", + "delete-extensions-text": "Attenzione, dopo la conferma tutte le estensioni selezionate saranno eliminate.", + "converters": "Convertitori", + "converter-id": "Id convertitore", + "configuration": "Configurazione", + "converter-configurations": "Configurazioni convertitore", + "token": "Token di sicurezza", + "add-converter": "Aggiungi convertitore", + "add-config": "Aggiungi configurazione convertitore", + "device-name-expression": "Espressione nome dispositivo", + "device-type-expression": "Espressione tipo dispositivo", + "custom": "Custom", + "to-double": "To Double", + "transformer": "Transformer", + "json-required": "Transformer json is required.", + "json-parse": "Unable to parse transformer json.", + "attributes": "Attributi", + "add-attribute": "Aggiungi attributo", + "add-map": "Add mapping element", + "timeseries": "Serie temporali", + "add-timeseries": "Add timeseries", + "field-required": "Campo obbligatorio", + "brokers": "Broker", + "add-broker": "Aggiungi broker", + "host": "Host", + "port": "Porta", + "port-range": "Il numero di porta deve essere compreso tra 1 e 65535.", + "ssl": "Ssl", + "credentials": "Credenziali", + "username": "Nome utente", + "password": "Password", + "retry-interval": "Intervallo di ripetizione in millisecondi", + "anonymous": "Anonimo", + "basic": "Basic", + "pem": "PEM", + "ca-cert": "File certificato CA *", + "private-key": "File chiave privata *", + "cert": "File certificato *", + "no-file": "Nessun file selezionato.", + "drop-file": "Trascina un file o fai clic per selezionare un file da caricare.", + "mapping": "Mapping", + "topic-filter": "Filtro topic", + "converter-type": "Tipo convertitore", + "converter-json": "Json", + "json-name-expression": "Device name json expression", + "topic-name-expression": "Device name topic expression", + "json-type-expression": "Device type json expression", + "topic-type-expression": "Device type topic expression", + "attribute-key-expression": "Attribute key expression", + "attr-json-key-expression": "Attribute key json expression", + "attr-topic-key-expression": "Attribute key topic expression", + "request-id-expression": "Request id expression", + "request-id-json-expression": "Request id json expression", + "request-id-topic-expression": "Request id topic expression", + "response-topic-expression": "Response topic expression", + "value-expression": "Value expression", + "topic": "Topic", + "timeout": "Timeout in millisecondi", + "converter-json-required": "Convertitore json obbligatorio.", + "converter-json-parse": "Unable to parse converter json.", + "filter-expression": "Filter expression", + "connect-requests": "Richieste di connessione", + "add-connect-request": "Aggiungi richiesta di connessione", + "disconnect-requests": "Richieste di disconnessione", + "add-disconnect-request": "Aggiungi richiesta di disconnessione", + "attribute-requests": "Attribute requests", + "add-attribute-request": "Add attribute request", + "attribute-updates": "Attribute updates", + "add-attribute-update": "Add attribute update", + "server-side-rpc": "RPC lato server", + "add-server-side-rpc-request": "Add server-side RPC request", + "device-name-filter": "Filtro nome dispositivo", + "attribute-filter": "Filtro attributo", + "method-filter": "Filtro metodo", + "request-topic-expression": "Request topic expression", + "response-timeout": "Response timeout in milliseconds", + "topic-expression": "Topic expression", + "client-scope": "Visibilità client", + "add-device": "Aggiungi dispositivo", + "opc-server": "Server", + "opc-add-server": "Aggiungi server", + "opc-add-server-prompt": "Aggiungi server", + "opc-application-name": "Nome applicazione", + "opc-application-uri": "Uri applicazione", + "opc-scan-period-in-seconds": "Intervallo di scansione in secondi", + "opc-security": "Sicurezza", + "opc-identity": "Identità", + "opc-keystore": "Keystore", + "opc-type": "Tipo", + "opc-keystore-type": "Tipo", + "opc-keystore-location": "Location *", + "opc-keystore-password": "Password", + "opc-keystore-alias": "Alias", + "opc-keystore-key-password": "Chiave password", + "opc-device-node-pattern": "Device node pattern", + "opc-device-name-pattern": "Device name pattern", + "modbus-server": "Server/slave", + "modbus-add-server": "Aggiungi server/slave", + "modbus-add-server-prompt": "Aggiungi server/slave", + "modbus-transport": "Transport", + "modbus-tcp-reconnect": "Riconnessione automatica", + "modbus-rtu-over-tcp": "RTU over TCP", + "modbus-port-name": "Nome porta seriale", + "modbus-encoding": "Codifica", + "modbus-parity": "Parità", + "modbus-baudrate": "Baud rate", + "modbus-databits": "Data bits", + "modbus-stopbits": "Stop bits", + "modbus-databits-range": "Data bits deve essere compreso nell'intervallo 7-8.", + "modbus-stopbits-range": "Stop bits deve essere compreso nell'intervallo 1-2.", + "modbus-unit-id": "ID unità", + "modbus-unit-id-range": "ID unità deve essere compreso nell'intervallo 1-247.", + "modbus-device-name": "Nome dispositivo", + "modbus-poll-period": "Intervallo di polling (ms)", + "modbus-attributes-poll-period": "Intervallo di polling degli attributi (ms)", + "modbus-timeseries-poll-period": "Intervallo di polling delle serie temporali (ms)", + "modbus-poll-period-range": "L'intervallo di polling deve essere un valore positivo.", + "modbus-tag": "Tag", + "modbus-function": "Funzione", + "modbus-register-address": "Indirizzo registro", + "modbus-register-address-range": "Indirizzo registro deve essere compreso tra 0 e 65535.", + "modbus-register-bit-index": "Bit index", + "modbus-register-bit-index-range": "Bit index should be in a range from 0 to 15.", + "modbus-register-count": "Register count", + "modbus-register-count-range": "Register count should be a positive value.", + "modbus-byte-order": "Byte order", + "sync": { + "status": "Stato", + "sync": "Sincronizzato", + "not-sync": "Non sincronizzato", + "last-sync-time": "Ultima sincronizzazione", + "not-available": "Non disponibile" + }, + "export-extensions-configuration": "Esporta configurazione estensioni", + "import-extensions-configuration": "Importa configurazione estensioni", + "import-extensions": "Importa estensione", + "import-extension": "Importa estensione", + "export-extension": "Esporta estensione", + "file": "File estensione", + "invalid-file-error": "File estensione non valido" + }, + "fullscreen": { + "expand": "Espandi a tutto schermo", + "exit": "Esci da schermo intero", + "toggle": "Commuta modalità schermo intero", + "fullscreen": "Schermo intero" + }, + "function": { + "function": "Funzione" + }, + "grid": { + "delete-item-title": "Sei sicuro di voler eliminare questo elemento?", + "delete-item-text": "Attenzione, dopo la conferma questo elemento e tutti i suoi dati non saranno più recuperabili.", + "delete-items-title": "Sei sicuro di voler eliminare { count, plural, 1 {1 elemento} other {# elementi} }?", + "delete-items-action-title": "Elimina { count, plural, 1 {1 elemento} other {# elementi} }", + "delete-items-text": "Attenzione, dopo la conferma tutti gli elementi selezionati saranno rimossi e i relativi dati non saranno più recuperabili.", + "add-item-text": "Aggiungi nuovo elemento", + "no-items-text": "Nessun elemento trovato", + "item-details": "Dettagli elemento", + "delete-item": "Elimina elemento", + "delete-items": "Elimina elementi", + "scroll-to-top": "Scorri verso l'alto" + }, + "help": { + "goto-help-page": "Vai all'help" + }, + "home": { + "home": "Home", + "profile": "Profilo", + "logout": "Logout", + "menu": "Menu", + "avatar": "Avatar", + "open-user-menu": "Apri menu utente" + }, + "import": { + "no-file": "Nessun file selezionato", + "drop-file": "Trascina un file JSON o fai clic per selezionare un file da caricare." + }, + "item": { + "selected": "Selezionata" + }, + "js-func": { + "no-return-error": "La funzione deve restituire un valore!", + "return-type-mismatch": "La funzione deve restituire un valore di tipo '{{type}}'!", + "tidy": "Tidy" + }, + "key-val": { + "key": "Chiave", + "value": "Valore", + "remove-entry": "Rimuovi voce", + "add-entry": "Aggiungi voce", + "no-data": "Nessuna voce" + }, + "layout": { + "layout": "Layout", + "manage": "Gestisci layout", + "settings": "Impostazioni layout", + "color": "Colore", + "main": "Main", + "right": "Destra", + "select": "Select target layout" + }, + "legend": { + "position": "Posizione Legenda", + "show-max": "Mostra valore max", + "show-min": "Mostra valore min", + "show-avg": "Mostra valore medio", + "show-total": "Mostra valore totale", + "settings": "Impostazioni legenda", + "min": "min", + "max": "max", + "avg": "avg", + "total": "totale" + }, + "login": { + "login": "Login", + "request-password-reset": "Richiesta reset password", + "reset-password": "Reset Password", + "create-password": "Crea Password", + "passwords-mismatch-error": "Le password inserite devono corrispondere!", + "password-again": "Ripeti Password", + "sign-in": "Please sign in", + "username": "Nome utente (email)", + "remember-me": "Ricordami", + "forgot-password": "Password dimenticata?", + "password-reset": "Password reset", + "new-password": "Nuova password", + "new-password-again": "Ripeti nuova password", + "password-link-sent-message": "Link reset password inviato con successo!", + "email": "Email" + }, + "position": { + "top": "Alto", + "bottom": "Basso", + "left": "Sinistra", + "right": "Destra" + }, + "profile": { + "profile": "Profilo", + "change-password": "Modifica Password", + "current-password": "Password attuale" + }, + "relation": { + "relations": "Relazioni", + "direction": "Direzione", + "search-direction": { + "FROM": "Da", + "TO": "A" + }, + "direction-type": { + "FROM": "da", + "TO": "a" + }, + "from-relations": "Relazioni in uscita", + "to-relations": "Relazioni in ingresso", + "selected-relations": "{ count, plural, 1 {1 relazione selezionata} other {# relazioni selezionate} }", + "type": "Tipo", + "to-entity-type": "A tipo entità", + "to-entity-name": "A nome entità", + "from-entity-type": "Da tipo entità", + "from-entity-name": "Da nome entità", + "to-entity": "A entità", + "from-entity": "Da entità", + "delete": "Elimina relazione", + "relation-type": "Tipo di relazione", + "relation-type-required": "Tipo di relazione obbligatorio.", + "any-relation-type": "Ogni tipo", + "add": "Aggiungi relazione", + "edit": "Modifica relazione", + "delete-to-relation-title": "Sei sicuro di voler eliminare la relazione con l'entità '{{entityName}}'?", + "delete-to-relation-text": "Attenzione, dopo la conferma l'entità '{{entityName}}' sarà scollegata dall'entità corrente.", + "delete-to-relations-title": "Sei sicuro di voler eliminare { count, plural, 1 {1 relazione} other {# relazioni} }?", + "delete-to-relations-text": "Attenzione, dopo la conferma tutte le relazioni selezionate saranno rimosse e le corrispondenti entità scollegate da quella corrente.", + "delete-from-relation-title": "Sei sicuro di voler eliminare la relazione dall'entità '{{entityName}}'?", + "delete-from-relation-text": "Attenzione, dopo la conferma l'entità corrente sarà scollegata dall'entità '{{entityName}}'.", + "delete-from-relations-title": "Sei sicuro di voler eliminare { count, plural, 1 {1 relazione} other {# relazioni} }?", + "delete-from-relations-text": "Attenzione, dopo la conferma tutte le relazioni selezionate saranno rimosse e l'entità corrente scollegata dalle corrispondenti entità.", + "remove-relation-filter": "Rimuovi filtro relazioni", + "add-relation-filter": "Aggiungi filtro relazioni", + "any-relation": "Qualsiasi relazione", + "relation-filters": "Filtri relazioni", + "additional-info": "Informazioni aggiuntive (JSON)", + "invalid-additional-info": "Impossibile analizzare le informazioni aggiuntive in JSON." + }, + "rulechain": { + "rulechain": "Rule chain", + "rulechains": "Rule chain", + "root": "Root", + "delete": "Cancella rule chain", + "name": "Nome", + "name-required": "Nome obbligatorio.", + "description": "Descrizione", + "add": "Aggiungi Rule Chain", + "set-root": "Imposta la rule chain come root", + "set-root-rulechain-title": "Sei sicuro di voler impostare la rule chain '{{ruleChainName}}' come root?", + "set-root-rulechain-text": "Dopo la conferma la rule chain diverrà root a gestirà tutti i messaggi in arrivo.", + "delete-rulechain-title": "Sei sicuro di voler eliminare la rule chain '{{ruleChainName}}'?", + "delete-rulechain-text": "Attenzione, dopo la conferma la rule chain e tutti i dati relativi non saranno più recuperabili.", + "delete-rulechains-title": "Sei sicuro di voler eliminare { count, plural, 1 {1 rule chain} other {# rule chain} }?", + "delete-rulechains-action-title": "Elimina { count, plural, 1 {1 rule chain} other {# rule chain} }", + "delete-rulechains-text": "Attenzione, dopo la conferma tutte le rule chain selezionate saranno rimosse e tutti i relativi dati non saranno più recuperabili.", + "add-rulechain-text": "Aggiungi nuova rule chain", + "no-rulechains-text": "Nessuna rule chain trovata", + "rulechain-details": "Dettagli rule chain", + "details": "Dettagli", + "events": "Eventi", + "system": "Sistema", + "import": "Importa rule chain", + "export": "Esporta rule chain", + "export-failed-error": "Impossibile esportare rule chain: {{error}}", + "create-new-rulechain": "Crea nuova rule chain", + "rulechain-file": "File rule chain", + "invalid-rulechain-file-error": "Impossibile importare rule chain: struttura dati rule chain non valida.", + "copyId": "Copia Id rule chain", + "idCopiedMessage": "Id rule chain copiato negli appunti", + "select-rulechain": "Seleziona rule chain", + "no-rulechains-matching": "Nessuna rule chain corrispondente a '{{entity}}' è stata trovata.", + "rulechain-required": "Rule chain obbligatoria", + "management": "Gestione regole", + "debug-mode": "Modalità debug" + }, + "rulenode": { + "details": "Dettagli", + "events": "Eventi", + "search": "Ricerca nodi", + "open-node-library": "Apri libreria nodi", + "add": "Aggiungi nodo regola", + "name": "Nome", + "name-required": "Nome obbligatorio.", + "type": "Tipo", + "description": "Descrizione", + "delete": "Elimina nodo regola", + "select-all-objects": "Seleziona tutti i nodi e le connessioni", + "deselect-all-objects": "Deseleziona tutti i nodi e le connessioni", + "delete-selected-objects": "Cancella nodi e connessioni selezionate", + "delete-selected": "Elimina selezionati", + "select-all": "Seleziona tutto", + "copy-selected": "Copia selezionata", + "deselect-all": "Deseleziona tutto", + "rulenode-details": "Dettagli nodo regola", + "debug-mode": "Modalità debug", + "configuration": "Configurazione", + "link": "Link", + "link-details": "Dettagli link nodo regola", + "add-link": "Aggiungi link", + "link-label": "Etichetta link", + "link-label-required": "Etichetta link obbligatoria.", + "custom-link-label": "Etichetta link personalizzata", + "custom-link-label-required": "Etichetta link personalizzata obbligatoria.", + "link-labels": "Etichette link", + "link-labels-required": "Etichette link richieste.", + "no-link-labels-found": "Nessuna etichetta link trovata.", + "no-link-label-matching": "'{{label}}' non trovata.", + "create-new-link-label": "Creane una nuova!", + "type-filter": "Filtro", + "type-filter-details": "Filtra i messaggi in arrivo con le condizioni configurate", + "type-enrichment": "Enrichment", + "type-enrichment-details": "Aggiungi informazioni addizionali nei metadati del messaggio", + "type-transformation": "Transformation", + "type-transformation-details": "Change Message payload and Metadata", + "type-action": "Azioni", + "type-action-details": "Perform special action", + "type-external": "External", + "type-external-details": "Interacts with external system", + "type-rule-chain": "Rule Chain", + "type-rule-chain-details": "Forwards incoming messages to specified Rule Chain", + "type-input": "Input", + "type-input-details": "Logical input of Rule Chain, forwards incoming messages to next related Rule Node", + "type-unknown": "Sconosciuto", + "type-unknown-details": "Nodo regola non trovato", + "directive-is-not-loaded": "Defined configuration directive '{{directiveName}}' is not available.", + "ui-resources-load-error": "Failed to load configuration ui resources.", + "invalid-target-rulechain": "Unable to resolve target rule chain!", + "test-script-function": "Test script function", + "message": "Messaggio", + "message-type": "Tipo messaggio", + "select-message-type": "Seleziona tipo messaggio", + "message-type-required": "Tipo messaggio obbligatorio", + "metadata": "Metadata", + "metadata-required": "Metadata entries can't be empty.", + "output": "Output", + "test": "Test", + "help": "Aiuto" + }, + "tenant": { + "tenant": "Tenant", + "tenants": "Tenant", + "management": "Gestione Tenant", + "add": "Aggiungi Tenant", + "admins": "Amministratori", + "manage-tenant-admins": "Gestisci amministratori tenant", + "delete": "Cancella tenant", + "add-tenant-text": "Aggiungi nuovo tenant", + "no-tenants-text": "Nessun tenant trovato", + "tenant-details": "Dettagli tenant", + "delete-tenant-title": "Sei sicuro di voler eliminare il tenant '{{tenantTitle}}'?", + "delete-tenant-text": "Attenzione, dopo la conferma il tenant e tutti i suoi dati non saranno più recuperabili.", + "delete-tenants-title": "Sei sicuro di voler eliminare { count, plural, 1 {1 tenant} other {# tenant} }?", + "delete-tenants-action-title": "Elimina { count, plural, 1 {1 tenant} other {# tenant} }", + "delete-tenants-text": "Attenzione, dopo la conferma tutti i tenant selezionati saranno eliminati e tutti i loro dati non saranno più recuperabili.", + "title": "Titolo", + "title-required": "Titolo obbligatorio.", + "description": "Descrizione", + "details": "Dettagli", + "events": "Eventi", + "copyId": "Copia Id tenant", + "idCopiedMessage": "Id tenant copiato negli appunti", + "select-tenant": "Seleziona tenant", + "no-tenants-matching": "Nessun tenant corrispondente a '{{entity}}' è stato trovato.", + "tenant-required": "Tenant obbligatorio" + }, + "timeinterval": { + "seconds-interval": "{ seconds, plural, 1 {1 secondo} other {# secondi} }", + "minutes-interval": "{ minutes, plural, 1 {1 minuto} other {# minuti} }", + "hours-interval": "{ hours, plural, 1 {1 ora} other {# ore} }", + "days-interval": "{ days, plural, 1 {1 giorno} other {# giorni} }", + "days": "Giorni", + "hours": "Ore", + "minutes": "Minuti", + "seconds": "Secondi", + "advanced": "Avanzate" + }, + "timewindow": { + "days": "{ days, plural, 1 { giorno } other {# giorni } }", + "hours": "{ hours, plural, 0 { ora } 1 {1 ora } other {# ore } }", + "minutes": "{ minutes, plural, 0 { minuto } 1 {1 minuto } other {# minuti } }", + "seconds": "{ seconds, plural, 0 { secondo } 1 {1 secondo } other {# secondi } }", + "realtime": "Realtime", + "history": "Cronologia", + "last-prefix": "ultimo", + "period": "da {{ startTime }} a {{ endTime }}", + "edit": "Modifica intervallo temporale", + "date-range": "Intervallo date", + "last": "Ultimo", + "time-period": "Intervallo temporale" + }, + "user": { + "user": "Utente", + "users": "Utenti", + "customer-users": "Utente cliente", + "tenant-admins": "Amministratori Tenant", + "sys-admin": "Amministratore di sistema", + "tenant-admin": "Amministratore tenant", + "customer": "Cliente", + "anonymous": "Anonimo", + "add": "Aggiungi Utente", + "delete": "Elimina utente", + "add-user-text": "Aggiungi nuovo utente", + "no-users-text": "Nessun utente trovato", + "user-details": "Dettagli utente", + "delete-user-title": "Sei sicuro di voler eliminare l'utente '{{userEmail}}'?", + "delete-user-text": "Attenzione, dopo la conferma l'utente e tutti i suoi dati non saranno più recuperabili.", + "delete-users-title": "Sei sicuro di voler eliminare { count, plural, 1 {1 utente} other {# utenti} }?", + "delete-users-action-title": "Elimina { count, plural, 1 {1 utente} other {# utenti} }", + "delete-users-text": "Attenzione, dopo la conferma tutti gli utenti selezionati saranno eliminati e tutti i relativi dati non saranno più recuperabili.", + "activation-email-sent-message": "Email di attivazione inviata con successo!", + "resend-activation": "Invia di nuovo attivazione", + "email": "Email", + "email-required": "Email obbligatoria.", + "invalid-email-format": "Formato email non valido.", + "first-name": "Nome", + "last-name": "Cognome", + "description": "Descrizione", + "default-dashboard": "Dashboard di default", + "always-fullscreen": "Sempre a schermo intero", + "select-user": "Seleziona utente", + "no-users-matching": "Nessun utente corrispondente a '{{entity}}' è stato trovato.", + "user-required": "Utente obbligatorio", + "activation-method": "Metodo di attivazione", + "display-activation-link": "Mostra link di attivazione", + "send-activation-mail": "Invia email di attivazione", + "activation-link": "Link attivazione utente", + "activation-link-text": "Per attivare l'utente utilizza il seguente link di attivazione :", + "copy-activation-link": "Copia link di attivazione", + "activation-link-copied-message": "Link di attivazione utente copiato negli appunti", + "details": "Dettagli", + "login-as-tenant-admin": "Accedi come Amministratore tenant", + "login-as-customer-user": "Accedi come Utente cliente" + }, + "value": { + "type": "Tipo valore", + "string": "String", + "string-value": "Valore string", + "integer": "Integer", + "integer-value": "Valore integer", + "invalid-integer-value": "Valore integer non valido", + "double": "Double", + "double-value": "Valore double", + "boolean": "Boolean", + "boolean-value": "Valore boolean", + "false": "Falso", + "true": "Vero", + "long": "Long" + }, + "widget": { + "widget-library": "Libreria Widget", + "widget-bundle": "Bundle widget", + "select-widgets-bundle": "Seleziona bundle widget", + "management": "Gestione widget", + "editor": "Editor Widget", + "widget-type-not-found": "Problem loading widget configuration.
Probably associated\n widget type was removed.", + "widget-type-load-error": "Widget non caricato a causa dei seguenti errori:", + "remove": "Elimina widget", + "edit": "Modifica widget", + "remove-widget-title": "sei sicuro di voler eliminare il widget '{{widgetTitle}}'?", + "remove-widget-text": "Dopo la conferma il widget e tutti i suoi dati non saranno più recuperabili.", + "timeseries": "Time series", + "search-data": "Cerca dati", + "no-data-found": "Nessun dato trovato", + "latest-values": "Ultimi valori", + "rpc": "Control widget", + "alarm": "Alarm widget", + "static": "Static widget", + "select-widget-type": "Seleziona tipo widget", + "missing-widget-title-error": "Il tiolo del widget deve essere specificato!", + "widget-saved": "Widget salvato", + "unable-to-save-widget-error": "Impossibile salvare il widget! Sono presenti degli errori!", + "save": "Salva widget", + "saveAs": "Salva widget come", + "save-widget-type-as": "Salva tipo widget come", + "save-widget-type-as-text": "Please enter new widget title and/or select target widgets bundle", + "toggle-fullscreen": "Commuta modalità schermo intero", + "run": "Esegui widget", + "title": "Titolo widget", + "title-required": "Titolo widget obbligatorio.", + "type": "Tipo widget", + "resources": "Risorse", + "resource-url": "JavaScript/CSS URL", + "remove-resource": "Rimuovi risorsa", + "add-resource": "Aggiungi risorsa", + "html": "HTML", + "tidy": "Tidy", + "css": "CSS", + "settings-schema": "Impostazioni schema", + "datakey-settings-schema": "Impostazioni Data key schema", + "javascript": "Javascript", + "remove-widget-type-title": "Sei sicuro di voler rimuovere il tipo di widget '{{widgetName}}'?", + "remove-widget-type-text": "Dopo la conferma il tipo di widget e tutti i suoi dati non saranno più recuperabili.", + "remove-widget-type": "Rimuovi tipo widget", + "add-widget-type": "Aggiungi nuovo tipo widget", + "widget-type-load-failed-error": "Caricamento tipo widget fallito!", + "widget-template-load-failed-error": "Caricamento template widget fallito!", + "add": "Aggiungi Widget", + "undo": "Annulla modifiche widget", + "export": "Esporta widget" + }, + "widget-action": { + "header-button": "Widget header button", + "open-dashboard-state": "Navigate to new dashboard state", + "update-dashboard-state": "Update current dashboard state", + "open-dashboard": "Navigate to other dashboard", + "custom": "Custom action", + "target-dashboard-state": "Target dashboard state", + "target-dashboard-state-required": "Target dashboard state is required", + "set-entity-from-widget": "Set entity from widget", + "target-dashboard": "Target dashboard", + "open-right-layout": "Open right dashboard layout (mobile view)" + }, + "widgets-bundle": { + "current": "Bundle corrente", + "widgets-bundles": "Bundle Widget", + "add": "Aggiungi Bundle Widget", + "delete": "Cancella bundle widget", + "title": "Titolo", + "title-required": "Titolo obbligatorio.", + "add-widgets-bundle-text": "Aggiungi nuovo bundle widget", + "no-widgets-bundles-text": "Nessun bundle widget trovato", + "empty": "Bundle widget vuoto", + "details": "Dettagli", + "widgets-bundle-details": "Dettagli bundle widget", + "delete-widgets-bundle-title": "Sei sicuro di voler eliminare il bundle widget '{{widgetsBundleTitle}}'?", + "delete-widgets-bundle-text": "Attenzione, dopo la conferma il bundle widget e tutti i suoi dati non saranno più recuperabili.", + "delete-widgets-bundles-title": "Sei sicuro di voler eliminare { count, plural, 1 {1 bundle widget} other {# bundle widget} }?", + "delete-widgets-bundles-action-title": "Elimina { count, plural, 1 {1 bundle widget} other {# bundle widget} }", + "delete-widgets-bundles-text": "Attenzione, dopo la conferma tutti i bundle widget selezionati saranno rimossi e tutti i loro dati non saranno più recuperabili.", + "no-widgets-bundles-matching": "Nessun bundle widget corrispondente a '{{widgetsBundle}}' è stato trovato.", + "widgets-bundle-required": "Bundle widget obbligatorio.", + "system": "Sistema", + "import": "Importa bundle widget", + "export": "Esporta bundle widget", + "export-failed-error": "Impossibile esportare bundle widget: {{error}}", + "create-new-widgets-bundle": "Crea nuovo bundle widget", + "widgets-bundle-file": "File bundle widget", + "invalid-widgets-bundle-file-error": "Impossibile importare bundle widget: struttura dati non valida." + }, + "widget-config": { + "data": "Dati", + "settings": "Impostazioni", + "advanced": "Avanzate", + "title": "Titolo", + "general-settings": "Impostazioni generali", + "display-title": "Mostra titolo", + "drop-shadow": "Drop shadow", + "enable-fullscreen": "Abilita schermo intero", + "background-color": "Colore sfondo", + "text-color": "Colore testo", + "padding": "Padding", + "margin": "Margin", + "widget-style": "Stile Widget", + "title-style": "Stile titolo", + "mobile-mode-settings": "Impostazioni modalità mobile", + "order": "Ordinamento", + "height": "Altezza", + "units": "Simbolo speciale da mostrare accanto al valore", + "decimals": "Numero di cifre decimali", + "timewindow": "Intervallo temporale", + "use-dashboard-timewindow": "Usa intervallo temporale dashboard", + "display-timewindow": "Mostra intervallo temporale", + "display-legend": "Mostra legenda", + "datasources": "Sorgenti dei dati", + "maximum-datasources": "Massimo { count, plural, 1 {1 sorgente dati consentita.} other {# sorgenti dati consentite} }", + "datasource-type": "Tipo", + "datasource-parameters": "Parametri", + "remove-datasource": "Rimuovi sorgente dati", + "add-datasource": "Aggiungi sorgente dati", + "target-device": "Dispositivo Target", + "alarm-source": "Sorgente Allarme", + "actions": "Azioni", + "action": "Azione", + "add-action": "Aggiungi azione", + "search-actions": "Ricerca azioni", + "action-source": "Sorgente azione", + "action-source-required": "Sorgente azione obbligatoria.", + "action-name": "Nome", + "action-name-required": "Nome azione obbligatorio.", + "action-name-not-unique": "Un'altra azione con lo stesso nome è già presente.
Il nome di una azione dovrebbe essere univoco all'interno della stessa sorgente.", + "action-icon": "Icona", + "action-type": "Tipo", + "action-type-required": "Tipo azione obbligatorio.", + "edit-action": "Modifica azione", + "delete-action": "Cancella azione", + "delete-action-title": "Cancella azione del widget", + "delete-action-text": "Sei sicuro di voler cancellare l'azione del widget '{{actionName}}'?", + "display-icon": "Mostra icona titolo", + "icon-color": "Colore dell'icona", + "icon-size": "Dimensione dell'icona" + }, + "widget-type": { + "import": "Importa un tipo di widget", + "export": "Esporta un tipo di widget", + "export-failed-error": "Impossibile esportare il tipo di widget: {{error}}", + "create-new-widget-type": "Crea un nuovo tipo di widget", + "widget-type-file": "File tipo di widget", + "invalid-widget-type-file-error": "Impossibile importare un tipo di widget: struttura dati del widget non valida." + }, + "widgets": { + "date-range-navigator": { + "localizationMap": { + "Sun": "Dom", + "Mon": "Lun", + "Tue": "Mar", + "Wed": "Mer", + "Thu": "Gio", + "Fri": "Ven", + "Sat": "Sab", + "Jan": "Gen", + "Feb": "Feb", + "Mar": "Mar", + "Apr": "Apr", + "May": "Mag", + "Jun": "Giu", + "Jul": "Lug", + "Aug": "Ago", + "Sep": "Set", + "Oct": "Ott", + "Nov": "Nov", + "Dec": "Dic", + "January": "Gennaio", + "February": "Febbraio", + "March": "Marzo", + "April": "Aprile", + "June": "Giugno", + "July": "Luglio", + "August": "Agosto", + "September": "Settembre", + "October": "Ottobre", + "November": "Novembre", + "December": "Dicembre", + "Custom Date Range": "Intervallo di date personalizzato", + "Date Range Template": "Modello di intervallo di date", + "Today": "Oggi", + "Yesterday": "Ieri", + "This Week": "Questa settimana", + "Last Week": "La settimana scorsa", + "This Month": "Questo mese", + "Last Month": "Lo scorso mese", + "Year": "Anno", + "This Year": "Quest'anno", + "Last Year": "L'anno scorso", + "Date picker": "Date picker", + "Hour": "Ora", + "Day": "Giorno", + "Week": "Settimana", + "2 weeks": "2 Settimane", + "Month": "Mese", + "3 months": "3 Mesi", + "6 months": "6 Mesi", + "Custom interval": "Intervallo personalizzato", + "Interval": "Intervallo", + "Step size": "Dimensione del passo", + "Ok": "Ok" + } + } + }, + "icon": { + "icon": "Icona", + "select-icon": "Seleziona icona", + "material-icons": "Icone Material", + "show-all": "Mostra tutte le icone" + }, + "custom": { + "widget-action": { + "action-cell-button": "Pulsante azione cella", + "row-click": "Click sulla riga", + "polygon-click": "Click sul poligono", + "marker-click": "Click sul marker", + "tooltip-tag-action": "Azione tooltip", + "node-selected": "Click su nodo selezionato", + "element-click": "Click su elemento HTML" + } + }, + "language": { + "language": "Lingua", + "locales": { + "de_DE": "Tedesco", + "fr_FR": "Francese", + "zh_CN": "Cinese", + "en_US": "Inglese", + "it_IT": "Italiano", + "ko_KR": "Coreano", + "ru_RU": "Russo", + "es_ES": "Spagnolo", + "ja_JA": "Giapponese", + "tr_TR": "Turco", + "fa_IR": "Persiana", + "uk_UA": "Ucraino", + "cs_CZ": "Ceco" + } + } +} diff --git a/ui-ngx/src/assets/locale/locale.constant-ja_JA.json b/ui-ngx/src/assets/locale/locale.constant-ja_JA.json new file mode 100644 index 0000000000..c79390534e --- /dev/null +++ b/ui-ngx/src/assets/locale/locale.constant-ja_JA.json @@ -0,0 +1,1527 @@ +{ + "access": { + "unauthorized": "無許可", + "unauthorized-access": "不正アクセス", + "unauthorized-access-text": "このリソースにアクセスするにはサインインする必要があります。", + "access-forbidden": "アクセス禁止", + "access-forbidden-text": "あなたはこの場所へのアクセス権を持っていません!この場所にアクセスしたい場合は、別のユーザーとサインインしてみてください。", + "refresh-token-expired": "セッションが終了しました", + "refresh-token-failed": "セッションをリフレッシュできません" + }, + "action": { + "activate": "アクティブ化する", + "suspend": "サスペンド", + "save": "セーブ", + "saveAs": "名前を付けて保存", + "cancel": "キャンセル", + "ok": "[OK]", + "delete": "削除", + "add": "追加", + "yes": "はい", + "no": "いいえ", + "update": "更新", + "remove": "削除する", + "search": "サーチ", + "clear-search": "検索をクリアする", + "assign": "割り当てます", + "unassign": "割り当て解除", + "share": "シェア", + "make-private": "プライベートにする", + "apply": "適用", + "apply-changes": "変更を適用する", + "edit-mode": "編集モード", + "enter-edit-mode": "編集モードに入る", + "decline-changes": "変更を拒否する", + "close": "閉じる", + "back": "バック", + "run": "走る", + "sign-in": "サインイン!", + "edit": "編集", + "view": "ビュー", + "create": "作成する", + "drag": "ドラッグ", + "refresh": "リフレッシュ", + "undo": "元に戻す", + "copy": "コピー", + "paste": "ペースト", + "copy-reference": "コピーリファレンス", + "paste-reference": "参照貼り付け", + "import": "インポート", + "export": "輸出する", + "share-via": "{{provider}}" + }, + "aggregation": { + "aggregation": "集約", + "function": "データ集約機能", + "limit": "最大値", + "group-interval": "グループ化の間隔", + "min": "分", + "max": "最大", + "avg": "平均", + "sum": "和", + "count": "カウント", + "none": "なし" + }, + "admin": { + "general": "一般", + "general-settings": "一般設定", + "outgoing-mail": "送信メール", + "outgoing-mail-settings": "送信メールの設定", + "system-settings": "システム設定", + "test-mail-sent": "テストメールが正常に送信されました!", + "base-url": "ベースURL", + "base-url-required": "ベースURLは必須です。", + "mail-from": "メール", + "mail-from-required": "メールの送信元が必要です。", + "smtp-protocol": "SMTPプロトコル", + "smtp-host": "SMTPホスト", + "smtp-host-required": "SMTPホストが必要です。", + "smtp-port": "SMTPポート", + "smtp-port-required": "smtpポートを指定する必要があります。", + "smtp-port-invalid": "それは有効なsmtpポートのようには見えません。", + "timeout-msec": "タイムアウト(ミリ秒)", + "timeout-required": "タイムアウトが必要です。", + "timeout-invalid": "それは有効なタイムアウトのようには見えません。", + "enable-tls": "TLSを有効にする", + "send-test-mail": "テストメールを送信する" + }, + "alarm": { + "alarm": "警報", + "alarms": "アラーム", + "select-alarm": "アラームを選択", + "no-alarms-matching": "'{{entity}}'発見されました。", + "alarm-required": "アラームが必要です", + "alarm-status": "アラーム状態", + "search-status": { + "ANY": "どれか", + "ACTIVE": "アクティブ", + "CLEARED": "クリアされた", + "ACK": "承認された", + "UNACK": "未確認の" + }, + "display-status": { + "ACTIVE_UNACK": "アクティブ未確認", + "ACTIVE_ACK": "Active Acknowledged", + "CLEARED_UNACK": "クリアされた未確認のメッセージ", + "CLEARED_ACK": "承認された承認済み" + }, + "no-alarms-prompt": "アラームが見つかりません", + "created-time": "作成時刻", + "type": "タイプ", + "severity": "重大度", + "originator": "創始者", + "originator-type": "発信者タイプ", + "details": "詳細", + "status": "状態", + "alarm-details": "アラームの詳細", + "start-time": "始まる時間", + "end-time": "終了時間", + "ack-time": "確認された時間", + "clear-time": "クリアされた時間", + "severity-critical": "クリティカル", + "severity-major": "メジャー", + "severity-minor": "マイナー", + "severity-warning": "警告", + "severity-indeterminate": "不確定", + "acknowledge": "認める", + "clear": "クリア", + "search": "アラームの検索", + "selected-alarms": "{ count, plural, 1 {1 alarm} other {# alarms} }選択された", + "no-data": "表示するデータがありません", + "polling-interval": "アラームポーリング間隔(秒)", + "polling-interval-required": "アラームのポーリング間隔が必要です。", + "min-polling-interval-message": "少なくとも1秒間のポーリング間隔が許可されます。", + "aknowledge-alarms-title": "{ count, plural, 1 {1 alarm} other {# alarms} }", + "aknowledge-alarms-text": "{ count, plural, 1 {1 alarm} other {# alarms} }?", + "clear-alarms-title": "{ count, plural, 1 {1 alarm} other {# alarms} }", + "clear-alarms-text": "{ count, plural, 1 {1 alarm} other {# alarms} }?" + }, + "alias": { + "add": "エイリアスを追加する", + "edit": "エイリアスを編集する", + "name": "エイリアス名", + "name-required": "エイリアス名は必須です", + "duplicate-alias": "同じ名前のエイリアスは既に存在します。", + "filter-type-single-entity": "単一のエンティティ", + "filter-type-entity-list": "エンティティリスト", + "filter-type-entity-name": "エンティティ名", + "filter-type-state-entity": "ダッシュボード状態からのエンティティ", + "filter-type-state-entity-description": "ダッシュボードの状態パラメータから取得されたエンティティ", + "filter-type-asset-type": "資産の種類", + "filter-type-asset-type-description": "'{{assetType}}'", + "filter-type-asset-type-and-name-description": "'{{assetType}}''{{prefix}}'", + "filter-type-device-type": "デバイスタイプ", + "filter-type-device-type-description": "'{{deviceType}}'", + "filter-type-device-type-and-name-description": "'{{deviceType}}''{{prefix}}'", + "filter-type-relations-query": "関係クエリ", + "filter-type-relations-query-description": "{{entities}}{{relationType}}{{direction}}{{rootEntity}}", + "filter-type-asset-search-query": "資産検索クエリ", + "filter-type-asset-search-query-description": "{{assetTypes}}{{relationType}}{{direction}}{{rootEntity}}", + "filter-type-device-search-query": "デバイス検索クエリ", + "filter-type-device-search-query-description": "{{deviceTypes}}{{relationType}}{{direction}}{{rootEntity}}", + "entity-filter": "エンティティフィルタ", + "resolve-multiple": "複数のエンティティとして解決する", + "filter-type": "フィルタタイプ", + "filter-type-required": "フィルタタイプが必要です。", + "entity-filter-no-entity-matched": "指定されたフィルタに一致するエンティティは見つかりませんでした。", + "no-entity-filter-specified": "エンティティフィルタが指定されていない", + "root-state-entity": "ルートとしてダッシュボードの状態エンティティを使用する", + "root-entity": "ルートエンティティ", + "state-entity-parameter-name": "状態エンティティのパラメータ名", + "default-state-entity": "デフォルト状態エンティティ", + "default-entity-parameter-name": "デフォルトでは", + "max-relation-level": "最大関連レベル", + "unlimited-level": "無制限レベル", + "state-entity": "ダッシュボードの状態エンティティ", + "all-entities": "すべてのエンティティ", + "any-relation": "どれか" + }, + "asset": { + "asset": "資産", + "assets": "資産", + "management": "資産運用管理", + "view-assets": "アセットの表示", + "add": "アセットを追加", + "assign-to-customer": "顧客に割り当てる", + "assign-asset-to-customer": "顧客に資産を割り当てる", + "assign-asset-to-customer-text": "顧客に割り当てる資産を選択してください", + "no-assets-text": "アセットが見つかりません", + "assign-to-customer-text": "資産を割り当てる顧客を選択してください", + "public": "パブリック", + "assignedToCustomer": "顧客に割り当てられた", + "make-public": "アセットを公開する", + "make-private": "アセットをプライベートにする", + "unassign-from-customer": "顧客からの割り当て解除", + "delete": "アセットを削除", + "asset-public": "資産は公開されています", + "asset-type": "資産の種類", + "asset-type-required": "資産の種類が必要です。", + "select-asset-type": "アセットタイプを選択", + "enter-asset-type": "アセットタイプを入力", + "any-asset": "すべてのアセット", + "no-asset-types-matching": "'{{entitySubtype}}'発見されました。", + "asset-type-list-empty": "選択されたアセットタイプはありません。", + "asset-types": "資産タイプ", + "name": "名", + "name-required": "名前は必須です。", + "description": "説明", + "type": "タイプ", + "type-required": "タイプが必要です。", + "details": "詳細", + "events": "イベント", + "add-asset-text": "新しいアセットを追加する", + "asset-details": "資産の詳細", + "assign-assets": "アセットの割り当て", + "assign-assets-text": "{ count, plural, 1 {1 asset} other {# assets} }顧客に", + "delete-assets": "アセットを削除する", + "unassign-assets": "アセットの割り当てを解除する", + "unassign-assets-action-title": "{ count, plural, 1 {1 asset} other {# assets} }顧客から", + "assign-new-asset": "新しいアセットを割り当てる", + "delete-asset-title": "'{{assetName}}'?", + "delete-asset-text": "確認後、資産と関連するすべてのデータが回復不能になることに注意してください。", + "delete-assets-title": "{ count, plural, 1 {1 asset} other {# assets} }?", + "delete-assets-action-title": "{ count, plural, 1 {1 asset} other {# assets} }", + "delete-assets-text": "確認後、選択したすべての資産が削除され、関連するすべてのデータは回復不能になりますので注意してください。", + "make-public-asset-title": "'{{assetName}}'パブリック?", + "make-public-asset-text": "確認後、資産とそのすべてのデータは公開され、他の人がアクセスできるようになります。", + "make-private-asset-title": "'{{assetName}}'プライベート?", + "make-private-asset-text": "確認後、資産とそのすべてのデータは非公開にされ、他の人がアクセスすることはできません。", + "unassign-asset-title": "'{{assetName}}'?", + "unassign-asset-text": "確認後、資産は割り当て解除され、顧客はアクセスできなくなります。", + "unassign-asset": "アセットの割り当てを解除する", + "unassign-assets-title": "{ count, plural, 1 {1 asset} other {# assets} }?", + "unassign-assets-text": "確認後、選択されたすべての資産が割り当て解除され、顧客がアクセスできなくなります。", + "copyId": "アセットIDをコピーする", + "idCopiedMessage": "アセットIDがクリップボードにコピーされました", + "select-asset": "アセットを選択", + "no-assets-matching": "'{{entity}}'発見されました。", + "asset-required": "資産が必要です", + "name-starts-with": "アセット名はで始まります" + }, + "attribute": { + "attributes": "属性", + "latest-telemetry": "最新テレメトリ", + "attributes-scope": "エンティティ属性のスコープ", + "scope-latest-telemetry": "最新テレメトリ", + "scope-client": "クライアントの属性", + "scope-server": "サーバーの属性", + "scope-shared": "共有属性", + "add": "属性を追加する", + "key": "キー", + "last-update-time": "最終更新時間", + "key-required": "属性キーは必須です。", + "value": "値", + "value-required": "属性値は必須です。", + "delete-attributes-title": "{ count, plural, 1 {1 attribute} other {# attributes} }?", + "delete-attributes-text": "注意してください。確認後、選択したすべての属性が削除されます。", + "delete-attributes": "属性を削除する", + "enter-attribute-value": "属性値を入力", + "show-on-widget": "ウィジェットで表示", + "widget-mode": "ウィジェットモード", + "next-widget": "次のウィジェット", + "prev-widget": "前のウィジェット", + "add-to-dashboard": "ダッシュボードに追加", + "add-widget-to-dashboard": "ウィジェットをダッシュ​​ボードに追加する", + "selected-attributes": "{ count, plural, 1 {1 attribute} other {# attributes} }選択された", + "selected-telemetry": "{ count, plural, 1 {1 telemetry unit} other {# telemetry units} }選択された" + }, + "audit-log": { + "audit": "監査", + "audit-logs": "監査ログ", + "timestamp": "タイムスタンプ", + "entity-type": "エンティティタイプ", + "entity-name": "エンティティ名", + "user": "ユーザー", + "type": "タイプ", + "status": "状態", + "details": "詳細", + "type-added": "追加された", + "type-deleted": "削除済み", + "type-updated": "更新しました", + "type-attributes-updated": "属性が更新されました", + "type-attributes-deleted": "属性が削除されました", + "type-rpc-call": "RPC呼び出し", + "type-credentials-updated": "資格が更新されました", + "type-assigned-to-customer": "顧客に割り当てられた", + "type-unassigned-from-customer": "顧客から割り当てられていない", + "type-activated": "活性化", + "type-suspended": "一時停止中", + "type-credentials-read": "信用証明書を読む", + "type-attributes-read": "読み取られた属性", + "type-relation-add-or-update": "関係が更新されました", + "type-relation-delete": "関係が削除されました", + "type-relations-delete": "すべてのリレーションを削除", + "type-alarm-ack": "承認された", + "type-alarm-clear": "クリアされた", + "status-success": "成功", + "status-failure": "失敗", + "audit-log-details": "監査ログの詳細", + "no-audit-logs-prompt": "ログが見つかりません", + "action-data": "行動データ", + "failure-details": "失敗の詳細", + "search": "監査ログの検索", + "clear-search": "検索をクリアする" + }, + "confirm-on-exit": { + "message": "保存されていない変更があります。あなたは本当にこのページを出るのですか?", + "html-message": "保存していない変更があります。
このページを終了してもよろしいですか?", + "title": "保存されていない変更" + }, + "contact": { + "country": "国", + "city": "シティ", + "state": "州/県", + "postal-code": "郵便番号", + "postal-code-invalid": "無効な郵便番号形式です。", + "address": "住所", + "address2": "アドレス2", + "phone": "電話", + "email": "Eメール", + "no-address": "住所がありません" + }, + "common": { + "username": "ユーザー名", + "password": "パスワード", + "enter-username": "ユーザーネームを入力してください", + "enter-password": "パスワードを入力する", + "enter-search": "検索を入力" + }, + "content-type": { + "json": "Json", + "text": "テキスト", + "binary": "バイナリ(Base64)" + }, + "customer": { + "customer": "顧客", + "customers": "顧客", + "management": "顧客管理", + "dashboard": "カスタマーダッシュボード", + "dashboards": "カスタマーダッシュボード", + "devices": "顧客デバイス", + "assets": "顧客資産", + "public-dashboards": "パブリックダッシュボード", + "public-devices": "パブリックデバイス", + "public-assets": "公的資産", + "add": "顧客を追加", + "delete": "顧客を削除する", + "manage-customer-users": "顧客ユーザーを管理する", + "manage-customer-devices": "顧客のデバイスを管理する", + "manage-customer-dashboards": "顧客ダッシュボードの管理", + "manage-public-devices": "パブリックデバイスを管理する", + "manage-public-dashboards": "公開ダッシュボードの管理", + "manage-customer-assets": "顧客資産の管理", + "manage-public-assets": "公的資産を管理する", + "add-customer-text": "新規顧客を追加", + "no-customers-text": "顧客が見つかりません", + "customer-details": "お客様情報", + "delete-customer-title": "'{{customerTitle}}'?", + "delete-customer-text": "確認後、お客様および関連するすべてのデータが回復不能になるので注意してください。", + "delete-customers-title": "{ count, plural, 1 {1 customer} other {# customers} }?", + "delete-customers-action-title": "{ count, plural, 1 {1 customer} other {# customers} }", + "delete-customers-text": "確認後、選択したすべての顧客は削除され、関連するすべてのデータは回復不能になります。", + "manage-users": "ユーザーを管理する", + "manage-assets": "アセットを管理する", + "manage-devices": "デバイスを管理する", + "manage-dashboards": "ダッシュボードの管理", + "title": "タイトル", + "title-required": "タイトルは必須です。", + "description": "説明", + "details": "詳細", + "events": "イベント", + "copyId": "顧客IDをコピー", + "idCopiedMessage": "顧客IDがクリップボードにコピーされました", + "select-customer": "顧客を選択", + "no-customers-matching": "'{{entity}}'発見されました。", + "customer-required": "顧客は必須です", + "select-default-customer": "デフォルトの顧客を選択", + "default-customer": "デフォルトの顧客", + "default-customer-required": "テナントレベルのダッシュボードをデバッグするには、デフォルトの顧客が必要です" + }, + "datetime": { + "date-from": "デートから", + "time-from": "からの時間", + "date-to": "日付", + "time-to": "の時間" + }, + "dashboard": { + "dashboard": "ダッシュボード", + "dashboards": "ダッシュボード", + "management": "ダッシュボード管理", + "view-dashboards": "ダッシュボードを表示する", + "add": "ダッシュボードを追加", + "assign-dashboard-to-customer": "顧客にダッシュボードを割り当てる", + "assign-dashboard-to-customer-text": "顧客に割り当てるダッシュボードを選択してください", + "assign-to-customer-text": "ダッシュボードを割り当てる顧客を選択してください", + "assign-to-customer": "顧客に割り当てる", + "unassign-from-customer": "顧客からの割り当て解除", + "make-public": "ダッシュボードを公開する", + "make-private": "ダッシュボードを非公開にする", + "manage-assigned-customers": "割り当てられた顧客を管理する", + "assigned-customers": "割り当てられた顧客", + "assign-to-customers": "顧客にダッシュボードを割り当てる", + "assign-to-customers-text": "ダッシュボードを割り当てる顧客を選択してください", + "unassign-from-customers": "顧客からのダッシュボードの割り当て解除", + "unassign-from-customers-text": "ダッシュボードから割り当て解除する顧客を選択してください", + "no-dashboards-text": "ダッシュボードが見つかりません", + "no-widgets": "ウィジェットは設定されていません", + "add-widget": "新しいウィジェットを追加", + "title": "タイトル", + "select-widget-title": "ウィジェットを選択", + "select-widget-subtitle": "利用可能なウィジェットタイプのリスト", + "delete": "ダッシュボードの削除", + "title-required": "タイトルは必須です。", + "description": "説明", + "details": "詳細", + "dashboard-details": "ダッシュボードの詳細", + "add-dashboard-text": "新しいダッシュボードを追加する", + "assign-dashboards": "ダッシュボードの割り当て", + "assign-new-dashboard": "新しいダッシュボードを割り当てる", + "assign-dashboards-text": "{ count, plural, 1 {1 dashboard} other {# dashboards} }顧客に", + "unassign-dashboards-action-text": "{ count, plural, 1 {1 dashboard} other {# dashboards} }顧客から", + "delete-dashboards": "ダッシュボードの削除", + "unassign-dashboards": "ダッシュボードの割り当てを解除する", + "unassign-dashboards-action-title": "{ count, plural, 1 {1 dashboard} other {# dashboards} }顧客から", + "delete-dashboard-title": "'{{dashboardTitle}}'?", + "delete-dashboard-text": "確認後、ダッシュボードとすべての関連データが回復不能になるので注意してください。", + "delete-dashboards-title": "{ count, plural, 1 {1 dashboard} other {# dashboards} }?", + "delete-dashboards-action-title": "{ count, plural, 1 {1 dashboard} other {# dashboards} }", + "delete-dashboards-text": "注意してください。確認後、選択したダッシュボードはすべて削除され、関連するすべてのデータは回復不能になります。", + "unassign-dashboard-title": "'{{dashboardTitle}}'?", + "unassign-dashboard-text": "確認後、ダッシュボードは割り当てられなくなり、顧客はアクセスできなくなります。", + "unassign-dashboard": "ダッシュボードの割り当てを解除する", + "unassign-dashboards-title": "{ count, plural, 1 {1 dashboard} other {# dashboards} }?", + "unassign-dashboards-text": "確認の後、選択したすべてのダッシュボードは割り当てられなくなり、顧客はアクセスできなくなります。", + "public-dashboard-title": "ダッシュボードは公開されました", + "public-dashboard-text": "{{dashboardTitle}} is now public and accessible via next public link:", + "public-dashboard-notice": "注:データにアクセスするために、関連するデバイスを公開することを忘れないでください。", + "make-private-dashboard-title": "'{{dashboardTitle}}'プライベート?", + "make-private-dashboard-text": "確認の後、ダッシュボードはプライベートにされ、他の人がアクセスすることはできません。", + "make-private-dashboard": "ダッシュボードを非公開にする", + "socialshare-text": "'{{dashboardTitle}}'ThingsBoardを搭載", + "socialshare-title": "'{{dashboardTitle}}'ThingsBoardを搭載", + "select-dashboard": "ダッシュボードを選択", + "no-dashboards-matching": "'{{entity}}'発見されました。", + "dashboard-required": "ダッシュボードが必要です。", + "select-existing": "既存のダッシュボードを選択", + "create-new": "新しいダッシュボードを作成する", + "new-dashboard-title": "新しいダッシュボードのタイトル", + "open-dashboard": "ダッシュボードを開く", + "set-background": "背景を設定する", + "background-color": "背景色", + "background-image": "背景画像", + "background-size-mode": "背景サイズモード", + "no-image": "選択した画像がありません", + "drop-image": "画像をドロップするか、クリックしてアップロードするファイルを選択します。", + "settings": "設定", + "columns-count": "列数", + "columns-count-required": "列数が必要です。", + "min-columns-count-message": "わずか10の最小列数が許可されます。", + "max-columns-count-message": "最大1000の列カウントのみが許可されます。", + "widgets-margins": "ウィジェット間のマージン", + "horizontal-margin": "水平マージン", + "horizontal-margin-required": "水平余白値が必要です。", + "min-horizontal-margin-message": "最小水平マージン値としては0だけが許容されます。", + "max-horizontal-margin-message": "最大水平マージン値は50だけです。", + "vertical-margin": "垂直マージン", + "vertical-margin-required": "垂直マージン値が必要です。", + "min-vertical-margin-message": "最小の垂直マージン値として0のみが許可されます。", + "max-vertical-margin-message": "最大垂直マージン値は50のみです。", + "autofill-height": "自動レイアウトの高さ", + "mobile-layout": "モバイルレイアウトの設定", + "mobile-row-height": "モバイル行の高さ、px", + "mobile-row-height-required": "モバイル行の高さ値が必要です。", + "min-mobile-row-height-message": "最小の行の高さの値として、5ピクセルしか許可されません。", + "max-mobile-row-height-message": "移動可能な行の高さの最大値として許可されるのは200ピクセルだけです。", + "display-title": "ダッシュボードのタイトルを表示する", + "toolbar-always-open": "ツールバーを開いたままにする", + "title-color": "タイトルカラー", + "display-dashboards-selection": "ダッシュボードの選択を表示する", + "display-entities-selection": "エンティティの選択を表示する", + "display-dashboard-timewindow": "タイムウィンドウを表示する", + "display-dashboard-export": "エクスポートの表示", + "import": "インポートダッシュボード", + "export": "エクスポートダッシュボード", + "export-failed-error": "{{error}}", + "create-new-dashboard": "新しいダッシュボードを作成する", + "dashboard-file": "ダッシュボードファイル", + "invalid-dashboard-file-error": "ダッシュボードをインポートできません:ダッシュボードのデータ構造が無効です。", + "dashboard-import-missing-aliases-title": "インポートされたダッシュボードで使用されるエイリアスを設定する", + "create-new-widget": "新しいウィジェットを作成する", + "import-widget": "インポートウィジェット", + "widget-file": "ウィジェットファイル", + "invalid-widget-file-error": "ウィジェットをインポートできません:ウィジェットのデータ構造が無効です。", + "widget-import-missing-aliases-title": "インポートされたウィジェットで使用されるエイリアスを設定する", + "open-toolbar": "ダッシュボードツールバーを開く", + "close-toolbar": "ツールバーを閉じる", + "configuration-error": "設定エラー", + "alias-resolution-error-title": "ダッシュボードエイリアス設定エラー", + "invalid-aliases-config": "エイリアスフィルタの一部に一致するデバイスを見つけることができません。
この問題を解決するには、管理者に連絡してください。", + "select-devices": "デバイスの選択", + "assignedToCustomer": "顧客に割り当てられた", + "assignedToCustomers": "顧客に割り当てられた", + "public": "パブリック", + "public-link": "パブリックリンク", + "copy-public-link": "パブリックリンクをコピーする", + "public-link-copied-message": "ダッシュボードのパブリックリンクがクリップボードにコピーされました", + "manage-states": "ダッシュボードの状態を管理する", + "states": "ダッシュボードの状態", + "search-states": "検索ダッシュボードの状態", + "selected-states": "{ count, plural, 1 {1 dashboard state} other {# dashboard states} }選択された", + "edit-state": "ダッシュボードの状態を編集する", + "delete-state": "ダッシュボードの状態を削除する", + "add-state": "ダッシュボードの状態を追加する", + "state": "ダッシュボードの状態", + "state-name": "名", + "state-name-required": "ダッシュボードの状態名は必須です。", + "state-id": "状態ID", + "state-id-required": "ダッシュボードの状態IDは必須です。", + "state-id-exists": "同じIDを持つダッシュボードの状態は既に存在します。", + "is-root-state": "ルート状態", + "delete-state-title": "ダッシュボードの状態を削除する", + "delete-state-text": "'{{stateName}}'?", + "show-details": "詳細を表示", + "hide-details": "詳細を隠す", + "select-state": "ターゲット状態を選択する", + "state-controller": "状態コントローラ" + }, + "datakey": { + "settings": "設定", + "advanced": "上級", + "label": "ラベル", + "color": "色", + "units": "値の隣に表示する特別なシンボル", + "decimals": "浮動小数点の後の桁数", + "data-generation-func": "データ生成関数", + "use-data-post-processing-func": "データ後処理機能を使用する", + "configuration": "データキー設定", + "timeseries": "タイムズ", + "attributes": "属性", + "alarm": "アラームフィールド", + "timeseries-required": "エンティティの時系列データが必要です。", + "timeseries-or-attributes-required": "エンティティのtimeseries /属性は必須です。", + "maximum-timeseries-or-attributes": "{ count, plural, 1 {1 timeseries/attribute is allowed.} other {# timeseries/attributes are allowed} }", + "alarm-fields-required": "アラームフィールドが必要です。", + "function-types": "関数型", + "function-types-required": "関数型が必要です。", + "maximum-function-types": "{ count, plural, 1 {1 function type is allowed.} other {# function types are allowed} }" + }, + "datasource": { + "type": "データソースタイプ", + "name": "名", + "add-datasource-prompt": "データソースを追加してください" + }, + "details": { + "edit-mode": "編集モード", + "toggle-edit-mode": "編集モードを切り替える" + }, + "device": { + "device": "デバイス", + "device-required": "デバイスが必要です。", + "devices": "デバイス", + "management": "端末管理", + "view-devices": "デバイスの表示", + "device-alias": "デバイスエイリアス", + "aliases": "デバイスエイリアス", + "no-alias-matching": "'{{alias}}'見つかりません。", + "no-aliases-found": "別名は見つかりませんでした。", + "no-key-matching": "'{{key}}'見つかりません。", + "no-keys-found": "キーが見つかりません。", + "create-new-alias": "新しいものを作成してください!", + "create-new-key": "新しいものを作成してください!", + "duplicate-alias-error": "'{{alias}}'
デバイスエイリアスは、ダッシュボード内で一意である必要があります。", + "configure-alias": "'{{alias}}'エイリアス", + "no-devices-matching": "'{{entity}}'発見されました。", + "alias": "エイリアス", + "alias-required": "デバイスエイリアスが必要です。", + "remove-alias": "デバイスエイリアスを削除する", + "add-alias": "デバイスエイリアスを追加する", + "name-starts-with": "デバイス名はで始まります", + "device-list": "デバイスリスト", + "use-device-name-filter": "フィルタを使用する", + "device-list-empty": "デバイスが選択されていません。", + "device-name-filter-required": "デバイス名フィルタが必要です。", + "device-name-filter-no-device-matched": "'{{device}}'発見されました。", + "add": "デバイスを追加", + "assign-to-customer": "顧客に割り当てる", + "assign-device-to-customer": "顧客にデバイスを割り当てる", + "assign-device-to-customer-text": "顧客に割り当てるデバイスを選択してください", + "make-public": "端末を公開する", + "make-private": "デバイスを非公開にする", + "no-devices-text": "デバイスが見つかりません", + "assign-to-customer-text": "デバイスを割り当てる顧客を選択してください", + "device-details": "デバイスの詳細", + "add-device-text": "新しいデバイスを追加する", + "credentials": "資格情報", + "manage-credentials": "資格情報を管理する", + "delete": "デバイスを削除する", + "assign-devices": "デバイスを割り当てる", + "assign-devices-text": "{ count, plural, 1 {1 device} other {# devices} }顧客に", + "delete-devices": "デバイスを削除する", + "unassign-from-customer": "顧客からの割り当て解除", + "unassign-devices": "デバイスの割り当てを解除する", + "unassign-devices-action-title": "{ count, plural, 1 {1 device} other {# devices} }顧客から", + "assign-new-device": "新しいデバイスを割り当てる", + "make-public-device-title": "'{{deviceName}}'パブリック?", + "make-public-device-text": "確認後、デバイスとそのすべてのデータは公開され、他のユーザーがアクセスできるようになります。", + "make-private-device-title": "'{{deviceName}}'プライベート?", + "make-private-device-text": "確認後、デバイスとそのすべてのデータは非公開になり、他人がアクセスできなくなります。", + "view-credentials": "資格情報を表示する", + "delete-device-title": "'{{deviceName}}'?", + "delete-device-text": "確認後、デバイスと関連するすべてのデータが回復不能になるので注意してください。", + "delete-devices-title": "{ count, plural, 1 {1 device} other {# devices} }?", + "delete-devices-action-title": "{ count, plural, 1 {1 device} other {# devices} }", + "delete-devices-text": "注意してください。確認後、選択したすべてのデバイスが削除され、関連するすべてのデータは回復不能になります。", + "unassign-device-title": "'{{deviceName}}'?", + "unassign-device-text": "確認の後、デバイスは割り当てが解除され、顧客がアクセスできなくなります。", + "unassign-device": "デバイスの割り当てを解除する", + "unassign-devices-title": "{ count, plural, 1 {1 device} other {# devices} }?", + "unassign-devices-text": "確認の後、選択されたすべてのデバイスが割り当て解除され、顧客がアクセスできなくなります。", + "device-credentials": "デバイス資格情報", + "credentials-type": "資格情報タイプ", + "access-token": "アクセストークン", + "access-token-required": "アクセストークンが必要です。", + "access-token-invalid": "アクセストークンの長さは、1〜20文字でなければなりません。", + "rsa-key": "RSA公開鍵", + "rsa-key-required": "RSA公開鍵が必要です。", + "secret": "秘密", + "secret-required": "秘密が必要です。", + "device-type": "デバイスタイプ", + "device-type-required": "デバイスタイプが必要です。", + "select-device-type": "デバイスタイプを選択", + "enter-device-type": "デバイスタイプを入力", + "any-device": "すべてのデバイス", + "no-device-types-matching": "'{{entitySubtype}}'発見されました。", + "device-type-list-empty": "選択されたデバイスタイプはありません。", + "device-types": "デバイスの種類", + "name": "名", + "name-required": "名前は必須です。", + "description": "説明", + "events": "イベント", + "details": "詳細", + "copyId": "デバイスIDをコピーする", + "copyAccessToken": "コピーアクセストークン", + "idCopiedMessage": "デバイスIDがクリップボードにコピーされました", + "accessTokenCopiedMessage": "デバイスアクセストークンがクリップボードにコピーされました", + "assignedToCustomer": "顧客に割り当てられた", + "unable-delete-device-alias-title": "デバイスエイリアスを削除できません", + "unable-delete-device-alias-text": "'{{deviceAlias}}'{{widgetsList}}", + "is-gateway": "ゲートウェイです", + "public": "パブリック", + "device-public": "デバイスは公開されています", + "select-device": "デバイスの選択" + }, + "dialog": { + "close": "ダイアログを閉じる" + }, + "error": { + "unable-to-connect": "サーバーに接続できません!インターネット接続を確認してください。", + "unhandled-error-code": "{{errorCode}}", + "unknown-error": "不明なエラー" + }, + "entity": { + "entity": "エンティティ", + "entities": "エンティティ", + "aliases": "エンティティエイリアス", + "entity-alias": "エンティティエイリアス", + "unable-delete-entity-alias-title": "エンティティエイリアスを削除できません", + "unable-delete-entity-alias-text": "'{{entityAlias}}'{{widgetsList}}", + "duplicate-alias-error": "'{{alias}}'
エンティティのエイリアスは、ダッシュボード内で一意である必要があります。", + "missing-entity-filter-error": "'{{alias}}'.", + "configure-alias": "'{{alias}}'エイリアス", + "alias": "エイリアス", + "alias-required": "エンティティエイリアスが必要です。", + "remove-alias": "エンティティエイリアスを削除する", + "add-alias": "エンティティエイリアスを追加する", + "entity-list": "エンティティリスト", + "entity-type": "エンティティタイプ", + "entity-types": "エンティティタイプ", + "entity-type-list": "エンティティタイプリスト", + "any-entity": "任意のエンティティ", + "enter-entity-type": "エンティティタイプを入力", + "no-entities-matching": "'{{entity}}'発見されました。", + "no-entity-types-matching": "'{{entityType}}'発見されました。", + "name-starts-with": "名前はで始まる", + "use-entity-name-filter": "フィルタを使用する", + "entity-list-empty": "選択されたエンティティはありません", + "entity-type-list-empty": "エンティティタイプは選択されていません。", + "entity-name-filter-required": "エンティティ名フィルタが必要です。", + "entity-name-filter-no-entity-matched": "'{{entity}}'発見されました。", + "all-subtypes": "すべて", + "select-entities": "エンティティの選択", + "no-aliases-found": "別名は見つかりませんでした。", + "no-alias-matching": "'{{alias}}'見つかりません。", + "create-new-alias": "新しいものを作成してください!", + "key": "キー", + "key-name": "キー名", + "no-keys-found": "キーが見つかりません。", + "no-key-matching": "'{{key}}'見つかりません。", + "create-new-key": "新しいものを作成してください!", + "type": "タイプ", + "type-required": "エンティティタイプが必要です。", + "type-device": "デバイス", + "type-devices": "デバイス", + "list-of-devices": "{ count, plural, 1 {One device} other {List of # devices} }", + "device-name-starts-with": "'{{prefix}}'", + "type-asset": "資産", + "type-assets": "資産", + "list-of-assets": "{ count, plural, 1 {One asset} other {List of # assets} }", + "asset-name-starts-with": "'{{prefix}}'", + "type-rule": "ルール", + "type-rules": "ルール", + "list-of-rules": "{ count, plural, 1 {One rule} other {List of # rules} }", + "rule-name-starts-with": "'{{prefix}}'", + "type-plugin": "プラグイン", + "type-plugins": "プラグイン", + "list-of-plugins": "{ count, plural, 1 {One plugin} other {List of # plugins} }", + "plugin-name-starts-with": "'{{prefix}}'", + "type-tenant": "テナント", + "type-tenants": "テナント", + "list-of-tenants": "{ count, plural, 1 {One tenant} other {List of # tenants} }", + "tenant-name-starts-with": "'{{prefix}}'", + "type-customer": "顧客", + "type-customers": "顧客", + "list-of-customers": "{ count, plural, 1 {One customer} other {List of # customers} }", + "customer-name-starts-with": "'{{prefix}}'", + "type-user": "ユーザー", + "type-users": "ユーザー", + "list-of-users": "{ count, plural, 1 {One user} other {List of # users} }", + "user-name-starts-with": "'{{prefix}}'", + "type-dashboard": "ダッシュボード", + "type-dashboards": "ダッシュボード", + "list-of-dashboards": "{ count, plural, 1 {One dashboard} other {List of # dashboards} }", + "dashboard-name-starts-with": "'{{prefix}}'", + "type-alarm": "警報", + "type-alarms": "アラーム", + "list-of-alarms": "{ count, plural, 1 {One alarms} other {List of # alarms} }", + "alarm-name-starts-with": "'{{prefix}}'", + "type-rulechain": "ルールチェーン", + "type-rulechains": "ルールチェーン", + "list-of-rulechains": "{ count, plural, 1 {One rule chain} other {List of # rule chains} }", + "rulechain-name-starts-with": "'{{prefix}}'", + "type-rulenode": "ルールノード", + "type-rulenodes": "ルールノード", + "list-of-rulenodes": "{ count, plural, 1 {One rule node} other {List of # rule nodes} }", + "rulenode-name-starts-with": "'{{prefix}}'", + "type-current-customer": "現在の顧客", + "search": "検索エンティティ", + "selected-entities": "{ count, plural, 1 {1 entity} other {# entities} }選択された", + "entity-name": "エンティティ名", + "details": "エンティティの詳細", + "no-entities-prompt": "エンティティが見つかりません", + "no-data": "表示するデータがありません" + }, + "event": { + "event-type": "イベントタイプ", + "type-error": "エラー", + "type-lc-event": "ライフサイクルイベント", + "type-stats": "統計", + "type-debug-rule-node": "デバッグ", + "type-debug-rule-chain": "デバッグ", + "no-events-prompt": "イベントは見つかりませんでした", + "error": "エラー", + "alarm": "警報", + "event-time": "イベント時間", + "server": "サーバ", + "body": "体", + "method": "方法", + "type": "タイプ", + "entity": "エンティティ", + "message-id": "メッセージID", + "message-type": "メッセージタイプ", + "data-type": "データ・タイプ", + "relation-type": "関係タイプ", + "metadata": "メタデータ", + "data": "データ", + "event": "イベント", + "status": "状態", + "success": "成功", + "failed": "失敗", + "messages-processed": "処理されたメッセージ", + "errors-occurred": "エラーが発生しました" + }, + "extension": { + "extensions": "拡張機能", + "selected-extensions": "{ count, plural, 1 {1 extension} other {# extensions} }選択された", + "type": "タイプ", + "key": "キー", + "value": "値", + "id": "イド", + "extension-id": "内線番号", + "extension-type": "拡張タイプ", + "transformer-json": "JSON *", + "unique-id-required": "現在の拡張IDは既に存在します。", + "delete": "拡張子を削除", + "add": "内線番号を追加", + "edit": "拡張機能を編集する", + "delete-extension-title": "'{{extensionId}}'?", + "delete-extension-text": "確認後、拡張子と関連するすべてのデータが回復不能になることに注意してください。", + "delete-extensions-title": "{ count, plural, 1 {1 extension} other {# extensions} }?", + "delete-extensions-text": "注意してください。確認後、選択したすべての内線番号が削除されます。", + "converters": "コンバーター", + "converter-id": "コンバーターID", + "configuration": "構成", + "converter-configurations": "コンバータ構成", + "token": "セキュリティトークン", + "add-converter": "コンバータを追加する", + "add-config": "コンバータ設定を追加する", + "device-name-expression": "デバイス名式", + "device-type-expression": "デバイスタイプの式", + "custom": "カスタム", + "to-double": "ダブル", + "transformer": "トランス", + "json-required": "トランスフォーマーjsonが必要です。", + "json-parse": "変圧器jsonを解析できません。", + "attributes": "属性", + "add-attribute": "属性を追加する", + "add-map": "マッピング要素を追加する", + "timeseries": "タイムズ", + "add-timeseries": "時系列を追加する", + "field-required": "フィールドは必須項目です", + "brokers": "ブローカー", + "add-broker": "ブローカーを追加", + "host": "ホスト", + "port": "ポート", + "port-range": "ポートは1〜65535の範囲内にある必要があります。", + "ssl": "SSL", + "credentials": "資格情報", + "username": "ユーザー名", + "password": "パスワード", + "retry-interval": "ミリ秒単位の再試行間隔", + "anonymous": "匿名", + "basic": "ベーシック", + "pem": "PEM", + "ca-cert": "CA証明書ファイル*", + "private-key": "秘密鍵ファイル*", + "cert": "証明書ファイル*", + "no-file": "ファイルが選択されていません。", + "drop-file": "ファイルをドロップするか、クリックしてアップロードするファイルを選択します。", + "mapping": "マッピング", + "topic-filter": "トピックフィルタ", + "converter-type": "コンバータタイプ", + "converter-json": "Json", + "json-name-expression": "デバイス名json式", + "topic-name-expression": "デバイス名トピック表現", + "json-type-expression": "デバイスタイプjson式", + "topic-type-expression": "デバイスタイプトピック表現", + "attribute-key-expression": "属性キー式", + "attr-json-key-expression": "属性キーjson式", + "attr-topic-key-expression": "属性キートピック式", + "request-id-expression": "要求ID式", + "request-id-json-expression": "リクエストID json式", + "request-id-topic-expression": "リクエストIDトピック表現", + "response-topic-expression": "応答トピック表現", + "value-expression": "値式", + "topic": "トピック", + "timeout": "タイムアウト(ミリ秒)", + "converter-json-required": "コンバータjsonが必要です。", + "converter-json-parse": "コンバータjsonを解析できません。", + "filter-expression": "フィルタ式", + "connect-requests": "接続要求", + "add-connect-request": "接続要求を追加", + "disconnect-requests": "切断要求", + "add-disconnect-request": "切断リクエストを追加する", + "attribute-requests": "属性要求", + "add-attribute-request": "属性要求を追加する", + "attribute-updates": "属性の更新", + "add-attribute-update": "属性の更新を追加する", + "server-side-rpc": "サーバー側RPC", + "add-server-side-rpc-request": "サーバー側RPC要求を追加する", + "device-name-filter": "デバイス名フィルタ", + "attribute-filter": "属性フィルタ", + "method-filter": "方法フィルター", + "request-topic-expression": "トピック表現を要求する", + "response-timeout": "応答タイムアウト(ミリ秒)", + "topic-expression": "トピック表現", + "client-scope": "クライアントスコープ", + "add-device": "デバイスを追加", + "opc-server": "サーバー", + "opc-add-server": "サーバーを追加", + "opc-add-server-prompt": "サーバーを追加してください", + "opc-application-name": "アプリケーション名", + "opc-application-uri": "アプリケーションURI", + "opc-scan-period-in-seconds": "スキャン時間(秒)", + "opc-security": "セキュリティ", + "opc-identity": "身元", + "opc-keystore": "キーストア", + "opc-type": "タイプ", + "opc-keystore-type": "タイプ", + "opc-keystore-location": "ロケーション*", + "opc-keystore-password": "パスワード", + "opc-keystore-alias": "エイリアス", + "opc-keystore-key-password": "キーのパスワード", + "opc-device-node-pattern": "デバイスノードパターン", + "opc-device-name-pattern": "デバイス名パターン", + "modbus-server": "サーバー/スレーブ", + "modbus-add-server": "サーバー/スレーブを追加する", + "modbus-add-server-prompt": "サーバー/スレーブを追加してください", + "modbus-transport": "輸送", + "modbus-port-name": "シリアルポート名", + "modbus-encoding": "エンコーディング", + "modbus-parity": "パリティ", + "modbus-baudrate": "ボーレート", + "modbus-databits": "データビット", + "modbus-stopbits": "ストップビット", + "modbus-databits-range": "データビットは7〜8の範囲内にある必要があります。", + "modbus-stopbits-range": "ストップビットは1〜2の範囲内でなければなりません。", + "modbus-unit-id": "ユニットID", + "modbus-unit-id-range": "ユニットIDは1〜247の範囲で指定してください。", + "modbus-device-name": "装置名", + "modbus-poll-period": "投票期間(ミリ秒)", + "modbus-attributes-poll-period": "属性のポーリング期間(ミリ秒)", + "modbus-timeseries-poll-period": "時系列ポーリング期間(ミリ秒)", + "modbus-poll-period-range": "投票期間は正の値でなければなりません。", + "modbus-tag": "タグ", + "modbus-function": "関数", + "modbus-register-address": "登録アドレス", + "modbus-register-address-range": "レジスタのアドレスは0〜65535の範囲内である必要があります。", + "modbus-register-bit-index": "ビットインデックス", + "modbus-register-bit-index-range": "ビットインデックスは0〜15の範囲内である必要があります。", + "modbus-register-count": "レジスタ数", + "modbus-register-count-range": "レジスタ数は正の値でなければなりません。", + "modbus-byte-order": "バイト順", + "sync": { + "status": "状態", + "sync": "同期", + "not-sync": "同期しない", + "last-sync-time": "前回の同期時間", + "not-available": "利用不可" + }, + "export-extensions-configuration": "エクステンション設定のエクスポート", + "import-extensions-configuration": "エクステンション設定のインポート", + "import-extensions": "拡張機能のインポート", + "import-extension": "インポート拡張", + "export-extension": "輸出延長", + "file": "拡張機能ファイル", + "invalid-file-error": "無効な拡張ファイル" + }, + "fullscreen": { + "expand": "フルスクリーンに拡大", + "exit": "全画面表示を終了", + "toggle": "フルスクリーンモードを切り替える", + "fullscreen": "全画面表示" + }, + "function": { + "function": "関数" + }, + "grid": { + "delete-item-title": "このアイテムを削除してもよろしいですか?", + "delete-item-text": "注意してください。確認後、この項目と関連するすべてのデータは回復不能になります。", + "delete-items-title": "{ count, plural, 1 {1 item} other {# items} }?", + "delete-items-action-title": "{ count, plural, 1 {1 item} other {# items} }", + "delete-items-text": "注意してください。確認後、選択したすべてのアイテムが削除され、関連するすべてのデータは回復不能になります。", + "add-item-text": "新しいアイテムを追加", + "no-items-text": "項目は見つかりませんでした", + "item-details": "商品詳細", + "delete-item": "アイテムを削除", + "delete-items": "アイテムを削除する", + "scroll-to-top": "トップにスクロールします" + }, + "help": { + "goto-help-page": "ヘルプページに行く" + }, + "home": { + "home": "ホーム", + "profile": "プロフィール", + "logout": "ログアウト", + "menu": "メニュー", + "avatar": "アバター", + "open-user-menu": "ユーザーメニューを開く" + }, + "import": { + "no-file": "ファイルが選択されていません", + "drop-file": "JSONファイルをドロップするか、アップロードするファイルをクリックして選択します。" + }, + "item": { + "selected": "選択された" + }, + "js-func": { + "no-return-error": "関数は値を返す必要があります!", + "return-type-mismatch": "'{{type}}'タイプ!", + "tidy": "きちんとした" + }, + "key-val": { + "key": "キー", + "value": "値", + "remove-entry": "エントリを削除", + "add-entry": "エントリを追加", + "no-data": "エントリなし" + }, + "layout": { + "layout": "レイアウト", + "manage": "レイアウトの管理", + "settings": "レイアウト設定", + "color": "色", + "main": "メイン", + "right": "右", + "select": "ターゲットレイアウトを選択" + }, + "legend": { + "position": "伝説の位置", + "show-max": "最大値を表示", + "show-min": "最小値を表示する", + "show-avg": "平均値を表示", + "show-total": "合計値を表示", + "settings": "凡例の設定", + "min": "分", + "max": "最大", + "avg": "平均", + "total": "合計" + }, + "login": { + "login": "ログイン", + "request-password-reset": "リクエストパスワードのリセット", + "reset-password": "パスワードを再設定する", + "create-password": "パスワードの作成", + "passwords-mismatch-error": "入力されたパスワードは同じでなければなりません!", + "password-again": "パスワードをもう一度", + "sign-in": "サインインしてください", + "username": "ユーザー名(電子メール)", + "remember-me": "私を覚えてますか", + "forgot-password": "パスワードをお忘れですか?", + "password-reset": "パスワードのリセット", + "new-password": "新しいパスワード", + "new-password-again": "新しいパスワードを再入力", + "password-link-sent-message": "パスワードリセットリンクが正常に送信されました!", + "email": "Eメール" + }, + "position": { + "top": "上", + "bottom": "ボトム", + "left": "左", + "right": "右" + }, + "profile": { + "profile": "プロフィール", + "change-password": "パスワードを変更する", + "current-password": "現在のパスワード" + }, + "relation": { + "relations": "関係", + "direction": "方向", + "search-direction": { + "FROM": "から", + "TO": "に" + }, + "direction-type": { + "FROM": "から", + "TO": "に" + }, + "from-relations": "アウトバウンド関係", + "to-relations": "インバウンド関係", + "selected-relations": "{ count, plural, 1 {1 relation} other {# relations} }選択された", + "type": "タイプ", + "to-entity-type": "エンティティタイプへ", + "to-entity-name": "エンティティ名に", + "from-entity-type": "エンティティタイプから", + "from-entity-name": "エンティティ名から", + "to-entity": "実体へ", + "from-entity": "エンティティから", + "delete": "関係を削除する", + "relation-type": "関係タイプ", + "relation-type-required": "関係タイプが必要です。", + "any-relation-type": "いかなるタイプ", + "add": "関係を追加する", + "edit": "関係を編集する", + "delete-to-relation-title": "'{{entityName}}'?", + "delete-to-relation-text": "'{{entityName}}'現在のエンティティとは無関係です。", + "delete-to-relations-title": "{ count, plural, 1 {1 relation} other {# relations} }?", + "delete-to-relations-text": "注意してください。確認後、選択されたリレーションはすべて削除され、対応するエンティティは現在のエンティティとは無関係になります。", + "delete-from-relation-title": "'{{entityName}}'?", + "delete-from-relation-text": "'{{entityName}}'.", + "delete-from-relations-title": "{ count, plural, 1 {1 relation} other {# relations} }?", + "delete-from-relations-text": "注意してください。確認後、選択されたリレーションはすべて削除され、現在のエンティティは対応するエンティティとは無関係になります。", + "remove-relation-filter": "関係フィルタを削除する", + "add-relation-filter": "関係フィルタを追加する", + "any-relation": "関係", + "relation-filters": "関係フィルタ", + "additional-info": "追加情報(JSON)", + "invalid-additional-info": "追加情報jsonを解析できません。" + }, + "rulechain": { + "rulechain": "ルールチェーン", + "rulechains": "ルールチェーン", + "root": "ルート", + "delete": "ルールチェーンの削除", + "name": "名", + "name-required": "名前は必須です。", + "description": "説明", + "add": "ルールチェーンを追加する", + "set-root": "ルールチェーンのルートを作る", + "set-root-rulechain-title": "'{{ruleChainName}}'ルート?", + "set-root-rulechain-text": "確認後、ルールチェーンはルートになり、すべての受信トランスポートメッセージを処理します。", + "delete-rulechain-title": "'{{ruleChainName}}'?", + "delete-rulechain-text": "確認後、ルールチェーンと関連するすべてのデータが回復不能になるので注意してください。", + "delete-rulechains-title": "{ count, plural, 1 {1 rule chain} other {# rule chains} }?", + "delete-rulechains-action-title": "{ count, plural, 1 {1 rule chain} other {# rule chains} }", + "delete-rulechains-text": "確認後、選択したすべてのルールチェーンが削除され、関連するすべてのデータが回復不能になるので注意してください。", + "add-rulechain-text": "新しいルールチェーンを追加する", + "no-rulechains-text": "ルールチェーンが見つかりません", + "rulechain-details": "ルールチェーンの詳細", + "details": "詳細", + "events": "イベント", + "system": "システム", + "import": "ルールチェーンのインポート", + "export": "ルールチェーンのエクスポート", + "export-failed-error": "{{error}}", + "create-new-rulechain": "新しいルールチェーンを作成する", + "rulechain-file": "ルールチェーンファイル", + "invalid-rulechain-file-error": "ルールチェーンをインポートできません:ルールチェーンのデータ構造が無効です。", + "copyId": "ルールチェーンIDのコピー", + "idCopiedMessage": "ルールチェーンIDがクリップボードにコピーされました", + "select-rulechain": "ルールチェーンの選択", + "no-rulechains-matching": "'{{entity}}'発見されました。", + "rulechain-required": "ルールチェーンが必要です", + "management": "ルール管理", + "debug-mode": "デバッグモード" + }, + "rulenode": { + "details": "詳細", + "events": "イベント", + "search": "検索ノード", + "open-node-library": "オープンノードライブラリ", + "add": "ルールノードを追加する", + "name": "名", + "name-required": "名前は必須です。", + "type": "タイプ", + "description": "説明", + "delete": "ルールノードを削除", + "select-all-objects": "すべてのノードと接続を選択する", + "deselect-all-objects": "すべてのノードと接続の選択を解除する", + "delete-selected-objects": "選択したノードと接続を削除する", + "delete-selected": "選択を削除します", + "select-all": "すべて選択", + "copy-selected": "選択したコピー", + "deselect-all": "すべての選択を解除", + "rulenode-details": "ルールノードの詳細", + "debug-mode": "デバッグモード", + "configuration": "構成", + "link": "リンク", + "link-details": "ルールノードのリンクの詳細", + "add-link": "リンクを追加", + "link-label": "リンクラベル", + "link-label-required": "リンクラベルが必要です。", + "custom-link-label": "カスタムリンクラベル", + "custom-link-label-required": "カスタムリンクラベルが必要です。", + "link-labels": "リンクラベル", + "link-labels-required": "リンクラベルが必要です。", + "no-link-labels-found": "リンクラベルが見つかりません", + "no-link-label-matching": "'{{label}}'見つかりません。", + "create-new-link-label": "新しいものを作成してください!", + "type-filter": "フィルタ", + "type-filter-details": "設定された条件で着信メッセージをフィルタリングする", + "type-enrichment": "豊かな", + "type-enrichment-details": "メッセージメタデータに追加情報を追加する", + "type-transformation": "変換", + "type-transformation-details": "メッセージペイロードとメタデータの変更", + "type-action": "アクション", + "type-action-details": "特別なアクションを実行する", + "type-external": "外部", + "type-external-details": "外部システムとの相互作用", + "type-rule-chain": "ルールチェーン", + "type-rule-chain-details": "受信したメッセージを指定したルールチェーンに転送する", + "type-input": "入力", + "type-input-details": "ルールチェーンの論理入力、次の関連ルールノードへの着信メッセージの転送", + "type-unknown": "未知の", + "type-unknown-details": "未解決のルールノード", + "directive-is-not-loaded": "'{{directiveName}}'利用できません。", + "ui-resources-load-error": "構成UIリソースをロードできませんでした。", + "invalid-target-rulechain": "ターゲットルールチェーンを解決できません!", + "test-script-function": "テストスクリプト機能", + "message": "メッセージ", + "message-type": "メッセージタイプ", + "select-message-type": "メッセージタイプを選択", + "message-type-required": "メッセージタイプは必須です", + "metadata": "メタデータ", + "metadata-required": "メタデータのエントリを空にすることはできません。", + "output": "出力", + "test": "テスト", + "help": "助けて" + }, + "tenant": { + "tenant": "テナント", + "tenants": "テナント", + "management": "テナント管理", + "add": "テナントを追加", + "admins": "管理者", + "manage-tenant-admins": "テナント管理者の管理", + "delete": "テナントの削除", + "add-tenant-text": "新しいテナントを追加する", + "no-tenants-text": "テナントは見つかりませんでした", + "tenant-details": "テナントの詳細", + "delete-tenant-title": "'{{tenantTitle}}'?", + "delete-tenant-text": "確認後、テナントと関連するすべてのデータが回復不能になるので注意してください。", + "delete-tenants-title": "{ count, plural, 1 {1 tenant} other {# tenants} }?", + "delete-tenants-action-title": "{ count, plural, 1 {1 tenant} other {# tenants} }", + "delete-tenants-text": "注意してください。確認後、選択されたすべてのテナントが削除され、関連するすべてのデータは回復不能になります。", + "title": "タイトル", + "title-required": "タイトルは必須です。", + "description": "説明", + "details": "詳細", + "events": "イベント", + "copyId": "テナントIDをコピーする", + "idCopiedMessage": "テナントIDがクリップボードにコピーされました", + "select-tenant": "テナントを選択", + "no-tenants-matching": "'{{entity}}'発見されました。", + "tenant-required": "テナントが必要です" + }, + "timeinterval": { + "seconds-interval": "{ seconds, plural, 1 {1 second} other {# seconds} }", + "minutes-interval": "{ minutes, plural, 1 {1 minute} other {# minutes} }", + "hours-interval": "{ hours, plural, 1 {1 hour} other {# hours} }", + "days-interval": "{ days, plural, 1 {1 day} other {# days} }", + "days": "日々", + "hours": "時間", + "minutes": "分", + "seconds": "秒", + "advanced": "上級" + }, + "timewindow": { + "days": "{ days, plural, 1 { day } other {# days } }", + "hours": "{ hours, plural, 0 { hour } 1 {1 hour } other {# hours } }", + "minutes": "{ minutes, plural, 0 { minute } 1 {1 minute } other {# minutes } }", + "seconds": "{ seconds, plural, 0 { second } 1 {1 second } other {# seconds } }", + "realtime": "リアルタイム", + "history": "歴史", + "last-prefix": "最終", + "period": "{{ startTime }}{{ endTime }}", + "edit": "タイムウィンドウを編集", + "date-range": "期間", + "last": "最終", + "time-period": "期間" + }, + "user": { + "user": "ユーザー", + "users": "ユーザー", + "customer-users": "顧客ユーザー", + "tenant-admins": "テナント管理者", + "sys-admin": "システム管理者", + "tenant-admin": "テナント管理者", + "customer": "顧客", + "anonymous": "匿名", + "add": "ユーザーを追加する", + "delete": "ユーザーを削除", + "add-user-text": "新しいユーザーを追加", + "no-users-text": "ユーザが見つかりませんでした", + "user-details": "ユーザーの詳細", + "delete-user-title": "'{{userEmail}}'?", + "delete-user-text": "確認後、ユーザーと関連するすべてのデータが回復不能になるので注意してください。", + "delete-users-title": "{ count, plural, 1 {1 user} other {# users} }?", + "delete-users-action-title": "{ count, plural, 1 {1 user} other {# users} }", + "delete-users-text": "注意してください。確認後、選択したすべてのユーザーが削除され、関連するすべてのデータは回復不能になります。", + "activation-email-sent-message": "アクティベーション電子メールが正常に送信されました!", + "resend-activation": "アクティブ化を再送", + "email": "Eメール", + "email-required": "電子メールが必要です。", + "invalid-email-format": "メールフォーマットが無効です。", + "first-name": "ファーストネーム", + "last-name": "苗字", + "description": "説明", + "default-dashboard": "デフォルトのダッシュボード", + "always-fullscreen": "常に全画面表示", + "select-user": "ユーザーを選択", + "no-users-matching": "'{{entity}}'発見されました。", + "user-required": "ユーザーは必須です", + "activation-method": "起動方法", + "display-activation-link": "アクティブ化リンクを表示する", + "send-activation-mail": "アクティベーションメールを送信する", + "activation-link": "ユーザーアクティベーションリンク", + "activation-link-text": "activation link :", + "copy-activation-link": "アクティブ化リンクをコピーする", + "activation-link-copied-message": "ユーザーのアクティベーションリンクがクリップボードにコピーされました", + "details": "詳細" + }, + "value": { + "type": "値のタイプ", + "string": "文字列", + "string-value": "文字列値", + "integer": "整数", + "integer-value": "整数値", + "invalid-integer-value": "整数値が無効です", + "double": "ダブル", + "double-value": "二重価値", + "boolean": "ブール", + "boolean-value": "ブール値", + "false": "偽", + "true": "真", + "long": "長いです" + }, + "widget": { + "widget-library": "ウィジェットライブラリ", + "widget-bundle": "ウィジェットバンドル", + "select-widgets-bundle": "ウィジェットのバンドルを選択", + "management": "ウィジェット管理", + "editor": "ウィジェットエディタ", + "widget-type-not-found": "ウィジェットの設定を読み込む際に問題が発生しました。
おそらく関連付けられているウィジェットのタイプが削除されています。", + "widget-type-load-error": "次のエラーのためにウィジェットが読み込まれませんでした:", + "remove": "ウィジェットを削除", + "edit": "ウィジェットの編集", + "remove-widget-title": "'{{widgetTitle}}'?", + "remove-widget-text": "確認後、ウィジェットと関連するすべてのデータは回復不能になります。", + "timeseries": "時系列", + "search-data": "検索データ", + "no-data-found": "何もデータが見つかりませんでした", + "latest-values": "最新の値", + "rpc": "コントロールウィジェット", + "alarm": "アラームウィジェット", + "static": "静的ウィジェット", + "select-widget-type": "ウィジェットタイプを選択", + "missing-widget-title-error": "ウィジェットのタイトルを指定する必要があります!", + "widget-saved": "ウィジェットが保存されました", + "unable-to-save-widget-error": "ウィジェットを保存できません!ウィジェットにエラーがあります!", + "save": "ウィジェットを保存", + "saveAs": "ウィジェットを次のように保存する", + "save-widget-type-as": "ウィジェットタイプを次のように保存します", + "save-widget-type-as-text": "新しいウィジェットのタイトルを入力したり、ターゲットウィジェットのバンドルを選択してください", + "toggle-fullscreen": "フルスクリーン切り替え", + "run": "ウィジェットを実行する", + "title": "ウィジェットのタイトル", + "title-required": "ウィジェットのタイトルが必要です。", + "type": "ウィジェットタイプ", + "resources": "リソース", + "resource-url": "JavaScript / CSS URL", + "remove-resource": "リソースを削除する", + "add-resource": "リソースを追加", + "html": "HTML", + "tidy": "きちんとした", + "css": "CSS", + "settings-schema": "設定スキーマ", + "datakey-settings-schema": "データキー設定のスキーマ", + "javascript": "Javascript", + "remove-widget-type-title": "'{{widgetName}}'?", + "remove-widget-type-text": "確認後、ウィジェットのタイプと関連するすべてのデータは回復不能になります。", + "remove-widget-type": "ウィジェットタイプを削除", + "add-widget-type": "新しいウィジェットタイプを追加する", + "widget-type-load-failed-error": "ウィジェットタイプの読み込みに失敗しました!", + "widget-template-load-failed-error": "ウィジェットテンプレートを読み込めませんでした!", + "add": "ウィジェットを追加", + "undo": "ウィジェットの変更を元に戻す", + "export": "ウィジェットの書き出し" + }, + "widget-action": { + "header-button": "ウィジェットのヘッダーボタン", + "open-dashboard-state": "新しいダッシュボードの状態に移動する", + "update-dashboard-state": "現在のダッシュボードの状態を更新する", + "open-dashboard": "他のダッシュボードに移動する", + "custom": "カスタムアクション", + "target-dashboard-state": "ターゲットダッシュボードの状態", + "target-dashboard-state-required": "ターゲットダッシュボードの状態が必要です", + "set-entity-from-widget": "エンティティをウィジェットから設定する", + "target-dashboard": "ターゲットダッシュボード", + "open-right-layout": "右ダッシュボードレイアウトを開く(モバイルビュー)" + }, + "widgets-bundle": { + "current": "現在のバンドル", + "widgets-bundles": "ウィジェットバンドル", + "add": "ウィジェットのバンドルを追加", + "delete": "ウィジェットのバンドルを削除する", + "title": "タイトル", + "title-required": "タイトルは必須です。", + "add-widgets-bundle-text": "新しいウィジェットのバンドルを追加する", + "no-widgets-bundles-text": "ウィジェットバンドルが見つかりません", + "empty": "ウィジェットのバンドルが空です", + "details": "詳細", + "widgets-bundle-details": "ウィジェットのバンドルの詳細", + "delete-widgets-bundle-title": "'{{widgetsBundleTitle}}'?", + "delete-widgets-bundle-text": "確認後、ウィジェットはバンドルされ、関連するすべてのデータは回復不能になります。", + "delete-widgets-bundles-title": "{ count, plural, 1 {1 widgets bundle} other {# widgets bundles} }?", + "delete-widgets-bundles-action-title": "{ count, plural, 1 {1 widgets bundle} other {# widgets bundles} }", + "delete-widgets-bundles-text": "確認後、選択したすべてのウィジェットバンドルは削除され、関連するすべてのデータは回復不能になります。", + "no-widgets-bundles-matching": "'{{widgetsBundle}}'発見されました。", + "widgets-bundle-required": "ウィジェットバンドルが必要です。", + "system": "システム", + "import": "インポートウィジェットバンドル", + "export": "ウィジェットのエクスポートバンドル", + "export-failed-error": "{{error}}", + "create-new-widgets-bundle": "新しいウィジェットバンドルを作成する", + "widgets-bundle-file": "ウィジェットのバンドルファイル", + "invalid-widgets-bundle-file-error": "ウィジェットをインポートできません。bundle:データ構造が無効です。" + }, + "widget-config": { + "data": "データ", + "settings": "設定", + "advanced": "上級", + "title": "タイトル", + "general-settings": "一般設定", + "display-title": "タイトルを表示", + "drop-shadow": "影を落とす", + "enable-fullscreen": "フルスクリーンを有効にする", + "background-color": "背景色", + "text-color": "テキストの色", + "padding": "パディング", + "margin": "マージン", + "widget-style": "ウィジェットスタイル", + "title-style": "タイトルスタイル", + "mobile-mode-settings": "モバイルモードの設定", + "order": "注文", + "height": "高さ", + "units": "値の隣に表示する特別なシンボル", + "decimals": "浮動小数点の後の桁数", + "timewindow": "タイムウィンドウ", + "use-dashboard-timewindow": "ダッシュボードのタイムウィンドウを使用する", + "display-legend": "伝説を表示", + "datasources": "データソース", + "maximum-datasources": "{ count, plural, 1 {1 datasource is allowed.} other {# datasources are allowed} }", + "datasource-type": "タイプ", + "datasource-parameters": "パラメーター", + "remove-datasource": "データソースを削除", + "add-datasource": "データソースを追加", + "target-device": "ターゲットデバイス", + "alarm-source": "アラームソース", + "actions": "行動", + "action": "アクション", + "add-action": "アクションを追加", + "search-actions": "検索アクション", + "action-source": "アクションソース", + "action-source-required": "アクションソースが必要です。", + "action-name": "名", + "action-name-required": "アクション名は必須です。", + "action-name-not-unique": "同じ名前の別のアクションがすでに存在します。
アクション名は、同じアクションソース内で一意である必要があります。", + "action-icon": "アイコン", + "action-type": "タイプ", + "action-type-required": "アクションタイプが必要です。", + "edit-action": "アクションの編集", + "delete-action": "アクションの削除", + "delete-action-title": "ウィジェットアクションを削除する", + "delete-action-text": "'{{actionName}}'?" + }, + "widget-type": { + "import": "インポートウィジェットタイプ", + "export": "ウィジェットのタイプをエクスポートする", + "export-failed-error": "{{error}}", + "create-new-widget-type": "新しいウィジェットタイプを作成する", + "widget-type-file": "ウィジェットタイプファイル", + "invalid-widget-type-file-error": "ウィジェットタイプをインポートできません:ウィジェットタイプのデータ構造が無効です。" + }, + "widgets": { + "date-range-navigator": { + "localizationMap": { + "Sun": "日", + "Mon": "月", + "Tue": "火", + "Wed": "水", + "Thu": "木", + "Fri": "金", + "Sat": "土", + "Jan": "1月", + "Feb": "2月", + "Mar": "3月", + "Apr": "4月", + "May": "5月", + "Jun": "6月", + "Jul": "7月", + "Aug": "8月", + "Sep": "9月", + "Oct": "10月", + "Nov": "11月", + "Dec": "12月", + "January": "1月", + "February": "2月", + "March": "行進", + "April": "4月", + "June": "六月", + "July": "7月", + "August": "8月", + "September": "9月", + "October": "10月", + "November": "11月", + "December": "12月", + "Custom Date Range": "カスタム期間", + "Date Range Template": "日付範囲テンプレート", + "Today": "今日", + "Yesterday": "昨日", + "This Week": "今週", + "Last Week": "先週", + "This Month": "今月", + "Last Month": "先月", + "Year": "年", + "This Year": "今年", + "Last Year": "昨年", + "Date picker": "日付ピッカー", + "Hour": "時", + "Day": "日", + "Week": "週間", + "2 weeks": "2週間", + "Month": "月", + "3 months": "3ヶ月", + "6 months": "6ヵ月", + "Custom interval": "カスタム間隔", + "Interval": "間隔", + "Step size": "刻み幅", + "Ok": "Ok" + } + } + }, + "icon": { + "icon": "アイコン", + "select-icon": "選択アイコン", + "material-icons": "マテリアルアイコン", + "show-all": "すべてのアイコンを表示する" + }, + "custom": { + "widget-action": { + "action-cell-button": "アクションセルボタン", + "row-click": "行のクリック", + "polygon-click": "ポリゴンクリック", + "marker-click": "マーカークリック", + "tooltip-tag-action": "ツールチップのタグアクション" + } + }, + "language": { + "language": "言語", + "locales": { + "de_DE": "ドイツ語", + "fr_FR": "フランス語", + "en_US": "英語", + "ko_KR": "韓国語", + "it_IT": "イタリアの", + "zh_CN": "中国語", + "ru_RU": "ロシア", + "es_ES": "スペイン語", + "ja_JA": "日本語", + "tr_TR": "トルコ語", + "fa_IR": "ペルシャ語", + "uk_UA": "ウクライナ語", + "cs_CZ": "チェコ語で" + } + } +} \ No newline at end of file diff --git a/ui-ngx/src/assets/locale/locale.constant-ko_KR.json b/ui-ngx/src/assets/locale/locale.constant-ko_KR.json new file mode 100644 index 0000000000..fde60c77bb --- /dev/null +++ b/ui-ngx/src/assets/locale/locale.constant-ko_KR.json @@ -0,0 +1,1403 @@ +{ + "access": { + "unauthorized": "권한 없음.", + "unauthorized-access": "허가되지 않은 접근", + "unauthorized-access-text": "이 리소스에 접근하려면 로그인해야 합니다!", + "access-forbidden": "접근 금지", + "access-forbidden-text": "접근 권한이 없습니다.!
만일 이 페이지에 계속 접근하려면 다른 사용자로 로그인 하세요.", + "refresh-token-expired": "세션이 만료되었습니다.", + "refresh-token-failed": "세션을 새로 고칠 수 없습니다." + }, + "action": { + "activate": "활설화", + "suspend": "비활성화", + "save": "저장", + "saveAs": "다른 이름으로 저장", + "cancel": "취소", + "ok": "확인", + "delete": "삭제", + "add": "추가", + "yes": "네", + "no": "아니오", + "update": "업데이트", + "remove": "제거", + "search": "검색", + "clear-search": "Clear search", + "assign": "할당", + "unassign": "비할당", + "share": "Share", + "make-private": "Make private", + "apply": "적용", + "apply-changes": "변경사항 적용", + "edit-mode": "수정 모드", + "enter-edit-mode": "수정 모드 진입", + "decline-changes": "변경사항 포기", + "close": "닫기", + "back": "뒤로", + "run": "실행", + "sign-in": "로그인!", + "edit": "수정", + "view": "보기", + "create": "만들기", + "drag": "끌기", + "refresh": "새로고침", + "undo": "취소", + "copy": "복사", + "paste": "붙여넣기", + "copy-reference": "Copy reference", + "paste-reference": "Paste reference", + "import": "가져오기", + "export": "내보내기", + "share-via": "Share via {{provider}}" + }, + "aggregation": { + "aggregation": "집합", + "function": "데이터 집합 함수", + "limit": "최대 값", + "group-interval": "그룹 간격", + "min": "최소", + "max": "최대", + "avg": "평균", + "sum": "합계", + "count": "숫자", + "none": "없음" + }, + "admin": { + "general": "일반", + "general-settings": "일반 설정", + "outgoing-mail": "메일 전송", + "outgoing-mail-settings": "메일 전송 설정", + "system-settings": "시스템 설정", + "test-mail-sent": "테스트 메일이 성공적으로 전송되었습니다!", + "base-url": "기본 URL", + "base-url-required": "기본 URL을 입력해야 합니다.", + "mail-from": "보내는 사람", + "mail-from-required": "보내는 사람을 입력해야 합니다.", + "smtp-protocol": "SMTP 프로토콜", + "smtp-host": "SMTP 호스트", + "smtp-host-required": "SMTP 호스트를 입력해야 합니다.", + "smtp-port": "SMTP 포트", + "smtp-port-required": "SMTP 포트를 입력해야 합니다.", + "smtp-port-invalid": "올바른 SMTP 포트가 아닙니다.", + "timeout-msec": "제한시간 (msec)", + "timeout-required": "제한시간을 입력해야 합니다.", + "timeout-invalid": "올바른 제한시간이 아닙니다.", + "enable-tls": "TLS 사용", + "send-test-mail": "테스트 메일 보내기" + }, + "alarm": { + "alarm": "Alarm", + "alarms": "Alarms", + "select-alarm": "Select alarm", + "no-alarms-matching": "No alarms matching '{{entity}}' were found.", + "alarm-required": "Alarm is required", + "alarm-status": "Alarm status", + "search-status": { + "ANY": "Any", + "ACTIVE": "Active", + "CLEARED": "Cleared", + "ACK": "Acknowledged", + "UNACK": "Unacknowledged" + }, + "display-status": { + "ACTIVE_UNACK": "Active Unacknowledged", + "ACTIVE_ACK": "Active Acknowledged", + "CLEARED_UNACK": "Cleared Unacknowledged", + "CLEARED_ACK": "Cleared Acknowledged" + }, + "no-alarms-prompt": "No alarms found", + "created-time": "Created time", + "type": "Type", + "severity": "Severity", + "originator": "Originator", + "originator-type": "Originator type", + "details": "Details", + "status": "Status", + "alarm-details": "Alarm details", + "start-time": "Start time", + "end-time": "End time", + "ack-time": "Acknowledged time", + "clear-time": "Cleared time", + "severity-critical": "Critical", + "severity-major": "Major", + "severity-minor": "Minor", + "severity-warning": "Warning", + "severity-indeterminate": "Indeterminate", + "acknowledge": "Acknowledge", + "clear": "Clear", + "search": "Search alarms", + "selected-alarms": "{ count, plural, 1 {1 alarm} other {# alarms} } selected", + "no-data": "No data to display", + "polling-interval": "Alarms polling interval (sec)", + "polling-interval-required": "Alarms polling interval is required.", + "min-polling-interval-message": "At least 1 sec polling interval is allowed.", + "aknowledge-alarms-title": "Acknowledge { count, plural, 1 {1 alarm} other {# alarms} }", + "aknowledge-alarms-text": "Are you sure you want to acknowledge { count, plural, 1 {1 alarm} other {# alarms} }?", + "clear-alarms-title": "Clear { count, plural, 1 {1 alarm} other {# alarms} }", + "clear-alarms-text": "Are you sure you want to clear { count, plural, 1 {1 alarm} other {# alarms} }?" + }, + "alias": { + "add": "Add alias", + "edit": "Edit alias", + "name": "Alias name", + "name-required": "Alias name is required", + "duplicate-alias": "Alias with same name is already exists.", + "filter-type-single-entity": "Single entity", + "filter-type-entity-list": "Entity list", + "filter-type-entity-name": "Entity name", + "filter-type-state-entity": "Entity from dashboard state", + "filter-type-state-entity-description": "Entity taken from dashboard state parameters", + "filter-type-asset-type": "Asset type", + "filter-type-asset-type-description": "Assets of type '{{assetType}}'", + "filter-type-asset-type-and-name-description": "Assets of type '{{assetType}}' and with name starting with '{{prefix}}'", + "filter-type-device-type": "Device type", + "filter-type-device-type-description": "Devices of type '{{deviceType}}'", + "filter-type-device-type-and-name-description": "Devices of type '{{deviceType}}' and with name starting with '{{prefix}}'", + "filter-type-relations-query": "Relations query", + "filter-type-relations-query-description": "{{entities}} that have {{relationType}} relation {{direction}} {{rootEntity}}", + "filter-type-asset-search-query": "Asset search query", + "filter-type-asset-search-query-description": "Assets with types {{assetTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}", + "filter-type-device-search-query": "Device search query", + "filter-type-device-search-query-description": "Devices with types {{deviceTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}", + "entity-filter": "Entity filter", + "resolve-multiple": "Resolve as multiple entities", + "filter-type": "Filter type", + "filter-type-required": "Filter type is required.", + "entity-filter-no-entity-matched": "No entities matching specified filter were found.", + "no-entity-filter-specified": "No entity filter specified", + "root-state-entity": "Use dashboard state entity as root", + "root-entity": "Root entity", + "state-entity-parameter-name": "State entity parameter name", + "default-state-entity": "Default state entity", + "default-entity-parameter-name": "By default", + "max-relation-level": "Max relation level", + "unlimited-level": "Unlimited level", + "state-entity": "Dashboard state entity", + "all-entities": "All entities", + "any-relation": "any" + }, + "asset": { + "asset": "Asset", + "assets": "Assets", + "management": "Asset management", + "view-assets": "View Assets", + "add": "Add Asset", + "assign-to-customer": "Assign to customer", + "assign-asset-to-customer": "Assign Asset(s) To Customer", + "assign-asset-to-customer-text": "Please select the assets to assign to the customer", + "no-assets-text": "No assets found", + "assign-to-customer-text": "Please select the customer to assign the asset(s)", + "public": "Public", + "assignedToCustomer": "Assigned to customer", + "make-public": "Make asset public", + "make-private": "Make asset private", + "unassign-from-customer": "Unassign from customer", + "delete": "Delete asset", + "asset-public": "Asset is public", + "asset-type": "Asset type", + "asset-type-required": "Asset type is required.", + "select-asset-type": "Select asset type", + "enter-asset-type": "Enter asset type", + "any-asset": "Any asset", + "no-asset-types-matching": "No asset types matching '{{entitySubtype}}' were found.", + "asset-type-list-empty": "No asset types selected.", + "asset-types": "Asset types", + "name": "Name", + "name-required": "Name is required.", + "description": "Description", + "type": "Type", + "type-required": "Type is required.", + "details": "Details", + "events": "Events", + "add-asset-text": "Add new asset", + "asset-details": "Asset details", + "assign-assets": "Assign assets", + "assign-assets-text": "Assign { count, plural, 1 {1 asset} other {# assets} } to customer", + "delete-assets": "Delete assets", + "unassign-assets": "Unassign assets", + "unassign-assets-action-title": "Unassign { count, plural, 1 {1 asset} other {# assets} } from customer", + "assign-new-asset": "Assign new asset", + "delete-asset-title": "Are you sure you want to delete the asset '{{assetName}}'?", + "delete-asset-text": "Be careful, after the confirmation the asset and all related data will become unrecoverable.", + "delete-assets-title": "Are you sure you want to delete { count, plural, 1 {1 asset} other {# assets} }?", + "delete-assets-action-title": "Delete { count, plural, 1 {1 asset} other {# assets} }", + "delete-assets-text": "Be careful, after the confirmation all selected assets will be removed and all related data will become unrecoverable.", + "make-public-asset-title": "Are you sure you want to make the asset '{{assetName}}' public?", + "make-public-asset-text": "After the confirmation the asset and all its data will be made public and accessible by others.", + "make-private-asset-title": "Are you sure you want to make the asset '{{assetName}}' private?", + "make-private-asset-text": "After the confirmation the asset and all its data will be made private and won't be accessible by others.", + "unassign-asset-title": "Are you sure you want to unassign the asset '{{assetName}}'?", + "unassign-asset-text": "After the confirmation the asset will be unassigned and won't be accessible by the customer.", + "unassign-asset": "Unassign asset", + "unassign-assets-title": "Are you sure you want to unassign { count, plural, 1 {1 asset} other {# assets} }?", + "unassign-assets-text": "After the confirmation all selected assets will be unassigned and won't be accessible by the customer.", + "copyId": "Copy asset Id", + "idCopiedMessage": "Asset Id has been copied to clipboard", + "select-asset": "Select asset", + "no-assets-matching": "No assets matching '{{entity}}' were found.", + "asset-required": "Asset is required", + "name-starts-with": "Asset name starts with" + }, + "attribute": { + "attributes": "속성", + "latest-telemetry": "최근 데이터", + "attributes-scope": "디바이스 속성 범위", + "scope-latest-telemetry": "최근 데이터", + "scope-client": "클라이언트 속성", + "scope-server": "서버 속성", + "scope-shared": "공유 속성", + "add": "속성 추가", + "key": "Key", + "key-required": "속성 key를 입력하세요.", + "value": "Value", + "value-required": "속성 value를 입력하세요.", + "delete-attributes-title": "{ count, plural, 1 {속성} other {여러 속성들을} } 삭제하시겠습니까??", + "delete-attributes-text": "모든 선택된 속성들이 제거 될 것이므로 주의하십시오.", + "delete-attributes": "속성 삭제", + "enter-attribute-value": "속성 값 입력", + "show-on-widget": "위젯 보기", + "widget-mode": "위젯 모드", + "next-widget": "다음 위젯", + "prev-widget": "이전 위젯", + "add-to-dashboard": "대시보드에 추가", + "add-widget-to-dashboard": "대시보드에 위젯 추가", + "selected-attributes": "{ count, plural, 1 {속성 1개} other {속성 #개} } 선택됨", + "selected-telemetry": "{ count, plural, 1 {최근 데이터 1개} other {최근 데이터 #개} } 선택됨" + }, + "audit-log": { + "audit": "Audit", + "audit-logs": "Audit Logs", + "timestamp": "Timestamp", + "entity-type": "Entity Type", + "entity-name": "Entity Name", + "user": "User", + "type": "Type", + "status": "Status", + "details": "Details", + "type-added": "Added", + "type-deleted": "Deleted", + "type-updated": "Updated", + "type-attributes-updated": "Attributes updated", + "type-attributes-deleted": "Attributes deleted", + "type-rpc-call": "RPC call", + "type-credentials-updated": "Credentials updated", + "type-assigned-to-customer": "Assigned to Customer", + "type-unassigned-from-customer": "Unassigned from Customer", + "type-activated": "Activated", + "type-suspended": "Suspended", + "type-credentials-read": "Credentials read", + "type-attributes-read": "Attributes read", + "status-success": "Success", + "status-failure": "Failure", + "audit-log-details": "Audit log details", + "no-audit-logs-prompt": "No logs found", + "action-data": "Action data", + "failure-details": "Failure details", + "search": "Search audit logs", + "clear-search": "Clear search" + }, + "confirm-on-exit": { + "message": "변경 사항을 저장하지 않았습니다. 이 페이지를 나가시겠습니까?", + "html-message": "변경 사항을 저장하지 않았습니다.
이 페이지를 나가시겠습니까?", + "title": "저장되지 않은 변경사항" + }, + "contact": { + "country": "국가", + "city": "시", + "state": "도", + "postal-code": "우편 번호", + "postal-code-invalid": "숫자만 입력하세요.", + "address": "주소", + "address2": "상세주소", + "phone": "전화번호", + "email": "Email", + "no-address": "주소 정보 없음" + }, + "common": { + "username": "사용자명", + "password": "비밀번호", + "enter-username": "사용자명을 입력하세요.", + "enter-password": "비밀번호를 입력하세요.", + "enter-search": "검색어 입력" + }, + "content-type": { + "json": "Json", + "text": "Text", + "binary": "Binary (Base64)" + }, + "customer": { + "customers": "커스터머", + "management": "커스터머 관리", + "dashboard": "커스터머 대시보드", + "dashboards": "커스터머 대시보드", + "devices": "커스터머 디바이스", + "add": "커스터머 추가", + "delete": "커스터머 삭제", + "manage-customer-users": "커스터머 사용자 관리", + "manage-customer-devices": "커스터머 디바이스 관리", + "manage-customer-dashboards": "커스터머 대시보드 관리", + "manage-public-devices": "Manage public devices", + "manage-public-dashboards": "Manage public dashboards", + "manage-customer-assets": "Manage customer assets", + "manage-public-assets": "Manage public assets", + "add-customer-text": "커스터머 추가", + "no-customers-text": "커스터머가 없습니다.", + "customer-details": "커스터머 상세정보", + "delete-customer-title": "'{{customerTitle}}' 커스터머를 삭제하시겠습니까?", + "delete-customer-text": "커스터머 및 관련된 모든 데이터를 복구할 수 없으므로 주의하십시오.", + "delete-customers-title": "{ count, plural, 1 {커스터머 1개} other {커스터머 #개} }를 삭제하시겠습니까?", + "delete-customers-action-title": "{ count, plural, 1 {커스터머 1개} other {커스터머 #개} } 삭제", + "delete-customers-text": "선택된 커스터머는 삭제되고 관련된 모든 데이터를 복구할 수 없으므로 주의하십시오.", + "manage-users": "사용자 관리", + "manage-devices": "디바이스 관리", + "manage-dashboards": "대시보드 관리", + "title": "타이틀", + "title-required": "타이틀을 입력하세요.", + "description": "설명", + "details": "Details", + "events": "Events", + "copyId": "Copy customer Id", + "idCopiedMessage": "Customer Id has been copied to clipboard", + "select-customer": "Select customer", + "no-customers-matching": "No customers matching '{{entity}}' were found.", + "customer-required": "Customer is required", + "select-default-customer": "Select default customer", + "default-customer": "Default customer", + "default-customer-required": "Default customer is required in order to debug dashboard on Tenant level" + }, + "datetime": { + "date-from": "시작 날짜", + "time-from": "시작 시간", + "date-to": "종료 날짜", + "time-to": "종료 시간" + }, + "dashboard": { + "dashboard": "대시보드", + "dashboards": "대시보드", + "management": "대시보드 관리", + "view-dashboards": "대시보드 보기", + "add": "대시보드 추가", + "assign-dashboard-to-customer": "대시보드 커스터머 선택", + "assign-dashboard-to-customer-text": "대시보드 커스터머를 선택하세요.", + "assign-to-customer-text": "대시보드 커스터머를 선택하세요.", + "assign-to-customer": "커스터머 선택", + "unassign-from-customer": "커스터머 해제", + "no-dashboards-text": "대시보드가 없습니다", + "no-widgets": "설정된 위젯 없음", + "add-widget": "위젯 추가", + "title": "타이틀", + "select-widget-title": "위젯 선택", + "select-widget-subtitle": "사용가능한 위젯 타입 목록", + "delete": "대시보드 삭제", + "title-required": "타이틀을 입력하세요.", + "description": "설명", + "details": "상세", + "dashboard-details": "대시보드 상세정보", + "add-dashboard-text": "대시보드 추가", + "assign-dashboards": "대시보드 지정", + "assign-new-dashboard": "새 대시보드 할당", + "assign-dashboards-text": "{ count, plural, 1 {대시보드 1개} other {대시보드 #개} }를 커스터머 할당", + "delete-dashboards": "대시보드 삭제", + "unassign-dashboards": "대시보드 할당 취소", + "unassign-dashboards-action-title": "{ count, plural, 1 {대시보드 1개} other {대시보드 #개} }를 커스터머 할당 취소", + "delete-dashboard-title": "'{{dashboardTitle}}' 대시보드를 삭제하시겠습니까?", + "delete-dashboard-text": "대시보드 및 관련된 모든 데이터를 복구할 수 없으므로 주의하십시오.", + "delete-dashboards-title": "{ count, plural, 1 {대시보드 1개} other {대시보드 #개} }를 삭제하시겠습니까?", + "delete-dashboards-action-title": "{ count, plural, 1 {대시보드 1개} other {대시보드 #개} } 삭제", + "delete-dashboards-text": "선택된 대시보드가 삭제되고 관련된 모든 데이터를 복구할 수 없으므로 주의하십시오.", + "unassign-dashboard-title": "'{{dashboardTitle}}' 대시보드 할당을 해제하시겠습니까?", + "unassign-dashboard-text": "대시보드가 할당 해제되고 커스터머는 액세스 할 수 없게됩니다.", + "unassign-dashboard": "대시보드 할달 취소", + "unassign-dashboards-title": "{ count, plural, 1 {대시보드 1개} other {대시보드 #개} }의 할당을 취소하시겠습니까?", + "unassign-dashboards-text": "선택된 대시보드가 할당 해제되고 커스터머는 액세스 할 수 없게됩니다.", + "select-dashboard": "대시보드 선택", + "no-dashboards-matching": "'{{entity}}'와 일치하는 대시보드가 없습니다.", + "dashboard-required": "대시보드를 입력하세요.", + "select-existing": "기존 대시보드 선택", + "create-new": "대시보드 생성", + "new-dashboard-title": "새로운 대시보드 타이틀", + "open-dashboard": "대시보드 열기", + "set-background": "대시보드 설정", + "background-color": "배경색", + "background-image": "배경 이미지", + "background-size-mode": "배경 사이즈 모드", + "no-image": "이미지 없음", + "drop-image": "이곳에 이미지를 끌어다놓거나 이곳을 클릭하여 파일을 선택하고 업로드하세요.", + "settings": "설정", + "columns-count": "열 개수", + "columns-count-required": "열 개수를 입력하세요.", + "min-columns-count-message": "열 개수를 최소 10 이상 입력하세요.", + "max-columns-count-message": "열 개수를 최대 100 이하로 입력하세요.", + "widgets-margins": "위젯 사이 여백 크기", + "horizontal-margin": "세로 여백", + "horizontal-margin-required": "세로 여백 값을 입력하세요.", + "min-horizontal-margin-message": "세로 여백 값을 최소 0 이상 입력하세요.", + "max-horizontal-margin-message": "세로 여백 값을 최대 50 이하로 입력하세요.", + "vertical-margin": "가로 여백", + "vertical-margin-required": "가로 여백 값을 입력하세요.", + "min-vertical-margin-message": "가로 여백 값을 최소 0 이상 입력하세요.", + "max-vertical-margin-message": "가로 여백 값을 최대 50 이하로 입력하세요.", + "display-title": "대시보드 타이틀 표시", + "title-color": "타이틀 색상", + "import": "대시보드 가져오기", + "export": "대시보드 내보내기", + "export-failed-error": "대시보드 내보내기를 할 수 없습니다.: {error}", + "create-new-dashboard": "대시보드 생성", + "dashboard-file": "대시보드 파일", + "invalid-dashboard-file-error": "대시보드 가져오기를 할 수 없습니다.: 대시보드 데이터 구조가 잘못되었습니다.", + "dashboard-import-missing-aliases-title": "대시보드 앨리어스를 위해 누락 된 디바이스 선택", + "create-new-widget": "새로운 위젯 생성", + "import-widget": "위젯 가져오기", + "widget-file": "위젯 파일", + "invalid-widget-file-error": "위젯 가져오기를 할 수 없습니다: 위젯 데이터 구조가 잘못되었습니다.", + "widget-import-missing-aliases-title": "위젯에서 사용하는 누락 된 디바이스 선택", + "open-toolbar": "대시보드 툴바 열기", + "close-toolbar": "툴바 닫기", + "configuration-error": "구성 오류", + "alias-resolution-error-title": "대시보드 앨리어스 구성 오류", + "invalid-aliases-config": "일부 앨리어스 필터와 일치하는 디바이스를 찾을 수 없습니다.
이 문제를 해결하려면 관리자에게 문의하십시오.", + "select-devices": "디바이스 선택", + "assignedToCustomer": "커스터머에 할당됨" + }, + "datakey": { + "settings": "설정", + "advanced": "고급", + "label": "Label", + "color": "색상", + "data-generation-func": "데이터 생성 기능", + "use-data-post-processing-func": "데이터 후처리 기능 사용", + "configuration": "데이터 key 구성", + "timeseries": "Timeseries", + "attributes": "Attributes", + "timeseries-required": "디바이스 timeseries 를 입력하세요.", + "timeseries-or-attributes-required": "디바이스 timeseries/attributes 를 입력하세요.", + "maximum-timeseries-or-attributes": "Maximum { count, plural, 1 {1 timeseries/attribute is allowed.} other {# timeseries/attributes are allowed} }", + "alarm-fields-required": "Alarm fields are required.", + "function-types": "함수 유형", + "function-types-required": "함수 유형을 입력하세요.", + "maximum-function-types": "Maximum { count, plural, 1 {1 function type is allowed.} other {# function types are allowed} }" + }, + "datasource": { + "type": "데이터소스 유형", + "name": "Name", + "add-datasource-prompt": "데이터소스를 추가하세요." + }, + "details": { + "edit-mode": "편집 모드", + "toggle-edit-mode": "편집 모드 전환" + }, + "device": { + "device": "디바이스", + "device-required": "디바이스를 입력하세요.", + "devices": "디바이스", + "management": "디바이스 관리", + "view-devices": "디바이스 보기", + "device-alias": "디바이스 앨리어스", + "aliases": "디바이스 앨리어스", + "no-alias-matching": "'{{alias}}' 를 찾을 수 없습니다.", + "no-aliases-found": "앨리어스가 없습니다.", + "no-key-matching": "'{{key}}' 를 찾을 수 없습니다.", + "no-keys-found": "Key가 없습니다.", + "create-new-alias": "새로 만들기!", + "create-new-key": "새로 만들기!", + "duplicate-alias-error": "중복된 '{{alias}}' 앨리어스가 있습니다.
디바이스 앨리어스는 대시보드 내에서 고유해야 합니다.", + "configure-alias": "'{{alias}}' 앨리어스 구성", + "no-devices-matching": "'{{entity}}'와 일치하는 디바이스를 찾을 수 없습니다.", + "alias": "앨리어스", + "alias-required": "디바이스 앨리어스를 입력하세요.", + "remove-alias": "디바이스 앨리어스 삭제", + "add-alias": "디바이스 앨리어스 추가", + "name-starts-with": "시작되는 이름", + "device-list": "디바이스 리스트", + "use-device-name-filter": "필터 사용", + "device-list-empty": "선택된 디바이스가 없습니다.", + "device-name-filter-required": "디바이스 필터 이름을 입력하세요.", + "device-name-filter-no-device-matched": "'{{device}}' 로 시작되는 디바이스를 찾을 수 없습니다.", + "add": "디바이스 추가", + "assign-to-customer": "커스터머에게 할당", + "assign-device-to-customer": "디바이스를 커스터머에게 할당", + "assign-device-to-customer-text": "고객에게 할당할 디바이스를 선택하십시오.", + "no-devices-text": "디바이스 없음", + "assign-to-customer-text": "디바이스를 할당할 커스터머를 선택하세요.", + "device-details": "디바이스 상세정보", + "add-device-text": "디바이스 추가", + "credentials": "크리덴셜", + "manage-credentials": "크리덴셜 관리", + "delete": "디바이스 삭제", + "assign-devices": "디바이스 할당", + "assign-devices-text": "{ count, plural, 1 {디바이스 1개} other {디바이스 #개} }를 커서터머에 할당", + "delete-devices": "디바이스 삭제", + "unassign-from-customer": "커스터머 할당 해제", + "unassign-devices": "디바이스 할당 취소", + "unassign-devices-action-title": "{ count, plural, 1 {디바이스 1개} other {디바이스 #개} }를 커스터머에게서 할당 해제", + "assign-new-device": "새로운 디바이스 할당", + "view-credentials": "크리덴셜 보기", + "delete-device-title": "'{{deviceName}}' 디바이스를 삭제하시겠습니까?", + "delete-device-text": "디바이스 및 관련된 모든 데이터를 복구할 수 없으므로 주의하십시오.", + "delete-devices-title": "{ count, plural, 1 {디바이스 1개} other {디바이스 #개} }를 삭제하시겠습니까?", + "delete-devices-action-title": "{ count, plural, 1 {디바이스 1개} other {디바이스 #개} } 삭제", + "delete-devices-text": "선택된 디바이스가 삭제되고 관련된 모든 데이터를 복구할 수 없으므로 주의하십시오.", + "unassign-device-title": "'{{deviceName}}' 디바이스 할당을 해제하시겠습니까?", + "unassign-device-text": "디바이스가 할당 해제되고 커스터머는 액세스 할 수 없게됩니다.", + "unassign-device": "디바이스 할당 취소", + "unassign-devices-title": "{ count, plural, 1 {디바이스 1개} other {디바이스 #개} }의 할당을 해제하시겠습니까??", + "unassign-devices-text": "선택된 디바이스가 할당 해제되고 커스터머는 액세스 할 수 없게됩니다.", + "device-credentials": "디바이스 크리덴셜", + "credentials-type": "크리덴셜 타입", + "access-token": "억세스 토큰", + "access-token-required": "액세스 토큰을 입력하세요.", + "access-token-invalid": "액세스 토큰 길이는 1 - 20 자 여야합니다.", + "rsa-key": "RSA public key", + "rsa-key-required": "RSA public key 를 입력하세요.", + "secret": "시크릿", + "secret-required": "시크릿을 입력하세요.", + "name": "이름", + "name-required": "이름을 입력하세요.", + "description": "설명", + "events": "이벤트", + "details": "상세", + "copyId": "디바이스 아이디 복사", + "copyAccessToken": "억세스 토큰 복사", + "idCopiedMessage": "디바이스 아이디가 클립보드에 복사되었습니다.", + "accessTokenCopiedMessage": "디바이스 억세스 토큰이 클립보드에 복사되었습니다.", + "assignedToCustomer": "커스터머에 할당됨", + "unable-delete-device-alias-title": "디바이스 앨리어스를 삭제할 수 없습니다.", + "unable-delete-device-alias-text": "'{{deviceAlias}}' 디바이스 앨리어스를 삭제할 수 없습니다. 다음 위젯에서 사용하고 있습니다.
{{widgetsList}}", + "is-gateway": "게이트웨이 여부" + }, + "dialog": { + "close": "다이얼로그 닫기" + }, + "error": { + "unable-to-connect": "서버에 연결할 수 없습니다! 인터넷 연결을 확인하십시오.", + "unhandled-error-code": "처리되지 않은 오류 코드: {{errorCode}}", + "unknown-error": "알 수 없는 오류" + }, + "entity": { + "entity": "Entity", + "entities": "Entities", + "aliases": "Entity aliases", + "entity-alias": "Entity alias", + "unable-delete-entity-alias-title": "Unable to delete entity alias", + "unable-delete-entity-alias-text": "Entity alias '{{entityAlias}}' can't be deleted as it used by the following widget(s):
{{widgetsList}}", + "duplicate-alias-error": "Duplicate alias found '{{alias}}'.
Entity aliases must be unique whithin the dashboard.", + "missing-entity-filter-error": "Filter is missing for alias '{{alias}}'.", + "configure-alias": "Configure '{{alias}}' alias", + "alias": "Alias", + "alias-required": "Entity alias is required.", + "remove-alias": "Remove entity alias", + "add-alias": "Add entity alias", + "entity-list": "Entity list", + "entity-type": "Entity type", + "entity-types": "Entity types", + "entity-type-list": "Entity type list", + "any-entity": "Any entity", + "enter-entity-type": "Enter entity type", + "no-entities-matching": "No entities matching '{{entity}}' were found.", + "no-entity-types-matching": "No entity types matching '{{entityType}}' were found.", + "name-starts-with": "Name starts with", + "use-entity-name-filter": "Use filter", + "entity-list-empty": "No entities selected.", + "entity-type-list-empty": "No entity types selected.", + "entity-name-filter-required": "Entity name filter is required.", + "entity-name-filter-no-entity-matched": "No entities starting with '{{entity}}' were found.", + "all-subtypes": "All", + "select-entities": "Select entities", + "no-aliases-found": "No aliases found.", + "no-alias-matching": "'{{alias}}' not found.", + "create-new-alias": "Create a new one!", + "key": "Key", + "key-name": "Key name", + "no-keys-found": "No keys found.", + "no-key-matching": "'{{key}}' not found.", + "create-new-key": "Create a new one!", + "type": "Type", + "type-required": "Entity type is required.", + "type-device": "Device", + "type-devices": "Devices", + "list-of-devices": "{ count, plural, 1 {One device} other {List of # devices} }", + "device-name-starts-with": "Devices whose names start with '{{prefix}}'", + "type-asset": "Asset", + "type-assets": "Assets", + "list-of-assets": "{ count, plural, 1 {One asset} other {List of # assets} }", + "asset-name-starts-with": "Assets whose names start with '{{prefix}}'", + "type-rule": "Rule", + "type-rules": "Rules", + "list-of-rules": "{ count, plural, 1 {One rule} other {List of # rules} }", + "rule-name-starts-with": "Rules whose names start with '{{prefix}}'", + "type-plugin": "Plugin", + "type-plugins": "Plugins", + "list-of-plugins": "{ count, plural, 1 {One plugin} other {List of # plugins} }", + "plugin-name-starts-with": "Plugins whose names start with '{{prefix}}'", + "type-tenant": "Tenant", + "type-tenants": "Tenants", + "list-of-tenants": "{ count, plural, 1 {One tenant} other {List of # tenants} }", + "tenant-name-starts-with": "Tenants whose names start with '{{prefix}}'", + "type-customer": "Customer", + "type-customers": "Customers", + "list-of-customers": "{ count, plural, 1 {One customer} other {List of # customers} }", + "customer-name-starts-with": "Customers whose names start with '{{prefix}}'", + "type-user": "User", + "type-users": "Users", + "list-of-users": "{ count, plural, 1 {One user} other {List of # users} }", + "user-name-starts-with": "Users whose names start with '{{prefix}}'", + "type-dashboard": "Dashboard", + "type-dashboards": "Dashboards", + "list-of-dashboards": "{ count, plural, 1 {One dashboard} other {List of # dashboards} }", + "dashboard-name-starts-with": "Dashboards whose names start with '{{prefix}}'", + "type-alarm": "Alarm", + "type-alarms": "Alarms", + "list-of-alarms": "{ count, plural, 1 {One alarms} other {List of # alarms} }", + "alarm-name-starts-with": "Alarms whose names start with '{{prefix}}'", + "type-rulechain": "Rule chain", + "type-rulechains": "Rule chains", + "list-of-rulechains": "{ count, plural, 1 {One rule chain} other {List of # rule chains} }", + "rulechain-name-starts-with": "Rule chains whose names start with '{{prefix}}'", + "type-current-customer": "Current Customer", + "search": "Search entities", + "selected-entities": "{ count, plural, 1 {1 entity} other {# entities} } selected", + "entity-name": "Entity name", + "details": "Entity details", + "no-entities-prompt": "No entities found", + "no-data": "No data to display" + }, + "event": { + "event-type": "이벤트 타입", + "type-error": "에러", + "type-lc-event": "주기적 이벤트", + "type-stats": "통계", + "type-debug-rule-node": "Debug", + "type-debug-rule-chain": "Debug", + "no-events-prompt": "이벤트 없음", + "error": "에러", + "alarm": "알람", + "event-time": "이벤트 발생 시간", + "server": "서버", + "body": "Body", + "method": "Method", + "type": "Type", + "entity": "Entity", + "message-id": "Message Id", + "message-type": "Message Type", + "data-type": "Data Type", + "relation-type": "Relation Type", + "metadata": "Metadata", + "data": "Data", + "event": "이벤트", + "status": "상태", + "success": "성공", + "failed": "실패", + "messages-processed": "처리된 메시지", + "errors-occurred": "오류가 발생했습니다" + }, + "extension": { + "extensions": "Extensions", + "selected-extensions": "{ count, plural, 1 {1 extension} other {# extensions} } selected", + "type": "Type", + "key": "Key", + "value": "Value", + "id": "Id", + "extension-id": "Extension id", + "extension-type": "Extension type", + "transformer-json": "JSON *", + "unique-id-required": "Current extension id already exists.", + "delete": "Delete extension", + "add": "Add extension", + "edit": "Edit extension", + "delete-extension-title": "Are you sure you want to delete the extension '{{extensionId}}'?", + "delete-extension-text": "Be careful, after the confirmation the extension and all related data will become unrecoverable.", + "delete-extensions-title": "Are you sure you want to delete { count, plural, 1 {1 extension} other {# extensions} }?", + "delete-extensions-text": "Be careful, after the confirmation all selected extensions will be removed.", + "converters": "Converters", + "converter-id": "Converter id", + "configuration": "Configuration", + "converter-configurations": "Converter configurations", + "token": "Security token", + "add-converter": "Add converter", + "add-config": "Add converter configuration", + "device-name-expression": "Device name expression", + "device-type-expression": "Device type expression", + "custom": "Custom", + "to-double": "To Double", + "transformer": "Transformer", + "json-required": "Transformer json is required.", + "json-parse": "Unable to parse transformer json.", + "attributes": "Attributes", + "add-attribute": "Add attribute", + "add-map": "Add mapping element", + "timeseries": "Timeseries", + "add-timeseries": "Add timeseries", + "field-required": "Field is required", + "brokers": "Brokers", + "add-broker": "Add broker", + "host": "Host", + "port": "Port", + "port-range": "Port should be in a range from 1 to 65535.", + "ssl": "Ssl", + "credentials": "Credentials", + "username": "Username", + "password": "Password", + "retry-interval": "Retry interval in milliseconds", + "anonymous": "Anonymous", + "basic": "Basic", + "pem": "PEM", + "ca-cert": "CA certificate file *", + "private-key": "Private key file *", + "cert": "Certificate file *", + "no-file": "No file selected.", + "drop-file": "Drop a file or click to select a file to upload.", + "mapping": "Mapping", + "topic-filter": "Topic filter", + "converter-type": "Converter type", + "converter-json": "Json", + "json-name-expression": "Device name json expression", + "topic-name-expression": "Device name topic expression", + "json-type-expression": "Device type json expression", + "topic-type-expression": "Device type topic expression", + "attribute-key-expression": "Attribute key expression", + "attr-json-key-expression": "Attribute key json expression", + "attr-topic-key-expression": "Attribute key topic expression", + "request-id-expression": "Request id expression", + "request-id-json-expression": "Request id json expression", + "request-id-topic-expression": "Request id topic expression", + "response-topic-expression": "Response topic expression", + "value-expression": "Value expression", + "topic": "Topic", + "timeout": "Timeout in milliseconds", + "converter-json-required": "Converter json is required.", + "converter-json-parse": "Unable to parse converter json.", + "filter-expression": "Filter expression", + "connect-requests": "Connect requests", + "add-connect-request": "Add connect request", + "disconnect-requests": "Disconnect requests", + "add-disconnect-request": "Add disconnect request", + "attribute-requests": "Attribute requests", + "add-attribute-request": "Add attribute request", + "attribute-updates": "Attribute updates", + "add-attribute-update": "Add attribute update", + "server-side-rpc": "Server side RPC", + "add-server-side-rpc-request": "Add server-side RPC request", + "device-name-filter": "Device name filter", + "attribute-filter": "Attribute filter", + "method-filter": "Method filter", + "request-topic-expression": "Request topic expression", + "response-timeout": "Response timeout in milliseconds", + "topic-expression": "Topic expression", + "client-scope": "Client scope", + "add-device": "Add device", + "opc-server": "Servers", + "opc-add-server": "Add server", + "opc-add-server-prompt": "Please add server", + "opc-application-name": "Application name", + "opc-application-uri": "Application uri", + "opc-scan-period-in-seconds": "Scan period in seconds", + "opc-security": "Security", + "opc-identity": "Identity", + "opc-keystore": "Keystore", + "opc-type": "Type", + "opc-keystore-type": "Type", + "opc-keystore-location": "Location *", + "opc-keystore-password": "Password", + "opc-keystore-alias": "Alias", + "opc-keystore-key-password": "Key password", + "opc-device-node-pattern": "Device node pattern", + "opc-device-name-pattern": "Device name pattern", + "modbus-server": "Servers/slaves", + "modbus-add-server": "Add server/slave", + "modbus-add-server-prompt": "Please add server/slave", + "modbus-transport": "Transport", + "modbus-port-name": "Serial port name", + "modbus-encoding": "Encoding", + "modbus-parity": "Parity", + "modbus-baudrate": "Baud rate", + "modbus-databits": "Data bits", + "modbus-stopbits": "Stop bits", + "modbus-databits-range": "Data bits should be in a range from 7 to 8.", + "modbus-stopbits-range": "Stop bits should be in a range from 1 to 2.", + "modbus-unit-id": "Unit ID", + "modbus-unit-id-range": "Unit ID should be in a range from 1 to 247.", + "modbus-device-name": "Device name", + "modbus-poll-period": "Poll period (ms)", + "modbus-attributes-poll-period": "Attributes poll period (ms)", + "modbus-timeseries-poll-period": "Timeseries poll period (ms)", + "modbus-poll-period-range": "Poll period should be positive value.", + "modbus-tag": "Tag", + "modbus-function": "Function", + "modbus-register-address": "Register address", + "modbus-register-address-range": "Register address should be in a range from 0 to 65535.", + "modbus-register-bit-index": "Bit index", + "modbus-register-bit-index-range": "Bit index should be in a range from 0 to 15.", + "modbus-register-count": "Register count", + "modbus-register-count-range": "Register count should be a positive value.", + "modbus-byte-order": "Byte order", + "sync": { + "status": "Status", + "sync": "Sync", + "not-sync": "Not sync", + "last-sync-time": "Last sync time", + "not-available": "Not available" + }, + "export-extensions-configuration": "Export extensions configuration", + "import-extensions-configuration": "Import extensions configuration", + "import-extensions": "Import extensions", + "import-extension": "Import extension", + "export-extension": "Export extension", + "file": "Extensions file", + "invalid-file-error": "Invalid extension file" + }, + "fullscreen": { + "expand": "전체화면으로 확장", + "exit": "전체화면 종료", + "toggle": "전체화면 모드 전환", + "fullscreen": "전체화면" + }, + "function": { + "function": "기능" + }, + "grid": { + "delete-item-title": "이 항목을 삭제 하시겠습니까?", + "delete-item-text": "항목과 모든 관련 데이터를 복구 할 수 없으므로 주의하십시오.", + "delete-items-title": "{ count, plural, 1 {아이템 1개} other {아이템 #개} }를 삭제하시겠습니까?", + "delete-items-action-title": "{ count, plural, 1 {아이템 1개} other {아이템 #개} } 삭제", + "delete-items-text": "선택한 모든 아이템이 제거되고 관련된 모든 데이터는 복구 할 수 없으므로 주의하십시오.", + "add-item-text": "새로운 아이템 추가", + "no-items-text": "아이템이 없습니다.", + "item-details": "아이템 상세", + "delete-item": "아이템 삭제", + "delete-items": "아이템 삭제", + "scroll-to-top": "스크롤 맨 위로" + }, + "help": { + "goto-help-page": "도움" + }, + "home": { + "home": "홈", + "profile": "프로파일", + "logout": "로그아웃", + "menu": "메뉴", + "avatar": "Avatar", + "open-user-menu": "사용자 메뉴 열기" + }, + "import": { + "no-file": "선택된 파일이 없습니다.", + "drop-file": "JSON 파일을 끌어다 놓거나 클릭하여 업로드 할 파일을 선택하십시오." + }, + "item": { + "selected": "선택됨" + }, + "js-func": { + "no-return-error": "함수는 값을 반환해야 합니다!", + "return-type-mismatch": "함수는 '{{type}}' 유형의 값을 반환해야 합니다!", + "tidy": "Tidy" + }, + "key-val": { + "key": "Key", + "value": "Value", + "remove-entry": "Remove entry", + "add-entry": "Add entry", + "no-data": "No entries" + }, + "layout": { + "layout": "Layout", + "manage": "Manage layouts", + "settings": "Layout settings", + "color": "Color", + "main": "Main", + "right": "Right", + "select": "Select target layout" + }, + "legend": { + "position": "범례 위치", + "show-max": "최대값 표시", + "show-min": "최소값 표시", + "show-avg": "평균값 표시", + "show-total": "총합 표시", + "settings": "범례 설정", + "min": "최소", + "max": "최대", + "avg": "평균", + "total": "합계" + }, + "login": { + "login": "로그인", + "request-password-reset": "비밀번호 재설정", + "reset-password": "비밀번호 재설정", + "create-password": "비밀번호 생성", + "passwords-mismatch-error": "입력된 비밀번호는 같아야 합니다!", + "password-again": "비밀번호 확인", + "sign-in": "로그인", + "username": "사용자명 (이메일)", + "remember-me": "아이디 저장", + "forgot-password": "비밀번호찾기", + "password-reset": "비밀번호 재설정", + "new-password": "새 비밀번호", + "new-password-again": "새 비밀번호 확인", + "password-link-sent-message": "비밀번호 재설정 링크가 성공적으로 전송되었습니다!", + "email": "이메일" + }, + "position": { + "top": "상단", + "bottom": "하단", + "left": "왼쪽", + "right": "오른쪽" + }, + "profile": { + "profile": "프로파일", + "change-password": "비밀번호 변경", + "current-password": "현재 비밀번호" + }, + "relation": { + "relations": "Relations", + "direction": "Direction", + "search-direction": { + "FROM": "From", + "TO": "To" + }, + "direction-type": { + "FROM": "from", + "TO": "to" + }, + "from-relations": "Outbound relations", + "to-relations": "Inbound relations", + "selected-relations": "{ count, plural, 1 {1 relation} other {# relations} } selected", + "type": "Type", + "to-entity-type": "To entity type", + "to-entity-name": "To entity name", + "from-entity-type": "From entity type", + "from-entity-name": "From entity name", + "to-entity": "To entity", + "from-entity": "From entity", + "delete": "Delete relation", + "relation-type": "Relation type", + "relation-type-required": "Relation type is required.", + "any-relation-type": "Any type", + "add": "Add relation", + "edit": "Edit relation", + "delete-to-relation-title": "Are you sure you want to delete relation to the entity '{{entityName}}'?", + "delete-to-relation-text": "Be careful, after the confirmation the entity '{{entityName}}' will be unrelated from the current entity.", + "delete-to-relations-title": "Are you sure you want to delete { count, plural, 1 {1 relation} other {# relations} }?", + "delete-to-relations-text": "Be careful, after the confirmation all selected relations will be removed and corresponding entities will be unrelated from the current entity.", + "delete-from-relation-title": "Are you sure you want to delete relation from the entity '{{entityName}}'?", + "delete-from-relation-text": "Be careful, after the confirmation current entity will be unrelated from the entity '{{entityName}}'.", + "delete-from-relations-title": "Are you sure you want to delete { count, plural, 1 {1 relation} other {# relations} }?", + "delete-from-relations-text": "Be careful, after the confirmation all selected relations will be removed and current entity will be unrelated from the corresponding entities.", + "remove-relation-filter": "Remove relation filter", + "add-relation-filter": "Add relation filter", + "any-relation": "Any relation", + "relation-filters": "Relation filters", + "additional-info": "Additional info (JSON)", + "invalid-additional-info": "Unable to parse additional info json." + }, + "rulechain": { + "rulechain": "Rule chain", + "rulechains": "Rule chains", + "root": "Root", + "delete": "Delete rule chain", + "name": "Name", + "name-required": "Name is required.", + "description": "Description", + "add": "Add Rule Chain", + "set-root": "Make rule chain root", + "set-root-rulechain-title": "Are you sure you want to make the rule chain '{{ruleChainName}}' root?", + "set-root-rulechain-text": "After the confirmation the rule chain will become root and will handle all incoming transport messages.", + "delete-rulechain-title": "Are you sure you want to delete the rule chain '{{ruleChainName}}'?", + "delete-rulechain-text": "Be careful, after the confirmation the rule chain and all related data will become unrecoverable.", + "delete-rulechains-title": "Are you sure you want to delete { count, plural, 1 {1 rule chain} other {# rule chains} }?", + "delete-rulechains-action-title": "Delete { count, plural, 1 {1 rule chain} other {# rule chains} }", + "delete-rulechains-text": "Be careful, after the confirmation all selected rule chains will be removed and all related data will become unrecoverable.", + "add-rulechain-text": "Add new rule chain", + "no-rulechains-text": "No rule chains found", + "rulechain-details": "Rule chain details", + "details": "Details", + "events": "Events", + "system": "System", + "import": "Import rule chain", + "export": "Export rule chain", + "export-failed-error": "Unable to export rule chain: {{error}}", + "create-new-rulechain": "Create new rule chain", + "rulechain-file": "Rule chain file", + "invalid-rulechain-file-error": "Unable to import rule chain: Invalid rule chain data structure.", + "copyId": "Copy rule chain Id", + "idCopiedMessage": "Rule chain Id has been copied to clipboard", + "select-rulechain": "Select rule chain", + "no-rulechains-matching": "No rule chains matching '{{entity}}' were found.", + "rulechain-required": "Rule chain is required", + "management": "Rules management", + "debug-mode": "Debug mode" + }, + "rulenode": { + "details": "Details", + "events": "Events", + "search": "Search nodes", + "open-node-library": "Open node library", + "add": "Add rule node", + "name": "Name", + "name-required": "Name is required.", + "type": "Type", + "description": "Description", + "delete": "Delete rule node", + "select-all-objects": "Select all nodes and connections", + "deselect-all-objects": "Deselect all nodes and connections", + "delete-selected-objects": "Delete selected nodes and connections", + "delete-selected": "Delete selected", + "select-all": "Select all", + "copy-selected": "Copy selected", + "deselect-all": "Deselect all", + "rulenode-details": "Rule node details", + "debug-mode": "Debug mode", + "configuration": "Configuration", + "link": "Link", + "link-details": "Rule node link details", + "add-link": "Add link", + "link-label": "Link label", + "link-label-required": "Link label is required.", + "custom-link-label": "Custom link label", + "custom-link-label-required": "Custom link label is required.", + "type-filter": "Filter", + "type-filter-details": "Filter incoming messages with configured conditions", + "type-enrichment": "Enrichment", + "type-enrichment-details": "Add additional information into Message Metadata", + "type-transformation": "Transformation", + "type-transformation-details": "Change Message payload and Metadata", + "type-action": "Action", + "type-action-details": "Perform special action", + "type-external": "External", + "type-external-details": "Interacts with external system", + "type-rule-chain": "Rule Chain", + "type-rule-chain-details": "Forwards incoming messages to specified Rule Chain", + "type-input": "Input", + "type-input-details": "Logical input of Rule Chain, forwards incoming messages to next related Rule Node", + "directive-is-not-loaded": "Defined configuration directive '{{directiveName}}' is not available.", + "ui-resources-load-error": "Failed to load configuration ui resources.", + "invalid-target-rulechain": "Unable to resolve target rule chain!", + "test-script-function": "Test script function", + "message": "Message", + "message-type": "Message type", + "message-type-required": "Message type is required", + "metadata": "Metadata", + "metadata-required": "Metadata entries can't be empty.", + "output": "Output", + "test": "Test", + "help": "Help" + }, + "tenant": { + "tenants": "테넌트", + "management": "테넌트 관리", + "add": "테넌트 추가", + "admins": "Admins", + "manage-tenant-admins": "테넌트 관리자 관리", + "delete": "테넌트 삭제", + "add-tenant-text": "테넌트 추가", + "no-tenants-text": "테넌트가 없습니다.", + "tenant-details": "테넌트 상세정보", + "delete-tenant-title": "'{{tenantTitle}}' 테넌트를 삭제하시겠습니까?", + "delete-tenant-text": "테넌트와 관련된 모든 정보를 복구할 수 없으므로 주의하십시오.", + "delete-tenants-title": "{ count, plural, 1 {테넌트 1개} other {테넌트 #개} }를 삭제하시겠습니까?", + "delete-tenants-action-title": "{ count, plural, 1 {테넌트 1개} other {테넌트 #개} } 삭제", + "delete-tenants-text": "선택된 테넌트가 삭제되고 관련된 모든 정보를 복구할 수 없으므로 주의하십시오.", + "title": "타이틀", + "title-required": "타이틀을 입력하세요.", + "description": "설명", + "details": "Details", + "events": "Events", + "copyId": "Copy tenant Id", + "idCopiedMessage": "Tenant Id has been copied to clipboard", + "select-tenant": "Select tenant", + "no-tenants-matching": "No tenants matching '{{entity}}' were found.", + "tenant-required": "Tenant is required" + }, + "timeinterval": { + "seconds-interval": "{ seconds, plural, 1 {1 second} other {# seconds} }", + "minutes-interval": "{ minutes, plural, 1 {1 minute} other {# minutes} }", + "hours-interval": "{ hours, plural, 1 {1 hour} other {# hours} }", + "days-interval": "{ days, plural, 1 {1 day} other {# days} }", + "days": "Days", + "hours": "Hours", + "minutes": "Minutes", + "seconds": "Seconds", + "advanced": "고급" + }, + "timewindow": { + "days": "{ days, plural, 1 { day } other {# days } }", + "hours": "{ hours, plural, 0 { hour } 1 {1 hour } other {# hours } }", + "minutes": "{ minutes, plural, 0 { minute } 1 {1 minute } other {# minutes } }", + "seconds": "{ seconds, plural, 0 { second } 1 {1 second } other {# seconds } }", + "realtime": "Realtime", + "history": "History", + "last-prefix": "last", + "period": "from {{ startTime }} to {{ endTime }}", + "edit": "타임윈도우 편집", + "date-range": "날짜 범위", + "last": "Last", + "time-period": "기간" + }, + "user": { + "users": "사용자", + "customer-users": "커스터머 사용자", + "tenant-admins": "테넌트 관리자", + "sys-admin": "시스템 관리자", + "tenant-admin": "테넌트 관리자", + "customer": "커스터머", + "anonymous": "Anonymous", + "add": "사용자 추가", + "delete": "사용자 삭제", + "add-user-text": "새로운 사용자 추가", + "no-users-text": "사용자가 없습니다.", + "user-details": "사용자 상세정보", + "delete-user-title": "'{{userEmail}}' 사용자를 삭제하시겠습니까?", + "delete-user-text": "사용자와 관련된 모든 데이터를 복구할 수 없으므로 주의하십시오.", + "delete-users-title": "{ count, plural, 1 {사용자 1명} other {사용자 #명} }을 삭제하시겠니까?", + "delete-users-action-title": "{ count, plural, 1 {사용자 1명} other {사용자 #명} } 삭제", + "delete-users-text": "선택된 사용자가 삭제된고 관련된 모든 데이터를 복구할 수 없으므로 주의하십시오.", + "activation-email-sent-message": "활성화 이메일을 보냈습니다!", + "resend-activation": "활성화 재전송", + "email": "Email", + "email-required": "Email을 입력하세요.", + "first-name": "이름", + "last-name": "성", + "description": "설명", + "default-dashboard": "기본 대시보드", + "always-fullscreen": "항상 전체화면", + "select-user": "Select user", + "no-users-matching": "No users matching '{{entity}}' were found.", + "user-required": "User is required", + "activation-method": "Activation method", + "display-activation-link": "Display activation link", + "send-activation-mail": "Send activation mail", + "activation-link": "User activation link", + "activation-link-text": "In order to activate user use the following activation link :", + "copy-activation-link": "Copy activation link", + "activation-link-copied-message": "User activation link has been copied to clipboard", + "details": "Details" + }, + "value": { + "type": "Value type", + "string": "String", + "string-value": "String value", + "integer": "Integer", + "integer-value": "Integer value", + "invalid-integer-value": "Invalid integer value", + "double": "Double", + "double-value": "Double value", + "boolean": "Boolean", + "boolean-value": "Boolean value", + "false": "False", + "true": "True" + }, + "widget": { + "widget-library": "위젯 저장소", + "widget-bundle": "위젯 번들", + "select-widgets-bundle": "위젯 번들 선택", + "management": "위젯 관리", + "editor": "위젯 편집기", + "widget-type-not-found": "위젯 구성을 로드하는 중 문제가 발생했습니다.
연결된 위젯 타입이 삭제되었습니다.", + "widget-type-load-error": "다음과 같은 오류로 인해 위젯이로드되지 않았습니다:", + "remove": "위젯 삭제", + "edit": "위젯 수정", + "remove-widget-title": "'{{widgetTitle}}' 위젯을 삭제하시겠습니까?", + "remove-widget-text": "위젯과 관련된 모든 데이터를 복구할 수 없으므로 주의하십시오.", + "timeseries": "Time series", + "latest-values": "Latest values", + "rpc": "Control 위젯", + "static": "Static 위젯", + "select-widget-type": "위젯 타입 선택", + "missing-widget-title-error": "위젯 타이틀을 입력하세요!", + "widget-saved": "위젯이 저장되었습니다.", + "unable-to-save-widget-error": "위젯을 저장할 수 없습니다! 위젯에 오류가 있습니다!", + "save": "위젯 저장", + "saveAs": "다른 이름으로 위젯 저장", + "save-widget-type-as": "다른 이름으로 위젯 타입 저장", + "save-widget-type-as-text": "새로운 위젯 이름과 위젯 번들을 선택하세요.", + "toggle-fullscreen": "전체화면 전환", + "run": "위젯 실행", + "title": "위젯 타이틀", + "title-required": "위젯 타이틀을 입력하세요.", + "type": "위젯 타입", + "resources": "리소스", + "resource-url": "JavaScript/CSS URI", + "remove-resource": "리소스 삭제", + "add-resource": "리소스 추가", + "html": "HTML", + "tidy": "Tidy", + "css": "CSS", + "settings-schema": "스키마 설정", + "datakey-settings-schema": "데이터 키 설정 스키마", + "javascript": "Javascript", + "remove-widget-type-title": "'{{widgetName}}' 위젯 타입을 삭제하시겠습니까?", + "remove-widget-type-text": "위젯 타입과 관련된 모든 데이터를 복구할 수 없으므로 주의하십시오.", + "remove-widget-type": "위젯 타입 삭제", + "add-widget-type": "새로운 위젯 타입 추가", + "widget-type-load-failed-error": "위젯 타입을 로드하지 못했습니다!", + "widget-template-load-failed-error": "위젯 템플릿을 로드하지 못했습니다!", + "add": "위젯 추가", + "undo": "위젯 변경사항 취소", + "export": "위젯 내보내기" + }, + "widget-action": { + "header-button": "Widget header button", + "open-dashboard-state": "Navigate to new dashboard state", + "update-dashboard-state": "Update current dashboard state", + "open-dashboard": "Navigate to other dashboard", + "custom": "Custom action", + "target-dashboard-state": "Target dashboard state", + "target-dashboard-state-required": "Target dashboard state is required", + "set-entity-from-widget": "Set entity from widget", + "target-dashboard": "Target dashboard", + "open-right-layout": "Open right dashboard layout (mobile view)" + }, + "widgets-bundle": { + "current": "현재 번들", + "widgets-bundles": "위젯 번들", + "add": "위젯 번들 추가", + "delete": "위젯 번들 삭제", + "title": "타이틀", + "title-required": "타이틀을 입력하세요.", + "add-widgets-bundle-text": "위젯 번들 추가", + "no-widgets-bundles-text": "위젯 번들이 없습니다.", + "empty": "위젯 번들이 비어있습니다.", + "details": "상세", + "widgets-bundle-details": "위젯 번들 상세정보", + "delete-widgets-bundle-title": "'{{widgetsBundleTitle}}' 위젯 번들을 삭제하시겠습니까?", + "delete-widgets-bundle-text": "위젯 번들과 관련된 모든 데이터를 복구할 수 없으므로 주의하십시오.", + "delete-widgets-bundles-title": "{ count, plural, 1 {위젯 번들 1개} other {위젯 번들 #개} }를 삭제하시겠습니까?", + "delete-widgets-bundles-action-title": "{ count, plural, 1 {위젯 번들 1개} other {위젯 번들 #개} } 삭제", + "delete-widgets-bundles-text": "선택된 위젯 번들이 삭제되고 관련된 모든 데이터를 복구할 수 없으므로 주의하십시오.", + "no-widgets-bundles-matching": "'{{widgetsBundle}}' 와(과) 일치하는 위젯 번들을 찾을 수 없습니다.", + "widgets-bundle-required": "위젯 번들을 입력하세요.", + "system": "시스템", + "import": "위젯 번들 가져오기", + "export": "위젯 번들 내보내기", + "export-failed-error": "위젯 번들을 내보내기 할 수 없습니다.: {{error}}", + "create-new-widgets-bundle": "새로운 위젯 번들 생성", + "widgets-bundle-file": "위젯 번들 파일", + "invalid-widgets-bundle-file-error": "위젯 번들을 가져오기 할 수 없습니다.: 잘못된 위젯 번들 데이터 구조입니다." + }, + "widget-config": { + "data": "데이터", + "settings": "설정", + "advanced": "고급", + "title": "타이틀", + "general-settings": "일반 설정", + "display-title": "타이틀 표시", + "drop-shadow": "그림자", + "enable-fullscreen": "전체화면 사용 ", + "background-color": "배경 색", + "text-color": "글자 색", + "padding": "패딩", + "title-style": "타이틀 스타일", + "mobile-mode-settings": "모바일 모드 설정", + "order": "순서", + "height": "높이", + "units": "값 옆에 표시할 특수 기호", + "decimals": "소수점 이하 자릿수", + "timewindow": "타임윈도우", + "use-dashboard-timewindow": "대시보드 타임윈도우", + "display-legend": "범례 표시", + "datasources": "데이터소스", + "datasource-type": "유형", + "datasource-parameters": "파라미터", + "remove-datasource": "데이터소스 삭제", + "add-datasource": "데이터소스 추가", + "target-device": "대상 디바이스" + }, + "widget-type": { + "import": "위젯 타입 가져오기", + "export": "위젯 타입 내보내기", + "export-failed-error": "위젯 타입을 내보내기 할 수 없습니다.: {{error}}", + "create-new-widget-type": "새로운 위젯 타입 생성", + "widget-type-file": "위젯 타입 파일", + "invalid-widget-type-file-error": "위젯 타입을 가져오기 할 수 없습니다.: 잘못된 위젯 타입 데이터 구조입니다." + }, + "widgets": { + "date-range-navigator": { + "localizationMap": { + "Sun": "일", + "Mon": "월", + "Tue": "화", + "Wed": "수", + "Thu": "목", + "Fri": "금", + "Sat": "토", + "Jan": "1월", + "Feb": "2월", + "Mar": "3월", + "Apr": "4월", + "May": "5월", + "Jun": "6월", + "Jul": "7월", + "Aug": "8월", + "Sep": "9월", + "Oct": "10월", + "Nov": "11월", + "Dec": "12월", + "January": "일월", + "February": "이월", + "March": "행진", + "April": "4 월", + "June": "유월", + "July": "칠월", + "August": "팔월", + "September": "구월", + "October": "십월", + "November": "십일월", + "December": "12 월", + "Custom Date Range": "맞춤 기간", + "Date Range Template": "기간 템플릿", + "Today": "오늘", + "Yesterday": "어제", + "This Week": "이번 주", + "Last Week": "지난주", + "This Month": "이번 달", + "Last Month": "지난 달", + "Year": "년", + "This Year": "올해", + "Last Year": "작년", + "Date picker": "날짜 선택기", + "Hour": "시간", + "Day": "일", + "Week": "주", + "2 weeks": "이주", + "Month": "달", + "3 months": "3 개월", + "6 months": "6 개월", + "Custom interval": "사용자 지정 간격", + "Interval": "간격", + "Step size": "단계 크기", + "Ok": "Ok" + } + } + }, + "icon": { + "icon": "Icon", + "select-icon": "Select icon", + "material-icons": "Material icons", + "show-all": "Show all icons" + }, + "custom": { + "widget-action": { + "action-cell-button": "Action cell button", + "row-click": "On row click", + "polygon-click": "On polygon click", + "marker-click": "On marker click", + "tooltip-tag-action": "Tooltip tag action" + } + }, + "language": { + "language": "언어", + "locales": { + "de_DE": "독일어", + "en_US": "영어", + "fr_FR": "프랑스의", + "ko_KR": "한글", + "zh_CN": "중국어", + "ru_RU": "러시아어", + "es_ES": "스페인어", + "it_IT": "이탈리아 사람", + "ja_JA": "일본어", + "tr_TR": "터키어", + "fa_IR": "페르시아 인", + "uk_UA": "우크라이나의", + "cs_CZ": "체코 어로" + } + } +} \ No newline at end of file diff --git a/ui-ngx/src/assets/locale/locale.constant-ru_RU.json b/ui-ngx/src/assets/locale/locale.constant-ru_RU.json new file mode 100644 index 0000000000..504344c749 --- /dev/null +++ b/ui-ngx/src/assets/locale/locale.constant-ru_RU.json @@ -0,0 +1,1649 @@ +{ + "access": { + "unauthorized": "Неавторизированный", + "unauthorized-access": "Несанкционированный доступ", + "unauthorized-access-text": "Вы должны войти в систему для получения доступа к этому ресурсу!", + "access-forbidden": "Доступ запрещен", + "access-forbidden-text": "У вас нет прав доступа к этому ресурсу!
Для получения доступа попробуйте войти под другим пользователем.", + "refresh-token-expired": "Сессия истекла", + "refresh-token-failed": "Не удалось обновить сессию" + }, + "action": { + "activate": "Активировать", + "suspend": "Приостановить", + "save": "Сохранить", + "saveAs": "Сохранить как", + "cancel": "Отмена", + "ok": "ОК", + "delete": "Удалить", + "add": "Добавить", + "yes": "Да", + "no": "Нет", + "update": "Обновить", + "remove": "Удалить", + "search": "Поиск", + "clear-search": "Очистить", + "assign": "Присвоить", + "unassign": "Отозвать", + "share": "Поделиться", + "make-private": "Закрыть для общего доступа", + "apply": "Применить", + "apply-changes": "Применить изменения", + "edit-mode": "Режим редактирования", + "enter-edit-mode": "Режим редактирования", + "decline-changes": "Отменить изменения", + "close": "Закрыть", + "back": "Назад", + "run": "Запуск", + "sign-in": "Войти", + "edit": "Редактировать", + "view": "Просмотреть", + "create": "Создать", + "drag": "Переместить", + "refresh": "Обновить", + "undo": "Откатить", + "copy": "Копировать", + "paste": "Вставить", + "copy-reference": "Копировать ссылку", + "paste-reference": "Вставить ссылку", + "import": "Импортировать", + "export": "Экспортировать", + "share-via": "Поделиться в {{provider}}" + }, + "aggregation": { + "aggregation": "Агрегация", + "function": "Тип агрегации данных", + "limit": "Максимальное значение", + "group-interval": "Интервал группировки", + "min": "Мин", + "max": "Maкс", + "avg": "Среднее", + "sum": "Сумма", + "count": "Количество", + "none": "Без агрегации" + }, + "admin": { + "general": "Общие", + "general-settings": "Общие настройки", + "outgoing-mail": "Исходящая почта", + "outgoing-mail-settings": "Настройки исходящей почты", + "system-settings": "Системные настройки", + "test-mail-sent": "Пробное письмо успешно отправлено!", + "base-url": "Базовая URL", + "base-url-required": "Базовая URL обязательна.", + "mail-from": "Отправитель", + "mail-from-required": "Отправитель обязателен.", + "smtp-protocol": "SMTP протокол", + "smtp-host": "SMTP хост", + "smtp-host-required": "SMTP хост обязателен.", + "smtp-port": "SMTP порт", + "smtp-port-required": "SMTP порт обязателен.", + "smtp-port-invalid": "Недействительный SMTP порт.", + "timeout-msec": "Таймаут (мс)", + "timeout-required": "Таймаут обязателен.", + "timeout-invalid": "Недействительный таймаут.", + "enable-tls": "Включить TLS", + "send-test-mail": "Отправить пробное письмо" + }, + "alarm": { + "alarm": "Оповещение", + "alarms": "Оповещения", + "select-alarm": "Выбрать оповещение", + "no-alarms-matching": "Оповещения '{{entity}}' не найдены.", + "alarm-required": "Оповещение обязательно", + "alarm-status": "Статус оповещения", + "search-status": { + "ANY": "Все", + "ACTIVE": "Активные", + "CLEARED": "Сброшенные", + "ACK": "Подтвержденные", + "UNACK": "Неподтвержденные" + }, + "display-status": { + "ACTIVE_UNACK": "Активные неподтвержденные", + "ACTIVE_ACK": "Активные подтвержденные", + "CLEARED_UNACK": "Сброшенные неподтвержденные", + "CLEARED_ACK": "Сброшенные подтвержденные" + }, + "no-alarms-prompt": "Оповещения отсутствуют", + "created-time": "Время создания", + "type": "Тип", + "severity": "Уровень", + "originator": "Инициатор", + "originator-type": "Тип инициатора", + "details": "Подробности", + "status": "Статус", + "alarm-details": "Подробности об оповещении", + "start-time": "Время начала", + "end-time": "Время окончания", + "ack-time": "Время подтверждения", + "clear-time": "Время сброса", + "severity-critical": "Критический", + "severity-major": "Основной", + "severity-minor": "Второстепенный", + "severity-warning": "Предупреждение", + "severity-indeterminate": "Неопределенный", + "acknowledge": "Подтвердить", + "clear": "Сбросить", + "search": "Поиск оповещений", + "selected-alarms": "Выбрано { count, plural, 1 {1 оповещение} few {# оповещения} other {# оповещений} }", + "no-data": "Нет данных для отображения", + "polling-interval": "Интервал опроса оповещений (сек)", + "polling-interval-required": "Интервал опроса оповещений обязателен.", + "min-polling-interval-message": "Минимальный интервал опроса оповещений 1 секунда.", + "aknowledge-alarms-title": "Подтвердить { count, plural, 1 {1 оповещение} other {# оповещений} }", + "aknowledge-alarms-text": "Вы точно хотите подтвердить { count, plural, 1 {1 оповещение} other {# оповещений} }?", + "aknowledge-alarm-title": "Подтвердить оповещение", + "aknowledge-alarm-text": "Вы точно хотите подтвердить оповещение?", + "clear-alarms-title": "Сбросить { count, plural, 1 {1 оповещение} other {# оповещений} }", + "clear-alarms-text": "Вы точно хотите сбросить { count, plural, 1 {1 оповещение} other {# оповещений} }?", + "clear-alarm-title": "Сбросить оповещение", + "clear-alarm-text": "Вы точно хотите сбросить оповещение?", + "alarm-status-filter": "Фильтр оповещений" + }, + "alias": { + "add": "Добавить псевдоним", + "edit": "Редактировать псевдоним", + "name": "Псевдоним", + "name-required": "Псевдоним обязателен", + "duplicate-alias": "Такой псевдоним уже существует.", + "filter-type-single-entity": "Отдельный объект", + "filter-type-entity-list": "Список объектов", + "filter-type-entity-name": "Название объекта", + "filter-type-state-entity": "Объект, полученный из дашборда", + "filter-type-state-entity-description": "Объект, полученный из параметров дашборда", + "filter-type-asset-type": "Тип актива", + "filter-type-asset-type-description": "Активы типа '{{assetType}}'", + "filter-type-asset-type-and-name-description": "Активы типа '{{assetType}}' и названием, начинающимся с '{{prefix}}'", + "filter-type-device-type": "Тип устройства", + "filter-type-device-type-description": "Устройства типа '{{deviceType}}'", + "filter-type-device-type-and-name-description": "Устройства типа '{{deviceType}}' и названием, начинающимся с '{{prefix}}'", + "filter-type-entity-view-type": "Тип Представления Объекта", + "filter-type-entity-view-type-description": "Представления Объекта типа '{{entityView}}'", + "filter-type-entity-view-type-and-name-description": "Представления Объекта типа '{{entityView}}' и названием, начинающимся с '{{prefix}}'", + "filter-type-relations-query": "Запрос по типу отношений", + "filter-type-relations-query-description": "{{entities}}, имеющие отношение типа {{relationType}} {{direction}} {{rootEntity}}", + "filter-type-asset-search-query": "Поисковый запрос по активам", + "filter-type-asset-search-query-description": "Активы типа {{assetTypes}}, имеющие отношение типа {{relationType}} {{direction}} {{rootEntity}}", + "filter-type-device-search-query": "Поисковый запрос по устройствам", + "filter-type-device-search-query-description": "Устройства типа {{deviceTypes}}, имеющие отношение типа {{relationType}} {{direction}} {{rootEntity}}", + "filter-type-entity-view-search-query": "Поисковый запрос по представлениям объектов", + "filter-type-entity-view-search-query-description": "Представления объектов типа {{entityViewTypes}}, имеющие отношение типа {{relationType}} {{direction}} {{rootEntity}}", + "entity-filter": "Фильтр объектов", + "resolve-multiple": "Принять как несколько объектов", + "filter-type": "Тип фильтра", + "filter-type-required": "Тип фильтра обязателен.", + "entity-filter-no-entity-matched": "Объекты, соответствующие фильтру, не найдены.", + "no-entity-filter-specified": "Не указан фильтр объектов", + "root-state-entity": "Использовать объект, полученный из дашборда, как корневой", + "root-entity": "Корневой объект", + "state-entity-parameter-name": "Название объекта состояния", + "default-state-entity": "Объект состояния по умолчанию", + "default-entity-parameter-name": "По умолчанию", + "max-relation-level": "Максимальная глубина отношений", + "unlimited-level": "Неограниченная глубина", + "state-entity": "Объект состояния дашборда", + "all-entities": "Все объекты", + "any-relation": "не указано" + }, + "asset": { + "asset": "Актив", + "assets": "Активы", + "management": "Управление активами", + "view-assets": "Просмотреть активы", + "add": "Добавить актив", + "assign-to-customer": "Присвоить клиенту", + "assign-asset-to-customer": "Присвоить актив(ы) клиенту", + "assign-asset-to-customer-text": "Пожалуйста, выберите активы, которые нужно присвоить объекту", + "no-assets-text": "Активы не найдены", + "assign-to-customer-text": "Пожалуйста, выберите клиента, которому нужно присвоить актив(ы)", + "public": "Общедоступные", + "assignedToCustomer": "Присвоить клиенту", + "make-public": "Открыть общий доступ к активу", + "make-private": "Закрыть общий доступ к активу", + "unassign-from-customer": "Отозвать у клиента", + "delete": "Удалить актив", + "asset-public": "Актив общедоступный", + "asset-type": "Тип актива", + "asset-type-required": "Тип актива обязателен.", + "select-asset-type": "Выберите тип актива", + "enter-asset-type": "Введите тип актива", + "any-asset": "Любой актив", + "no-asset-types-matching": "Активы типа '{{entitySubtype}}' не найдены.", + "asset-type-list-empty": "Типы активов не выбраны.", + "asset-types": "Типы активов", + "name": "Название", + "name-required": "Название обязательно.", + "description": "Описание", + "type": "Тип", + "type-required": "Тип обязателен.", + "details": "Подробности", + "events": "События", + "add-asset-text": "Добавить новый актив", + "asset-details": "Подробности об активе", + "assign-assets": "Присвоить активы", + "assign-assets-text": "Присвоить { count, plural, 1 {1 актив} few {# актива} other {# активов} } клиенту", + "delete-assets": "Удалить активы", + "unassign-assets": "Отозвать активы", + "unassign-assets-action-title": "Отозвать { count, plural, 1 {1 актив} few {# актива} other {# активов} } у клиента", + "assign-new-asset": "Присвоить новый актив", + "delete-asset-title": "Вы точно хотите удалить '{{assetName}}'?", + "delete-asset-text": "Внимание, после подтверждения актив и все связанные с ним данные будут безвозвратно удалены.", + "delete-assets-title": "Вы точно хотите удалить { count, plural, 1 {1 актив} few {# актива} other {# активов} }", + "delete-assets-action-title": "Удалить { count, plural, 1 {1 актив} few {# актива} other {# активов} }", + "delete-assets-text": "Внимание, после подтверждения выбранные активы и все связанные с ними данные будут безвозвратно удалены.", + "make-public-asset-title": "Вы точно хотите открыть общий доступ к активу '{{assetName}}'?", + "make-public-asset-text": "Внимание, после подтверждения актив и все связанные с ним данные станут общедоступными.", + "make-private-asset-title": "Вы точно хотите закрыть общий доступ к активу '{{assetName}}'?", + "make-private-asset-text": "После подтверждения актив и все связанные с ним данные будут закрыты для общего доступа", + "unassign-asset-title": "Вы точно хотите отозвать актив '{{assetName}}'?", + "unassign-asset-text": "После подтверждения актив будут отозван, и клиент потеряет к нему доступ.", + "unassign-asset": "Отозвать актив", + "unassign-assets-title": "Вы точно хотите отозвать { count, plural, 1 {1 актив} few {# актива} other {# активов} }?", + "unassign-assets-text": "После подтверждения активы будут отозваны, и клиент потеряет к ним доступ.", + "copyId": "Копировать ИД актива", + "idCopiedMessage": "ИД актива скопировано в буфер обмена", + "select-asset": "Выбрать активы", + "no-assets-matching": "Активы, соответствующие '{{entity}}', не найдены.", + "asset-required": "Актив обязателен", + "name-starts-with": "Название актива, начинающееся с" + }, + "attribute": { + "attributes": "Атрибуты", + "latest-telemetry": "Последняя телеметрия", + "attributes-scope": "Контекст атрибутов объекта", + "scope-latest-telemetry": "Последняя телеметрия", + "scope-client": "Клиентские атрибуты", + "scope-server": "Серверные атрибуты", + "scope-shared": "Общие атрибуты", + "add": "Добавить атрибут", + "key": "Ключ", + "last-update-time": "Последнее обновление", + "key-required": "Ключ атрибута обязателен.", + "value": "Значение", + "value-required": "Значение атрибута обязательно.", + "delete-attributes-title": "Вы уверенны, что хотите удалить { count, plural, one {1 атрибут} few {# атрибута} other {# атрибутов} }? ", + "delete-attributes-text": "Внимание, после подтверждения выбранные атрибуты будут удалены.", + "delete-attributes": "Удалить атрибуты", + "enter-attribute-value": "Введите значение атрибута", + "show-on-widget": "Показать на виджете", + "widget-mode": "Виджет-режим", + "next-widget": "Следующий виджет", + "prev-widget": "Предыдущий виджет", + "add-to-dashboard": "Добавить на дашборд", + "add-widget-to-dashboard": "Добавить виджет на дашборд", + "selected-attributes": "{ count, plural, 1 {Выбран} other {Выбраны} } { count, plural, one {1 атрибут} few {# атрибута} other {# атрибутов} }", + "selected-telemetry": "{ count, plural, 1 {Выбран} other {Выбраны} } { count, plural, 1 {1 параметр} few {# параметра} other {# параметров} } телеметрии" + }, + "audit-log": { + "audit": "Аудит", + "audit-logs": "Логи аудита", + "timestamp": "Время", + "entity-type": "Тип объекта", + "entity-name": "Название объекта", + "user": "Пользователь", + "type": "Тип", + "status": "Статус", + "details": "Подробности", + "type-added": "Добавленный", + "type-deleted": "Удаленный", + "type-updated": "Обновленный", + "type-attributes-updated": "Обновлены атрибуты", + "type-attributes-deleted": "Удалены атрибуты", + "type-rpc-call": "RPC вызов", + "type-credentials-updated": "Обновлены учетные данные", + "type-assigned-to-customer": "Присвоен клиенту", + "type-unassigned-from-customer": "Отозван у клиента", + "type-activated": "Активирован", + "type-suspended": "Приостановлен", + "type-credentials-read": "Чтение учетные данных", + "type-attributes-read": "Чтение атрибутов", + "type-relation-add-or-update": "Обновлены отношения", + "type-relation-delete": "Удалены отношения", + "type-relations-delete": "Удалены все отношения", + "type-alarm-ack": "Подтвержден", + "type-alarm-clear": "Сброшен", + "status-success": "Успех", + "status-failure": "Сбой", + "audit-log-details": "Подробности аудит лога", + "no-audit-logs-prompt": "Логи не найдены", + "action-data": "Данные действия", + "failure-details": "Подробности сбоя", + "search": "Поиск аудит логов", + "clear-search": "Очистить поиск" + }, + "confirm-on-exit": { + "message": "У вас есть несохраненные изменения. Вы точно хотите покинуть эту страницу?", + "html-message": "У вас есть несохраненные изменения.
Вы точно хотите покинуть эту страницу?", + "title": "Несохраненные изменения" + }, + "contact": { + "country": "Страна", + "city": "Город", + "state": "Штат", + "postal-code": "Почтовый код", + "postal-code-invalid": "Допустимы только цифры", + "address": "Адрес", + "address2": "Адрес 2", + "phone": "Телефон", + "email": "Эл. адрес", + "no-address": "Адрес не указан" + }, + "common": { + "username": "Имя пользователя", + "password": "Пароль", + "enter-username": "Введите имя пользователя", + "enter-password": "Введите пароль", + "enter-search": "Введите условие поиска" + }, + "content-type": { + "json": "Json", + "text": "Текстовый", + "binary": "Бинарный (Base64)" + }, + "customer": { + "customer": "Клиент", + "customers": "Клиенты", + "management": "Управление клиентами", + "dashboard": "Дашборд клиента", + "dashboards": "Дашборды клиента", + "devices": "Устройства клиента", + "entity-views": "Представления объектов клиента", + "assets": "Активы клиента", + "public-dashboards": "Общедоступные дашборды", + "public-devices": "Общедоступные устройства", + "public-assets": "Общедоступные активы", + "public-entity-views": "Общедоступные представления объектов", + "add": "Добавить клиента", + "delete": "Удалить клиента", + "manage-customer-users": "Управление пользователями клиента", + "manage-customer-devices": "Управление устройствами клиента", + "manage-customer-dashboards": "Управление дашбордами клиента", + "manage-public-devices": "Управление общедоступными устройствами", + "manage-public-dashboards": "Управление общедоступными дашбордами", + "manage-customer-assets": "Управление активами клиента", + "manage-public-assets": "Управление общедоступными активами", + "add-customer-text": "Добавить нового клиента", + "no-customers-text": "Клиенты не найдены", + "customer-details": "Подробности о клиенте", + "delete-customer-title": "Вы точно хотите удалить клиента '{{customerTitle}}'?", + "delete-customer-text": "Внимание, после подтверждения клиент и все связанные с ним данные будут безвозвратно удалены.", + "delete-customers-title": "Вы точно хотите удалить { count, plural, 1 {1 клиент} few {# клиента} other {# клиентов} }?", + "delete-customers-action-title": "Удалить { count, plural, 1 {1 клиент} few {# клиента} other {# клиентов} }", + "delete-customers-text": "Внимание, после подтверждения выбранные клиенты и все связанные с ними данные будут безвозвратно удалены.", + "manage-users": "Управление пользователями", + "manage-assets": "Управление активами", + "manage-devices": "Управление устройствами", + "manage-dashboards": "Управление дашбордами", + "title": "Имя", + "title-required": "Название обязательно.", + "description": "Описание", + "details": "Подробности", + "events": "События", + "copyId": "Копировать ИД клиента", + "idCopiedMessage": "ИД клиента скопирован в буфер обмена", + "select-customer": "Выбрать клиента", + "no-customers-matching": "Клиенты, соответствующие '{{entity}}', не найдены.", + "customer-required": "Клиент обязателен", + "select-default-customer": "Выбрать клиента по умолчанию", + "default-customer": "Клиент по умолчанию", + "default-customer-required": "Клиент по умолчанию обязателен для отладки дашборда на уровне на уровне Владельца" + }, + "datetime": { + "date-from": "Дата с", + "time-from": "Время с", + "date-to": "Дата до", + "time-to": "Время до" + }, + "dashboard": { + "dashboard": "Дашборд", + "dashboards": "Дашборды", + "management": "Управление дашбордами", + "view-dashboards": "Просмотреть дашборды", + "add": "Добавить дашборд", + "assign-dashboard-to-customer": "Прикрепить дашборд(ы) к клиенту", + "assign-dashboard-to-customer-text": "Пожалуйста, выберите дашборды, которые нужно прикрепить к клиенту", + "assign-to-customer-text": "Пожалуйста, выберите клиента, к которому нужно прикрепить дашборд(ы)", + "assign-to-customer": "Прикрепить к клиенту", + "unassign-from-customer": "Отозвать у клиента", + "make-public": "Открыть дашборд для общего доступа", + "make-private": "Закрыть дашборд для общего доступа", + "manage-assigned-customers": "Управление назначенными клиентами", + "assigned-customers": "Назначенные клиенты", + "assign-to-customers": "Присвоить дашборд(ы) клиенту", + "assign-to-customers-text": "Пожалуйста, выбери клиентов, которым нужно присвоить дашборд(ы)", + "unassign-from-customers": "Отозвать дашборд(ы) у клиентов", + "unassign-from-customers-text": "Пожалуйста, выберите клиентов, у которых нужно отозвать дашборд(ы)", + "no-dashboards-text": "Дашборды не найдены", + "no-widgets": "Виджеты не сконфигурированы", + "add-widget": "Добавить новый виджет", + "title": "Название", + "select-widget-title": "Выберите виджет", + "select-widget-subtitle": "Список доступных виджетов", + "delete": "Удалить дашборд", + "title-required": "Название обязательно.", + "description": "Описание", + "details": "Подробности", + "dashboard-details": "Подробности о дашборде", + "add-dashboard-text": "Добавить новый дашборд", + "assign-dashboards": "Прикрепить дашборды", + "assign-new-dashboard": "Прикрепить новый дашборд", + "assign-dashboards-text": "Прикрепить { count, plural, 1 {1 дашборд} few {# дашборда} other {# дашбордов} } к клиенту", + "unassign-dashboards-action-text": "Отозвать { count, plural, 1 {1 дашборд} few {# дашборда} other {# дашбордов} } у клиента", + "delete-dashboards": "Удалить дашборды", + "unassign-dashboards": "Отозвать дашборды", + "unassign-dashboards-action-title": "Отозвать { count, plural, one {1 дашборд} few {# дашборда} other {# дашбордов} } у клиента", + "delete-dashboard-title": "Вы точно хотите удалить дашборд '{{dashboardTitle}}'?", + "delete-dashboard-text": "Внимание, после подтверждения дашборд и все связанные с ним данные будут безвозвратно утеряны.", + "delete-dashboards-title": "Вы точно хотите удалить { count, plural, one {1 дашборд} few {# дашборда} other {# дашбордов} }?", + "delete-dashboards-action-title": "Удалить { count, plural, one {1 дашборд} few {# дашборда} other {# дашбордов} }", + "delete-dashboards-text": "Внимание, после подтверждения дашборды и все связанные с ними данные будут безвозвратно утеряны.", + "unassign-dashboard-title": "Вы точно хотите отозвать дашборд '{{dashboardTitle}}'?", + "unassign-dashboard-text": "После подтверждения дашборд не будет доступен клиенту.", + "unassign-dashboard": "Отозвать дашборд", + "unassign-dashboards-title": "Вы точно хотите отозвать { count, plural, one {1 дашборд} few {# дашборда} other {# дашбордов} }?", + "unassign-dashboards-text": "После подтверждения выбранные дашборды не будут доступны клиенту.", + "public-dashboard-title": "Теперь дашборд общедоступный", + "public-dashboard-text": "Теперь ваш дашборд {{dashboardTitle}} доступен всем по ссылке:", + "public-dashboard-notice": "Примечание: Для получения доступа к данным устройства нужно открыть общий доступ к этому устройству.", + "make-private-dashboard-title": "Вы точно хотите закрыть общий доступ к дашборду '{{dashboardTitle}}'?", + "make-private-dashboard-text": "После подтверждения дашборд будет закрыт для общего доступа.", + "make-private-dashboard": "Закрыть дашборд для общего доступа", + "socialshare-text": "'{{dashboardTitle}}' сделано ThingsBoard", + "socialshare-title": "'{{dashboardTitle}}' сделано ThingsBoard", + "select-dashboard": "Выберите дашборд", + "no-dashboards-matching": "Дашборд '{{entity}}' не найден.", + "dashboard-required": "Дашборд обязателен.", + "select-existing": "Выберите существующий дашборд", + "create-new": "Создать новый дашборд", + "new-dashboard-title": "Новое название дашборда", + "open-dashboard": "Открыть дашборд", + "set-background": "Установить фон", + "background-color": "Фоновый цвет", + "background-image": "Фоновая картинка", + "background-size-mode": "Размер фона", + "no-image": "Картинка не выбрана", + "drop-image": "Перетащите картинку или кликните для выбора файла.", + "settings": "Настройки", + "columns-count": "Количество колонок", + "columns-count-required": "Количество колонок обязательно.", + "min-columns-count-message": "Минимальное число колонок - 10.", + "max-columns-count-message": "Максимальное число колонок - 1000.", + "widgets-margins": "Величина отступа между виджетами", + "horizontal-margin": "Величина горизонтального отступа", + "horizontal-margin-required": "Величина горизонтального отступа обязательна.", + "min-horizontal-margin-message": "Минимальная величина горизонтального отступа - 0.", + "max-horizontal-margin-message": "Максимальная величина горизонтального отступа - 50.", + "vertical-margin": "Величина вертикального отступа", + "vertical-margin-required": "Величина вертикального отступа обязательна.", + "min-vertical-margin-message": "Минимальная величина вертикального отступа - 0.", + "max-vertical-margin-message": "Максимальная величина вертикального отступа - 50.", + "autofill-height": "Автозаполнение по высоте", + "mobile-layout": "Настройки мобильного режима", + "mobile-row-height": "Высота строки в мобильном режиме, px", + "mobile-row-height-required": "Высота строки в мобильном режиме обязательна.", + "min-mobile-row-height-message": "Минимальная высота строки в мобильном режиме составляет 5 px.", + "max-mobile-row-height-message": "Максимальная высота строки в мобильном режиме составляет 200 px.", + "display-title": "Показать название дашборда", + "toolbar-always-open": "Отображать панель инструментов", + "title-color": "Цвет названия", + "display-dashboards-selection": "Отображать выборку дашбордов", + "display-entities-selection": "Отображать выбору объектов", + "display-dashboard-timewindow": "Показать временное окно", + "display-dashboard-export": "Показать экспорт", + "import": "Импортировать дашборд", + "export": "Экспортировать дашборд", + "export-failed-error": "Не удалось экспортировать дашборд: {{error}}", + "create-new-dashboard": "Создать новый дашборд", + "dashboard-file": "Файл дашборда", + "invalid-dashboard-file-error": "Не удалось импортировать дашборд: неизвестная схема данных дашборда.", + "dashboard-import-missing-aliases-title": "Настроить псевдонимы импортированного дашборда", + "create-new-widget": "Создать новый виджет", + "import-widget": "Импортировать виджет", + "widget-file": "Виджет-файл", + "invalid-widget-file-error": "Не удалось импортировать виджет: неправильный формат данных.", + "widget-import-missing-aliases-title": "Настроить псевдонимы, которые использует импортированный виджет", + "open-toolbar": "Открыть панель инструментов дашборда", + "close-toolbar": "Закрыть панель инструментов", + "configuration-error": "Ошибка в настройках", + "alias-resolution-error-title": "Ошибка в настройках псевдонимов дашборда", + "invalid-aliases-config": "Не удалось найти устройство, соответствующее фильтру псевдонимов.
Пожалуйста, обратитесь к администратору для устранения неполадки.", + "select-devices": "Выберите устройства", + "assignedToCustomer": "Присвоенные клиенту", + "assignedToCustomers": "Присвоенные клиентам", + "public": "Публичный", + "public-link": "Публичная ссылка", + "copy-public-link": "Копировать публичную ссылку", + "public-link-copied-message": "Публичная ссылка на дашборд скопирована в буфер обмена.", + "manage-states": "Управление состоянием дашборда", + "states": "Состояния дашборда", + "search-states": "Поиск состояния дашборда", + "selected-states": "Выбрано { count, plural, 1 {1 состояние} few {# состояния} other {# состояний} } дашборда", + "edit-state": "Изменить состояние дашборда", + "delete-state": "Удалить состояние дашборда", + "add-state": "Добавить состояние дашборда", + "state": "Состояние дашборда", + "state-name": "Название", + "state-name-required": "Название состояния дашборда обязательно.", + "state-id": "ИД состояния", + "state-id-required": "ИД состояния дашборда обязателен.", + "state-id-exists": "Состояния дашборда с таким именем уже существует.", + "is-root-state": "Корневое состояние", + "delete-state-title": "Удалить состояние дашборда", + "delete-state-text": "Вы точно хотите удалить состояние дашборда '{{stateName}}'?", + "show-details": "Показать подробности", + "hide-details": "Скрыть подробности", + "select-state": "Выбрать состояние", + "state-controller": "Контроллер состояния" + }, + "datakey": { + "settings": "Настройки", + "advanced": "Дополнительно", + "label": "Метка", + "color": "Цвет", + "units": "Укажите символы, которые нужно указывать после значения", + "decimals": "Число знаков после запятой", + "data-generation-func": "Функция генерации данных", + "use-data-post-processing-func": "Использовать функцию пост-обработки данных", + "configuration": "Конфигурация ключа данных", + "timeseries": "Телеметрия", + "attributes": "Атрибуты", + "alarm": "Параметры оповещения", + "timeseries-required": "Телеметрия объекта обязательна.", + "timeseries-or-attributes-required": "Телеметрия/атрибуты обязательны.", + "maximum-timeseries-or-attributes": "Максимальное количество параметров телеметрии или атрибутов равно {{count}}", + "alarm-fields-required": "Параметры оповещения обязательны.", + "function-types": "Тип функции", + "function-types-required": "Тип функции обязателен.", + "maximum-function-types": "Максимальное количество типов функции равно {{count}}", + "time-description": "время текущего значения;", + "value-description": "текущее значение;", + "prev-value-description": "результат предыдущего вызова функции;", + "time-prev-description": "время предыдущего значения;", + "prev-orig-value-description": "исходное предыдущее значение;" + }, + "datasource": { + "type": "Тип источника данных", + "name": "Название", + "add-datasource-prompt": "Пожалуйста, добавьте источник данных" + }, + "details": { + "edit-mode": "Режим редактирования", + "toggle-edit-mode": "Режим редактирования" + }, + "device": { + "device": "Устройство", + "device-required": "Устройство обязательно.", + "devices": "Устройства", + "management": "Управление устройствами", + "view-devices": "Просмотреть устройства", + "device-alias": "Псевдоним устройства", + "aliases": "Псевдонимы устройства", + "no-alias-matching": "'{{alias}}' не найден.", + "no-aliases-found": "Псевдонимы не найдены.", + "no-key-matching": "'{{key}}' не найден.", + "no-keys-found": "Ключи не найдены.", + "create-new-alias": "Создать новый!", + "create-new-key": "Создать новый!", + "duplicate-alias-error": "Найден дублирующийся псевдоним '{{alias}}'.
В рамках дашборда псевдонимы устройств должны быть уникальными.", + "configure-alias": "Настроить '{{alias}}' псевдоним", + "no-devices-matching": "Устройство '{{entity}}' не найдено.", + "alias": "Псевдоним", + "alias-required": "Псевдоним устройства обязателен.", + "remove-alias": "Удалить псевдоним устройства", + "add-alias": "Добавить псевдоним устройства", + "name-starts-with": "Название начинается с", + "device-list": "Список устройств", + "use-device-name-filter": "Использовать фильтр", + "device-list-empty": "Устройства не выбраны.", + "device-name-filter-required": "Фильтр названия устройства обязателен.", + "device-name-filter-no-device-matched": "Устройства, названия которых начинаются с '{{device}}', не найдены.", + "add": "Добавить устройство", + "assign-to-customer": "Присвоить клиенту", + "assign-device-to-customer": "Присвоить устройство(а) клиенту", + "assign-device-to-customer-text": "Пожалуйста, выберите устройства, которые нужно присвоить клиенту", + "make-public": "Открыть общий доступ к устройству", + "make-private": "Закрыть общий доступ к устройству", + "no-devices-text": "Устройства не найдены", + "assign-to-customer-text": "Пожалуйста, выберите клиента, которому нужно присвоить устройство(а)", + "device-details": "Подробности об устройстве", + "add-device-text": "Добавить новое устройство", + "credentials": "Учетные данные", + "manage-credentials": "Управление учетными данными", + "delete": "Удалить устройство", + "assign-devices": "Присвоить устройство", + "assign-devices-text": "Присвоить { count, plural, one {1 устройство} few {# устройства} other {# устройств} } клиенту", + "delete-devices": "Удалить устройства", + "unassign-from-customer": "Отозвать у клиенту", + "unassign-devices": "Отозвать устройства", + "unassign-devices-action-title": "Отозвать у клиента { count, plural, one {1 устройство} few {# устройства} other {# устройств} }", + "assign-new-device": "Присвоить новое устройство", + "make-public-device-title": "Вы точно хотите открыть общий доступ к устройству '{{deviceName}}'?", + "make-public-device-text": "После подтверждения устройство и все связанные с ним данные будут общедоступными.", + "make-private-device-title": "Вы точно хотите закрыть общий доступ к устройству '{{deviceName}}'", + "make-private-device-text": "После подтверждения устройство и все связанные с ним данные будут закрыты для общего доступа.", + "view-credentials": "Просмотреть учетные данные", + "delete-device-title": "Вы точно хотите удалить устройство '{{deviceName}}'?", + "delete-device-text": "Внимание, после подтверждения устройство и все связанные с ним данные будут безвозвратно утеряны.", + "delete-devices-title": "Вы точно хотите удалить { count, plural, one {1 устройство} few {# устройства} other {# устройств} }?", + "delete-devices-action-title": "Удалить { count, plural, one {1 устройство} few {# устройства} other {# устройств} } }", + "delete-devices-text": "Внимание, после подтверждения выбранные устройства и все связанные с ними данные будут безвозвратно утеряны..", + "unassign-device-title": "Вы точно хотите отозвать устройство '{{deviceName}}'?", + "unassign-device-text": "После подтверждения устройство будет недоступно клиенту.", + "unassign-device": "Отозвать устройство", + "unassign-devices-title": "Вы точно хотите отозвать { count, plural, one {1 устройство} few {# устройства} other {# устройств} }?", + "unassign-devices-text": "После подтверждения выбранные устройства будут недоступны клиенту.", + "device-credentials": "Учетные данные устройства", + "credentials-type": "Тип учетных данных", + "access-token": "Токен", + "access-token-required": "Токен обязателен.", + "access-token-invalid": "Длина токена должна быть от 1 до 20 символов.", + "rsa-key": "Открытый ключ RSA", + "rsa-key-required": "Открытый ключ RSA обязателен.", + "secret": "Секрет", + "secret-required": "Секрет обязателен.", + "device-type": "Тип устройства", + "device-type-required": "Тип устройства обязатеен.", + "select-device-type": "Выберите тип устройства", + "enter-device-type": "Введите тип устройства", + "any-device": "Любое устройство", + "no-device-types-matching": "Тип устройства, соответствующий '{{entitySubtype}}', не найден.", + "device-type-list-empty": "Не выбран тип устройства.", + "device-types": "Типы устройств", + "name": "Название", + "name-required": "Название обязательно.", + "description": "Описание", + "events": "События", + "details": "Подробности", + "copyId": "Копировать идентификатор устройства", + "copyAccessToken": "Копировать токен", + "idCopiedMessage": "Идентификатор устройства скопирован в буфер обмена", + "accessTokenCopiedMessage": "Токен устройства скопирован в буфер обмена", + "assignedToCustomer": "Присвоен клиенту", + "unable-delete-device-alias-title": "Не удалось удалить псевдоним устройства", + "unable-delete-device-alias-text": "Не удалось удалить псевдоним '{{deviceAlias}}' устройства, т.к. он используется следующими виджетами:
{{widgetsList}}", + "is-gateway": "Гейтвей", + "public": "Общедоступный", + "device-public": "Устройство общедоступно", + "select-device": "Выбрать устройство" + }, + "dialog": { + "close": "Закрыть диалог" + }, + "direction": { + "column": "Колонка", + "row": "Строка" + }, + "error": { + "unable-to-connect": "Не удалось подключиться к серверу! Пожалуйста, проверьте интернет-соединение.", + "unhandled-error-code": "Код необработанной ошибки: {{errorCode}}", + "unknown-error": "Неизвестная ошибка" + }, + "entity": { + "entity": "Объект", + "entities": "Объекты", + "aliases": "Псевдонимы объекта", + "entity-alias": "Псевдоним объекта", + "unable-delete-entity-alias-title": "Не удалось удалить псевдоним объекта", + "unable-delete-entity-alias-text": "Псевдоним объекта '{{entityAlias}}' не может быть удален, так как используется следующим(и) виджетом(ами):
{{widgetsList}}", + "duplicate-alias-error": "Найден дубликат псевдонима '{{alias}}'.
В рамках одного дашборда псевдонимы объектов должны быть уникальными.", + "missing-entity-filter-error": "Отсутствует фильтр для псевдонима '{{alias}}'.", + "configure-alias": "Настроить псевдоним '{{alias}}'", + "alias": "Псевдоним", + "alias-required": "Псевдоним объекта обязателен.", + "remove-alias": "Убрать псевдоним объекта", + "add-alias": "Добавить псевдоним объекта", + "entity-list": "Список объектов", + "entity-type": "Тип объекта", + "entity-types": "Типы объекта", + "entity-type-list": "Список типов объекта", + "any-entity": "Любой объект", + "enter-entity-type": "Введите тип объекта", + "no-entities-matching": "Объекты, соответствующие '{{entity}}', не найдены.", + "no-entity-types-matching": "Типы объектов, соответствующие '{{entityType}}', не найдены.", + "name-starts-with": "Название, начинающееся с", + "use-entity-name-filter": "Использовать фильтр", + "entity-list-empty": "Не выбраны объекты.", + "entity-type-list-empty": "Не выбраны типы объекта.", + "entity-name-filter-required": "Фильтр по названию объекта обязателен.", + "entity-name-filter-no-entity-matched": "Объекты, чье название начинается с '{{entity}}', не найдены.", + "all-subtypes": "Все", + "select-entities": "Выберите объекты", + "no-aliases-found": "Псевдонимы не найдены.", + "no-alias-matching": "Псевдоним '{{alias}}' не найден.", + "create-new-alias": "Создать новый!", + "key": "Ключ", + "key-name": "Название ключа", + "no-keys-found": "Ключ не найден.", + "no-key-matching": "Ключ '{{key}}' не найден.", + "create-new-key": "Создать новый!", + "type": "Тип", + "type-required": "Тип объекта обязателен.", + "type-device": "Устройство", + "type-devices": "Устройства", + "list-of-devices": "{ count, plural, 1 {Одно устройство} other {Список из # устройств} }", + "device-name-starts-with": "Устройства, чьи название начинается с '{{prefix}}'", + "type-asset": "Актив", + "type-assets": "Активы", + "list-of-assets": "{ count, plural, 1 {Один актив} other {Список из # активов} }", + "asset-name-starts-with": "Активы, чьи название начинается с '{{prefix}}'", + "type-entity-view": "Представление Объекта", + "type-entity-views": "Представления Объекта", + "list-of-entity-views": "{ count, plural, 1 {Одно представление объекта} other {Список из # представлений объекта} }", + "entity-view-name-starts-with": "Представления Объекта, чьи название начинается с '{{prefix}}'", + "type-rule": "Правило", + "type-rules": "Правила", + "list-of-rules": "{ count, plural, 1 {Одно правило} other {Список из # правил} }", + "rule-name-starts-with": "Правила, чьи названия начинаются с '{{prefix}}'", + "type-plugin": "Плагин", + "type-plugins": "Плагины", + "list-of-plugins": "{ count, plural, 1 {Один плагин} other {Список из # плагинов} }", + "plugin-name-starts-with": "Плагины, чьи имена начинаются с '{{prefix}}'", + "type-tenant": "Владелец", + "type-tenants": "Владельцы", + "list-of-tenants": "{ count, plural, 1 {Один владелец} other {Список из # владельцев} }", + "tenant-name-starts-with": "Владельцы, чьи имена начинаются с '{{prefix}}'", + "type-customer": "Клиент", + "type-customers": "Клиенты", + "list-of-customers": "{ count, plural, 1 {Один клиент} other {Список из # клиентов} }", + "customer-name-starts-with": "Клиенты, чьи имена начинаются с '{{prefix}}'", + "type-user": "Пользователь", + "type-users": "Пользователи", + "list-of-users": "{ count, plural, 1 {Один пользователь} other {Список из # пользователей} }", + "user-name-starts-with": "Пользователи, чьи имена начинаются с '{{prefix}}'", + "type-dashboard": "Дашборд", + "type-dashboards": "Дашборды", + "list-of-dashboards": "{ count, plural, 1 {Один дашборд} other {Список из # дашбордов} }", + "dashboard-name-starts-with": "Дашборды, чьи названия начинаются с '{{prefix}}'", + "type-alarm": "Оповещение", + "type-alarms": "Оповещения", + "list-of-alarms": "{ count, plural, 1 {Одно оповещение} other {Список из # оповещений} }", + "alarm-name-starts-with": "Оповещения, чьи названия начинаются с '{{prefix}}'", + "type-rulechain": "Цепочка правил", + "type-rulechains": "Цепочки правил", + "list-of-rulechains": "{ count, plural, 1 {Одна цепочка правил} other {Список из # цепочек правил} }", + "rulechain-name-starts-with": "Цепочки правил, чьи названия начинаются с '{{prefix}}'", + "type-rulenode": "Правило", + "type-rulenodes": "Правила", + "list-of-rulenodes": "{ count, plural, 1 {Одно правило} other {Список из # правил} }", + "rulenode-name-starts-with": "Правила, чьи названия начинаются с '{{prefix}}'", + "type-current-customer": "Текущий клиент", + "search": "Поиск объектов", + "selected-entities": "Выбран(ы) { count, plural, 1 {1 объект} few {# объекта} other {# объектов} }", + "entity-name": "Название объекта", + "details": "Подробности об объекте", + "no-entities-prompt": "Объекты не найдены", + "no-data": "Нет данных для отображения", + "columns-to-display": "Отобразить следующие колонки" + }, + "entity-view": { + "entity-view": "Представление Объекта", + "entity-view-required": "Представление объекта обязательно.", + "entity-views": "Представления Объектов", + "management": "Управление представлениями объектов", + "view-entity-views": "Просмотр представлений объектов", + "entity-view-alias": "Псевдоним Представления Объекта", + "aliases": "Псевдонимы Представления Объекта", + "no-alias-matching": "Псевдоним '{{alias}}' не найден.", + "no-aliases-found": "Псевдонимы не найдены.", + "no-key-matching": "Ключ '{{key}}' не найден.", + "no-keys-found": "Ключи не найдены.", + "create-new-alias": "Создать новый!", + "create-new-key": "Создать новый!", + "duplicate-alias-error": "Найден дубликат псевдонима '{{alias}}'.
В рамках одного дашборда псевдонимы представлений объектов должны быть уникальными.", + "configure-alias": "Настроить псевдоним '{{alias}}'", + "no-entity-views-matching": "Объекты, соответствующие '{{entity}}', не найдены.", + "alias": "Псевдонимы", + "alias-required": "Псевдоним представления объекта обязателен.", + "remove-alias": "Убрать псевдоним представления объекта", + "add-alias": "Добавить псевдоним представления объекта", + "name-starts-with": "Представления объектов, чьи название начинается с", + "entity-view-list": "Список представлений объектов", + "use-entity-view-name-filter": "Использовать фильтр", + "entity-view-list-empty": "Не выбраны представления объектов.", + "entity-view-name-filter-required": "Для представлений объектов фильтр по названиям обязателен.", + "entity-view-name-filter-no-entity-view-matched": "Представление объектов, чьи название начинаются с '{{entityView}}', не найдены.", + "add": "Представление объекта", + "assign-to-customer": "Назначить клиенту", + "assign-entity-view-to-customer": "Назначить представление(я) объекта(ов) клиенту", + "assign-entity-view-to-customer-text": "Пожалуйста, выберите представления объектов, которые нужно назначить клиенту", + "no-entity-views-text": "Представления объектов не найдены", + "assign-to-customer-text": "Пожалуйста, выберите клиента, которому нужно назначить представления объектов", + "entity-view-details": "Подробности о представлении объекта", + "add-entity-view-text": "Добавить новое представление объекта", + "delete": "Удалить представление объекта", + "assign-entity-views": "Назначить представления объектов", + "assign-entity-views-text": "Назначить клиенту { count, plural, 1 {1 представление объекта} few {# представления объектов} other {# представлений объектов} }", + "delete-entity-views": "Удалить представления объектов", + "unassign-from-customer": "Отозвать у клиента", + "unassign-entity-views": "Отозвать представления объектов", + "unassign-entity-views-action-title": "Отозвать у клиента { count, plural, 1 {1 представление объекта} few {# представления объектов} other {# представлений объектов} }", + "assign-new-entity-view": "Назначит новое представление объекта", + "delete-entity-view-title": "Вы точно хотите удалить представление объекта '{{entityViewName}}'?", + "delete-entity-view-text": "Внимание, после подтверждения представление объекта и все связанные с ним данные будут безвозвратно удалены.", + "delete-entity-views-title": "Вы точно хотите удалить { count, plural, 1 {1 представление объекта} few {# представления объектов} other {# представлений объектов} }?", + "delete-entity-views-action-title": "Удалить { count, plural, 1 {1 представление объекта} few {# представления объектов} other {# представлений объектов} }", + "delete-entity-views-text": "Внимание, после подтверждения выбранные представления объектов и все связанные с ними данные будут безвозвратно удалены.", + "unassign-entity-view-title": "Вы точно хотите отозвать представление объекта '{{entityViewName}}'?", + "unassign-entity-view-text": "После подтверждение представление объекта будет недоступно клиенту.", + "unassign-entity-view": "Отозвать представление объекта", + "unassign-entity-views-title": "Вы точно хотите отозвать { count, plural, 1 {1 представление объекта} few {# представления объектов} other {# представлений объектов} }?", + "unassign-entity-views-text": "После подтверждение выбранные представления объектов будет недоступно клиенту.", + "entity-view-type": "Тип представления объекта", + "entity-view-type-required": "Тип представления объекта обязателен.", + "select-entity-view-type": "Выберите тип представления объекта", + "enter-entity-view-type": "Введите тип представления объекта", + "any-entity-view": "Любое представление объекта", + "no-entity-view-types-matching": "Типы представления объекта, соответствующие '{{entitySubtype}}', не найдены.", + "entity-view-type-list-empty": "Не выбраны типы представления объекта.", + "entity-view-types": "Типы представления объекта", + "name": "Название", + "name-required": "Название обязательно.", + "description": "Описание", + "events": "События", + "details": "Подробности", + "copyId": "Копировать ИД представление объекта", + "assignedToCustomer": "Назначенные клиенту", + "unable-entity-view-device-alias-title": "Не удалось удалить псевдоним представления объекта", + "unable-entity-view-device-alias-text": "Не удалось удалить псевдоним устройства '{{entityViewAlias}}', т.к. он используется следующими виджетами:
{{widgetsList}}", + "select-entity-view": "Выбрать представление объекта", + "make-public": "Открыть общий доступ к представлению объекта", + "start-date": "Дата начала", + "start-ts": "Время начала", + "end-date": "Дата окончания", + "end-ts": "Время окончания", + "date-limits": "Временной лимит", + "client-attributes": "Клиентские атрибуты", + "shared-attributes": "Общие атрибуты", + "server-attributes": "Серверные атрибуты", + "timeseries": "Телеметрия", + "client-attributes-placeholder": "Клиентские атрибуты", + "shared-attributes-placeholder": "Общие атрибуты", + "server-attributes-placeholder": "Серверные атрибуты", + "timeseries-placeholder": "Телеметрия", + "target-entity": "Целевой объект", + "attributes-propagation": "Пробросить атрибуты", + "attributes-propagation-hint": "Представление объекта автоматически копирует указанные атрибуты с Целевого Объекта каждый раз, когда вы сохраняете или обновляете это представление. В целях производительности атрибуты целевого объекта не пробрасываются в представление объекта на каждом их изменении. Вы можете включить автоматический проброс, настроив в вашей цепочке правило \"copy to view\" и соединив его с сообщениями типа \"Post attributes\" и \"Attributes Updated\".", + "timeseries-data": "Данные телеметрии", + "timeseries-data-hint": "Настроить ключи данных телеметрии целевого объекта, которые будут доступны представлению объекта. Эти данные только для чтения." + }, + "event": { + "event-type": "Тип события", + "type-error": "Ошибка", + "type-lc-event": "Событие жизненного цикла", + "type-stats": "Статистика", + "type-debug-rule-node": "Отладка", + "type-debug-rule-chain": "Отладка", + "no-events-prompt": "События не найдены", + "error": "Ошибка", + "alarm": "Аварийное оповещение", + "event-time": "Время возникновения события", + "server": "Сервер", + "body": "Тело", + "method": "Метод", + "type": "Тип", + "entity": "Объект", + "message-id": "ИД сообщения", + "message-type": "Тип сообщения", + "data-type": "Тип данных", + "relation-type": "Тип отношения", + "metadata": "Метаданные", + "data": "Данные", + "event": "Событие", + "status": "Статус", + "success": "Успех", + "failed": "Неудача", + "messages-processed": "Сообщения обработаны", + "errors-occurred": "Возникли ошибки" + }, + "extension": { + "extensions": "Расширение", + "selected-extensions": "Выбрано { count, plural, 1 {1 расширение} few {# расширения} other {# расширений} }", + "type": "Тип", + "key": "Ключ", + "value": "Значение", + "id": "ИД", + "extension-id": "ИД расширения", + "extension-type": "Тип расширения", + "transformer-json": "JSON *", + "unique-id-required": "Такое ИД расширения уже существует.", + "delete": "Удалить расширение", + "add": "Добавить расширение", + "edit": "Редактировать расширение", + "delete-extension-title": "Вы точно хотите удалить расширение '{{extensionId}}'?", + "delete-extension-text": "Внимание, после подтверждения расширение и все связанные с ним данные будут безвозвратно удалены.", + "delete-extensions-title": "Вы точно хотите удалить { count, plural, 1 {1 расширение} few {# расширения} other {# расширений} }?", + "delete-extensions-text": "Внимание, после подтверждения выбранные расширения будут удалены.", + "converters": "Конвертеры", + "converter-id": "ИД конвертера", + "configuration": "Конфигурация", + "converter-configurations": "Конфигурация конвертера", + "token": "Токен безопасности", + "add-converter": "Добавить конвертер", + "add-config": "Добавить конфигурацию конвертера", + "device-name-expression": "Маска названия устройства", + "device-type-expression": "Маска типа устройства", + "custom": "Пользовательский", + "to-double": "To Double", + "transformer": "Преобразователь", + "json-required": "JSON преобразователя обязателен.", + "json-parse": "Не удалось распознать JSON преобразователя.", + "attributes": "Атрибуты", + "add-attribute": "Добавить атрибут", + "add-map": "Добавить элемент сопоставления", + "timeseries": "Телеметрия", + "add-timeseries": "Добавить параметр телеметрии", + "field-required": "Параметр обязателен", + "brokers": "Брокеры", + "add-broker": "Добавить брокер", + "host": "Хост", + "port": "Порт", + "port-range": "Значение порта лежит в диапазоне от 1 до 65535.", + "ssl": "SSL", + "credentials": "Учетные данные", + "username": "Имя пользователя", + "password": "Пароль", + "retry-interval": "Интервал повтора в миллисекундах", + "anonymous": "Анонимный", + "basic": "Общий", + "pem": "PEM", + "ca-cert": "Файл CA сертификата *", + "private-key": "Файл приватного ключа *", + "cert": "Файл сертификата *", + "no-file": "Не выбран файл.", + "drop-file": "Перетяните файл или нажмите для выбора файла.", + "mapping": "Сопоставление", + "topic-filter": "Фильтр тем", + "converter-type": "Тип конвертера", + "converter-json": "JSON", + "json-name-expression": "JSON выражение для названия устройства", + "topic-name-expression": "Выражение для названия устройства в названии темы", + "json-type-expression": "JSON выражение для типа устройства", + "topic-type-expression": "Выражение для типа устройства в названии темы", + "attribute-key-expression": "Выражение для атрибута", + "attr-json-key-expression": "JSON выражение для атрибута", + "attr-topic-key-expression": "Выражение для атрибута в названии темы", + "request-id-expression": "Выражение для ИД запроса", + "request-id-json-expression": "JSON выражение для ИД запроса", + "request-id-topic-expression": "Выражение для ИД запроса в названии темы", + "response-topic-expression": "Выражение для темы ответов", + "value-expression": "Выражение для значения", + "topic": "Тема", + "timeout": "Таймаут в миллисекундах", + "converter-json-required": "JSON конвертер обязателен.", + "converter-json-parse": "Не удалось распознать JSON конвертера.", + "filter-expression": "Выражение для фильтрации", + "connect-requests": "Запросы о подключении устройства", + "add-connect-request": "Добавить запросы о подключении устройства", + "disconnect-requests": "Запросы об отсоединении устройства", + "add-disconnect-request": "Добавить запрос об отсоединении устройства", + "attribute-requests": "Запросы для атрибутов", + "add-attribute-request": "Добавить запрос для атрибутов", + "attribute-updates": "Обновление атрибутов", + "add-attribute-update": "Добавить обновление атрибутов", + "server-side-rpc": "Серверный RPC", + "add-server-side-rpc-request": "Добавить серверный RPC", + "device-name-filter": "Фильтр для названия устройства", + "attribute-filter": "Фильтр для атрибутов", + "method-filter": "Фильтр для процедур", + "request-topic-expression": "Выражение для темы запросов", + "response-timeout": "Время ожидания ответа в миллисекундах", + "topic-expression": "Выражение для названия темы", + "client-scope": "Клиентский", + "add-device": "Добавить устройство", + "opc-server": "Серверы", + "opc-add-server": "Добавить сервер", + "opc-add-server-prompt": "Пожалуйста, добавьте сервер", + "opc-application-name": "Название приложения", + "opc-application-uri": "URI приложения", + "opc-scan-period-in-seconds": "Частота сканирования в секундах", + "opc-security": "Безопасность", + "opc-identity": "Идентификация", + "opc-keystore": "Хранилище ключей", + "opc-type": "Тип", + "opc-keystore-type": "Тип", + "opc-keystore-location": "Расположение *", + "opc-keystore-password": "Пароль", + "opc-keystore-alias": "Псевдоним", + "opc-keystore-key-password": "Пароль для ключ", + "opc-device-node-pattern": "Паттерн OPC узла устройства", + "opc-device-name-pattern": "Паттерн названия устройства", + "modbus-server": "Серверы/ведомые устройства", + "modbus-add-server": "Добавить сервер/ведомое устройство", + "modbus-add-server-prompt": "Пожалуйста, добавить сервер/ведомое устройство", + "modbus-transport": "Транспорт", + "modbus-port-name": "Название последовательного порта", + "modbus-encoding": "Кодирование символов", + "modbus-parity": "Паритет", + "modbus-baudrate": "Скорость передачи", + "modbus-databits": "Биты данных", + "modbus-stopbits": "Стоп-биты", + "modbus-databits-range": "Параметр \"Биты данных\" может принимать значения 7 или 8.", + "modbus-stopbits-range": "Параметр \"Стоп-биты\" может принимать значения 1 или 2.", + "modbus-unit-id": "ИД устройства", + "modbus-unit-id-range": "ИД устройства должен быть в диапазоне от 1 до 247.", + "modbus-device-name": "Название устройства", + "modbus-poll-period": "Частота опроса (в миллисекундах)", + "modbus-attributes-poll-period": "Частота опроса для атрибутов (в миллисекундах)", + "modbus-timeseries-poll-period": "Частота опроса для телеметрии (в миллисекундах)", + "modbus-poll-period-range": "Значение параметра \"Частота опроса\" должно быть больше ноля.", + "modbus-tag": "Тег", + "modbus-function": "Modbus функция", + "modbus-register-address": "Адрес регистра", + "modbus-register-address-range": "Адрес регистра должен быть в диапазоне от 0 до 65535.", + "modbus-register-bit-index": "Номер бита", + "modbus-register-bit-index-range": "Номер бита должен быть в диапазоне от 0 до 15.", + "modbus-register-count": "Количество регистров", + "modbus-register-count-range": "Количество регистров должно быть больше ноля.", + "modbus-byte-order": "Порядок байтов", + "sync": { + "status": "Статус", + "sync": "Синхронизирован", + "not-sync": "Не синхронизирован", + "last-sync-time": "Время последней синхронизации", + "not-available": "Не доступен" + }, + "export-extensions-configuration": "Экспортировать конфигурацию расширений", + "import-extensions-configuration": "Импортировать конфигурацию расширений", + "import-extensions": "Импортировать расширения", + "import-extension": "Импортировать расширение", + "export-extension": "Экспортировать расширение", + "file": "Файл расширений", + "invalid-file-error": "Не правильный формат файла" + }, + "fullscreen": { + "expand": "Во весь экран", + "exit": "Выйти из полноэкранного режима", + "toggle": "Во весь экран", + "fullscreen": "Полноэкранный режим" + }, + "function": { + "function": "Функция" + }, + "grid": { + "delete-item-title": "Вы точно хотите удалить этот объект?", + "delete-item-text": "Внимание, после подтверждения объект и все связанные с ним данные будут безвозвратно утеряны.", + "delete-items-title": "Вы точно хотите удалить { count, plural, one {1 объект} few {# объекта} other {# объектов} }?", + "delete-items-action-title": "Удалить { count, plural, one {1 объект} few {# объекта} other {# объектов}", + "delete-items-text": "Внимание, после подтверждения выбранные объекты и все связанные с ними данные будут безвозвратно утеряны.", + "add-item-text": "Добавить новый объект", + "no-items-text": "Объекты не найдены", + "item-details": "Подробности об объекте", + "delete-item": "Удалить объект", + "delete-items": "Удалить объекты", + "scroll-to-top": "Прокрутка к началу" + }, + "help": { + "goto-help-page": "Перейти к справке" + }, + "home": { + "home": "Главная", + "profile": "Профиль", + "logout": "Выйти из системы", + "menu": "Меню", + "avatar": "Аватар", + "open-user-menu": "Открыть меню пользователя" + }, + "import": { + "no-file": "Файл не выбран", + "drop-file": "Перетащите JSON файл или кликните для выбора файла." + }, + "item": { + "selected": "Выбранные" + }, + "js-func": { + "no-return-error": "Функция должна возвращать значение!", + "return-type-mismatch": "Функция должна возвращать значение типа '{{type}}'!", + "tidy": "Tidy" + }, + "key-val": { + "key": "Ключ", + "value": "Значение", + "remove-entry": "Удалить элемент", + "add-entry": "Добавить элемент", + "no-data": "Элементы отсутствуют" + }, + "layout": { + "layout": "Макет", + "manage": "Управление макетами", + "settings": "Настройки макета", + "color": "Цвет", + "main": "Основной", + "right": "Правый", + "select": "Выбрать макет" + }, + "legend": { + "direction": "Расположение элементов легенды", + "position": "Расположение легенды", + "show-max": "Показать максимальное значение", + "show-min": "Показать минимальное значение", + "show-avg": "Показать среднее значение", + "show-total": "Показать сумму", + "settings": "Настройки легенды", + "min": "Мин", + "max": "Макс", + "avg": "Среднее", + "total": "Сумма" + }, + "login": { + "login": "Войти", + "request-password-reset": "Запрос на сброс пароля", + "reset-password": "Сбросить пароль", + "create-password": "Создать пароль", + "passwords-mismatch-error": "Введенные пароли должны быть одинаковыми!", + "password-again": "Введите пароль еще раз", + "sign-in": "Пожалуйста, войдите в систему", + "username": "Имя пользователя (эл. адрес)", + "remember-me": "Запомнить меня", + "forgot-password": "Забыли пароль?", + "password-reset": "Пароль сброшен", + "new-password": "Новый пароль", + "new-password-again": "Повторите новый пароль", + "password-link-sent-message": "Ссылка для сброса пароля была успешно отправлена!", + "email": "Эл. адрес" + }, + "position": { + "top": "Верх", + "bottom": "Низ", + "left": "Левый край", + "right": "Правый край" + }, + "profile": { + "profile": "Профиль", + "change-password": "Изменить пароль", + "current-password": "Текущий пароль" + }, + "relation": { + "relations": "Отношения", + "direction": "Направления", + "search-direction": { + "FROM": "От", + "TO": "К" + }, + "direction-type": { + "FROM": "от", + "TO": "к" + }, + "from-relations": "Исходящие отношения", + "to-relations": "Входящие отношения", + "selected-relations": "Выбрано { count, plural, 1 {1 отношение} few {# отношения} other {# отношений} }", + "type": "Тип", + "to-entity-type": "К типу объекта", + "to-entity-name": "К объекта", + "from-entity-type": "От типа объекта", + "from-entity-name": "От объекта", + "to-entity": "К объекту", + "from-entity": "От объекта", + "delete": "Удалить отношение", + "relation-type": "Тип отношения", + "relation-type-required": "Тип отношения обязателен.", + "any-relation-type": "Любой тип", + "add": "Добавить отношение", + "edit": "Редактировать отношение", + "delete-to-relation-title": "Вы точно хотите удалить отношение, идущее к объекту '{{entityName}}'?", + "delete-to-relation-text": "Внимание, после подтверждения объект '{{entityName}}' будет отвязан от текущего объекта.", + "delete-to-relations-title": "Вы точно хотите удалить { count, plural, 1 {1 отношение} few {# отношения} other {# отношений} }?", + "delete-to-relations-text": "Внимание, после подтверждения выбранные объекты будут отвязаны от текущего объекта.", + "delete-from-relation-title": "Вы точно хотите удалить отношение, идущее от объекта '{{entityName}}'?", + "delete-from-relation-text": "Внимание, после подтверждения текущий объект будет отвязан от объекта '{{entityName}}'.", + "delete-from-relations-title": "Вы точно хотите удалить { count, plural, 1 {1 отношение} few {# отношения} other {# отношений} }?", + "delete-from-relations-text": "Внимание, после подтверждения выбранные объекты будут отвязаны от соответствующих объектов.", + "remove-relation-filter": "Удалить фильтр отношений", + "add-relation-filter": "Добавить фильтр отношений", + "any-relation": "Любое отношение", + "relation-filters": "Фильтры отношений", + "additional-info": "Дополнительная информация (JSON)", + "invalid-additional-info": "Не удалось распознать JSON с дополнительной информацией." + }, + "rulechain": { + "rulechain": "Цепочка правил", + "rulechains": "Цепочки правил", + "root": "Корневая", + "delete": "Удалить цепочку правил", + "name": "Названия", + "name-required": "Название необходимо.", + "description": "Описание", + "add": "Добавить цепочку правил", + "set-root": "Сделать цепочку корневой", + "set-root-rulechain-title": "Вы точно хотите сделать цепочку правил '{{ruleChainName}}' корневой?", + "set-root-rulechain-text": "После подтверждения цепочка правил станет корневой и будет обрабатывать все входящие сообщения.", + "delete-rulechain-title": "Вы точно хотите удалить цепочку правил '{{ruleChainName}}'?", + "delete-rulechain-text": "Внимание, после подтверждения цепочка правил и все связанные с ней данные будут безвозвратно удалены.", + "delete-rulechains-title": "Вы точно хотите удалить { count, plural, 1 {1 цепочку правил} few {# цепочки правил} other {# цепочек правил} }?", + "delete-rulechains-action-title": "Удалить { count, plural, 1 {1 цепочку правил} few {# цепочки правил} other {# цепочек правил} }", + "delete-rulechains-text": "Внимание, после подтверждения выбранные цепочки правил и все связанные с ними данные будут безвозвратно удалены.", + "add-rulechain-text": "Добавить новую цепочку правил", + "no-rulechains-text": "Цепочки правил не найдены", + "rulechain-details": "Подробности о цепочке правил", + "details": "Подробности", + "events": "События", + "system": "Системная", + "import": "Импортировать цепочку правил", + "export": "Экспортировать цепочку правил", + "export-failed-error": "Не удалось экспортировать цепочку правил: {{error}}", + "create-new-rulechain": "Создать новую цепочку правил", + "rulechain-file": "Файл цепочки правил", + "invalid-rulechain-file-error": "Не удалось импортировать цепочку правил: неправильный формат.", + "copyId": "Копировать ИД цепочки правил", + "idCopiedMessage": "ИД цепочки правил скопирован в буфер обмена", + "select-rulechain": "Выбрать цепочку правил", + "no-rulechains-matching": "Цепочки правил, соответствующие '{{entity}}', не найдены.", + "rulechain-required": "Цепочка правил обязательна", + "management": "Управление цепочками правил", + "debug-mode": "Режим отладки" + }, + "rulenode": { + "details": "Подробности", + "events": "События", + "search": "Поиск правил", + "open-node-library": "Открыть библиотеку правил", + "add": "Добавить правило", + "name": "Название", + "name-required": "Название обязательно.", + "type": "Тип", + "description": "Описание", + "delete": "Удалить правило", + "select-all-objects": "Выделить все правила и связи", + "deselect-all-objects": "Отменить выделение правил и связей", + "delete-selected-objects": "Удалить выделенные правила и связи", + "delete-selected": "Удалить выделенные", + "select-all": "Выделить всё", + "copy-selected": "Копировать выделенное", + "deselect-all": "Отменить выделение", + "rulenode-details": "Подробности о правиле", + "debug-mode": "Режим отладки", + "configuration": "Настройки", + "link": "Связь", + "link-details": "Подробности о связи правила", + "add-link": "Добавить связь", + "link-label": "Метка связи", + "link-label-required": "Метка связи обязателен.", + "custom-link-label": "Пользовательская метка связи", + "custom-link-label-required": "Пользовательская метка связи обязателен.", + "link-labels": "Метки связи", + "link-labels-required": "Метки связи обязательны.", + "no-link-labels-found": "Метки связи не найдены", + "no-link-label-matching": "Метка '{{label}}' не найдена.", + "create-new-link-label": "Создать новую!", + "type-filter": "Фильтр", + "type-filter-details": "Фильтр входящих сообщений с заданными условиями", + "type-enrichment": "Насыщение", + "type-enrichment-details": "Добавить данные в метадату сообщения", + "type-transformation": "Преобразование", + "type-transformation-details": "Изменить содержимое сообщение и его метадату", + "type-action": "Действие", + "type-action-details": "Выполнить заданное действие", + "type-external": "Сторонние", + "type-external-details": "Взаимодействовать со сторонними системами", + "type-rule-chain": "Цепочка правил", + "type-rule-chain-details": "Перенаправить входящее сообщение в другую цепочку правил", + "type-input": "Вход", + "type-input-details": "Логический вход цепочки правил перенаправляет входящие сообщения в следующее правило", + "type-unknown": "Неизвестный", + "type-unknown-details": "Неопределенное правило", + "directive-is-not-loaded": "Указанная директива конфигурации '{{directiveName}}' не доступна.", + "ui-resources-load-error": "Не удалось загрузить UI ресурсы.", + "invalid-target-rulechain": "Не удалось определить целевую цепочку правил!", + "test-script-function": "Протестировать скрипт", + "message": "Сообщение", + "message-type": "Тип сообщения", + "select-message-type": "Выбрать тип сообщения", + "message-type-required": "Тип сообщения обязателен", + "metadata": "Метаданные", + "metadata-required": "Метаданные объекта не могут быть пустыми.", + "output": "Выход", + "test": "Протестировать", + "help": "Помощь", + "reset-debug-mode": "Сбросить режим отладки во всех правилах" + }, + "tenant": { + "tenant": "Владелец", + "tenants": "Владельцы", + "management": "Управление владельцами", + "add": "Добавить владельца", + "admins": "Администраторы", + "manage-tenant-admins": "Управление администраторами владельца", + "delete": "Удалить владельца", + "add-tenant-text": "Добавить нового владельца", + "no-tenants-text": "Владельцы не найдены", + "tenant-details": "Подробности об владельце", + "delete-tenant-title": "Вы точно хотите удалить владельца '{{tenantTitle}}'?", + "delete-tenant-text": "Внимание, после подтверждения владелец и все связанные с ним данные будут безвозвратно утеряны.", + "delete-tenants-title": "Вы точно хотите удалить { count, plural, one {1 владельца} other {# владельцев} }?", + "delete-tenants-action-title": "Удалить { count, plural, one {1 владельца} other {# владельцев} }", + "delete-tenants-text": "Внимание, после подтверждения выбранные Владельцы и все связанные с ними данные будут безвозвратно утеряны.", + "title": "Имя", + "title-required": "Имя обязательно.", + "description": "Описание", + "details": "Подробности", + "events": "События", + "copyId": "Копировать ИД владельца", + "idCopiedMessage": "ИД владельца скопирован в буфер обмена", + "select-tenant": "Выбрать владельца", + "no-tenants-matching": "Владельцы, соответствующие '{{entity}}', не найдены.", + "tenant-required": "Владелец обязателен" + }, + "timeinterval": { + "seconds-interval": "{ seconds, plural, one {1 секунда} few {# секунды} other {# секунд} }", + "minutes-interval": "{ minutes, plural, one {1 минута} few {# минуты} other {# минут} }", + "hours-interval": "{ hours, plural, one {1 час} few {# часа} other {# часов} }", + "days-interval": "{ days, plural, one {1 день} few {# дня} other {# дней} }", + "days": "Дни", + "hours": "Часы", + "minutes": "Минуты", + "seconds": "Секунды", + "advanced": "Дополнительно" + }, + "timewindow": { + "days": "{ days, plural, one {1 день} few {# дня} other {# дней} }", + "hours": "{ hours, plural, one {1 час} few {# часа} other {# часов} }", + "minutes": "{ minutes, plural, one {1 минута} few {# минуты} other {# минут} }", + "seconds": "{ seconds, plural, one {1 секунда} few {# секунды} other {# секунд} }", + "realtime": "Режим реального времени", + "history": "История", + "last-prefix": "Последние", + "period": "с {{ startTime }} до {{ endTime }}", + "edit": "Изменить временное окно", + "date-range": "Диапазон дат", + "last": "Последние", + "time-period": "Период времени" + }, + "user": { + "user": "Пользователь", + "users": "Пользователи", + "customer-users": "Пользователи клиента", + "tenant-admins": "Администраторы владельца", + "sys-admin": "Системный администратор", + "tenant-admin": "Администратор владельца", + "customer": "Клиент", + "anonymous": "Аноним", + "add": "Добавить пользователя", + "delete": "Удалить пользователя", + "add-user-text": "Добавить нового пользователя", + "no-users-text": "Пользователи не найдены", + "user-details": "Подробности о пользователе", + "delete-user-title": "Вы точно хотите удалить пользователя '{{userEmail}}'?", + "delete-user-text": "Внимание, после подтверждения пользователь и все связанные с ним данные будут безвозвратно утеряны.", + "delete-users-title": "Вы точно хотите удалить { count, plural, one {1 пользователя} other {# пользователей} }?", + "delete-users-action-title": "Удалить { count, plural, one {1 пользователя} other {# пользователей} }", + "delete-users-text": "Внимание, после подтверждения выбранные пользователи и все связанные с ними данные будут безвозвратно утеряны.", + "activation-email-sent-message": "Активационное письмо успешно отправлено!", + "resend-activation": "Повторить отправку активационного письма", + "email": "Эл. адрес", + "email-required": "Эл. адрес обязателен.", + "invalid-email-format": "Не правильный формат письма.", + "first-name": "Имя", + "last-name": "Фамилия", + "description": "Описание", + "default-dashboard": "Дашборд по умолчанию", + "always-fullscreen": "Всегда в полноэкранном режиме", + "select-user": "Выбрать пользователя", + "no-users-matching": "Пользователи, соответствующие '{{entity}}', не найдены.", + "user-required": "Пользователь обязателен", + "activation-method": "Метод активации", + "display-activation-link": "Отобразить ссылку для активации", + "send-activation-mail": "Отправить активационное письмо", + "activation-link": "Активационная ссылка для пользователя", + "activation-link-text": "Для активации пользователя используйте ссылку :", + "copy-activation-link": "Копировать активационную ссылку", + "activation-link-copied-message": "Ссылка для активации пользователя скопировано в буфер обмена", + "details": "Подробности", + "login-as-tenant-admin": "Войти как администратор владельца", + "login-as-customer-user": "Войти как пользователь клиента" + }, + "value": { + "type": "Тип значения", + "string": "Строка", + "string-value": "Строковое значение", + "integer": "Целое число", + "integer-value": "Целочисленное значение", + "invalid-integer-value": "Неправильный формат целого числа", + "double": "Число двойной точности", + "double-value": "Значение двойной точности", + "boolean": "Логический тип", + "boolean-value": "Логическое значение", + "false": "Ложь", + "true": "Правда", + "long": "Целое число" + }, + "widget": { + "widget-library": "Галерея виджетов", + "widget-bundle": "Набор виджетов", + "select-widgets-bundle": "Выберите набор виджетов", + "management": "Управление виджетами", + "editor": "Редактор виджетов", + "widget-type-not-found": "Ошибка при загрузке конфигурации виджета.
Возможно, связанный с ней\n тип виджета уже удален.", + "widget-type-load-error": "Не удалось загрузить виджет по следующим причинам:", + "remove": "Удалить виджет", + "edit": "Редактировать виджет", + "remove-widget-title": "Вы точно хотите удалить виджет '{{widgetTitle}}'?", + "remove-widget-text": "Внимание, после подтверждения виджет и все связанные с ним данные будут безвозвратно утеряны.", + "timeseries": "Выборка по времени", + "search-data": "Search data", + "no-data-found": "No data found", + "latest-values": "Последние значения", + "rpc": "Управляющий виджет", + "alarm": "Alarm widget", + "static": "Статический виджет", + "select-widget-type": "Выберите тип виджета", + "missing-widget-title-error": "Укажите название виджета!", + "widget-saved": "Виджет сохранен", + "unable-to-save-widget-error": "Не удалось сохранить виджет! Виджет содержит ошибки!", + "save": "Сохранить виджет", + "saveAs": "Сохранить виджет как", + "save-widget-type-as": "Сохранить тип виджета как", + "save-widget-type-as-text": "Пожалуйста, введите название виджета и/или укажите целевой набор виджетов", + "toggle-fullscreen": "Во весь экран", + "run": "Запустить виджет", + "title": "Название виджета", + "title-required": "Название виджета обязательно.", + "type": "Тип виджета", + "resources": "Ресурсы", + "resource-url": "JavaScript/CSS URL", + "remove-resource": "Удалить ресурс", + "add-resource": "Добавить ресурс", + "html": "HTML", + "tidy": "Форматировать", + "css": "CSS", + "settings-schema": "Схема конфигурации", + "datakey-settings-schema": "Схема конфигурации ключа данных", + "javascript": "Javascript", + "remove-widget-type-title": "Вы точно хотите удалить виджет '{{widgetName}}'?", + "remove-widget-type-text": "Внимание, после подтверждения тип виджета и все связанные с ним данные будут безвозвратно утеряны.", + "remove-widget-type": "Удалить тип виджета", + "add-widget-type": "Добавить новый тип виджета", + "widget-type-load-failed-error": "Не удалось загрузить тип виджета!", + "widget-template-load-failed-error": "Не удалось загрузить шаблон виджета!", + "add": "Добавить виджет", + "undo": "Откатить изменения в виджете", + "export": "Экспортировать виджет" + }, + "widget-action": { + "header-button": "Кнопка заголовка виджета", + "open-dashboard-state": "Перейти к новомум состоянию дашборда", + "update-dashboard-state": "Обновить текущее состояние дашборда", + "open-dashboard": "Перейти к другому дашборду", + "custom": "Пользовательское действие", + "custom-pretty": "Пользовательское действие (с HTML шаблоном)", + "target-dashboard-state": "Целевое состояние дашборда", + "target-dashboard-state-required": "Целевое состояние дашборда обязательно", + "set-entity-from-widget": "Установить объект из виджета", + "target-dashboard": "Целевой дашборд", + "open-right-layout": "Открыть мобильный режим дашборда" + }, + "widgets-bundle": { + "current": "Текущий набор", + "widgets-bundles": "Наборы виджетов", + "add": "Добавить набор виджетов", + "delete": "Удалить набор виджетов", + "title": "Название", + "title-required": "Название обязательно.", + "add-widgets-bundle-text": "Добавить новый набор виджетов", + "no-widgets-bundles-text": "Наборы виджетов не найдены", + "empty": "Пустой набор виджетов", + "details": "Подробности", + "widgets-bundle-details": "Подробности о наборе виджетов", + "delete-widgets-bundle-title": "Вы точно хотите удалить набор виджетов '{{widgetsBundleTitle}}'?", + "delete-widgets-bundle-text": "Внимание, после подтверждения набор виджетов и все связанные с ним данные будут безвозвратно утеряны.", + "delete-widgets-bundles-title": "Вы точно хотите удалить { count, plural, one {1 набор виджетов} few {# набора виджетов} other {# наборов виджетов} }?", + "delete-widgets-bundles-action-title": "Удалить { count, plural, one {1 набор виджетов} few {# набора виджетов} other {# наборов виджетов} }", + "delete-widgets-bundles-text": "Внимание, после подтверждения выбранные наборы виджетов и все связанные с ними данные будут безвозвратно утеряны..", + "no-widgets-bundles-matching": "Набор виджетов '{{widgetsBundle}}' не найден.", + "widgets-bundle-required": "Набор виджетов обязателен.", + "system": "Системный", + "import": "Импортировать набор виджетов", + "export": "Экспортировать набор виджетов", + "export-failed-error": "Не удалось экспортировать набор виджетов: {{error}}", + "create-new-widgets-bundle": "Создать новый набор виджетов", + "widgets-bundle-file": "Файл набора виджетов", + "invalid-widgets-bundle-file-error": "Не удалось импортировать набор виджетов: неизвестная схема данных набора виджетов." + }, + "widget-config": { + "data": "Данные", + "settings": "Настройки", + "advanced": "Дополнительно", + "title": "Название", + "general-settings": "Общие настройки", + "display-title": "Показать название", + "drop-shadow": "Тень", + "enable-fullscreen": "Во весь экран", + "background-color": "Цвет фона", + "text-color": "Цвет текста", + "padding": "Отступ", + "margin": "Margin", + "widget-style": "Стиль виджета", + "title-style": "Стиль названия", + "mobile-mode-settings": "Настройки мобильного режима", + "order": "Порядок", + "height": "Высота", + "units": "Специальный символ после значения", + "decimals": "Количество цифр после запятой", + "timewindow": "Временное окно", + "use-dashboard-timewindow": "Использовать временное окно дашборда", + "display-legend": "Показать легенду", + "datasources": "Источники данных", + "maximum-datasources": "Максимальной количество источников данных равно {{count}}", + "datasource-type": "Тип", + "datasource-parameters": "Параметры", + "remove-datasource": "Удалить источник данных", + "add-datasource": "Добавить источник данных", + "target-device": "Целевое устройство", + "alarm-source": "Источник оповещения", + "actions": "Действия", + "action": "Действие", + "add-action": "Добавить действие", + "search-actions": "Поиск действий", + "action-source": "Источник действий", + "action-source-required": "Источник действий обязателен.", + "action-name": "Название", + "action-name-required": "Название действия обязательно.", + "action-name-not-unique": "Действие с таким именем уже существует.
Название должно быть уникально в рамках одного источника действий.", + "action-icon": "Иконка", + "action-type": "Тип", + "action-type-required": "Тип действий обязателен.", + "edit-action": "Редактировать действие", + "delete-action": "Удалить действие", + "delete-action-title": "Удалить действие виджета", + "delete-action-text": "Вы точно хотите удалить действие виджета '{{actionName}}'?" + }, + "widget-type": { + "import": "Импортировать тип виджета", + "export": "Экспортировать тип виджета", + "export-failed-error": "Не удалось экспортировать тип виджета: {{error}}", + "create-new-widget-type": "Создать новый тип виджета", + "widget-type-file": "Файл типа виджета", + "invalid-widget-type-file-error": "Не удалось импортировать виджет: неизвестная схема данных типа виджета." + }, + "widgets": { + "date-range-navigator": { + "localizationMap": { + "Sun": "Вс", + "Mon": "Пн", + "Tue": "Вт", + "Wed": "Ср", + "Thu": "Чт", + "Fri": "Пт", + "Sat": "Сб", + "Jan": "Янв.", + "Feb": "Февр.", + "Mar": "Март", + "Apr": "Апр.", + "May": "Май", + "Jun": "Июнь", + "Jul": "Июль", + "Aug": "Авг.", + "Sep": "Сент.", + "Oct": "Окт.", + "Nov": "Нояб.", + "Dec": "Дек.", + "January": "Январь", + "February": "Февраль", + "March": "Март", + "April": "Апрель", + "June": "Июнь", + "July": "Июль", + "August": "Август", + "September": "Сентябрь", + "October": "Октября", + "November": "Ноябрь", + "December": "Декабрь", + "Custom Date Range": "Пользовательский диапазон дат", + "Date Range Template": "Шаблон диапазона дат", + "Today": "Сегодня", + "Yesterday": "Вчера", + "This Week": "На этой неделе", + "Last Week": "Прошлая неделя", + "This Month": "Этот месяц", + "Last Month": "Прошлый месяц", + "Year": "Год", + "This Year": "В этом году", + "Last Year": "Прошлый год", + "Date picker": "Выбор даты", + "Hour": "Час", + "Day": "День", + "Week": "Неделю", + "2 weeks": "2 Недели", + "Month": "Месяц", + "3 months": "3 Месяца", + "6 months": "6 Месяцев", + "Custom interval": "Пользовательский интервал", + "Interval": "Интервал", + "Step size": "Размер шага", + "Ok": "Ok" + } + } + }, + "icon": { + "icon": "Иконка", + "select-icon": "Выбрать иконку", + "material-icons": "Иконки в стиле Material", + "show-all": "Показать все иконки" + }, + "custom": { + "widget-action": { + "action-cell-button": "Кнопка действия ячейки", + "row-click": "Действий при щелчке на строку", + "marker-click": "Действия при щелчке на указателе", + "polygon-click": "Действия при щелчке на полигон", + "tooltip-tag-action": "Действие при подсказке" + } + }, + "language": { + "language": "Язык", + "locales": { + "de_DE": "Немецкий", + "en_US": "Английский", + "zh_CN": "Китайский", + "ko_KR": "Корейский", + "es_ES": "Испанский", + "it_IT": "Итальянский", + "ru_RU": "Русский", + "tr_TR": "Турецкий", + "fr_FR": "Французский", + "ja_JA": "Японский", + "fa_IR": "Персидский", + "uk_UA": "Украинский", + "cs_CZ": "Чешский" + } + } +} \ No newline at end of file diff --git a/ui-ngx/src/assets/locale/locale.constant-tr_TR.json b/ui-ngx/src/assets/locale/locale.constant-tr_TR.json new file mode 100644 index 0000000000..e3726ec841 --- /dev/null +++ b/ui-ngx/src/assets/locale/locale.constant-tr_TR.json @@ -0,0 +1,1609 @@ +{ + "access": { + "unauthorized": "Yetkisiz", + "unauthorized-access": "Yetkisiz Erişim", + "unauthorized-access-text": "Bu kaynağa erişmek için giriş yapmalısınız!", + "access-forbidden": "Erişim Yasaklandı", + "access-forbidden-text": "Bu konuma erişim haklarınız yok!
Bu yere hala erişmek istiyorsanız farklı kullanıcılarla oturum açmayı deneyin.", + "refresh-token-expired": "Oturum süresi doldu", + "refresh-token-failed": "Oturum yenilenemiyor" + }, + "action": { + "activate": "Etkinleştir", + "suspend": "Askıya al", + "save": "Kaydet", + "saveAs": "Farklı Kaydet", + "cancel": "İptal", + "ok": "Tamam", + "delete": "Sil", + "add": "Ekle", + "yes": "Evet", + "no": "Hayır", + "update": "Güncelle", + "remove": "Kaldır", + "search": "Ara", + "clear-search": "Aramayı Temizle", + "assign": "Ata", + "unassign": "Atamayı kaldır", + "share": "Paylaş", + "make-private": "Özel yap", + "apply": "Uygula", + "apply-changes": "Değişiklikleri Uygula", + "edit-mode": "Düzenleme Modu", + "enter-edit-mode": "Düzenleme moduna gir", + "decline-changes": "Değişiklikleri reddet", + "close": "Kapat", + "back": "Geri", + "run": "Çalıştır", + "sign-in": "Giriş yap!", + "edit": "Düzenle", + "view": "Görüntüle", + "create": "Oluştur", + "drag": "Sürükle", + "refresh": "Yenile", + "undo": "Geri al", + "copy": "Kopyala", + "paste": "Yapıştır", + "copy-reference": "Referansı kopyala", + "paste-reference": "Referansı yapıştır", + "import": "İçe aktar", + "export": "Dışa aktar", + "share-via": "{{provider}} ile paylaş" + }, + "aggregation": { + "aggregation": "Toplama", + "function": "Veri toplama işlevi", + "limit": "Maksimum değerler", + "group-interval": "Gruplama aralığı", + "min": "Min", + "max": "Maks", + "avg": "Ortalama", + "sum": "Toplam", + "count": "Sayı", + "none": "Yok" + }, + "admin": { + "general": "Genel", + "general-settings": "Genel Ayarlar", + "outgoing-mail": "Giden Posta", + "outgoing-mail-settings": "Giden Posta Ayarları", + "system-settings": "Sistem Ayarları", + "test-mail-sent": "Test e-postası başarıyla gönderildi!", + "base-url": "Temel URL", + "base-url-required": "Temel URL gerekli.", + "mail-from": "Gönderen Kişi", + "mail-from-required": "Gönderen Kişi gerekli.", + "smtp-protocol": "SMTP protokolü", + "smtp-host": "SMTP sunucusu", + "smtp-host-required": "SMTP sunucusu gerekli.", + "smtp-port": "SMTP portu", + "smtp-port-required": "Bir SMTP portu sağlamalısınız.", + "smtp-port-invalid": "Bu geçerli bir smtp portu gibi görünmüyor.", + "timeout-msec": "Zaman aşımı (milisaniye)", + "timeout-required": "Zaman aşımı değeri gerekli.", + "timeout-invalid": "Bu geçerli bir zaman aşımı gibi görünmüyor.", + "enable-tls": "TLS'i etkinleştir.", + "send-test-mail": "Test e-postası gönder" + }, + "alarm": { + "alarm": "Alarm", + "alarms": "Alarmlar", + "select-alarm": "Alarm seç", + "no-alarms-matching": "'{{entity}}' ile eşleşen alarm bulunamadı.", + "alarm-required": "Alarm gerekli", + "alarm-status": "Alarm durumu", + "search-status": { + "ANY": "Herhangi biri", + "ACTIVE": "Aktif", + "CLEARED": "Temizlendi", + "ACK": "Onaylandı", + "UNACK": "Onaylanmadı" + }, + "display-status": { + "ACTIVE_UNACK": "Aktif Onaylanmadı", + "ACTIVE_ACK": "Aktif Onaylandı", + "CLEARED_UNACK": "Temizlendi Onaylanmadı", + "CLEARED_ACK": "Temizlendi Onaylandı" + }, + "no-alarms-prompt": "Alarm bulunamadı", + "created-time": "Oluşma zamanı", + "type": "Tip", + "severity": "Şiddet", + "originator": "Kaynak", + "originator-type": "Kaynak tipi", + "details": "Detaylar", + "status": "Durum", + "alarm-details": "Alarm detayları", + "start-time": "Başlangıç zamanı", + "end-time": "Bitiş zamanı", + "ack-time": "Onaylanma zamanı", + "clear-time": "Temizlenme zamanı", + "severity-critical": "Kritik", + "severity-major": "Birincil", + "severity-minor": "İkincil", + "severity-warning": "Uyarı", + "severity-indeterminate": "Belirsiz", + "acknowledge": "Onayla", + "clear": "Temizle", + "search": "Alarm ara", + "selected-alarms": "{ count, plural, 1 {1 alarm} other {# alarm} } seçildi", + "no-data": "Görüntülenecek veri bulunmuyor", + "polling-interval": "Alarm yoklama aralığı (saniye)", + "polling-interval-required": "Alarm yoklama aralığı gerekli.", + "min-polling-interval-message": "Alarm yoklama aralığı en az 1 saniye olmalıdır.", + "aknowledge-alarms-title": "{ count, plural, 1 {1 alarmı} other {# alarmı} } onayla", + "aknowledge-alarms-text": "{ count, plural, 1 {1 alarmı} other {# alarmı} } onaylamak istediğinize emin misiniz?", + "clear-alarms-title": "{ count, plural, 1 {1 alarmı} other {# alarmı} } temizle", + "clear-alarms-text": "{ count, plural, 1 {1 alarmı} other {# alarmı} } temizlemek istediğinize emin misiniz?" + }, + "alias": { + "add": "Kısa ad ekle", + "edit": "Kısa ad düzenle", + "name": "Kısa ad", + "name-required": "Kısa ad gerekli", + "duplicate-alias": "Aynı kısa ad daha önce kullanılmış.", + "filter-type-single-entity": "Tek öğe", + "filter-type-entity-list": "Öğe listesi", + "filter-type-entity-name": "Öğe adı", + "filter-type-state-entity": "Kontrol panelinden öğe", + "filter-type-state-entity-description": "Kontrol tablosu durum parametrelerinden alınan öğeler", + "filter-type-asset-type": "Varlık türü", + "filter-type-asset-type-description": "'{{assetType}}' türünde varlıklar", + "filter-type-asset-type-and-name-description": "Adı '{{prefix}}' ile başlayan '{{assetType}}' türünde varlıklar", + "filter-type-device-type": "Aygıt türü", + "filter-type-device-type-description": "'{{deviceType}}' türünde aygıtlar", + "filter-type-device-type-and-name-description": "Adı '{{prefix}}' ile başlayan'{{deviceType}}' türünde aygıtlar", + "filter-type-relations-query": "İlişkiler sorgusu", + "filter-type-relations-query-description": "{{relationType}} türünde ilişkili olan varlıklar: {{entities}}. {{direction}}: {{rootEntity}}", + "filter-type-asset-search-query": "Varlık arama sorgusu", + "filter-type-asset-search-query-description": "{{relationType}} türünde ilişkisi olan varlıklar {{assetTypes}}. {{direction}}: {{rootEntity}}", + "filter-type-device-search-query": "Aygıt arama sorgusu", + "filter-type-device-search-query-description": "{{relationType}} türünde ilişkisi olan aygıt tipleri {{deviceTypes}}. {{direction}}: {{rootEntity}}", + "entity-filter": "Öğe filtresi", + "resolve-multiple": "Çoklu öğe olarak çözümle", + "filter-type": "Filtre tipi", + "filter-type-required": "Filtre tipi gerekli.", + "entity-filter-no-entity-matched": "Belirlenen filtre ile eşleşen bir öğe bulunamadı.", + "no-entity-filter-specified": "Hiçbir öğe filtresi belirtilmedi", + "root-state-entity": "Kontrol panelini kök olarak kullan", + "root-entity": "Kök öğe", + "state-entity-parameter-name": "Durum varlığı parametre adı", + "default-state-entity": "Varsayılan durum öğesi", + "default-entity-parameter-name": "Varsayılan", + "max-relation-level": "Maksimum ilişki düzeyi", + "unlimited-level": "Sınırsız seviye", + "state-entity": "Kontrol paneli öğesi", + "all-entities": "Tüm öğeler", + "any-relation": "Herhangi biri" + }, + "asset": { + "asset": "Varlık", + "assets": "Varlıklar", + "management": "Varlık Yönetimi", + "view-assets": "Varlıkları Görüntüle", + "add": "Varlık ekle", + "assign-to-customer": "Kullanıcı grubuna ata", + "assign-asset-to-customer": "Varlıkları Kullanıcı Grubuna Ata", + "assign-asset-to-customer-text": "Lütfen kullanıcı grubuna atanacak varlıkları seçin", + "no-assets-text": "Varlık bulunamadı", + "assign-to-customer-text": "Lütfen varlıkları atamak için kullanıcı grubu seçin", + "public": "Açık", + "assignedToCustomer": "Kullanıcı grubuna atandı", + "make-public": "Varlığı açık hale getir", + "make-private": "Varlığı özel hale getir", + "unassign-from-customer": "Kullanıcı grubundan atamayı kaldır", + "delete": "Varlığı sil", + "asset-public": "Varlık açık halde", + "asset-type": "Varlık türü", + "asset-type-required": "Varlık türü gerekli.", + "select-asset-type": "Varlık türü seçin", + "enter-asset-type": "Varlık türü girin", + "any-asset": "Herhangi bir varlık", + "no-asset-types-matching": "'{{entitySubtype}}' ile eşleşen varlık bulunamadı.", + "asset-type-list-empty": "Herhangi bir varlık türü bulunamadı.", + "asset-types": "Varlık türleri", + "name": "İsim", + "name-required": "İsim gerekli.", + "description": "Açıklama", + "type": "Tür", + "type-required": "Tür gerekli.", + "details": "Detaylar", + "events": "Olaylar", + "add-asset-text": "Yeni varlık ekle", + "asset-details": "Varlık detayları", + "assign-assets": "Varlıkları ata", + "assign-assets-text": "{ count, plural, 1 {1 varlığı} other {# varlığı} } kullanıcı grubuna ata", + "delete-assets": "Varlıkları sil", + "unassign-assets": "Varlıkların atamalarını kaldır", + "unassign-assets-action-title": "{ count, plural, 1 {1 varlığın} other {# varlığın} } atamalarını kullanıcı grubundan kaldır", + "assign-new-asset": "Yeni varlık ata", + "delete-asset-title": "'{{assetName}}' isimli varlığı silmek istediğinize emin misiniz?", + "delete-asset-text": "UYARI: Onaylandıktan sonra varlık ve ilgili tüm veriler geri yüklenemeyecek şekilde silinecek.", + "delete-assets-title": "{ count, plural, 1 {1 varlığı} other {# varlığı} } silmek istediğinize emin misiniz?", + "delete-assets-action-title": "{ count, plural, 1 {1 varlığı} other {# varlığı} } sil", + "delete-assets-text": "UYARI: Onaylandıktan sonra tüm seçili varlıklar ver ilgili tüm veriler geri yüklenemyeck şekilde silinecek.", + "make-public-asset-title": "'{{assetName}}' isimli varlığı açık hale getirmek istediğinize emin misiniz?", + "make-public-asset-text": "Onaylandıktan sonra varlık ve ilgili veriler açık hale gelecek ve başkaları tarafından erişilebilir olacaktır.", + "make-private-asset-title": "'{{assetName}}' isimli varlığı özel hale getirmek istediğinize emin misiniz?", + "make-private-asset-text": "Onaylandıktan sonra varlık ve ilgili veriler özel hale gelecek ve başkaları tarafından erişilemez olacaktır.", + "unassign-asset-title": "'{{assetName}}' isimli varlığın atamasını kaldırmak istediğinize emin misiniz?", + "unassign-asset-text": "Onaylandıktan sonra varlığın ataması kaldırılacak ve kullanıcı grubu tarafından erişilemez olacaktır.", + "unassign-asset": "Varlık atamasını kaldır", + "unassign-assets-title": " { count, plural, 1 {1 varlık} other {# varlık} } atamasını kaldırmak istediğinize emin misiniz?", + "unassign-assets-text": "Onaylandıktan sonra tüm seçili varlıkların ataması kaldırılacak ve kullanıcı grubu tarafından erişilemez olacaktır.", + "copyId": "Varlık kimliğini kopyala", + "idCopiedMessage": "Varlık kimliği panoya kopyalandı", + "select-asset": "Varlık seç", + "no-assets-matching": "'{{entity}}' isimli varlık bulunamadı.", + "asset-required": "Varlık gerekli", + "name-starts-with": "... ile başlayan varlık adı" + }, + "attribute": { + "attributes": "Öznitelikler", + "latest-telemetry": "Son telemetri", + "attributes-scope": "Varlık öznitelik kapsamı", + "scope-latest-telemetry": "Son telemetri", + "scope-client": "İstemci öznitelikler", + "scope-server": "Sunucu öznitelikler", + "scope-shared": "Paylaşılan öznitelikler", + "add": "Öznitelik ekle", + "key": "Anahtar", + "last-update-time": "Son güncelleme zamanı", + "key-required": "Öznitelik anahtarı gerekli.", + "value": "Değer", + "value-required": "Öznitelik değeri gerekli.", + "delete-attributes-title": "Silmek istediğinize emin misiniz { count, plural, 1 {1 öznitelik} other {# öznitelik} }?", + "delete-attributes-text": "UYARI: Onaylandıktan sonra tüm seçili öznitelikler kaldırılacak.", + "delete-attributes": "Öznitelikleri sil", + "enter-attribute-value": "Öznitelik değeri gir", + "show-on-widget": "Göstergede göster", + "widget-mode": "Gösterge modu", + "next-widget": "Sonraki gösterge", + "prev-widget": "Önceki gösterge", + "add-to-dashboard": "Kontrol paneline ekle", + "add-widget-to-dashboard": "Göstergeyi kontrol paneline ekle", + "selected-attributes": "{ count, plural, 1 {1 öznitelik} other {# öznitelik} } seçildi", + "selected-telemetry": "{ count, plural, 1 {1 telemetri birimi} other {# telemetri birimi} } seçildi" + }, + "audit-log": { + "audit": "Log ve Hata Yönetimi", + "audit-logs": "Loglar ve Hatalar", + "timestamp": "Zaman", + "entity-type": "Kaynak", + "entity-name": "İsim", + "user": "Kullanıcı", + "type": "Tür", + "status": "Durum", + "details": "Detaylar", + "type-added": "Eklendi", + "type-deleted": "Silindi", + "type-updated": "Güncellendi", + "type-attributes-updated": "Özellikler güncellendi", + "type-attributes-deleted": "Özellikler silindi", + "type-rpc-call": "Uzaktan işlem çağrısı", + "type-credentials-updated": "Kimlik bilgileri güncellendi", + "type-assigned-to-customer": "Kullanıcı grubuna atandı", + "type-unassigned-from-customer": "Kullanıcı grubundan atama kaldırıldı", + "type-activated": "Etkinleştirildi", + "type-suspended": "Askıya alındı", + "type-credentials-read": "Kimlik bilgileri okundu", + "type-attributes-read": "Özellikler okundu", + "type-relation-add-or-update": "İlişki güncellendi", + "type-relation-delete": "İlişki silindi", + "type-relations-delete": "Tüm ilişki silindi", + "type-alarm-ack": "Kabul edilen", + "type-alarm-clear": "Temizlendi", + "status-success": "Başarılı", + "status-failure": "Başarısız", + "audit-log-details": "Log ve hata detayları", + "no-audit-logs-prompt": "Log ve hata bulunamadı", + "action-data": "Eylem verisi", + "failure-details": "Başarısız işlem detayları", + "search": "Hata ve Log Geçmişinde Ara", + "clear-search": "Aramayı temizle" + }, + "confirm-on-exit": { + "message": "Kaydedilmemiş değişiklikler var. Sayfadan ayrılmak istediğinize emin misiniz?", + "html-message": "Kaydedilmemiş değişiklikler var.
Sayfadan ayrılmak istediğinize emin misiniz?", + "title": "Kaydedilmemiş Değişiklikler" + }, + "contact": { + "country": "Ülke", + "city": "Şehir", + "state": "Eyalet / İl", + "postal-code": "Posta Kodu", + "postal-code-invalid": "Geçersiz Posta Kodu.", + "address": "Addres", + "address2": "Addres 2", + "phone": "Telefon", + "email": "E-posta", + "no-address": "Adres yok" + }, + "common": { + "username": "Kullanıcı adı", + "password": "Parola", + "enter-username": "Kullanıcı adı gir", + "enter-password": "Parola gir", + "enter-search": "Arama gir" + }, + "content-type": { + "json": "Json", + "text": "Metin", + "binary": "İkili (Base64)" + }, + "customer": { + "customer": "Kullanıcı Grubu", + "customers": "Kullanıcı Grupları", + "management": "Kullanıcı Grubu Yönetimi", + "dashboard": "Kullanıcı Grubu Kontrol Paneli", + "dashboards": "Kullanıcı Grubu Kontrol Panellleri", + "devices": "Kullanıcı Grubu Aygıtları", + "entity-views": "Müşteri Varlığı Görüntüleme Sayısı", + "assets": "Kullanıcı Grubu Varlıkları", + "public-dashboards": "Açık Kontrol Panelleri", + "public-devices": "Açık Aygıtlar", + "public-assets": "Açık Varlıklar", + "public-entity-views": "Kamu Varlık Görüntüleme Sayısı", + "add": "Kullanıcı grubu ekle", + "delete": "Kullanıcı grubunu sil", + "manage-customer-users": "Kullanıcı grubu kullanıcılarını yönet", + "manage-customer-devices": "Kullanıcı grubu aygıtlarını yönet", + "manage-customer-dashboards": "Kullanıcı grubu kontrol panellerini yönet", + "manage-public-devices": "Açık aygıtları yönet", + "manage-public-dashboards": "Açık kontrol panellerini yönet", + "manage-customer-assets": "Kullanıcı Grubu varlıklarını yönet", + "manage-public-assets": "Açık varlıkları yönet", + "add-customer-text": "Yeni Kullanıcı Grubu ekle", + "no-customers-text": "Kullanıcı Grubu bulunamadı", + "customer-details": "Kullanıcı Grubu detayları", + "delete-customer-title": "'{{customerTitle}}' isimli kullanıcı grubunu silmek istediğinize emin misiniz?", + "delete-customer-text": "UYARI: Onaylandıktan sonra kullanıcı grubu ve tüm ilişkili veriler geri yüklenemeyecek şekilde silinecek.", + "delete-customers-title": "{ count, plural, 1 {1 kullanıcı grubunu} other {# kullanıcı grubunu} } silmek istediğinize emin misiniz?", + "delete-customers-action-title": "{ count, plural, 1 {1 kullanıcı grubunu} other {# kullanıcı grubunu} } sil", + "delete-customers-text": "UYARI: Onaylandıktan sonra tüm seçili kullanıcı grupları ve ilişkili veriler geri yüklenemez şekilde silinecek.", + "manage-users": "Kullanıcıları yönet", + "manage-assets": "Varlıkları yönet", + "manage-devices": "Aygıtları yönet", + "manage-dashboards": "Kontrol panellerini yönet", + "title": "Başlık", + "title-required": "Başlık gerekli.", + "description": "Açıklama", + "details": "Detaylar", + "events": "Olaylar", + "copyId": "Kullanıcı kimliğini kopyala", + "idCopiedMessage": "Kullanıcı kimliği panoya kopyalandı", + "select-customer": "Kullanıcı grubunu seç", + "no-customers-matching": "'{{entity}}' ile eşleşen kullanıcı grubu bulunamadı.", + "customer-required": "Kullanıcı grubu gerekli", + "select-default-customer": "Varsayılan müşteriyi seç", + "default-customer": "Varsayılan müşteri", + "default-customer-required": "Kiracı düzeyinde gösterge tablosunda hata ayıklamak için varsayılan müşteri gerekiyor" + }, + "datetime": { + "date-from": "Tarihinden", + "time-from": "Saatinden", + "date-to": "Tarihine", + "time-to": "Saatine" + }, + "dashboard": { + "dashboard": "Kontrol Paneli", + "dashboards": "Kontrol Panelleri", + "management": "Kontrol Paneli Yönetimi", + "view-dashboards": "Kontrol Panellerini Görüntüle", + "add": "Kontrol Paneli Ekle", + "assign-dashboard-to-customer": "Kullanıcı Grubuna Kontrol Panel(ler)i Ata", + "assign-dashboard-to-customer-text": "Lütfen kullanıcı grubuna atanacak kontrol panellerini seçin", + "assign-to-customer-text": "Lütfen kontrol panel(ler)ini atayacak kullanıcı grubu seçin", + "assign-to-customer": "Kullanıcı grubuna ata", + "unassign-from-customer": "Kullanıcı grubundan atamayı kaldır", + "make-public": "Kontrol panelini açık hale getir", + "make-private": "Kontrol panelini özel hale getir", + "manage-assigned-customers": "Atanan müşterileri yönet", + "assigned-customers": "Atanan müşteriler", + "assign-to-customers": "Gösterge Tablosunu / Müşterilerini Müşterilere Atama", + "assign-to-customers-text": "Lütfen gösterge panosunu atamak için müşterileri seçin", + "unassign-from-customers": "Müşterilerden Gösterge Tablosunu (Notlarını) Atama", + "unassign-from-customers-text": "Lütfen gösterge tablosundan atamak için müşterileri seçin", + "no-dashboards-text": "Kontrol paneli bulunamadı", + "no-widgets": "Hiçbir gösterge yapılandırılmadı", + "add-widget": "Yeni gösterge ekle", + "title": "Başlık", + "select-widget-title": "Gösterge seç", + "select-widget-subtitle": "Kullanılabilir gösterge türleri listesi", + "delete": "Kontrol paneli sil", + "title-required": "Başlık gerekli.", + "description": "Açıklama", + "details": "Detaylar", + "dashboard-details": "Kontrol paneli detayları", + "add-dashboard-text": "Yeni kontrol paneli ekle", + "assign-dashboards": "Kontrol panelleri ata", + "assign-new-dashboard": "Yeni kontrol paneli ata", + "assign-dashboards-text": "{ count, plural, 1 {1 kontrol panelini} other {# kontrol panelini} } kullanıcı grubuna ata", + "unassign-dashboards-action-text": "Müşterilerden atama { count, plural, 1 {1 gösterge tablosu} other {# panolar}}", + "delete-dashboards": "Kontrol panellerini sil", + "unassign-dashboards": "Kontrol panellerinden atamayı kaldır", + "unassign-dashboards-action-title": "{ count, plural, 1 {1 kontrol panelinin} other {# kontrol panelinin} } atamaları kullanıcı grubundan kaldır", + "delete-dashboard-title": "'{{dashboardTitle}}' isimli kontrol panelini silmek istediğinize emin misiniz?", + "delete-dashboard-text": "UYARI: Onaylandıktan sonra kontrol paneli ve ilişkili verileri geri yüklenemez şekilde silinecek.", + "delete-dashboards-title": "{ count, plural, 1 {1 kontrol panelini} other {# kontrol panelini} } silmek istediğinize emin misiniz?", + "delete-dashboards-action-title": "{ count, plural, 1 {1 kontrol panelini} other {# kontrol panelini} } sil", + "delete-dashboards-text": "UYARI: Onaylandıktan sonra tüm seçili kontrol panelleri ve ilişkili verileri geri yüklenemez şekilde silinecek.", + "unassign-dashboard-title": "'{{dashboardTitle}}' isimli kontrol panelindeki atamayı kaldırmak istediğinize emin misiniz?", + "unassign-dashboard-text": "Onaylandıktan sonra kontrol panelinin ataması kaldırılacak ve kullanıcı grubu tarafından erişilemez hale gelecektir.", + "unassign-dashboard": "Kontrol panelinin ataması kaldır", + "unassign-dashboards-title": "{count, plural, 1 {1 kontrol panelindeki} other {# kontrol panelindeki} } atamayı kaldırmak istediğinize emin misiniz?", + "unassign-dashboards-text": "Onaylandıktan {{dashboardTitle}}
açık hale getirildi ve bu bağlantıdan erişilebilir durumda", + "public-dashboard-notice": "Not: Kontrol panelinden tüm verilere erişebilmek adına ilişkili aygıtları da açık hale getirmeniz gerekmektedir.", + "make-private-dashboard-title": "'{{dashboardTitle}}' isimli kontrol panelini özel hale getirmek istediğinize emin misiniz?", + "make-private-dashboard-text": "Onaylandıktan sonra kontrol paneli özel hale getirilecek ve başkaları tarafından erişilemez olacak.", + "make-private-dashboard": "Kontrol panelini özel hale getir", + "socialshare-text": "'{{dashboardTitle}}'", + "socialshare-title": "'{{dashboardTitle}}'", + "select-dashboard": "Kontrol paneli seç", + "no-dashboards-matching": "'{{entity}}' ile eşleşen kontrol paneli bulunamadı.", + "dashboard-required": "Kontrol paneli gerekli.", + "select-existing": "Var olan bir kontrol paneli seç", + "create-new": "Yeni bir kontrol paneli oluştur", + "new-dashboard-title": "Yeni kontrol paneli başlığı", + "open-dashboard": "Kontrol panelini aç", + "set-background": "Arka plan belirle", + "background-color": "Arka plan rengi", + "background-image": "Arka plan resmi", + "background-size-mode": "Arka plan boyutu modu", + "no-image": "Hiçbir resim seçilmedi", + "drop-image": "Bir resim bırakın veya yüklenecek dosyayı seçmek için tıklayın.", + "settings": "Ayarlar", + "columns-count": "Kolon sayısı", + "columns-count-required": "Kolon sayısı gerekli.", + "min-columns-count-message": "Kolon sayısı en az 10 olabilir.", + "max-columns-count-message": "Kolon sayısı en fazla 1000 olabilir.", + "widgets-margins": "Göstergeler arasındaki aralık", + "horizontal-margin": "Yatay aralık", + "horizontal-margin-required": "Yatay aralık değeri gerekli.", + "min-horizontal-margin-message": "Yatay aralık değeri en az 0 olabilir.", + "max-horizontal-margin-message": "Yatay aralık değeri en fazla 50 olabilir.", + "vertical-margin": "Dikey aralık", + "vertical-margin-required": "Dikey aralık değeri gerekli.", + "min-vertical-margin-message": "Dikey aralık değeri en az 0 olabilir.", + "max-vertical-margin-message": "Dikey aralık değeri en fazla 50 olabilir.", + "autofill-height": "Otomatik doldurma düzeni yüksekliği", + "mobile-layout": "Mobil düzen ayarları", + "mobile-row-height": "Mobil satır yüksekliği, px", + "mobile-row-height-required": "Mobil satır yüksekliği değeri gerekli.", + "min-mobile-row-height-message": "Mobil satır yükseliği değeri en az 5 px olabilir.", + "max-mobile-row-height-message": "Mobil satır yükseliği değeri en çok 200 px olabilir.", + "display-title": "Kontrol paneli başlığını göster", + "toolbar-always-open": "Araç çubuğunu her zaman açık tut", + "title-color": "Başlık rengi", + "display-dashboards-selection": "Kontrol paneli seçimlerinş göster", + "display-entities-selection": "Varlık seçimlerini göster", + "display-dashboard-timewindow": "Zaman aralığını göster", + "display-dashboard-export": "Dışa aktar seçeneğini göster", + "import": "Kontrol panelini içe aktar", + "export": "Kontrol panelini dışa aktar", + "export-failed-error": "Kontrol paneli dışa aktarılamıyor: {{error}}", + "create-new-dashboard": "Yeni kontrol paneli oluştur", + "dashboard-file": "Kontrol paneli dosyası", + "invalid-dashboard-file-error": "Kontrol paneli içe aktarılamadı: Geçersiz kontrol paneli veri yapısı.", + "dashboard-import-missing-aliases-title": "İçe aktarılan kontrol paneli tarafından kullanılan aygıt kısa adlarını yapılandırın", + "create-new-widget": "Yeni gösterge oluştur", + "import-widget": "Göstergeyi içe aktar", + "widget-file": "Gösterge dosyası", + "invalid-widget-file-error": "Gösterge içe aktarılamadı: Geçersiz gösterge veri yapısı.", + "widget-import-missing-aliases-title": "İçe aktarılan gösterge tarafından kullanılan aygıt kısa adlarını yapılandırın", + "open-toolbar": "Kontrol paneli araç çubuğunu aç", + "close-toolbar": "Araç çubuğunu kapat", + "configuration-error": "Yapılandırma hatası", + "alias-resolution-error-title": "Kontro paneli kısa adları yapılandırma hatası", + "invalid-aliases-config": "Kısa ad filtresiyle eşleşen aygıt bulunamadı.
", + "select-devices": "Aygıt seçin", + "assignedToCustomer": "Kullanıcı grubuna atandı", + "assignedToCustomers": "Kullanıcılara atandı", + "public": "Açık", + "public-link": "Açık bağlantı", + "copy-public-link": "Açık bağlantıyı kopyala", + "public-link-copied-message": "Kontrol paneli açık bağlantısı panoya kopyalandı", + "manage-states": "Kontrol paneli durumlarını yönet", + "states": "Kontrol paneli durumları", + "search-states": "Kontrol paneli durumu ara", + "selected-states": "{ count, plural, 1 {1 kontrol paneli durumu} other {# kontrol paneli durumu} } seçildi", + "edit-state": "Kontrol paneli durumu düzenle", + "delete-state": "Kontrol paneli durumunu sil", + "add-state": "Kontrol paneli durumu ekle", + "state": "Kontrol paneli durumu", + "state-name": "İsim", + "state-name-required": "Kontrol paneli durumu ismi gerekli.", + "state-id": "Durum Kimliği", + "state-id-required": "Kontrol paneli durum kimliği gerekli.", + "state-id-exists": "Aynı kimlikte bir kontrol paneli durumu mevcut.", + "is-root-state": "Kök durum", + "delete-state-title": "Kontrol paneli durumunu sil", + "delete-state-text": "'{{stateName}}' isimli kontrol paneli durumunu silmek istediğinize emin misiniz?", + "show-details": "Detayları göster", + "hide-details": "Detayları gizle", + "select-state": "Hedef durumu seç", + "state-controller": "Durum denetleyicisi" + }, + "datakey": { + "settings": "Ayarlar", + "advanced": "İleri düzey", + "label": "Etiket", + "color": "Renk", + "units": "Değerin yanında göstermek için özel simge", + "decimals": "Noktadan sonraki basamak sayısı", + "data-generation-func": "Veri oluşturma fonksiyonu", + "use-data-post-processing-func": "Veri işleme sonrası fonksiyonunu kullanın", + "configuration": "Veri anahtarı yapılandırması", + "timeseries": "Zaman serisi", + "attributes": "Öznitelikler", + "alarm": "Alarm alanları", + "timeseries-required": "Zaman serisi öğesi gerekli.", + "timeseries-or-attributes-required": "Zaman serisi/öznitelikler öğesi gerekli.", + "maximum-timeseries-or-attributes": "Maksimum { count, plural, 1 {1 zamanserisi/öznitelik kabul edilir.} other {# zamanserisi/öznitelik kabul edilir} }", + "alarm-fields-required": "Alarm alanları gerekli.", + "function-types": "Fonksiyon türleri", + "function-types-required": "Fonksiyon türleri gerekli.", + "maximum-function-types": "Maksimum { count, plural, 1 {1 fonksiyon türü kabul edilir.} other {# fonksiyon türü kabul edilir} }" + }, + "datasource": { + "type": "Veri kaynağı türü", + "name": "İsim", + "add-datasource-prompt": "Lütfen veri kaynağı ekleyin" + }, + "details": { + "edit-mode": "Düzenleme modu", + "toggle-edit-mode": "Düzenleme modunu aç/kapat" + }, + "device": { + "device": "Aygıt", + "device-required": "Aygıt gerekli.", + "devices": "Aygıtlar", + "management": "Aygıt Yönetimi", + "view-devices": "Aygıtları görüntüle", + "device-alias": "Aygıt kısa adı", + "aliases": "Aygıt kısa adları", + "no-alias-matching": "'{{alias}}' bulunamadı.", + "no-aliases-found": "Hiçbir kısa ad bulunamadı.", + "no-key-matching": "'{{key}}' bulunamadı.", + "no-keys-found": "Hiçbir anahtar bulunamadı.", + "create-new-alias": "Yeni bir tane oluştur!", + "create-new-key": "Yeni bir tane oluştur!", + "duplicate-alias-error": "'{{alias}}' daha önce kaydedilmiş.
Aygıt kısa adları kontrol paneli özelinde emsalsiz olmalıdır.", + "configure-alias": "'{{alias}}' kısa adını yapılandırın", + "no-devices-matching": "'{{entity}}' ile eşleşen aygıt bulunamadı.", + "alias": "Kısa ad", + "alias-required": "Aygıt kısa adı gerekli.", + "remove-alias": "Aygıt kısa adını kaldır", + "add-alias": "Aygıt kısa adı ekle", + "name-starts-with": "... ile başlayan aygıt adı", + "device-list": "Aygıt listesi", + "use-device-name-filter": "Filtre kullan", + "device-list-empty": "Hiçbir aygıt seçilmedi.", + "device-name-filter-required": "Aygıt adı filtresi gerekli.", + "device-name-filter-no-device-matched": "'{{device}}' ile başlayan herhangi bir aygıt bulunamadı.", + "add": "Aygıt ekle", + "assign-to-customer": "Kullanıcı grubuna ata", + "assign-device-to-customer": "Aygıt(lar)ı Kullanıcı Grubuna Ata", + "assign-device-to-customer-text": "Lütfen kullanıcı grubuna atanacak aygıtları seçin", + "make-public": "Aygıtı açık hale getir", + "make-private": "Aygıtı gizli hale getir", + "no-devices-text": "Hiçbir aygıt bulunamadı", + "assign-to-customer-text": "Lütfen aygıt(lar)ı atayacak kullanıcı grubu seçin", + "device-details": "Aygıt detayları", + "add-device-text": "Yeni aygıt ekle", + "credentials": "Kimlik bilgileri", + "manage-credentials": "Kimlik bilgilerini yönet", + "delete": "Aygıt sil", + "assign-devices": "Aygıt ata", + "assign-devices-text": "{ count, plural, 1 {1 aygıtı} other {# aygıtı} } kullanıcı grubuna ata", + "delete-devices": "Aygıtları sil", + "unassign-from-customer": "Kullanıcı Grubundan atamayı kaldır", + "unassign-devices": "Aygıtlardan atamayı kaldır", + "unassign-devices-action-title": "{ count, plural, 1 {1 aygıtın} other {# aygıtın} } atamasını kullanıcı grubundan kaldır", + "assign-new-device": "Yeni aygıt ata", + "make-public-device-title": "'{{deviceName}}' isimli aygıtı açık hale getirmek istediğinizden emin misiniz?", + "make-public-device-text": "Onaylandıktan sonra aygıt ve verileri açık hale getirilecek ve diğerleri tarafından erişilebilir olacak.", + "make-private-device-title": "'{{deviceName}}' isimli aygıtı gizli hale getirmek istediğinizden emin misiniz?", + "make-private-device-text": "Onaylandıktan sonra aygıt ve verileri gizli hale getirilecek ve diğerleri tarafından erişilemez olacak.", + "view-credentials": "Kimlik bilgilerini görüntüle", + "delete-device-title": "'{{deviceName}}' isimli aygıtı silmek istediğinize emin misiniz?", + "delete-device-text": "UYARI: Onaylandıktan sonra aygıt ve ilişkili verileri geri yüklenemez şekilde silinecek.", + "delete-devices-title": "{ count, plural, 1 {1 aygıtı} other {# aygıtı} } silmek istediğinize emin misiniz?", + "delete-devices-action-title": "{ count, plural, 1 {1 aygıtı} other {# aygıtı} } sil", + "delete-devices-text": "UYARI: Onaylandıktan sonra tüm seçili aygıtlar ve ilişkili verileri geri yüklenemez şekilde silinecek.", + "unassign-device-title": "'{{deviceName}}' isimli aygıtın atamasını kaldırmak istediğinize emin misiniz?", + "unassign-device-text": "Onaylandıktan sonra aygıtın ataması kaldırılacak ve kullanıcı grubu tarafından erişilemez olacak.", + "unassign-device": "Aygıt atamasını kaldır", + "unassign-devices-title": "{ count, plural, 1 {1 aygıtın} other {# aygıtın} } atamasını kaldırmak istediğinize emin misiniz?", + "unassign-devices-text": "Onaylandıktan sonra seçili aygıtların atamaları kaldırılacak ve kullanıcı grubu tarafından erişilemez olacak.", + "device-credentials": "Aygıt Kimlik Bilgileri", + "credentials-type": "Kimlik Bilgi Türü", + "access-token": "Erişim şifresi", + "access-token-required": "Erişim şifresi gerekli.", + "access-token-invalid": "Erişim şifresi uzunluğu 1 ile 20 karakter arasında olmalıdır.", + "rsa-key": "RSA açık anahtarı", + "rsa-key-required": "RSA açık anahtarı gerekli.", + "secret": "Secret", + "secret-required": "Secret gerekli.", + "device-type": "Aygıt Türü", + "device-type-required": "Aygıt türü gereli.", + "select-device-type": "Aygıt türü seç", + "enter-device-type": "Aygıt türü gir", + "any-device": "Herhangi bir aygıt", + "no-device-types-matching": "'{{entitySubtype}}' ile eşleşen aygıt türü bulunamadı.", + "device-type-list-empty": "Hiçbir aygıt türü seçilmedi.", + "device-types": "Aygıt türleri", + "name": "İsim", + "name-required": "İsim gerekli.", + "description": "Açıklama", + "events": "Olaylar", + "details": "Detaylar", + "copyId": "Aygıt kimliğini kopyala", + "copyAccessToken": "Erişim şifresini kopyala", + "idCopiedMessage": "Aygıt kimliği panoya kopyalandı.", + "accessTokenCopiedMessage": "Aygıt erişim şifresi panoya kopyalandı", + "assignedToCustomer": "Kullanıcı Grubuna atandı", + "unable-delete-device-alias-title": "Aygıt kısa adı silinemedi", + "unable-delete-device-alias-text": "Aygıt kısa adı('{{deviceAlias}}'), şu göstergeler tarafından kullanıldığı için silinemedi:
{{widgetsList}}", + "is-gateway": "Ağ geçidi mi?", + "public": "Açık", + "device-public": "Aygıt açık", + "select-device": "Aygıt seç" + }, + "dialog": { + "close": "Kapat" + }, + "error": { + "unable-to-connect": "Sunucuya bağlanamadı! Lütfen internet bağlantınızı kontrol edin.", + "unhandled-error-code": "İşlenmeyen hata koud: {{errorCode}}", + "unknown-error": "Bilinmeyen hata" + }, + "entity": { + "entity": "Öğe", + "entities": "Öğeler", + "aliases": "Öğe kısa adları", + "entity-alias": "Öğe kısa adı", + "unable-delete-entity-alias-title": "Öğe kısa adı silinemedi", + "unable-delete-entity-alias-text": "Öğe kısa adı('{{entityAlias}}'), şu göstergeler tarafından kullanıldığı için silinemiyor:
{{widgetsList}}", + "duplicate-alias-error": "'{{alias}}' daha önce kaydedilmiş.
Öğe kısa adları kontrol paneli özelinde emsalsiz olmalı.", + "missing-entity-filter-error": "'{{alias}}' için filtre bulunmuyor.", + "configure-alias": "'{{alias}}' kısa adını yapılandır", + "alias": "Kısa ad", + "alias-required": "Öğe kısa adı gerekli.", + "remove-alias": "Öğe kısa adını kaldır", + "add-alias": "Öğe kısa adı ekle", + "entity-list": "Öğe listesi", + "entity-type": "Öğe türü", + "entity-types": "Öğe türleri", + "entity-type-list": "Öğe türü listesi", + "any-entity": "Herhangi bir öğe", + "enter-entity-type": "Öğe türü girin", + "no-entities-matching": "'{{entity}}' ile eşleşen öğe bulunamadı.", + "no-entity-types-matching": "'{{entityType}}' ile eşleşen öğe türü bulunamadı.", + "name-starts-with": "... ile başlayan isim", + "use-entity-name-filter": "Filtre kullan", + "entity-list-empty": "Hiçbir öğe seçilmedi.", + "entity-type-list-empty": "Hiçbir öğe türü seçilmedi.", + "entity-name-filter-required": "Öğe ismi filtresi gerekli.", + "entity-name-filter-no-entity-matched": "'{{entity}}' ile başlayan hiçbir öğe bulunamadı.", + "all-subtypes": "Tümü", + "select-entities": "Öğeleri seç", + "no-aliases-found": "Hiçbir kısa ad bulunamadı.", + "no-alias-matching": "'{{alias}}' bulunamadı.", + "create-new-alias": "Yeni bir tane oluştur!", + "key": "Anahtar", + "key-name": "Anahtar adı", + "no-keys-found": "Hiçbir anahtar bulunamadı.", + "no-key-matching": "'{{key}}' bulunamadı.", + "create-new-key": "Yeni bir tane oluştur!", + "type": "Tür", + "type-required": "Öğe türü gerekli.", + "type-device": "Aygıt", + "type-devices": "Aygıtlar", + "list-of-devices": "{ count, plural, 1 {Bir aygıt} other {# Aygıtın Listesi} }", + "device-name-starts-with": "İsimleri '{{prefix}}' ile başlayan aygıtlar", + "type-asset": "Varlık", + "type-assets": "Varlıklar", + "list-of-assets": "{ count, plural, 1 {Bir varlık} other {# Varlığın Listesi} }", + "asset-name-starts-with": "İsmi '{{prefix}}' ile başlayan varlıklar", + "type-entity-view": "Varlık Görünümü", + "type-entity-views": "Varlık Görünümleri", + "list-of-entity-views": "{ count, plural, 1 {Bir varlık görünümü} other {# varlık görüntüleme}} listesi", + "entity-view-name-starts-with": "Adı {{önek}} ile başlayan varlık görünümleri", + "type-rule": "Kural", + "type-rules": "Kurallar", + "list-of-rules": "{ count, plural, 1 {Bir kural} other {# Kuralın Listesi} }", + "rule-name-starts-with": "İsmi '{{prefix}}' ile başlayan kurallar", + "type-plugin": "Eklenti", + "type-plugins": "Eklentiler", + "list-of-plugins": "{ count, plural, 1 {Bir eklenti} other {# Eklentinin Listesi} }", + "plugin-name-starts-with": "İsmi '{{prefix}}' ile başlayan eklentiler", + "type-tenant": "Tenant", + "type-tenants": "Tenantlar", + "list-of-tenants": "{ count, plural, 1 {Bir tenant} other {# Tenantın Listesi} }", + "tenant-name-starts-with": "İsmi '{{prefix}}' ile başlayan tenantlar", + "type-customer": "Kullanıcı Grubu", + "type-customers": "Kullanıcı Grupları", + "list-of-customers": "{ count, plural, 1 {Bir Kullanıcı Grubu} other {# Kullanıcı Grupları} }", + "customer-name-starts-with": "İsmi '{{prefix}}' ile başlayan Kullanıcı Grupları", + "type-user": "Kullanıcı", + "type-users": "Kullanıcılar", + "list-of-users": "{ count, plural, 1 {Bir kullanıcı} other {# Kullanıcının Listesi} }", + "user-name-starts-with": "İsmi '{{prefix}}' ile başlayan kullanıcılar", + "type-dashboard": "Kontrol paneli", + "type-dashboards": "Kontrol panelleri", + "list-of-dashboards": "{ count, plural, 1 {Bir kontrol paneli} other {# Kontrol Panelinin Listesi} }", + "dashboard-name-starts-with": "İsmi '{{prefix}}' ile başlayan kontrol panelleri", + "type-alarm": "Alarm", + "type-alarms": "Alarmlar", + "list-of-alarms": "{ count, plural, 1 {Bir alarm} other {# Alarmın Listesi} }", + "alarm-name-starts-with": "İsmi '{{prefix}}' ile başlayan alarmlar", + "type-rulechain": "Kural zinciri", + "type-rulechains": "Kural zincirleri", + "list-of-rulechains": "{ count, plural, 1 {Bir kural zinciri} other {# kural zincirinin listesi}}", + "rulechain-name-starts-with": "İsimleri {{prefix}} ile başlayan kural zincirleri", + "type-rulenode": "Kural düğümü", + "type-rulenodes": "Kural düğümleri", + "list-of-rulenodes": "{ count, plural, 1 {Bir kural node} other {# kural düğümünün listesi}}", + "rulenode-name-starts-with": "İsimleri '{{prefix}} ile başlayan kural düğümleri", + "type-current-customer": "Mevcut Müşteri", + "search": "Öğeleri ara", + "selected-entities": "{ count, plural, 1 {1 öğe} other {# öğe} } seçildi", + "entity-name": "Öğe adı", + "details": "Öğe detayları", + "no-entities-prompt": "Hiçbir öğe bulunamadı", + "no-data": "Görüntülenecek veri yok" + }, + "entity-view": { + "entity-view": "Varlık Görünümü", + "entity-views": "Varlık Görünümleri", + "management": "Varlık Görünümü yönetimi", + "view-entity-views": "Varlık Görünümlerini Görüntüle", + "entity-view-alias": "Varlık Görünümü takma adı", + "aliases": "Varlık Görünümü takma adları", + "no-alias-matching": "'{{alias}} bulunamadı. ", + "no-aliases-found": "Takma ad bulunamadı", + "no-key-matching": "'{{anahtar bulunamadı.", + "no-keys-found": "Anahtar bulunamadı.", + "create-new-alias": "Yeni bir tane oluştur!", + "create-new-key": "Yeni bir tane oluştur!", + "duplicate-alias-error": "Yinelenen takma ad bulundu {{alias}} '.. Entity View diğer adlar, gösterge panosunda benzersiz olmalıdır. ", + "configure-alias": "Yapılandırma {{alias}} takma ad", + "no-entity-views-matching": "{{entity}} ile eşleşen hiçbir varlık yorumu bulunamadı. ", + "alias": "Alias", + "alias-required": "Varlık Görünümü takma adı gerekiyor.", + "remove-alias": "Varlık görünümü takma adını kaldır", + "add-alias": "Varlık görünümü takma adı ekle", + "name-starts-with": "Varlık Görünümü adı ile başlıyor", + "entity-view-list": "Varlık Görünümü listesi", + "use-entity-view-name-filter": "Filtre kullan", + "entity-view-list-empty": "Hiçbir varlık görüşü seçilmedi.", + "entity-view-name-filter-required": "Varlık görünüm adı filtresi gerekli.", + "entity-view-name-filter-no-entity-view-matched": "{{entityView}} ile başlayan hiçbir varlık sayısı bulunamadı.", + "add": "Varlık Görünümü Ekle", + "assign-to-customer": "Müşteriye atama", + "assign-entity-view-to-customer": "Varlık Görünümlerini Müşteriye Atama", + "assign-entity-view-to-customer-text": "Lütfen müşteriye atamak için varlık görünümlerini seçin", + "no-entity-views-text": "Varlık görüşü bulunamadı", + "assign-to-customer-text": "Lütfen varlık görünümlerini atamak için müşteriyi seçin", + "entity-view-details": "Varlık görünümü ayrıntıları", + "add-entity-view-text": "Yeni varlık görünümü ekle", + "delete": "Varlık görünümünü sil", + "assign-entity-views": "Varlık görünümleri atama", + "assign-entity-views-text": "Müşteriye { count, plural, 1 {1 entityView} other {# entityViews}} atayın ", + "delete-entity-views": "Varlık görünümlerini sil", + "unassign-from-customer": "Müşteriden atama", + "unassign-entity-views": "Varlık görünümlerini atama", + "unassign-entity-views-action-title": "Müşteriden atama { count, plural, 1 {1 entityView} other {# entityViews}}", + "assign-new-entity-view": "Yeni varlık görünümü atama", + "delete-entity-view-title": "Varlık görünümünü silmek istediğinizden emin misiniz?, {{entityViewName}} '? ", + "delete-entity-view-text": "Dikkatli olun, onaylandıktan sonra varlık görünümü ve ilgili tüm veriler kurtarılamayacak.", + "delete-entity-views-title": "{ count, plural, 1 {1 entityView} other {# entityViews}} varlık görünümüne sahip olmak istediğinizden emin misiniz?", + "delete-entity-views-action-title": "Sil { count, plural, 1 {1 entityView} other {# entityViews}}", + "delete-entity-views-text": "Dikkatli olun, onaylandıktan sonra tüm seçilen görünümler kaldırılacak ve ilgili tüm veriler kurtarılamayacaktır.", + "unassign-entity-view-title": "Varlık görünümünün atamasını kaldırmak istediğinizden emin misiniz? {{entityViewName}} '? ", + "unassign-entity-view-text": "Onaydan sonra varlık görünümü atanmamış olacak ve müşteri tarafından erişilemeyecektir.", + "unassign-entity-view": "Varlık görünümünün atamasını kaldır", + "unassign-entity-views-title": "{ count, plural, 1 {1 entityView} other {# entityViews}} hesabının atamasını kaldırmak istediğinizden emin misiniz?", + "unassign-entity-views-text": "Onaylandıktan sonra, seçilen tüm öğe görünümleri atamadan kaldırılacak ve müşteri tarafından erişilemeyecektir.", + "entity-view-type": "Varlık Görünümü türü", + "entity-view-type-required": "Varlık Görünümü türü gerekli.", + "select-entity-view-type": "Varlık görüntüleme türünü seç", + "enter-entity-view-type": "Varlık görüntüleme türünü girin", + "any-entity-view": "Herhangi bir varlık görünümü", + "no-entity-view-types-matching": "{{entitySubtype}} ile eşleşen hiçbir varlık görüntüleme türü bulunamadı. ", + "entity-view-type-list-empty": "Hiçbir varlık görünümü türü seçilmemiş.", + "entity-view-types": "Varlık Görünümü türleri", + "name": "Ad", + "name-required": "İsim gerekli.", + "description": "Açıklama", + "events": "Etkinlikler", + "details": "Ayrıntılar", + "copyId": "Varlık görüntüleme kimliğini kopyala", + "assignedToCustomer": "Müşteriye atandı", + "unable-entity-view-device-alias-title": "Varlık görünümü takma adı silinemiyor", + "unable-entity-view-device-alias-text": "Cihaz takma adı {{entityViewAlias}} ', aşağıdaki widget (lar) tarafından kullanıldığı şekliyle silinemez:
{{widgetsList}} ", + "select-entity-view": "Varlık görünümünü seç", + "make-public": "Varlığı herkese görünür yap", + "start-ts": "Ts", + "end-ts": "End ts" + }, + "event": { + "event-type": "Olay türü", + "type-error": "Hata", + "type-lc-event": "Yaşam döngüsü olayı", + "type-stats": "İstatistikler", + "type-debug-rule-node": "Hata ayıklama", + "type-debug-rule-chain": "Hata ayıklama", + "no-events-prompt": "Hiçbir olay bulunamadı", + "error": "Hata", + "alarm": "Alarm", + "event-time": "Olay zamanı", + "server": "Sunucu", + "body": "İçerik //(Body)", + "method": "Yöntem", + "type": "Tür", + "entity": "Varlık", + "message-id": "Mesaj Kimliği", + "message-type": "Mesaj tipi", + "data-type": "Veri tipi", + "relation-type": "İlişki Türü", + "metadata": "Meta veri", + "data": "Veri", + "event": "Olay", + "status": "Durum", + "success": "Başarı", + "failed": "Başarısız oldu", + "messages-processed": "Mesajlar işlendi", + "errors-occurred": "Hatalar oluştu" + }, + "extension": { + "extensions": "Uzantılar", + "selected-extensions": "{ count, plural, 1 {1 uzantı} other {# extensions}} seçildi", + "type": "Tür", + "key": "Anahtar", + "value": "Değer", + "id": "İD", + "extension-id": "Uzantı kimliği", + "extension-type": "Uzatma tipi", + "transformer-json": "JSON *", + "unique-id-required": "Mevcut uzantı kimliği zaten mevcut.", + "delete": "Uzantıyı sil", + "add": "Uzantı eklemek", + "edit": "Uzantıyı düzenle", + "delete-extension-title": "{{ExtensionId}} uzantısını silmek istediğinizden emin misiniz? ", + "delete-extension-text": "Dikkatli olun, onaylamadan sonra uzantı ve ilgili tüm veriler kurtarılamaz.", + "delete-extensions-title": "{ count, plural, 1 {1 uzantı} other {# extensions}} silmek istediğinizden emin misiniz?", + "delete-extensions-text": "Dikkatli olun, onaylandıktan sonra tüm seçilen uzantılar kaldırılacak.", + "converters": "Dönüştürücü", + "converter-id": "Dönüştürücü kimliği", + "configuration": "Yapılandırma", + "converter-configurations": "Dönüştürücü yapılandırmaları", + "token": "Güvenlik belirteci", + "add-converter": "Dönüştürücü ekle", + "add-config": "Dönüştürücü yapılandırması ekle", + "device-name-expression": "Cihaz adı ifadesi", + "device-type-expression": "Cihaz tipi ifadesi", + "custom": "Özel", + "to-double": "Çifte", + "transformer": "Transformer", + "json-required": "Trafo jsonu gerekli.", + "json-parse": "Trafo json ayrıştırılamıyor.", + "attributes": "Öznitellikler", + "add-attribute": "Özellik ekle", + "add-map": "Eşleme elemanı ekle", + "timeseries": "Zaman serisi", + "add-timeseries": "Zaman çizelgeleri ekle", + "field-required": "Alan gereklidir", + "brokers": "Komisyoncular", + "add-broker": "Broker ekle", + "host": "Host", + "port": "Liman", + "port-range": "Liman 1'den 65535'e kadar olmalıdır.", + "ssl": "SSL", + "credentials": "Kimlik bilgileri", + "username": "Kullanıcı adı", + "password": "Parola", + "retry-interval": "Milisaniye cinsinden tekrar deneme aralığı", + "anonymous": "Anonim", + "basic": "Temel", + "pem": "PEM", + "ca-cert": "CA sertifika dosyası *", + "private-key": "Özel anahtar dosya *", + "cert": "Sertifika dosyası *", + "no-file": "Dosya seçilmedi.", + "drop-file": "Bir dosya bırakın veya yüklenecek bir dosya seçmek için tıklayın.", + "mapping": "Mapping", + "topic-filter": "Konu filtresi", + "converter-type": "Dönüştürücü tipi", + "converter-json": "Json", + "json-name-expression": "Cihaz adı json ifadesi", + "topic-name-expression": "Cihaz adı konu ifadesi", + "json-type-expression": "Cihaz tipi json ifadesi", + "topic-type-expression": "Cihaz tipi konu ifadesi", + "attribute-key-expression": "Öznitelik anahtar ifadesi", + "attr-json-key-expression": "Öznitelik anahtar json ifadesi", + "attr-topic-key-expression": "Öznitelik anahtar konu ifadesi", + "request-id-expression": "Kimlik ifadesi iste", + "request-id-json-expression": "Kimlik json ifadesi iste", + "request-id-topic-expression": "Kimlik konu ifadesini isteyin", + "response-topic-expression": "Yanıt konusu ifadesi", + "value-expression": "Değer ifadesi", + "topic": "Konu", + "timeout": "Zaman aşımı milisaniye cinsinden", + "converter-json-required": "Dönüştürücü json gerekli.", + "converter-json-parse": "Dönüştürücü json ayrıştırılamıyor.", + "filter-expression": "Filtre ifadesi", + "connect-requests": "İstekleri bağla", + "add-connect-request": "Bağlantı talebi ekle", + "disconnect-requests": "İstekleri kes", + "add-disconnect-request": "Bağlantıyı kes isteği ekle", + "attribute-requests": "Özellik istekleri", + "add-attribute-request": "Özellik isteği ekle", + "attribute-updates": "Öznitelik güncellemeleri", + "add-attribute-update": "Özellik güncellemesi ekle", + "server-side-rpc": "Sunucu tarafı RPC", + "add-server-side-rpc-request": "Sunucu tarafı RPC isteği ekle", + "device-name-filter": "Cihaz adı filtresi", + "attribute-filter": "Özellik filtresi", + "method-filter": "Yöntem filtresi", + "request-topic-expression": "Konu ifadesi iste", + "response-timeout": "Milisaniye cinsinden yanıt zaman aşımı", + "topic-expression": "Konu ifadesi", + "client-scope": "Müşteri kapsamı", + "add-device": "Cihaz ekle", + "opc-server": "Sunucular", + "opc-add-server": "Sunucu ekle", + "opc-add-server-prompt": "Lütfen sunucu ekle", + "opc-application-name": "Uygulama Adı", + "opc-application-uri": "Uygulama uri", + "opc-scan-period-in-seconds": "Saniyeler içinde tarama süresi", + "opc-security": "Güvenlik", + "opc-identity": "Kimlik", + "opc-keystore": "Keystore", + "opc-type": "Tür", + "opc-keystore-type": "Tür", + "opc-keystore-location": "Yer *", + "opc-keystore-password": "Parola", + "opc-keystore-alias": "Alias", + "opc-keystore-key-password": "Anahtar şifre", + "opc-device-node-pattern": "Cihaz düğümü modeli", + "opc-device-name-pattern": "Cihaz adı deseni", + "modbus-server": "Sunucular / köle", + "modbus-add-server": "Sunucu ekle / köle", + "modbus-add-server-prompt": "Lütfen sunucu / slave ekle", + "modbus-transport": "Taşıma", + "modbus-port-name": "Seri port adı", + "modbus-encoding": "Kodlama", + "modbus-parity": "Parite", + "modbus-baudrate": "Baud hızı", + "modbus-databits": "Veri bitleri", + "modbus-stopbits": "Bitleri durdur", + "modbus-databits-range": "Veri bitleri 7 ila 8 arasında olmalıdır", + "modbus-stopbits-range": "Durma bitleri 1'den 2'ye kadar olmalıdır.", + "modbus-unit-id": "Birim Kimliği", + "modbus-unit-id-range": "Birim numarası 1 ile 247 arasında olmalıdır.", + "modbus-device-name": "Cihaz adı", + "modbus-poll-period": "Anket dönemi (ms)", + "modbus-attributes-poll-period": "Nitelikler yoklama süresi (ms)", + "modbus-timeseries-poll-period": "Timeseries anket süresi (ms)", + "modbus-poll-period-range": "Anket dönemi pozitif değer olmalı", + "modbus-tag": "Etiket", + "modbus-function": "İşlev", + "modbus-register-address": "Kayıt adresi", + "modbus-register-address-range": "Kayıt adresi 0 ile 65535 arasında olmalıdır.", + "modbus-register-bit-index": "Bit endeksi", + "modbus-register-bit-index-range": "Bit endeksi 0 ile 15 arasında olmalıdır", + "modbus-register-count": "Kayıt sayısı", + "modbus-register-count-range": "Kayıt sayısı pozitif bir değer olmalıdır.", + "modbus-byte-order": "Bayt sırası", + "sync": { + "status": "Durum", + "sync": "Senkronizasyon", + "not-sync": "Eşitleme", + "last-sync-time": "Son senkronizasyon zamanı", + "not-available": "Müsait değil" + }, + "export-extensions-configuration": "İhracat uzantıları yapılandırması", + "import-extensions-configuration": "Uzantılarını içe aktarma yapılandırması", + "import-extensions": "Uzantıları içe aktar", + "import-extension": "Uzantı içe aktar", + "export-extension": "İhracat uzantısı", + "file": "Uzantılar dosyası", + "invalid-file-error": "Geçersiz uzantı dosyası" + }, + "fullscreen": { + "expand": "Tam ekran yap", + "exit": "Tam ekrandan çık", + "toggle": "Tam ekran modu aç/kapat", + "fullscreen": "Tam ekran" + }, + "function": { + "function": "Fonksiyon" + }, + "grid": { + "delete-item-title": "Bu öğeyi silmek istediğinizden emin misiniz?", + "delete-item-text": "UYARI: Onayladıktan sonra bu öğe ve ilişkili tüm verileri geri yüklenemez şekilde silinecektir.", + "delete-items-title": "{ count, plural, 1 {1 öğeyi} other {# öğeyi} } silmek istediğinizden emin misiniz?", + "delete-items-action-title": "{ count, plural, 1 {1 öğeyi} other {# öğeyi} } sil", + "delete-items-text": "UYARI: Onayladıktan sonra tüm seçili öğeler ve ilişkili tüm verileri geri yüklenemez şekilde silinecektir.", + "add-item-text": "Yeni öğe ekle", + "no-items-text": "Hiç bir öğe bulunamadı", + "item-details": "Öğe detayları", + "delete-item": "Öğeyi sil", + "delete-items": "Öğeleri sil", + "scroll-to-top": "Üste kaydır" + }, + "help": { + "goto-help-page": "Yardım sayfasına git" + }, + "home": { + "home": "Ana sayfa", + "profile": "Profil", + "logout": "Çıkış", + "menu": "Menü", + "avatar": "Avatar", + "open-user-menu": "Kullanıcı menüsünü aç" + }, + "import": { + "no-file": "Hiçbir dosya seçilmedi", + "drop-file": "Bir JSON dosyası bırakın veya yüklenecek bir dosyayı seçmek için tıklayın." + }, + "item": { + "selected": "Seçildi" + }, + "js-func": { + "no-return-error": "Fonksiyon bir değer dönmeli!", + "return-type-mismatch": "Fonksiyon '{{type}}' türünde bir değer dönmeli!", + "tidy": "Düzenli" + }, + "key-val": { + "key": "Anahtar", + "value": "Değer", + "remove-entry": "Girişi kaldır", + "add-entry": "Giriş ekle", + "no-data": "Giriş yok" + }, + "layout": { + "layout": "Arayüz Düzeni", + "manage": "Arayüz düzenini yönet", + "settings": "Arayüz düzeni ayarları", + "color": "Renk", + "main": "Ana", + "right": "Sağ", + "select": "Hedef düzen seç" + }, + "legend": { + "position": "Lejant konumu", + "show-max": "Maksimum değeri göster", + "show-min": "Minimum değeri göster", + "show-avg": "Ortalama değeri göster", + "show-total": "Toplam değeri göster", + "settings": "Lejant ayarları", + "min": "min", + "max": "maks", + "avg": "ort.", + "total": "toplam" + }, + "login": { + "login": "Oturum aç", + "request-password-reset": "Parola Sıfırlama İsteği Gönder", + "reset-password": "Parola Sıfırla", + "create-password": "Parola Oluştur", + "passwords-mismatch-error": "Girilen parolalar eşleşmeli!", + "password-again": "Parola tekrarı", + "sign-in": "Lütfen girişi yapın", + "username": "Kullanıcı adı (e-posta)", + "remember-me": "Beni hatırla", + "forgot-password": "Parolamı unuttum", + "password-reset": "Parola sıfırla", + "new-password": "Yeni parola", + "new-password-again": "Yeni parola tekrarı", + "password-link-sent-message": "Parola sıfırlama e-postası başarıyla gönderildi!", + "email": "E-posta" + }, + "position": { + "top": "Üst", + "bottom": "Alt", + "left": "Sol", + "right": "Sağ" + }, + "profile": { + "profile": "Profil", + "change-password": "Şifre değiştir", + "current-password": "Şimdiki şifre" + }, + "relation": { + "relations": "İlişkiler", + "direction": "Yönelim", + "search-direction": { + "FROM": "KAYNAK", + "TO": "HEDEF" + }, + "direction-type": { + "FROM": "kaynak", + "TO": "hedef" + }, + "from-relations": "Giden ilişkiler", + "to-relations": "Gelen ilişkiler", + "selected-relations": "{ count, plural, 1 {1 ilişki} other {# ilişki} } seçildi", + "type": "Tür", + "to-entity-type": "Hedef Öğe Türü", + "to-entity-name": "Hedef Öğe Adı", + "from-entity-type": "Kaynak Öğe Türü", + "from-entity-name": "Kaynak Öğe Adı", + "to-entity": "Hedef Öğe", + "from-entity": "Kaynak Öğe", + "delete": "İlişkiyi sil", + "relation-type": "İlişki türü", + "relation-type-required": "İlişki türü gerekli.", + "any-relation-type": "Her hangi bir tür", + "add": "İlişki ekle", + "edit": "İlişki düzenle", + "delete-to-relation-title": "'{{entityName}}' öğesine olan ilişkiyi silmek istediğinize emin misiniz?", + "delete-to-relation-text": "UYARI: Onaylandıktan sonra '{{entityName}}' öğesinin şimdiki öğeyle olan ilişkisi sona erecektir.", + "delete-to-relations-title": "{ count, plural, 1 {1 ilişkiyi} other {# ilişkiyi} } silmek istediğinize emin misiniz?", + "delete-to-relations-text": "UYARI: Onaylandıktan sonra tüm seçili ilişkiler kaldırılacaktır ve ilgili öğelerin şimdiki öğeyle ilişkisi sona erecektir.", + "delete-from-relation-title": "'{{entityName}}' öğesinden ilişkiyi silmek istediğinize emin misiniz?", + "delete-from-relation-text": "UYARI: Onaylandıktan sonra şimdiki öğenin '{{entityName}}' öğesiyle ilişkisi sonlandırılacaktır.", + "delete-from-relations-title": "{ count, plural, 1 {1 ilişkiyi} other {# ilişkiyi} } silmek istediğinize emin misiniz?", + "delete-from-relations-text": "UYARI: Onaylandıktan sonra tüm seçili ilişkiler kaldırılacak ve şimdiki öğenin ilgili tüm öğelerle ilişkisi sona erecektir.", + "remove-relation-filter": "İlişki filtresini kaldır", + "add-relation-filter": "İlişkisi ekle", + "any-relation": "Herhangi bir ilişki", + "relation-filters": "İlişki filtreleri", + "additional-info": "Ek bilgi (JSON)", + "invalid-additional-info": "Ek bilgi JSON'ı parse edilip işlenemedi." + }, + "rulechain": { + "rulechain": "Kural", + "rulechains": "Kurallar", + "root": "Kök", + "delete": "Kuralı sil", + "name": "İsim", + "name-required": "İsim gerekli.", + "description": "Açıklama", + "add": "Kural Ekle", + "set-root": "Kural zincirinin kökü yap", + "set-root-rulechain-title": "Kural zincirini {{ruleChainName}} root? Yapmak istediğinizden emin misiniz?", + "set-root-rulechain-text": "Onaydan sonra kural zinciri kökleşecek ve gelen tüm iletilerle ilgilenecek.", + "delete-rulechain-title": "'{{ruleName}}' isimli kuralı silmek istediğinize emin misiniz?", + "delete-rulechain-text": "UYARI: Onaylandıktan sonra kural ve ilişkili tüm veriler geri yüklenemez şekilde silinecektir.", + "delete-rulechains-title": "{ count, plural, 1 {1 kuralı} other {# kuralı} } sikmek istediğinize emin misiniz?", + "delete-rulechains-action-title": "{ count, plural, 1 {1 kuralı} other {# kuralı} } sil", + "delete-rulechains-text": "UYARI: Onaylandıktan sonra seçili tüm kurallar ve ilişkili tüm veriler geri yüklenemez şekilde silinecektir.", + "add-rulechain-text": "Yeni kural ekle", + "no-rulechains-text": "Hiçbir kural bulunamadı", + "rulechain-details": "Kural detayları", + "details": "Detaylar", + "events": "Olaylar", + "system": "Sistem", + "import": "Kuralı içe aktar", + "export": "Kuralı dışa aktar", + "export-failed-error": "Kural dışa aktarılamadı: {{error}}", + "create-new-rule": "Yeni kural oluştur", + "rulechain-file": "Kural dosyası", + "invalid-rulechain-file-error": "Kural içe aktarılamadı: Geçersiz kural veri yapısı.", + "copyId": "Kural kimliğini kopyala", + "idCopiedMessage": "Kural kimliği panoya kopyalandı", + "select-rulechain": "Kural seç", + "no-rulechains-matching": "'{{entity}}' ile eşleşen kural bulunamadı.", + "rulechain-required": "Kural gerekli", + "management": "Kural yönetimi", + "debug-mode": "Hata ayıklama modu" + }, + "rulenode": { + "details": "Ayrıntılar", + "events": "Etkinlikler", + "search": "Arama düğümleri", + "open-node-library": "Düğüm kütüphanesini aç", + "add": "Kural düğümü ekle", + "name": "Ad", + "name-required": "İsim gerekli.", + "type": "Tür", + "description": "Açıklama", + "delete": "Kural düğümünü sil", + "select-all-objects": "Tüm düğümleri ve bağlantıları seç", + "deselect-all-objects": "Tüm düğümlerin ve bağlantıların seçimini kaldırın", + "delete-selected-objects": "Seçilen düğümleri ve bağlantıları sil", + "delete-selected": "Silme seçildi", + "select-all": "Hepsini seç", + "copy-selected": "Seçilenleri kopyala", + "deselect-all": "Hiçbirini seçme", + "rulenode-details": "Kural düğümü ayrıntıları", + "debug-mode": "Hata ayıklama modu", + "configuration": "Yapılandırma", + "link": "Bağlantı", + "link-details": "Kural düğüm bağlantı detayları", + "add-link": "Link ekle", + "link-label": "Bağlantı etiketi", + "link-label-required": "Bağlantı etiketi gerekli.", + "custom-link-label": "Özel bağlantı etiketi", + "custom-link-label-required": "Özel bağlantı etiketi gerekli.", + "link-labels": "Link etiketleri", + "link-labels-required": "Link etiketleri gerekli.", + "no-link-labels-found": "Bağlantı etiketi bulunamadı", + "no-link-label-matching": "{{label}} bulunamadı. ", + "create-new-link-label": "Yeni bir tane oluştur!", + "type-filter": "Filtre", + "type-filter-details": "Gelen iletileri yapılandırılmış koşullara göre filtrele", + "type-enrichment": "Zenginleştirme", + "type-enrichment-details": "Mesaj Meta Verilerine ek bilgi", + "type-transformation": "Dönüşüm", + "type-transformation-details": "Mesaj yükünü ve Meta Verileri Değiştir", + "type-action": "Aksiyon", + "type-action-details": "Özel eylem gerçekleştir", + "type-external": "Dış", + "type-external-details": "Dış sistemle etkileşir", + "type-rule-chain": "Kural Zinciri", + "type-rule-chain-details": "Belirtilen Kural Zincirine gelen mesajları ilet", + "type-input": "Giriş", + "type-input-details": "Kural Zinciri'nin mantıksal girdisi, bir sonraki ilgili Kural Düğümüne gelen iletileri iletme", + "type-unknown": "Bilinmeyen", + "type-unknown-details": "Çözümlenmemiş Kural Düğümü", + "directive-is-not-loaded": "Tanımlanmış yapılandırma yönergesi {{directiveName}} 'mevcut değil. ", + "ui-resources-load-error": "Yapılandırma kullanıcı arayüzü kaynakları yüklenemedi.", + "invalid-target-rulechain": "Hedef kural zinciri çözülemiyor!", + "test-script-function": "Test komut dosyası işlevi", + "message": "Mesaj", + "message-type": "Mesaj tipi", + "select-message-type": "Mesaj tipini seç", + "message-type-required": "Mesaj türü gerekli", + "metadata": "Meta veri", + "metadata-required": "Meta veri girişleri boş bırakılamaz.", + "output": "Çıktı", + "test": "Ölçek", + "help": "Yardım et" + }, + "tenant": { + "tenant": "Tenant", + "tenants": "Tenantlar", + "management": "Tenant yönetimi", + "add": "Tenant Ekle", + "admins": "Adminler", + "manage-tenant-admins": "Tenant Adminlerini Yönet", + "delete": "Tenant sil", + "add-tenant-text": "Yeni tenant ekle", + "no-tenants-text": "Hiçbir tenant bulunamadı", + "tenant-details": "Tenant detayları", + "delete-tenant-title": "'{{tenantTitle}}' isimli tenantı silmek istediğinize emin misiniz?", + "delete-tenant-text": "UYARI: Onaylandıktan sonra tenant ve ilişkili tüm veriler geri yüklenemez şekilde silinecektir.", + "delete-tenants-title": "{ count, plural, 1 {1 tenantı} other {# tenantı} } silmek istediğinize emin misiniz?", + "delete-tenants-action-title": "{ count, plural, 1 {1 tenantı} other {# tenantı} } sil", + "delete-tenants-text": "UYARI: Onaylandıktan sonra seçili tüm tenantlar ve ilişkili tüm veriler geri yüklenemez şekilde silinecektir", + "title": "Başlık", + "title-required": "Başlık gerekli.", + "description": "Açıklama", + "details": "Detaylar", + "events": "Olaylar", + "copyId": "Tenant kimliğini kopyala", + "idCopiedMessage": "Tenant kimliği panoya kopyalandı", + "select-tenant": "Tenant seç", + "no-tenants-matching": "'{{entity}}' ile eşleşen tenant bulunamadı.", + "tenant-required": "Tenant gerekli" + }, + "timeinterval": { + "seconds-interval": "{ seconds, plural, 1 {1 saniye} other {# saniye} }", + "minutes-interval": "{ minutes, plural, 1 {1 dakika} other {# dakika} }", + "hours-interval": "{ hours, plural, 1 {1 saat} other {# saat} }", + "days-interval": "{ days, plural, 1 {1 gün} other {# gün} }", + "days": "Gün", + "hours": "Saat", + "minutes": "Dakika", + "seconds": "Saniye", + "advanced": "İleri düzey" + }, + "timewindow": { + "days": "{ days, plural, 1 { gün } other {# gün } }", + "hours": "{ hours, plural, 0 { saat } 1 {1 saat } other {# saat } }", + "minutes": "{ minutes, plural, 0 { dakika } 1 {1 dakika } other {# dakika } }", + "seconds": "{ seconds, plural, 0 { saniye } 1 {1 saniye } other {# saniye } }", + "realtime": "Gerçek zaman", + "history": "Tarih", + "last-prefix": "son", + "period": "{{ startTime }}'dan {{ endTime }}'a kadar", + "edit": "Zaman aralığını düzenle", + "date-range": "Tarih aralığı", + "last": "Son", + "time-period": "Zaman periyodu" + }, + "user": { + "user": "Kullanıcı", + "users": "Kullanıcılar", + "customer-users": "Kullanıcılar", + "tenant-admins": "Tenant Adminleri", + "sys-admin": "Sistem yöneticisi", + "tenant-admin": "Tenant yöneticisi", + "customer": "Kullanıcı Grubu", + "anonymous": "Anonim", + "add": "Kullanıcı ekle", + "delete": "Kullanıcı sil", + "add-user-text": "Yeni kullanıcı ekle", + "no-users-text": "Hiçbir kullanıcı bulunamadı", + "user-details": "Kullanıcı detayları", + "delete-user-title": "'{{userEmail}}' kullanıcısını silmek istediğinize emin misiniz?", + "delete-user-text": "UYARI: Onaylandıktan sonra kullanıcı ve ilişkili tüm verileri geri yüklenemez şekilde silinecektir.", + "delete-users-title": "{ count, plural, 1 {1 kullanıcıyı} other {# kullanıcıyı} } sikmek istediğinize emin misiniz?", + "delete-users-action-title": "{ count, plural, 1 {1 kullancıyı} other {# kullanıcıyı} } sil", + "delete-users-text": "UYARI: Onaylandıktan sonra kullanıcı ve ilişkili tüm verileri geri yüklenemez şekilde silinecektir.", + "activation-email-sent-message": "Etkinleştirme e-postası başarılı bir şekilde gönderildi!", + "resend-activation": "Etkinleştirme e-postasını yeniden gönder", + "email": "E-posta", + "email-required": "E-posta gerekli.", + "invalid-email-format": "Geçersiz e-posta formatı.", + "first-name": "Ad", + "last-name": "Soyad", + "description": "Açıklama", + "default-dashboard": "Varsayılan kontrol paneli", + "always-fullscreen": "Her zaman tam ekran", + "select-user": "Kullanıcı se.", + "no-users-matching": "'{{entity}}' ile eşleşen kullanıcı bulunamadı.", + "user-required": "Kullanıcı gerekli", + "activation-method": "Etkinleştirme yöntemi", + "display-activation-link": "Etkinleştirme bağlantısını görüntüle", + "send-activation-mail": "Etkinleştirme e-postası gönder", + "activation-link": "Kullanıcı hesabını etkinleştirme bağlantısı", + "activation-link-text": "Kullanıcı hesabını etkinleştirmek için bağlantıyı kullanın:", + "copy-activation-link": "Etkinleştirme bağlantısını kopyala", + "activation-link-copied-message": "Kullanıcı hesabı etkinleştirme bağlantısı panoya kopyalandı", + "details": "Ayrıntılar", + "login-as-tenant-admin": "Tenant Yönetici Girişi", + "login-as-customer-user": "Kullanıcı olarak giriş yap" + }, + "value": { + "type": "Değer tğrğ", + "string": "String", + "string-value": "String değeri", + "integer": "Integer", + "integer-value": "Integer değeri", + "invalid-integer-value": "Geçersiz integer değeri", + "double": "Double", + "double-value": "Double değeri", + "boolean": "Boolean", + "boolean-value": "Boolean değeri", + "false": "Yanlış", + "true": "Doğru", + "long": "Uzun" + }, + "widget": { + "widget-library": "Gösterge Kütüphanesi", + "widget-bundle": "Gösterge Demeti", + "select-widgets-bundle": "Gösterge demeti seç", + "management": "Gösterge yönetimi", + "editor": "Gösterge düzenleyici", + "widget-type-not-found": "Gösterge yapılandırması yüklenemedi.
Muhtemelen ilgili\n gösterge türü kaldırılmış.", + "widget-type-load-error": "Gösterge şu sebeplerden dolayı yüklenemedi:", + "remove": "Göstergeyi kaldır", + "edit": "Göstergeyi düzenle", + "remove-widget-title": "'{{widgetTitle}}' isimli göstermeyi kaldırmak istediğinizden emin misiniz?", + "remove-widget-text": "UYARI: Onaylandıktan sonra gösterge ve tüm ilişkili verileri geri yüklenemez şekilde silinecek.", + "timeseries": "Zaman serisi", + "search-data": "Arama verileri", + "no-data-found": "Veri bulunamadı", + "latest-values": "Son değerler", + "rpc": "Kontrol göstergesi", + "alarm": "Alarm göstergesi", + "static": "Statik gösterge", + "select-widget-type": "Gösterge türü seç", + "missing-widget-title-error": "Gösterge başlığı belirtilmelidir!", + "widget-saved": "Gösterge kaydedildi", + "unable-to-save-widget-error": "Gösterge kaydedilemedi! Göstergede hatalar mevcut!", + "save": "Göstergeyi kaydet", + "saveAs": "Göstergeyi farklı kaydet", + "save-widget-type-as": "Gösterge türünü farklı kaydet", + "save-widget-type-as-text": "Lütfen gösterge başlığı girin veya hedef gösterge demeti seçin", + "toggle-fullscreen": "Tam ekran aç/kapat", + "run": "Göstergeyi çalıştır", + "title": "Gösterge başlığı", + "title-required": "Gösterge başlığı gerekli.", + "type": "Gösterge türü", + "resources": "Kaynaklar", + "resource-url": "JavaScript / CSS URL", + "remove-resource": "Kaynağı kaldır", + "add-resource": "Kaynak ekle", + "html": "HTML", + "tidy": "Tertiple", + "css": "CSS", + "settings-schema": "Ayarlar şeması", + "datakey-settings-schema": "Veri anahtarı ayarları şeması", + "javascript": "Javascript", + "remove-widget-type-title": "'{{widgetName}}' isimli gösterge türünü kaldırmak istediğinizden emin misiniz?", + "remove-widget-type-text": "UYARI: Onaylandıktan sonra, gösterge türü ve ilgili tüm veriler geri yüklenemez şekilde silinecektir.", + "remove-widget-type": "Gösterge türünü kaldır", + "add-widget-type": "Yeni gösterge türü ekle", + "widget-type-load-failed-error": "Gösterge türü yüklenemedi!", + "widget-template-load-failed-error": "Gösterge şablonu yüklenemedi!", + "add": "Gösterge ekle", + "undo": "Gösterge değişikliklerini geri al", + "export": "Göstergeyi dışa aktar" + }, + "widget-action": { + "header-button": "Gösterge başlık butonu", + "open-dashboard-state": "Yeni kontrol paneli durumunua git", + "update-dashboard-state": "Kontrol paneli durumunu güncelle", + "open-dashboard": "Diğer kontrol paneline git", + "custom": "Özel eylem", + "target-dashboard-state": "Hedef kontrol paneli durumu", + "target-dashboard-state-required": "Hedef kontrol paneli durumu gerekli", + "set-entity-from-widget": "Göstergeden öğe belirle", + "target-dashboard": "Hedef kontrol paneli", + "open-right-layout": "Sağdaki kontrol paneli arayüz düzenini aç(mobil görünüm)" + }, + "widgets-bundle": { + "current": "Şimdiki demet", + "widgets-bundles": "Gösterge Demetleri", + "add": "Gösterge Demeti Ekle", + "delete": "Gösterge demeti sil", + "title": "Başlık", + "title-required": "Başlık gerekli.", + "add-widgets-bundle-text": "Yeni gösterge demeti ekle", + "no-widgets-bundles-text": "Hiçbir gösterge demeti bulunamadı", + "empty": "Gösterge demeti boş", + "details": "Detaylar", + "widgets-bundle-details": "Gösterge demeti detayları", + "delete-widgets-bundle-title": "'{{widgetsBundleTitle}}' isimli gösterge demetini silmek istediğinize emin misiniz?", + "delete-widgets-bundle-text": "UYARI: Onaylandıktan sonra gösterge demeti ve ilişkili tüm veriler geri yüklenemez şekilde silinecektir.", + "delete-widgets-bundles-title": "{ count, plural, 1 {1 gösterge demetini} other {# gösterge demetini} } silmek istediğinize emin misiniz?", + "delete-widgets-bundles-action-title": "{ count, plural, 1 {1 gösterge demetini} other {# gösterge demetini} } sil", + "delete-widgets-bundles-text": "UYARI: Onaylandıktan sonra seçili tüm gösterge demetleri ve ilişkili tüm veriler geri yüklenemez şekilde silinecektir.", + "no-widgets-bundles-matching": "'{{widgetsBundle}}' ile eşleşen gösterge demeti bulunamadı.", + "widgets-bundle-required": "Gösterge demeti gerekli.", + "system": "Sistem", + "import": "Gösterge demetini içe aktar", + "export": "Gösterge demetini dışa aktar", + "export-failed-error": "Gösterge demetini dışa aktaramadı: {{error}}", + "create-new-widgets-bundle": "Yeni gösterge demeti oluştur", + "widgets-bundle-file": "Gösterge demeti dosyası", + "invalid-widgets-bundle-file-error": "Gösterge demeti içe aktarılamadı: Geçersiz gösterge demeti veri yapısı." + }, + "widget-config": { + "data": "Veri", + "settings": "Ayarlar", + "advanced": "İleri düzey", + "title": "Başlık", + "general-settings": "Genel ayarlar", + "display-title": "Başlığı göster", + "drop-shadow": "Gölge", + "enable-fullscreen": "Tam ekranı etkinleştir", + "background-color": "Arka plan rengi", + "text-color": "Yazı rengi", + "padding": "İç aralık (Padding)", + "margin": "Dış aralık (Margin)", + "widget-style": "Gösterge stili", + "title-style": "Başlık stili", + "mobile-mode-settings": "Mobil mod ayarları", + "order": "Sıra", + "height": "Yükseklik", + "units": "Değerin yanında göstermek için özel simge", + "decimals": "Noktadan sonraki basamak sayısı", + "timewindow": "Zaman aralığı", + "use-dashboard-timewindow": "Kontrol paneli zaman aralığı kullan", + "display-legend": "Lejant göster", + "datasources": "Veri kaynakları", + "maximum-datasources": "En fazla { count, plural, 1 {1 veri kaynağı kullanılabilir.} other {# veri kaynağı kullanılabilir} }", + "datasource-type": "Tür", + "datasource-parameters": "Parametreler", + "remove-datasource": "Veri kaynağını kaldır", + "add-datasource": "Veri kaynağı ekle", + "target-device": "Hedef aygıt", + "alarm-source": "Alarm kaynağı", + "actions": "Eylemler", + "action": "Eylem", + "add-action": "Eylem ekle", + "search-actions": "Eylem ara", + "action-source": "Eylem kaynağı", + "action-source-required": "Eylem kaynağı gerekli.", + "action-name": "İsim", + "action-name-required": "Eylem ismi gerekli.", + "action-name-not-unique": "Aynı ada sahip başka bir işlem zaten var.
Eylem adı, aynı eylem kaynağı içinde emsalsiz olmalıdır.", + "action-icon": "İkon", + "action-type": "Tür", + "action-type-required": "Eylem türü gerekli.", + "edit-action": "Eylemi düzenle", + "delete-action": "Eylemi sil", + "delete-action-title": "Gösterge eylemini sil", + "delete-action-text": "'{{actionName}}' isimli gösterge eylemini silmek istediğinizden emin misiniz?" + }, + "widget-type": { + "import": "Gösterge türünü içer aktar", + "export": "Gösterge türünü dışa aktar", + "export-failed-error": "Gösterge türü dışa aktarılamadı: {{error}}", + "create-new-widget-type": "Yeni gösterge türü oluştur", + "widget-type-file": "Gösterge türü dosyası", + "invalid-widget-type-file-error": "Gösterge türü içe aktarılamadı: Geçersiz gösterge türü veri yapısı." + }, + "widgets": { + "date-range-navigator": { + "localizationMap": { + "Sun": "Paz", + "Mon": "Pzt", + "Tue": "Sal", + "Wed": "Çar", + "Thu": "Per", + "Fri": "Cum", + "Sat": "Cmt", + "Jan": "Oca", + "Feb": "Şub", + "Mar": "Mar", + "Apr": "Nis", + "May": "May", + "Jun": "Haz", + "Jul": "Tem", + "Aug": "Ağu", + "Sep": "Eyl", + "Oct": "Eki", + "Nov": "Kas", + "Dec": "Ara", + "January": "Ocak", + "February": "Şubat", + "March": "Mart", + "April": "Nisan", + "June": "Haziran", + "July": "Temmuz", + "August": "Ağustos", + "September": "Eylül", + "October": "Ekim", + "November": "Kasım", + "December": "Aralık", + "Custom Date Range": "Özel Tarih Aralığı", + "Date Range Template": "Tarih Aralığı Şablonu", + "Today": "Bugün", + "Yesterday": "Dün", + "This Week": "Bu hafta", + "Last Week": "Geçen hafta", + "This Month": "Bu ay", + "Last Month": "Geçen ay", + "Year": "Yıl", + "This Year": "Bu yıl", + "Last Year": "Geçen yıl", + "Date picker": "Tarih seçici", + "Hour": "Saat", + "Day": "Gün", + "Week": "Hafta", + "2 weeks": "2 Hafta", + "Month": "Ay", + "3 months": "3 Ay", + "6 months": "6 Ay", + "Custom interval": "Özel aralık", + "Interval": "Aralık", + "Step size": "Adım boyutu", + "Ok": "Ok" + } + } + }, + "icon": { + "icon": "İkon", + "select-icon": "İkon seç", + "material-icons": "Material konları", + "show-all": "Tüm ikonları göster" + }, + "custom": { + "widget-action": { + "action-cell-button": "Eylem hücre butonu", + "row-click": "Satır tıklama eylemi", + "polygon-click": "Satır tıklama eylemi", + "marker-click": "Çokgen tıklama eylemi", + "tooltip-tag-action": "İpucu etiket eylemi" + } + }, + "language": { + "language": "Dil", + "locales": { + "de_DE": "Almanca", + "fr_FR": "Fransızca", + "zh_CN": "Çince", + "en_US": "İngilizce", + "it_IT": "İtalyan", + "ko_KR": "Koreli", + "ru_RU": "Rusça", + "es_ES": "İspanyol", + "ja_JA": "Japonca", + "tr_TR": "Türkçe", + "fa_IR": "Farsça", + "uk_UA": "Ukrayna", + "cs_CZ": "Çekçe" + } + } +} \ No newline at end of file diff --git a/ui-ngx/src/assets/locale/locale.constant-uk_UA.json b/ui-ngx/src/assets/locale/locale.constant-uk_UA.json new file mode 100644 index 0000000000..8c2e9c6def --- /dev/null +++ b/ui-ngx/src/assets/locale/locale.constant-uk_UA.json @@ -0,0 +1,2190 @@ + { + "access": { + "unauthorized": "Неавторизований", + "unauthorized-access": "Неавторизований доступ", + "unauthorized-access-text": "Щоб отримати доступ до цього ресурсу, потрібно ввійти в систему!", + "access-forbidden": "Доступ заборонено", + "access-forbidden-text": "Недостатньо прав для доступу!
Спробуйте увійти як інший користувач, якщо ви все ще хочете отримати доступ до цього ресурсу.", + "refresh-token-expired": "Дані про сесію застарілі", + "refresh-token-failed": "Не вдається відновити сеанс" + }, + "action": { + "activate": "Активувати", + "suspend": "Призупинити", + "save": "Зберегти", + "saveAs": "Зберегти як", + "cancel": "Скасувати", + "ok": "OK", + "delete": "Видалити", + "add": "Додати", + "yes": "Так", + "no": "Ні", + "update": "Оновити", + "remove": "Видалити", + "search": "Пошук", + "clear-search": "Очистити пошук", + "assign": "Надати", + "unassign": "Позбавити", + "share": "Поділитися", + "make-private": "Зробити приватним", + "apply": "Застосувати", + "apply-changes": "Застосувати зміни", + "edit-mode": "Режим редагування", + "enter-edit-mode": "Ввійти в режим редагування", + "decline-changes": "Відхилити зміни", + "close": "Закрити", + "back": "Назад", + "run": "Запустити", + "sign-in": "Увійти!", + "edit": "Редагувати", + "view": "Переглянути", + "create": "Створити", + "drag": "Перетягнути", + "refresh": "Оновити", + "undo": "Скасувати", + "copy": "Скопіювати", + "paste": "Вставити", + "copy-reference": "Копіювати посилання", + "paste-reference": "Вставити посилання", + "import": "Імпортувати", + "export": "Експортувати", + "share-via": "Поділитися через {{provider}}", + "move": "Перемістити", + "select": "Вибрати" + }, + "aggregation": { + "aggregation": "Агрегація", + "function": "Функція агрегації даних", + "limit": "Максимальні значення", + "group-interval": "Інтервал групування", + "min": "Мінімальний", + "max": "Максимальний", + "avg": "Середній", + "sum": "Сума", + "count": "Рахувати", + "none": "Відсутня" + }, + "admin": { + "general": "Загальне", + "general-settings": "Загальні налаштування", + "outgoing-mail": "Поштовий сервер", + "outgoing-mail-settings": "Налаштування сервера вихідної пошти", + "system-settings": "Налаштування системи", + "test-mail-sent": "Тестовий лист успішно відправлено!", + "base-url": "Базова URL-адреса", + "base-url-required": "Базова URL-адреса обов'язкова.", + "mail-from": "Електронна адреса", + "mail-from-required": "Електронна адреса обов'язкова.", + "smtp-protocol": "Протокол SMTP", + "smtp-host": "Хост SMTP", + "smtp-host-required": "Хост SMTP обов'язковий.", + "smtp-port": "SMTP-порт", + "smtp-port-required": "Ви повинні надати SMTP-порт.", + "smtp-port-invalid": "Це не схоже на дійсний SMTP-порт.", + "timeout-msec": "Час очікування (msec)", + "timeout-required": "Необхідно задати час очікування.", + "timeout-invalid": "Це не схоже на правильний час очікування.", + "enable-tls": "Увімкнути TLS", + "send-test-mail": "Надіслати тестове повідомлення", + "use-system-mail-settings": "Використовувати параметри системного поштового сервера", + "mail-templates": "Шаблони електронної пошти", + "mail-template-settings": "Налаштування шаблонів електронної пошти", + "use-system-mail-template-settings": "Використовувати шаблони системної електронної пошти", + "mail-template": { + "mail-template": "Шаблон електронної пошти", + "test": "Тестове повідомлення", + "activation": "Повідомлення про активацію рахунку", + "account-activated": "Обліковий запис активовано", + "reset-password": "Відновити повідомлення пароля", + "password-was-reset": "Пароль було надіслано повідомленням" + }, + "mail-subject": "Тема повідомлення", + "mail-body": "Вміст повідомлення" + }, + "alarm": { + "alarm": "Сигнал тривоги", + "alarms": "Сигнали тривоги", + "select-alarm": "Вибрати сигнал тривоги", + "no-alarms-matching": "Сигналів тривоги '{{entity}}' не знайдено.", + "alarm-required": "Сигнал тривоги необхідний", + "alarm-status": "Статус сигналу тривоги", + "search-status": { + "ANY": "Будь які", + "ACTIVE": "Активні", + "CLEARED": "Неактивні", + "ACK": "Прийняті", + "UNACK": "Неприйняті" + }, + "display-status": { + "ACTIVE_UNACK": "Активні та неприйняті", + "ACTIVE_ACK": "Активні та прийняті", + "CLEARED_UNACK": "Неактивні та неприйняті", + "CLEARED_ACK": "Неактивні та прийняті" + }, + "no-alarms-prompt": "Сигналів тривоги не знайдено", + "created-time": "Час створення", + "type": "Тип", + "severity": "Серйозність", + "originator": "Ініціатор", + "originator-type": "Тип ініціатору", + "details": "Деталі", + "status": "Статус", + "alarm-details": "Деталі сигналу тривоги", + "start-time": "Початок", + "end-time": "Кінець", + "ack-time": "Час прийняття", + "clear-time": "Час деактивації", + "severity-critical": "Критичні", + "severity-major": "Важливі", + "severity-minor": "Неважливі", + "severity-warning": "Попередження", + "severity-indeterminate": "Невизначені", + "acknowledge": "Прийняти", + "clear": "Деактивувати", + "search": "Шукати сигнали тривоги", + "selected-alarms": "{ count, plural, 1 {1 сигнал тривоги} other {# сигнали тривоги} } вибрані", + "no-data": "Немає даних для відображення", + "polling-interval": "Інтервал опитування (сек)", + "polling-interval-required": "Необхідно задати інтервал опитування.", + "min-polling-interval-message": "Дозволяється щонайменше 1 секунда інтервалу очікування.", + "aknowledge-alarms-title": "Підтвердити { count, plural, 1 {1 сигнал тривоги} other {# сигнали тривоги} }", + "aknowledge-alarms-text": "Ви впевнені, що хочете підтвердити { count, plural, 1 {1 сигнал тривоги} other {# сигнали тривоги} }?", + "aknowledge-alarm-title": "Підтвердити сигнал тривоги", + "aknowledge-alarm-text": "Ви впевнені, що хочете підтвердити сигнал тривоги?", + "clear-alarms-title": "Деактивувати { count, plural, 1 {1 сигнал тривоги} other {# сигнали тривоги} }", + "clear-alarms-text": "Ви впевнені, що хочете деактивувати { count, plural, 1 {1 сигнал тривоги} other {# сигнали тривоги} }?", + "clear-alarm-title": "Деактивувати сигнал тривоги", + "clear-alarm-text": "Ви впевнені, що хочете деактивувати сигнал тривоги?", + "alarm-status-filter": "Фільтр статусу сигналу тривоги" + }, + "alias": { + "add": "Додати псевдонім ", + "edit": "Редагувати псевдонім", + "name": "Ім'я", + "name-required": "Необхідно вказати псевдонім", + "duplicate-alias": "Псевдонім з такою назвою вже існує.", + "filter-type-single-entity": "Єдина сутність", + "filter-type-entity-group": "Група сутностей", + "filter-type-entity-list": "Список сутностей", + "filter-type-entity-name": "Назва сутності", + "filter-type-entity-group-list": "Список груп сутностей", + "filter-type-entity-group-name": "Назва групи сутностей", + "filter-type-state-entity": "Сутність з стану панелі пристроїв", + "filter-type-state-entity-description": "Сутність, взята з параметрів стану панелі пристроїв", + "filter-type-asset-type": "Тип активу", + "filter-type-asset-type-description": "Тип активів '{{assetType}}'", + "filter-type-asset-type-and-name-description": "Тип активів '{{assetType}}' і ім'я, що починаються з '{{prefix}}'", + "filter-type-device-type": "Тип пристрою", + "filter-type-device-type-description": "Тип пристроїв '{{deviceType}}'", + "filter-type-device-type-and-name-description": "Тип пристроїв '{{deviceType}}' і ім'я, що починаються з '{{prefix}}'", + "filter-type-entity-view-type": "Тип перегляду сутності", + "filter-type-entity-view-type-description": "Перегляд сутності з типом '{{entityView}}'", + "filter-type-entity-view-type-and-name-description": "Перегляд сутності з типом'{{entityView}}' і іменем, що починаються з '{{prefix}}'", + "filter-type-relations-query": "Запит відносин", + "filter-type-relations-query-description": "{{entities}}, які мають {{relationType}} відношення {{direction}} {{rootEntity}}", + "filter-type-asset-search-query": "Запит пошуку активу", + "filter-type-asset-search-query-description": "Активи з типами {{assetTypes}}, які мають {{relationType}} відношення {{direction}} {{rootEntity}}", + "filter-type-device-search-query": "Запит пошуку пристрою", + "filter-type-device-search-query-description": "Пристрої з типами {{deviceTypes}}, які мають {{relationType}} відношення {{direction}} {{rootEntity}}", + "filter-type-entity-view-search-query": "Запит пошуку переглядів сутностей", + "filter-type-entity-view-search-query-description": "Перегляд сутності з типами {{entityViewTypes}}, які мають {{relationType}} відношення {{direction}} {{rootEntity}}", + "entity-filter": "Фільтр сутності", + "resolve-multiple": "Як декілька сутностей", + "filter-type": "Тип фільтра", + "filter-type-required": "Необхідно вказати тип фільтра.", + "entity-filter-no-entity-matched": "Не знайдено жодних сутностей, які відповідають вказаному фільтру.", + "no-entity-filter-specified": "No entity filter specified", + "root-state-entity": "Use dashboard state entity as root", + "group-state-entity": "Use dashboard state entity as entity group", + "root-entity": "Root entity", + "state-entity-parameter-name": "State entity parameter name", + "default-state-entity": "Default state entity", + "default-state-entity-group": "Default state entity group", + "default-entity-parameter-name": "By default", + "max-relation-level": "Max relation level", + "unlimited-level": "Unlimited level", + "state-entity": "Dashboard state entity", + "entities-of-group-state-entity": "Entities from dashboard state entity group", + "all-entities": "All entities", + "any-relation": "any" + }, + "asset": { + "asset": "Актив", + "assets": "Активи", + "management": "Управління активами", + "view-assets": "Переглянути активи", + "add": "Додати активи", + "assign-to-customer": "Надати клієнту", + "assign-asset-to-customer": "Надати активи клієнту", + "assign-asset-to-customer-text": "Будь ласка, виберіть ресурси, призначені для клієнта", + "no-assets-text": "Не знайдено активів", + "assign-to-customer-text": "Будь ласка, виберіть клієнта, щоб надати активи", + "public": "Публічно", + "assignedToCustomer": "Наданий клієнту", + "make-public": "Зробити актив(и) публічним(и)", + "make-private": "Зробити актив(и) приватним(и)", + "unassign-from-customer": "Позбавити клієнта", + "delete": "Видалити актив", + "asset-public": "Актив є загальнодоступним", + "asset-type": "Тип активу", + "asset-type-required": "Тип активу обов'язковий.", + "select-asset-type": "Виберіть тип активу", + "enter-asset-type": "Введіть тип активу", + "any-asset": "Будь-який актив", + "no-asset-types-matching": "Не знайдено жодних активів, що відповідають даному типу '{{entitySubtype}}'.", + "asset-type-list-empty": "Не вибрано жодного типу активів.", + "asset-types": "Типи активів", + "name": "Ім'я", + "name-required": "Ім'я обов'язкове.", + "description": "Опис", + "type": "Тип", + "type-required": "Тип обов'язковий.", + "details": "Подробиці", + "events": "Події", + "add-asset-text": "Додати новий актив", + "asset-details": "Інформація про актив", + "assign-assets": "Надати активи", + "assign-assets-text": "Надати { count, plural, 1 {1 актив} other {# активи} } клієнту", + "delete-assets": "Видалити активи", + "unassign-assets": "Позбавити активів", + "unassign-assets-action-title": "Позбавити { count, plural, 1 {1 актив} other {# активи} } клієнта", + "assign-new-asset": "Надати новий актив", + "delete-asset-title": "Ви впевнені, що хочете видалити актив '{{assetName}}'?", + "delete-asset-text": "Будьте обережні, після підтвердження, актив і всі пов'язані з ним дані буде втрачено", + "delete-assets-title": "Ви впевнені, що хочете видалити { count, plural, 1 {1 актив} other {# активи} }?", + "delete-assets-action-title": "Видалити{ count, plural, 1 {1 актив} other {# активи} }", + "delete-assets-text": "Будьте обережні, після підтвердження всі вибрані об'єкти буде видалено, і всі пов'язані з ними дані буде втрачено.", + "make-public-asset-title": "Ви дійсно хочете, щоб актив '{{assetName}}' був загальнодоступним?", + "make-public-asset-text": "Після підтвердження, актив і всі його дані будуть доступними для інших.", + "make-private-asset-title": "Ви впевнені, що хочете зробити актив {{assetName}} приватним?", + "make-private-asset-text": "Після підтвердження, актив та всі його дані будуть приватними та не будуть доступні іншим.", + "unassign-asset-title": "Ви впевнені, що хочете позбавити активу '{{assetName}}'?", + "unassign-asset-text": "Після підтвердження клієнт буде позбавлений активу. Дані активу не будуть доступні клієнту.", + "unassign-asset": "Позбавити активу", + "unassign-assets-title": "Ви впевнені, що хочете позбавити активів { count, plural, 1 {1 актив} other {# активи} }?", + "unassign-assets-text": "Після підтвердження, клієнт буде позбавлений усіх вибраних активів. Дані активів не будуть доступні клієнту.", + "copyId": "Копіювати Id активу", + "idCopiedMessage": "Id активу був скопійований у буфер обміну", + "select-asset": "Виберіть актив", + "no-assets-matching": "Не знайдено жодних активів, що відповідають'{{entity}}'.", + "asset-required": "Необхідно задати актив", + "name-starts-with": "Назва активу починається з", + "selected-assets": "{ count, plural, 1 {1 актив} other {# активи} } selected", + "search": "Пошук активів", + "select-group-to-add": "Виберіть цільову групу, щоб додати вибрані активи", + "select-group-to-move": "Виберіть цільову групу для переміщення вибраних активів", + "remove-assets-from-group": "Ви впевнені, що хочете видалити { count, plural, 1 {1 актив} other {# актив} } з групи '{entityGroup}'?", + "group": "Група активів", + "list-of-groups": "{ count, plural, 1 {Одна група активів} other {Список # груп активів} }", + "group-name-starts-with": "Групи активів, чиї імена починаються з '{{prefix}}'" + }, + "attribute": { + "attributes": "Атрибути", + "latest-telemetry": "Остання телеметрія", + "attributes-scope": "Область видимості атрибутів", + "scope-latest-telemetry": "Остання телеметрія", + "scope-client": "Клієнтські атрибути", + "scope-server": "Серверні атрибути", + "scope-shared": "Спільні атрибути", + "add": "Додати атрибут", + "add-attribute-prompt": "Будь ласка, додайте атрибут", + "key": "Ключ", + "last-update-time": "Останнє оновлення", + "key-required": "Ключ атрибута обов'язковий.", + "value": "Значення", + "value-required": "Значення атрибута обов'язкове.", + "delete-attributes-title": "Ви впевнені, що хочете видалити { count, plural, 1 {1 attribute} other {# attributes} }?", + "delete-attributes-text": "Будьте обережні, після підтвердження, всі виділені атрибути будуть видалені.", + "delete-attributes": "Видалити атрибути", + "enter-attribute-value": "Введіть значення атрибута", + "show-on-widget": "Показати на віджеті", + "widget-mode": "Режим віджетів", + "next-widget": "Наступний віджет", + "prev-widget": "Попередній віджет", + "add-to-dashboard": "Додати до інформаційної панелі", + "add-widget-to-dashboard": "Додати віджет до інформаційної панелі", + "selected-attributes": "{ count, plural, 1 {1 attribute} other {# attributes} } selected ...вибрані вибрати", + "selected-telemetry": "{ count, plural, 1 {1 telemetry unit} other {# telemetry units} } selected" + }, + "audit-log": { + "audit": "Операція", + "audit-logs": "Журнал операцій", + "timestamp": "Тимчасова позначка", + "entity-type": "Тип одиниці", + "entity-name": "Назва організації", + "user": "Користувач", + "type": "Тип", + "status": "Статус", + "details": "Подробиці", + "type-added": "Додано", + "type-deleted": "Вилучено", + "type-updated": "Оновлено", + "type-attributes-updated": "Атрибути оновлені", + "type-attributes-deleted": "Атрибути видалені", + "type-rpc-call": "RPC дзвінок", + "type-credentials-updated": "Авторизаційні дані оновлено", + "type-assigned-to-customer": "Призначено клієнту", + "type-unassigned-from-customer": "Позбавлено від клієнта", + "type-activated": "Активовано", + "type-suspended": "Призупинено", + "type-credentials-read": "Авторизаційні дані прочитані", + "type-attributes-read": "Атрибути читаються", + "type-added-to-entity-group": "Додано до групи", + "type-removed-from-entity-group": "Вилучено з групи", + "type-relation-add-or-update": "Відношення оновлено", + "type-relation-delete": "Відношення видалено", + "type-relations-delete": "Всі відношення видалено", + "type-alarm-ack": "Визнано", + "type-alarm-clear": "Очищено", + "type-rest-api-rule-engine-call": "Rule engine REST API call", + "status-success": "Успішно", + "status-failure": "Невдало", + "audit-log-details": "Подробиці журналу операцій", + "no-audit-logs-prompt": "Жодних журналів операцій не знайдено", + "action-data": "Дані про дії", + "failure-details": "Невдалі подробиці", + "search": "Пошук журналів перевірки", + "clear-search": "Очистити пошук" + }, + "confirm-on-exit": { + "message": "У вас є незбережені зміни. Ви впевнені, що хочете залишити цю сторінку?", + "html-message": "У вас є незбережені зміни.
Ви впевнені, що хочете залишити цю сторінку?", + "title": "Незбережені зміни" + }, + "contact": { + "country": "Країна", + "city": "Місто", + "state": "Штат / Провінція", + "postal-code": "Поштовий індекс", + "postal-code-invalid": "Неправильний формат поштового індексу.", + "address": "Адреса", + "address2": "Адреса 2", + "phone": "Телефон", + "email": "Електронна пошта", + "no-address": "Немає адреси" + }, + "common": { + "username": "Ім'я користувача", + "password": "Пароль", + "enter-username": "Введіть ім'я користувача", + "enter-password": "Введіть пароль", + "enter-search": "Введіть пошук" + }, + "converter": { + "converter": "Перетворювач даних", + "converters": "Перетворювачі даних", + "select-converter": "Виберіть перетворювач даних", + "no-converters-matching": "Не має перетворювачів даних, які відповідають '{{entity}}'.", + "converter-required": "Необхідно вказати перетворювач даних", + "delete": "Видалити перетворювач даних", + "management": "Управління перетворювачами даних", + "add-converter-text": "Додати новий перетворювач даних", + "no-converters-text": "Перетворювачів даних не знайдено", + "selected-converters": "{ count, plural, 1 {1 перетворювач даних} other {# перетворювачі даних} } вибраний", + "delete-converter-title": "Ви впевнені, що хочете видалити перетворювач даних '{{converterName}}'?", + "delete-converter-text": "Будьте обережні, після підтвердження, перетворювач даних та всі пов'язані з ним дані,стануть недоступними).", + "delete-converters-title": "Ви впевнені, що хочете видалити{ count, plural, 1 {1 перетворювач даних} other {# перетворювачі даних} }?", + "delete-converters-action-title": "Видалити { count, plural, 1 {1 перетворювач даних} other {# перетворювачі даних} }", + "delete-converters-text": "Будьте обережні, після підтвердження всі вибрані перетворювачі даних буде видалено, і всі пов'язані з ними дані буде втрачено.", + "events": "Події", + "add": "Додати перетворювач даних", + "converter-details": "Подробиці про перетворювач даних", + "details": "Подробиці", + "copyId": "Копіювати Id перетворювача даних", + "idCopiedMessage": "Id перетворювача даних було скопійовано у буфер обміну", + "debug-mode": "Режим налагодження", + "name": "Ім'я", + "name-required": "Ім'я обов'язкове.", + "description": "Опис", + "decoder": "Декодер", + "encoder": "Кодер", + "test-decoder-fuction": "Тестування функції декодера", + "test-encoder-fuction": "Тестування функції кодера", + "decoder-input-params": "Параметри введення декодера", + "encoder-input-params": "Параметри введення кодера", + "payload": "Вхідне повідомлення", + "payload-content-type": "Тип контенту вхідного повідомлення", + "payload-content": "Зміст вхідного повідомлення", + "message": "Повідомлення", + "message-type": "Тип повідомлення", + "message-type-required": "Необхідно задати тип повідомлення", + "test": "Тест", + "metadata": "Метадані", + "metadata-required": "Записи метаданих не можуть бути порожніми.", + "integration-metadata": "Інтеграція метаданих", + "integration-metadata-required": "Записи інтеграції метаданих не можуть бути порожніми.", + "output": "Вихідні дані", + "import": "Імпорт перетворювача даних", + "export": "Експорт перетворювача даних", + "export-failed-error": "Неможливо експортувати перетворювач даних: {{помилка}}", + "create-new-converter": "Створити новий перетворювач даних", + "converter-file": "Файл перетворювача даних(конвектер файл)", + "invalid-converter-file-error": "Неможливо імпортувати перетворювач даних: недійсна структура даних перетворювача.", + "type": "Тип", + "type-required": "Необхідно задати тип.", + "type-uplink": "Від пристрою", + "type-downlink": "До пристрою" + }, + "content-type": { + "json": "Json", + "text": "Текст", + "binary": "Бінарний (Base64)" + }, + "customer": { + "customer": "Клієнт", + "customers": "Клієнти", + "management": "Клієнтський менеджмент", + "dashboard": "Інформаційна панель клієнта", + "dashboards": "Інформаційні панелі клієнта", + "devices": "Пристрої клієнта", + "entity-views": "Представлення сутностей", + "assets": "Клієнтські активи", + "public-dashboards": "Публічні інформаційні панелі", + "public-devices": "Публічні пристрої", + "public-assets": "Публічні активи", + "public-entity-views": "Публічне представлення сутностей 440", + "add": "Додати клієнта", + "delete": "Видалити клієнта", + "manage-customer-users": "Керування користувачами клієнта", + "manage-customer-devices": "Керування пристроями клієнта", + "manage-customer-dashboards": "Керування інформаційними панелями клієнта", + "manage-public-devices": "Керувати загальнодоступними пристроями", + "manage-public-dashboards": "Керування загальнодоступними інформаційними панелями", + "manage-customer-assets": "Керування активами клієнта", + "manage-public-assets": "Керування загальнодоступними активами", + "add-customer-text": "Додати нового клієнта", + "no-customers-text": "Клієнтів не знайдено", + "customer-details": "Інформація про клієнта", + "delete-customer-title": "Ви впевнені, що хочете видалити клієнта '{{customerTitle}}'?", + "delete-customer-text": "Будьте обережні, після підтвердження, клієнт та всі пов'язані з ним дані, стануть недоступними.", + "delete-customers-title": "Ви впевнені, що хочете видалити {count, plural, 1 {1 клієнт}, інші {# клієнти}}?", + "delete-customers-action-title": "Видалити{ count, plural, 1 {1 клієнт} other {# клієнти} }", + "delete-customers-text": "Будьте обережні, після підтвердження, всі вибрані клієнти будуть видалені і всі пов'язані з ними дані, стануть недоступними.", + "manage-users": "Керування користувачами", + "manage-assets": "Керування активами", + "manage-devices": "Керування пристроями", + "manage-dashboards": "Керування інформаційними панелями", + "title": "Назва", + "title-required": "Необхідно задати назву.", + "description": "Опис", + "details": "Подробиці", + "events": "Події", + "copyId": "Копіювати Id клієнта", + "idCopiedMessage": "Id клієнта було скопійовано в буфер обміну", + "select-customer": "Виберіть клієнта", + "no-customers-matching": "Клієнтів, які відповідають '{{entity}}' не знайдено.", + "customer-required": "Необхідно задати клієнта", + "selected-customers": "{ count, plural, 1 {1 клієнт} інші {# клієнти} } вибрано", + "search": "Пошук клієнтів", + "select-group-to-add": "Виберіть цільову групу, щоб додати вибраних клієнтів", + "select-group-to-move": "Виберіть цільову групу для переміщення вибраних клієнтів", + "remove-customers-from-group": "Ви впевнені, що хочете видалити{ count, plural, 1 {1 клієнт} other {# клієнти} } з групи'{entityGroup}'?", + "group": "Група клієнтів", + "list-of-groups": "{ count, plural, 1 {Одна група клієнтів} other {Список # груп клієнтів} }", + "group-name-starts-with": "Групи клієнтів, імена яких починаються з '{{prefix}}'", + "select-default-customer": "Виберати клієнта за замовчуванням", + "default-customer": "Клієнт за замовчуванням", + "default-customer-required": "Необхідно вказати клієнта за замовчуванням для налагодження панелі візуалізації на рівні замовника", + "allow-white-labeling": "Дозволити брендування" + }, + "custom-translation": { + "custom-translation": "Переклад для користувача", + "translation-map": "Карта перекладу", + "key": "Ключ перекладу", + "import": "Імпорт перекладу", + "export": "Експорт перекладу", + "export-data": "Дані про експорт перекладу", + "import-data": "Дані про імпорт перекладу", + "translation-file": "Файл перекладу", + "invalid-translation-file-error": "Неможливо імпортувати файл перекладу: недійсна структура даних перекладу.", + "custom-translation-hint": "Визначте індивідуальний переклад в JSON нижче. Цей JSON перезапише переклад за замовчуванням. Натисніть 'Завантажити файл перекладу', щоб отримати існуючий переклад. Ви також можете скористатись завантаженим файлом як посиланням на наявні пари параметрів перекладу ключ-значення.", + "download-locale-file": "Завантажити файл перекладу" + }, + "datetime": { + "date-from": "Дата від", + "time-from": "Час від", + "date-to": "Дата до", + "time-to": "Час до" + }, + "dashboard": { + "dashboard": "Панель приладів", + "dashboards": "Панелі приладів", + "management": "Управління панеллю приладів", + "view-dashboards": "Переглянути панелі приладів", + "add": "Додати панель приладів", + "assign-dashboard-to-customer": "Призначити панель(і) приладів замовнику", + "assign-dashboard-to-customer-text": "Будь ласка, виберіть панелі пристроїв, щоб призначити їх клієнту", + "assign-to-customer-text": "Виберіть клієнта, щоб призначити панелі пристроїв", + "assign-to-customer": "Призначити клієнту", + "unassign-from-customer": "Позбавити клієнта", + "make-public": "Зробити панель приладів публічною", + "make-private": "Зробити панель приладів приватною", + "manage-assigned-customers": "Керування призначеними клієнтами", + "assigned-customers": "Призначені клієнтам", + "assign-to-customers": "Призначити панелі приладів клієнтам", + "assign-to-customers-text": "Виберіть клієнтів для призначення панелей приладів", + "unassign-from-customers": "Позбавити клієнтів призначенних панелей приладів", + "unassign-from-customers-text": "Виберіть клієнтів для позбавлення їх призначених панелей приладів", + "no-dashboards-text": "Панелі приладів не знайдені", + "no-widgets": "Не налаштовано жодних віджетів", + "add-widget": "Додати новий віджет", + "title": "Назва", + "select-widget-title": "Вибрати віджет", + "select-widget-subtitle": "Список доступних типів віджетів", + "delete": "Видалити панель приладів", + "title-required": "Необхідно задати назву.", + "description": "Опис", + "details": "подробиці", + "dashboard-details": "Подробиці панелі приладів", + "add-dashboard-text": "Додати нову панель приладів", + "assign-dashboards": "Призначити панель приладів", + "assign-new-dashboard": "Призначити нову панель приладів", + "assign-dashboards-text": "Призначити { count, plural, 1 {1 панель приладів} other {# панелі приладів} } користувачам", + "unassign-dashboards-action-text": "Позбавити { count, plural, 1 {1 палелі приладів} other {# панелей приладів} } клієнтів", + "delete-dashboards": "Видалити панель приладів", + "unassign-dashboards": "Позбавити панелей приладів", + "unassign-dashboards-action-title": "Позбавити { count, plural, 1 {1 палелі приладів} other {# панелей приладів} } клієнтів", + "delete-dashboard-title": "Ви впевнені, що хочете видалити панель приладів '{{назва панелі приладів}}'?", + "delete-dashboard-text": "Будьте обережні, після підтвердження, панель приладів і всі пов'язані з нею дані стануть недоступними.", + "delete-dashboards-title": "Ви впевнені, що хочете видалити { count, plural, 1 {1 панель приладів} other {# панелі приладів} }?", + "delete-dashboards-action-title": "Видалити { count, plural, 1 {1 панель приладів} other {# панелі приладів} }", + "delete-dashboards-text": "Будьте обережні, після підтвердження, всі вибрані панелі приладів буде видалено, і всі пов'язані з ними дані стануть недоступними.", + "unassign-dashboard-title": "Ви впевнені, що хочете позбавити панелі приладів '{{назва інформаційної панелі}}'?", + "unassign-dashboard-text": "Після підтвердження, клієнт буде позбавлений панелі приладів. Панель приладів і пов'язані з нею дані будуть недоступні клієнтові.", + "unassign-dashboard": "Позбавити панелі приладів", + "unassign-dashboards-title": "Ви впевнені, що хочете позбавити { count, plural, 1 {1 панелі приладів} other {# панелей приладів} }?", + "unassign-dashboards-text": "Після підтвердження, клієтн буде позбавлений усіх вибраних панелей приладів і даних, які з ними пов'язані.", + "public-dashboard-title": "Панель приладів тепер публічна", + "public-dashboard-text": "Ваша панель приладів {{dashboardTitle}} тепер публічна і доступна іншим link:", + "public-dashboard-notice": "Note: Не забудьте зробити спільні пристрої загальнодоступними, щоб отримати доступ до їхніх даних.", + "make-private-dashboard-title": "Ви впевнені, що хочете зробити панель приладів '{{назва панелі приладів}}' приватною?", + "make-private-dashboard-text": "Після підтвердження панель приладів стане приватною і не буде доступною іншим.", + "make-private-dashboard": "Зробити панель приладів приватною", + "socialshare-text": "'{{dashboardTitle}}' powered by ThingsBoard", + "socialshare-title": "'{{dashboardTitle}}' powered by ThingsBoard", + "select-dashboard": "Вибрати панель приладів", + "no-dashboards-matching": "Не знайдено жодних панелей прилодів'{{entity}}' які відповідають.", + "dashboard-required": "Необхідно задати панель приладів.", + "select-existing": "Виберіть існуючу панель приладів", + "create-new": "Створити нову панель приладів", + "new-dashboard-title": "Нова назва панелі приладів", + "open-dashboard": "Відрити панель приладів", + "set-background": "Встановити фон", + "background-color": "Колір фону", + "background-image": "Фонове зображення", + "background-size-mode": "Режим фонового розміру", + "no-image": "Не вибрано жодного зображення", + "drop-image": "Перетягніть зображення або клацніть, щоб вибрати файл для завантаження.", + "settings": "Налаштування", + "columns-count": "Кількість стовпців", + "columns-count-required": "Необхідно вказати кількість стовпців.", + "min-columns-count-message": "Дозволений мінімум -10 стовпців.", + "max-columns-count-message": "Дозволений максимум - 1000 стовпців.", + "widgets-margins": "Відступ між віджетами", + "horizontal-margin": "Горизонтальний відступ", + "horizontal-margin-required": "Необхідно вказати горизонтальний відступ.", + "min-horizontal-margin-message": "Допустиме мінімальне значення горизонтального відступу - 0.", + "max-horizontal-margin-message": "Допустиме максимальне значення горизонтального відступу - 50.", + "vertical-margin": "Вертикальний відступ", + "vertical-margin-required": "Необхідно вказати вертикальний відступ.", + "min-vertical-margin-message": "Допустиме мінімальне значення вертикального відступу - 0.", + "max-vertical-margin-message": "Допустиме максимальне значення вертикального відступу - 50.", + "autofill-height": "Висота автоматичного заповнення макета", + "mobile-layout": "Налаштування макета для мобільних пристроїв", + "mobile-row-height": "Висота рядка для мобільних пристроїв, px", + "mobile-row-height-required": "Потрібно вказати значення висоти рядка для мобільних пристроїв.", + "min-mobile-row-height-message": "Допустиме мінімальне значення висоти рядка для мобільних пристроїв - 5 пікселів.", + "max-mobile-row-height-message": "Допустиме максимальне значення висоти рядка для мобільних пристроїв - 200 пікселів.", + "display-title": "Відображати назву панелі візуалізації", + "toolbar-always-open": "Тримайте панель візуалізації відкритою", + "title-color": "Колір назви", + "display-dashboards-selection": "Відображення вибору панелей візуалізації", + "display-entities-selection": "Вибір відображення сутності", + "display-dashboard-timewindow": "Відобразити налаштування часового проміжку", + "display-dashboard-export": "Відображення експорту", + "import": "Імпортувати панель візуалізації", + "export": "Експортувати панель візуалізації", + "export-failed-error": "Неможливо експортувати панель візуалізації: {{error}}", + "export-pdf": "Експортувати як PDF", + "export-png": "Експортувати як PNG", + "export-jpg": "Експортувати як JPEG", + "export-json-config": "Експортувати конфігурацію JSON", + "download-dashboard-progress": "Генерування панелі візуалізації {{reportType}} ...", + "create-new-dashboard": "Створити нову панель візуалізації", + "dashboard-file": "Файл панелі візуалізації", + "invalid-dashboard-file-error": "Неможливо імпортувати панель візуалізації: неправильна структура даних панелі візуалізації.", + "dashboard-import-missing-aliases-title": "Configure aliases used by imported dashboard Налаштування псевдонімів, що використовуються імпортованою панеллю візуалізації", + "create-new-widget": "Створити новий віджет", + "import-widget": "Імпортувати віджет", + "widget-file": "Файл віджета", + "invalid-widget-file-error": "Неможливо імпортувати віджет: неправильна структура даних віджета.", + "widget-import-missing-aliases-title": "Налаштувати псевдоніми, що використовуються імпортованим віджетом", + "open-toolbar": "Відкрити панель інструменів ", + "close-toolbar": "Закрити панель інструменів", + "configuration-error": "Помилка конфігурації", + "alias-resolution-error-title": "Помилка конфігурації псевдонімів панелі візуалізації", + "invalid-aliases-config": "Неможливо знайти пристрої, які відповідають певному фільтру псевдонімів.
Зверніться до свого адміністратора, щоб вирішити цю проблему.", + "select-devices": "Вибрати пристрої", + "assignedToCustomer": "Призначений клієнту", + "assignedToCustomers": "Призначений клієнтам", + "public": "Публічно", + "public-link": "Публічне посилання", + "copy-public-link": "Копіювати публічне посилання", + "public-link-copied-message": "Публічне посилання було скопійоване в буфер обміну панелі візуалізації", + "manage-states": "Керування станами панелі візуалізації", + "states": "Стани панелі візуалізації", + "search-states": "Пошук станів панелі візуалізації", + "selected-states": "{ count, plural, 1 {1 dashboard state} other {# dashboard states} } вибрано", + "edit-state": "Редагувати стан панелі візуалізації", + "delete-state": "Видалити стан панелі візуалізації ", + "add-state": "Додати стан панелі візуалізації", + "state": "Стан панелі візуалізації", + "state-name": "Ім'я", + "state-name-required": "Необхідно вказати назву стану панелі візуалізації.", + "state-id": "Стан Id", + "state-id-required": "Необхідно вказати id стану панелі візуалізації.", + "state-id-exists": "Стан інформаційної панелі з таким id вже існує.", + "is-root-state": "Основний стан", + "delete-state-title": "Видалити стан панелі візуалізації", + "delete-state-text": "Ви впевнені, що хочете видалити стан панелі візуалізації з іменем '{{stateName}}'?", + "show-details": "Показати деталі", + "hide-details": "Приховати деталі", + "select-state": "Виберіть цільовий стан", + "state-controller": "Контроль стану" + }, + "datakey": { + "settings": "Налаштування", + "advanced": "Додатково", + "label": "Мітка", + "color": "Колір", + "units": "Спеціальний символ, який показує наступне значення", + "decimals": "Кількість цифр після плаваючої точки", + "data-generation-func": "Функція генерації даних", + "use-data-post-processing-func": "Використовувати функцію пост-обробки даних", + "configuration": "Конфігурація ключа даних", + "timeseries": "Телеметрія", + "attributes": "Атрибути", + "alarm": "Поля сигнала тривоги", + "timeseries-required": "Необхідно вказати Телеметрія.", + "timeseries-or-attributes-required": "Необхідно вказати телеметрію/атрибути.", + "maximum-timeseries-or-attributes": "Максимальні { count, plural, 1 {1 телеметрія/атрибут дозволені.} other {# телеметрія/атрибути дозволені} }", + "alarm-fields-required": "Необхідно вказати поля сигнала тривоги.", + "function-types": "Типи функцій", + "function-types-required": "Необхідно вказати типи функцій.", + "maximum-function-types": "Maximum { count, plural, 1 {1 function type is allowed.} other {# function types are allowed} }", + "time-description": "мітка часу поточного значення;", + "value-description": "поточне значення;", + "prev-value-description": "результат попереднього виклику функції;", + "time-prev-description": "мітка часу попереднього значення;", + "prev-orig-value-description": "оригінальне попереднє значення;" + }, + "datasource": { + "type": "Тип джерела даних", + "name": "Ім'я", + "add-datasource-prompt": "Додайте джерело даних" + }, + "details": { + "details": "Деталі", + "edit-mode": "Режим редагування", + "toggle-edit-mode": "Перемкнути режим редагування" + }, + "device": { + "device": "Пристрій", + "device-required": "Необхідно задати пристрій.", + "devices": "Пристрої", + "management": "Управління пристроєм", + "view-devices": "Перегляд пристроїв", + "device-alias": "Псевдонім пристрою", + "aliases": "Псевдонім пристроїв", + "no-alias-matching": "'{{alias}}' не знайдено.", + "no-aliases-found": "Псевдонімів не знайдено.", + "no-key-matching": "'{{key}}' не знайдено.", + "no-keys-found": "Ключі не знайдено.", + "create-new-alias": "Створити новий!", + "create-new-key": "Створити новий!", + "duplicate-alias-error": "Псевдонім з таким іменем '{{alias}}' вже існює.
Псевдоніми пристроїв повинні бути унікальними на панелі візуалізації.", + "configure-alias": "Налаштувати псевдонім '{{alias}}'", + "no-devices-matching": "Не знайдено жодних пристроїв, які відповідають '{{entity}}'.", + "alias": "Псевдонім", + "alias-required": "Необхідно задати псевдонім пристрою.", + "remove-alias": "Видалити псевдонім пристрою", + "add-alias": "Додати псевдонім пристрою", + "name-starts-with": "Ім'я пристрою починається з", + "device-list": "Список пристроїв", + "use-device-name-filter": "Використати фільтр", + "device-list-empty": "Не вибрано жодного пристрою.", + "device-name-filter-required": "Необхідно задати назву фільтра пристрою.", + "device-name-filter-no-device-matched": "Не знайдено жодних пристроїв, що починаються з '{{device}}'.", + "add": "Додати пристрій", + "assign-to-customer": "Призначити клієнту", + "assign-device-to-customer": "Призначити пристрій (ої) клієнту", + "assign-device-to-customer-text": "Виберіть пристрої, які слід призначити клієнту", + "make-public": "Зробити пристрій публічним", + "make-private": "Зробити пристрій приватним", + "no-devices-text": "Не знайдено жодного пристрою", + "assign-to-customer-text": "Виберіть клієнта для призначення пристрою (їв)", + "device-details": "Деталі пристрою", + "add-device-text": "Додати новий пристрій", + "credentials": "Авторизаційні дані", + "manage-credentials": "Керування авторизаційними даними", + "delete": "Видалити пристрій", + "assign-devices": "Призначити пристрої", + "assign-devices-text": "Призначити { count, plural, 1 {1 пристрій} other {# пристрої} } клієнту", + "delete-devices": "Видалити пристрої", + "unassign-from-customer": "Позбавити клієнта пристроїв", + "unassign-devices": "Позбавити пристроїв", + "unassign-devices-action-title": "Позбавити клієнта { count, plural, 1 {1 пристрою} other {# пристроїв} }", + "assign-new-device": "Призначити новий пристрій", + "make-public-device-title": "Ви впевнені, що хочете зробити пристрій '{{deviceName}}' публічним?", + "make-public-device-text": "Після підтвердження пристрій і всі його дані будуть публічними та доступними для інших.", + "make-private-device-title": "Ви впевнені, що хочете зробити пристрій '{{deviceName}}' приватним?", + "make-private-device-text": "Після підтвердження пристрій і всі його дані будуть приватними та недоступними для інших.", + "view-credentials": "Переглянути авторизаційні дані", + "delete-device-title": "Ви впевнені, що хочете видалити пристрій '{{deviceName}}'?", + "delete-device-text": "Будьте обережні, після підтвердження, пристрій і всі пов'язані з ним дані стануть недоступними.", + "delete-devices-title": "Ви впевнені, що хочете видалити { count, plural, 1 {1 пристрій} other {# пристрої} }?", + "delete-devices-action-title": "Видалити { count, plural, 1 {1 пристрій} other {# пристрої} }", + "delete-devices-text": "Будьте обережні, після підтвердження, всі вибрані пристрої будуть видалені, і всі пов'язані з ними дані стануть недоступними.", + "unassign-device-title": "Ви впевнені, що хочете позбавити пристрою '{{deviceName}}'?", + "unassign-device-text": "Після підтвердження, клієнт буде позбавлений пристрою.", + "unassign-device": "Позбавити пристою", + "unassign-devices-title": "Ви впевненені, що хочете позбавити { count, plural, 1 {1 пристрою} other {# пристроїв} }?", + "unassign-devices-text": "Після підтвердження, клієнт буде позбавлений пристрою і пристрій стане не доступним клієнту", + "device-credentials": "Авторизаційні дані прстрою", + "credentials-type": "Тип авторизаційних даних", + "access-token": "Маркер доступу", + "access-token-required": "Необхідно вказати маркер доступу.", + "access-token-invalid": "Маркер доступу має бути від 1 до 20 символів.", + "rsa-key": "Публічний ключ RSA", + "rsa-key-required": "Необхідно вказати публічний ключ RSA.", + "secret": "Секрет", + "secret-required": "Необхідно вказати секрет.", + "device-type": "Тип пристрою", + "device-type-required": "Необхідно вказати тип пристрою.", + "select-device-type": "Виберіть тип пристрою", + "enter-device-type": "Введіть тип пристрою", + "any-device": "Будь-який пристрій", + "no-device-types-matching": "Не знайдено типів пристроїв, які відповідають '{{entitySubtype}}'.", + "device-type-list-empty": "Не вибрано типів пристроїв.", + "device-types": "Типи пристрою", + "name": "Ім'я", + "name-required": "Необхідно вказати ім'я.", + "description": "Опис", + "events": "Події", + "details": "Деталі", + "copyId": "Копіювати Id пристрою", + "copyAccessToken": "Копіювати маркер доступу", + "idCopiedMessage": "Id пристрою скопійовано в буфер обміну", + "accessTokenCopiedMessage": "Маркер доступу до пристрою скопійовано в буфер обміну", + "assignedToCustomer": "Призначений клієнту", + "unable-delete-device-alias-title": "Неможливо видалити псевдонім пристрою", + "unable-delete-device-alias-text": "Псевдонім пристрою '{{deviceAlias}}' не може бути видалений, оскільки він використовується наступним(и) віджетом(ами):
{{widgetsList}}", + "is-gateway": "Шлюз", + "public": "Публічно", + "device-public": "Пристрій є публічним", + "select-device": "Виберіть пристрій", + "selected-devices": "{ count, plural, 1 {1 пристрій} other {# пристрої} } вибрано", + "search": "Шукати пристрої", + "select-group-to-add": "Виберіть цільову групу, щоб додати вибраний пристрій", + "select-group-to-move": "Виберіть цільову групу для переміщення вибраних пристроїв", + "remove-devices-from-group": "Ви впевнені, що хочете видалити { count, plural, 1 {1 пристрій} other {# пристрої} } з групи '{entityGroup}'?", + "group": "Група пристроїв", + "list-of-groups": "{ count, plural, 1 {Одна група пристроїв} other {Список # груп пристроїв} }", + "group-name-starts-with": "Групи пристроїв, імена яких починаються з '{{prefix}}'" + }, + "dialog": { + "close": "Закрити діалогове вікно" + }, + "direction": { + "column": "Колонка", + "row": "Рядок" + }, + "error": { + "unable-to-connect": "Неможливо підключитися до сервера! Перевірте підключення до Інтернету.", + "unhandled-error-code": "Неопрацьований помилковий код: {{errorCode}}", + "unknown-error": "Невідома помилка" + }, + "entity": { + "entity": "Сутність", + "entities": "Сутності", + "aliases": "Псевдоніми сутності", + "entity-alias": "Псевдонім сутності", + "unable-delete-entity-alias-title": "Неможливо видалити псевдонім сутності", + "unable-delete-entity-alias-text": "Псевдонім сутності'{{entityAlias}}' неможливо видалити, так як це використовується наступним віджетом(s):
{{widgetsList}}", + "duplicate-alias-error": "Знайдено повторюваний псевдонім '{{alias}}'.
Псевдоніми сутностей повинні бути унікальними на панелі візуалізації.", + "missing-entity-filter-error": "Відсутній фільтр для псевдоніма '{{alias}}'.", + "configure-alias": "Налаштувати '{{alias}}' псевдонім", + "alias": "Псевдонім", + "alias-required": "Необхідно вказати псевдонім сутності.", + "remove-alias": "Видалити псевдонім сутності", + "add-alias": "Додати псевдонім сутності", + "entity-list": "Список сутності", + "entity-type": "Тип сутності", + "entity-types": "Типи сутності", + "entity-type-list": "Список типу сутності", + "any-entity": "Будь-яка сутність", + "enter-entity-type": "Введіть тип сутності", + "no-entities-matching": "Не знайдено жожних сутностей, що відповідають '{{entity}}' що відповідають.", + "no-entity-types-matching": "Не знайдено жожних типів сутностей, що відповідають '{{entityType}}'.", + "name-starts-with": "Назва починається з", + "use-entity-name-filter": "Використовуйте фільтр", + "entity-list-empty": "Не вибрано жодних сутностей.", + "entity-type-list-empty": "Не вибрано жодних типів сутностей.", + "entity-name-filter-required": "Необхідно задати фільтр по імені.", + "entity-name-filter-no-entity-matched": "Не знайдено жодних сутностей, що починаються з '{{entity}}'.", + "all-subtypes": "Всі", + "select-entities": "Виберіть сутність", + "no-aliases-found": "Псевдонімів не знайдено.", + "no-alias-matching": "'{{alias}}' не знайдено.", + "create-new-alias": "Створити новий псевдонім!", + "key": "Ключ", + "key-name": "Ім'я ключа", + "no-keys-found": "No keys found.", + "no-key-matching": "'{{key}}' не знайдено.", + "create-new-key": "Створити новий ключ!", + "type": "Тип", + "type-required": "Необхідно задати тип сутності.", + "type-device": "Пристрій", + "type-devices": "Пристрої", + "list-of-devices": "{ count, plural, 1 {Один пристрій} other {Список # пристроїв} }", + "device-name-starts-with": "Пристрої, імена яких починаються з '{{prefix}}'", + "type-asset": "Актив", + "type-assets": "Активи", + "list-of-assets": "{ count, plural, 1 {Один актив} other {Список # активів} }", + "asset-name-starts-with": "Активи, імена яких починаються з '{{prefix}}'", + "type-entity-view": "Перегляд сутності", + "type-entity-views": "Перегляди сутності", + "list-of-entity-views": "{ count, plural, 1 {Один перегляд сутності} other {Список # переглядів сутності} }", + "entity-view-name-starts-with": "Перегляди сутностей, імена яких починаються з '{{prefix}}'", + "type-rule": "Правило", + "type-rules": "Правила", + "list-of-rules": "{ count, plural, 1 {Одне правило} other {Список # правил} }", + "rule-name-starts-with": "Правила, імена яких починаються з '{{prefix}}'", + "type-plugin": "Плагін", + "type-plugins": "Плагіни", + "list-of-plugins": "{ count, plural, 1 {Один плагін} other {Список # плагінів} }", + "plugin-name-starts-with": "Плагіни, імена яких починаються з '{{prefix}}'", + "type-tenant": "Власник", + "type-tenants": "Власники", + "list-of-tenants": "{ count, plural, 1 {Один власник} other {Список # власників} }", + "tenant-name-starts-with": "Власники, імена яких починаються з '{{prefix}}'", + "type-customer": "Клієнт", + "type-customers": "Клієнти", + "list-of-customers": "{ count, plural, 1 {Один клієнт} other {Список # клієнтів} }", + "customer-name-starts-with": "Клієнти, імена яких починаються з '{{prefix}}'", + "type-user": "Користувач", + "type-users": "Користувачі", + "list-of-users": "{ count, plural, 1 {Один користувач} other {Список # користувачів } }", + "user-name-starts-with": "Користувачі, імена яких починаються з '{{prefix}}'", + "type-dashboard": "Панель візуалізації", + "type-dashboards": "Панелі візуалізації", + "list-of-dashboards": "{ count, plural, 1 {Одна панель візуалізації} other {Список # панелей візуалізації} }", + "dashboard-name-starts-with": "Панелі візуалізації, імена яких починаються з '{{prefix}}'", + "type-alarm": "Сигнал тривоги", + "type-alarms": "Сигнали тривоги", + "list-of-alarms": "{ count, plural, 1 {Один сигнал тривоги} other {Список # сигналів тривоги} }", + "alarm-name-starts-with": "Сигнали тривоги, імена яких починаються '{{prefix}}'", + "type-rulechain": "Правило ланцюжка", + "type-rulechains": "Правило ланцюжків", + "list-of-rulechains": "{ count, plural, 1 {Одне правило ланцюжка} other {Список # правил ланцюжків} }", + "rulechain-name-starts-with": "Правило ланцюжків, імена яких починаються '{{prefix}}'", + "type-scheduler-event": "Scheduler event", + "type-scheduler-events": "Scheduler events", + "list-of-scheduler-events": "{ count, plural, 1 {One scheduler event} other {List of # scheduler events} }", + "scheduler-event-name-starts-with": "Scheduler events whose names start with '{{prefix}}'", + "type-blob-entity": "Blob entity", + "type-blob-entities": "Blob entities", + "list-of-blob-entities": "{ count, plural, 1 {One blob entity} other {List of # blob entities} }", + "blob-entity-name-starts-with": "Blob entities whose names start with '{{prefix}}'", + "type-rulenode": "Правило", + "type-rulenodes": "Правила", + "list-of-rulenodes": "{ count, plural, 1 {Одне правило} other {Список # правил} }", + "rulenode-name-starts-with": "Список правил, імена яких починаються '{{prefix}}'", + "type-current-customer": "Поточний клієнт", + "search": "Пошук сутностей", + "selected-entities": "{ count, plural, 1 {1 сутність} other {# сутності} } вибрано", + "entity-name": "Ім'я сутності", + "details": "Подробиці сутності", + "no-entities-prompt": "Сутності не знайдено", + "no-data": "Немає даних для відображення", + "columns-to-display": "Стовпці для відображення", + "type-entity-group": "Група сутностей", + "type-converter": "Перетворювач даних", + "type-converters": "Перетворювачі даних", + "list-of-converters": "{ count, plural, 1 {Однин перетворювач даних} other {Список # перетворювачів даних} }", + "converter-name-starts-with": "Перетворювачі даних, імена яких починаються з '{{prefix}}'", + "type-integration": "Інтеграція", + "type-integrations": "Інтеграції", + "list-of-integrations": "{ count, plural, 1 {Одна інтеграція} other {Список # інтеграцій} }", + "integration-name-starts-with": "Інтеграції, імена яких починаються з '{{prefix}}'" + }, + "entity-group": { + "entity-group": "Група сутності", + "details": "Деталі", + "columns": "Стовпці", + "add-column": "Додати стовпець", + "column-value": "Значення", + "column-value-required": "Необхідно вказати значення.", + "column-title": "Назва", + "default-sort-order": "Основний порядок сортування", + "default-sort-order-required": "Необхідно вказати основний порядок сортування.", + "hide-in-mobile-view": "Мобільний приховано", + "use-cell-style-function": "Використовувати функцію стилю комірки", + "use-cell-content-function": "Use cell content function", + "edit-column": "Редагувати стовпець", + "column-details": "Деталі стовпця", + "actions": "Дії", + "settings": "Налаштування", + "delete": "Видалити групу сутностей", + "name": "Ім'я", + "name-required": "Необхідно вказати ім'я.", + "description": "Опис", + "add": "Додати групу сутностей", + "add-entity-group-text": "Додати нову групу сутностей", + "no-entity-groups-text": "Не знайдено жодних груп сутності", + "entity-group-details": "Деталі групи сутності", + "delete-entity-groups": "Видалити групи сутностей", + "delete-entity-group-title": "Ви впевнені, що хочете видалити групу сутності '{{entityGroupName}}'?", + "delete-entity-group-text": "Будьте обережні, після підтвердження, група сутностей і всі пов'язані з нею дані стануть недоступними.", + "delete-entity-groups-title": "Ви впевнені, що хочете видалити { count, plural, 1 {1 групу сутності} other {# групи сутностей} }?", + "delete-entity-groups-action-title": "Видалити { count, plural, 1 {1 групу сутності} other {# групи сутностей} }", + "delete-entity-groups-text": "Будьте обережні, після підтвердження, всі виділені групи сутностей і пов'язані з ними дані, стануть недоступними.", + "device-groups": "Групи пристроїв", + "asset-groups": "Групи активів", + "customer-groups": "Групи клієнтів", + "device-group": "Група пристроїв", + "asset-group": "Група активів", + "customer-group": "Група клієнтів", + "fetch-more": "Отримати більше", + "column-type": { + "column-type": "Тип стовпця", + "client-attribute": "Атрибут клієнта", + "shared-attribute": "Спільний атрибут", + "server-attribute": "Атрибут сервера", + "timeseries": "Телеметрія", + "entity-field": "Поле сутності" + }, + "column-type-required": "Необхідно вказати тип стовпця.", + "entity-field": { + "created-time": "Створений час", + "name": "Ім'я", + "type": "Тип", + "assigned_customer": "Призначений клієнт", + "authority": "Авторитет", + "first_name": "Ім'я", + "last_name": "Прізвище", + "email": "Електронна пошта", + "title": "Назва", + "country": "Країна", + "state": "Штат", + "city": "Місто", + "address": "Адреса", + "address2": "Адреса 2", + "zip": "Zip", + "phone": "Телефон" + }, + "sort-order": { + "asc": "У порядку зростання", + "desc": "У порядку зменшення", + "none": "Не має" + }, + "details-mode": { + "on-row-click": "Клацніть на рядок", + "on-action-button-click": "Клацніть на кнопку детелі", + "disabled": "Вимкнено" + }, + "add-to-group": "Додати до групи", + "move-to-group": "Перемістити до групи", + "select-entity-group": "Виберати групу сутностей", + "no-entity-groups-matching": "Не знайдено жодних груп сутностей, що відповідають '{{entityGroup}}'.", + "target-entity-group-required": "Необхідно вказати цільову групу сутності.", + "remove-from-group": "Видалити з групи", + "group-table-title": "Group table title", + "enable-search": "Увімкнути пошук сутностей", + "enable-add": "Увімкнути додавання сутностей", + "enable-delete": "Увімкнути видалення сутностей", + "enable-selection": "Увімкнути вибір сутностей", + "enable-group-transfer": "Увімкнути дії групового перенесення", + "display-pagination": "Відображення сторінок", + "default-page-size": "Розмір сторінки за замовчуванням", + "enable-assignment-actions": "Увімкнути дії призначення", + "enable-credentials-management": "Увімкнути керування авторизаційними даними", + "enable-users-management": "Увімкнути керування користувачами", + "enable-assets-management": "Увімкнути керування активами", + "enable-devices-management": "Увімкнути керування пристроями", + "enable-dashboards-management": "Увімкнути керування панелями візуалізації", + "open-details-on": "Відкрити деталі сутності", + "select-existing": "Виберіть існуючу групу сутностей", + "create-new": "Створити нову групу сутностей", + "new-entity-group-name": "Нове ім'я групи сутностей", + "entity-group-list": "Список групи сутностей", + "entity-group-list-empty": "Не вибрано жодної групи сутностей.", + "name-starts-with": "Назва групи сутностей починається з", + "entity-group-name-filter-required": "Необхідно задати назву групи сутностей." + }, + "entity-view": { + "entity-view": "Перегляд сутності", + "entity-view-required": "Необхідно вказати перегляд сутності.", + "entity-views": "Перегляди сутностей", + "management": "Керування переглядом сутностей", + "view-entity-views": "Переглянути перегляд сутностей", + "entity-view-alias": "Псевдонім перегляду сутності", + "aliases": "Псевдоніми перегляду сутності", + "no-alias-matching": "Псевдонім'{{alias}}' не знайдено.", + "no-aliases-found": "Псевдоніми не знайдено.", + "no-key-matching": "'Ключ {{key}}' не знайдено.", + "no-keys-found": "Ключі не знайдено.", + "create-new-alias": "Створити новий!", + "create-new-key": "Створити новий!", + "duplicate-alias-error": "Псевдонім з такою назвою вже існує '{{alias}}'.
Псевдоніми перегляду повинні бути унікальними на панелі візуалізації.", + "configure-alias": "Налаштувати псевдонім '{{alias}}'", + "no-entity-views-matching": "Сутності, які відповідають '{{entity}}' не знайдені.", + "alias": "Псевдонім", + "alias-required": "Необхідно вказати псевдонім перегляду сутності.", + "remove-alias": "Видалити псевдонім перегляду сутності", + "add-alias": "Додати псевдонім перегляду сутності", + "name-starts-with": "Ім'я перегляду сутності починається з", + "entity-view-list": "Список перегляду сутності", + "use-entity-view-name-filter": "Використати фільтр", + "entity-view-list-empty": "Не вибрано жодного перегляду сутності.", + "entity-view-name-filter-required": "Необхідно вказвти фільтр назв представлення сутності.", + "entity-view-name-filter-no-entity-view-matched": "Перегляди сутностей, назви яких починаються з '{{entityView}}' не знайдено.", + "add": "Додати перегляд сутності", + "assign-to-customer": "Призначити клієнту", + "assign-entity-view-to-customer": "Призначити перегляд(и) сутності(ей) клієнту", + "assign-entity-view-to-customer-text": "Будь ласка, виберіть перегляд сутності для призначення клієнту", + "no-entity-views-text": "Перегляду сутності не знайдено", + "assign-to-customer-text": "Будь ласка, виберіть клієнта, для призначиення перегляду(ів) сутності(ей)", + "entity-view-details": "Деталі перегляду сутності", + "add-entity-view-text": "Додати новий перегляд сутносі", + "delete": "Видалити перегляд сутності", + "assign-entity-views": "Призначити перегляд сутності", + "assign-entity-views-text": "Призначити { count, plural, 1 {1 перегляд сутності} other {# перегляди сутностей } } клієнту", + "delete-entity-views": "Видалити перегляди сутностей", + "unassign-from-customer": "Позбавити клієнта", + "unassign-entity-views": "Позбавити переглядів сутностей", + "unassign-entity-views-action-title": "Позбавити { count, plural, 1 {1 перегляду сутності} other {# переглядів сутностей} } клієнта", + "assign-new-entity-view": "Призначити новий перегляд сутності", + "delete-entity-view-title": "Ви впевнені, що хочете видалити перегляд сутності'{{entityViewName}}'?", + "delete-entity-view-text": "Будьте обережні, після підтвердження, перегляд сутності та всі пов'язані з нею дані стануть недоступними.", + "delete-entity-views-title": "Ви впевнені, що хочете видалити перегляд сутності { count, plural, 1 {1 перегляд сутності } other {# перегляди сутностей } }?", + "delete-entity-views-action-title": "Видалити { count, plural, 1 {1 перегляд сутності } other {# перегляди сутностей } }", + "delete-entity-views-text": "Будьте обережні, після підтвердження, всі виділені перегляди сутностей та дні, пов'язані з ними стануть недоступними.", + "unassign-entity-view-title": "Ви впевнені, що хочете позбавити перегляду сутності '{{entityViewName}}'?", + "unassign-entity-view-text": "Після підтвердження клієнт буде позбавлений перегляду сутності. Дані перегляду сутності не будуть доступні клієнту.", + "unassign-entity-view": "Позбавити перегляду сутності", + "unassign-entity-views-title": "Ви впевнені, що хочете позбавити { count, plural, 1 {1 перегляду сутності} other {# переглядів сутностей} }?", + "unassign-entity-views-text": "Після підтвердження, клієнта буде позбавлено всіх виділених переглядів сутності. Дані переглядів сутностей не будуть доступні клієнту .", + "entity-view-type": "Тип перегляду сутності", + "entity-view-type-required": "Необхідно вказати тип перегляду сутності.", + "select-entity-view-type": "Виберіть тип перегляду сутності", + "enter-entity-view-type": "Введіть тип перегляду сутності", + "any-entity-view": "Будь-який перегляд сутності", + "no-entity-view-types-matching": "Не знайдено жодних типів перегляду сутності, що відповідають '{{entitySubtype}}'.", + "entity-view-type-list-empty": "Не вибрано тип перегляду сутності.", + "entity-view-types": "Типи перегляду сутності", + "name": "Ім'я", + "name-required": "Необхідно вказати ім'я.", + "description": "Опис", + "events": "Події", + "details": "Деталі", + "copyId": "Скопіювати Id перегляду сутності", + "assignedToCustomer": "Призначений клієнту", + "unable-entity-view-device-alias-title": "Неможливо видалити псевдонім перегляду сутності", + "unable-entity-view-device-alias-text": "Не вдалося видалити псевдонім пристрою'{{entityViewAlias}}', так як він використовується наступним(и) віджетом(ами):
{{widgetsList}}", + "select-entity-view": "Виберати перегляд сутності", + "make-public": "Зробити перегляд сутності публічним", + "start-date": "Дата початку", + "start-ts": "Час початку", + "end-date": "Дата закінчення", + "end-ts": "Час завершення", + "date-limits": "Обмеження дати", + "client-attributes": "Атрибути клієнта", + "shared-attributes": "Спільні атрибути", + "server-attributes": "Атрибути сервера", + "timeseries": "Телеметрія", + "client-attributes-placeholder": "Атрибути клієнта", + "shared-attributes-placeholder": "Спільні атрибути", + "server-attributes-placeholder": "Атрибути сервера", + "timeseries-placeholder": "Телеметрія", + "target-entity": "Цільова сутність", + "attributes-propagation": "Поширення атрибутів", + "attributes-propagation-hint": "Перегляд сутностей автоматично копіюватиме вказані атрибути з цільової сутності кожного разу, коли ви зберігаєте або оновлюєте ці перегляди. В цілях продуктивності, атрибути цільової сутності не поширюються на представлення сутності на кожній зміні їх атрибутів. Можна ввімкнути автоматичне поширення, налаштувавши у вашому ланцюжку правило \"copy to view\" і пов'язуючи його з повідомленнями типу \"Post attributes\" і \"Attributes Updated\"..", + "timeseries-data": "Дані телеметрії", + "timeseries-data-hint": "Налаштуйте ключі даних телеметрії цільової сутності, які будуть доступні перегляду сутності. Ці дані доступні лише для читання." + }, + "event": { + "events": "Події", + "event-type": "Тип події", + "type-error": "Помилка", + "type-lc-event": "Подія життєвого циклу", + "type-stats": "Статистика", + "type-debug-converter": "Налагоджувати", + "type-debug-integration": "Налагоджувати", + "type-debug-rule-node": "Налагоджувати", + "type-debug-rule-chain": "Налагоджувати", + "no-events-prompt": "Не знайдено жодних подій", + "error": "Помилка", + "alarm": "Сигнал тривоги", + "event-time": "Час події", + "server": "Сервер", + "body": "Тіло", + "method": "Метод", + "type": "Тип", + "in": "In", + "out": "Out", + "metadata": "Метадані", + "message": "Повідомлення", + "entity": "Сутність", + "message-id": "Id повідомлення", + "message-type": "Тип повідомлення", + "data-type": "Тип даних", + "relation-type": "Тип зв'язку", + "data": "Дані", + "event": "Подія", + "status": "Статус", + "success": "Успіх", + "failed": "Невдача", + "messages-processed": "Повідомлення опрацьовані", + "errors-occurred": "Виникли помилки" + }, + "extension": { + "extensions": "Розширення", + "selected-extensions": " вибрано { count, plural, 1 {1 розширення} other {# розширення} }", + "type": "Тип", + "key": "Ключ", + "value": "Значення", + "id": "Id", + "extension-id": "Id розширення", + "extension-type": "Тип розширення", + "transformer-json": "JSON *", + "unique-id-required": "Таке Id розширення вже існує.", + "delete": "Видалити розширення", + "add": "Додати розширення", + "edit": "Редагувати розширення", + "delete-extension-title": "Ви дійсно бажаєте видалити розширення '{{extensionId}}'?", + "delete-extension-text": "Будьте обережні, після підтвердження, розширення та всі пов'язані з ним дані стануть недоступними.", + "delete-extensions-title": "Ви дійсно бажаєте видалити { count, plural, 1 {1 розширення} other {# розширення} }?", + "delete-extensions-text": "Будьте обережні, після підтвердження, всі вибрані розширення будуть видалені.", + "converters": "Перетворювачі", + "converter-id": "Id перетворювача", + "configuration": "Конфігурація", + "converter-configurations": "Конфігурації перетворювача", + "token": "Маркер безпеки", + "add-converter": "Додати конвертер", + "add-config": "Додати конфігурацію конвертера", + "device-name-expression": "Маска імені пристрою", + "device-type-expression": "Маска типу пристрою", + "custom": "Користувач", + "to-double": "Подвоїти", + "transformer": "Трансформатор", + "json-required": "Необхідно вказати json трансформатора.", + "json-parse": "Неможливо проаналізувати json трансформатора.", + "attributes": "Атрибути", + "add-attribute": "Додати атрибут", + "add-map": "Додати елемент відображення", + "timeseries": "Телеметрія", + "add-timeseries": "Додати параметри телеметрії", + "field-required": "Field is required", + "brokers": "Брокери", + "add-broker": "Додати брокера", + "host": "Хост", + "port": "Порт", + "port-range": "Значення порту має бути в діапазоні від 1 до 65535.", + "ssl": "Ssl", + "credentials": "Авторизаційні дані", + "username": "Ім'я користувача", + "password": "Пароль", + "retry-interval": "Інтервал повтору в мілісекундах", + "anonymous": "Анонімний", + "basic": "Основний", + "pem": "PEM", + "ca-cert": "Файл CA сертифіката *", + "private-key": "Файл приватного ключа *", + "cert": "Файл сертифіката *", + "no-file": "Не вибрано жодного файлу.", + "drop-file": "Перетягніть файл, або клацніть, щоб вибрати файл для завантаження.", + "mapping": "Зіставлення", + "topic-filter": "Фільтр тем", + "converter-type": "Тип конвертера", + "converter-json": "Json", + "json-name-expression": " Json вираз для назви пристрою", + "topic-name-expression": "Вираз для назви пристрою в назві теми", + "json-type-expression": "Json вираз для типу пристрою", + "topic-type-expression": "Вираз для типу пристрою в назві теми", + "attribute-key-expression": "Вираз для ключа атрибута", + "attr-json-key-expression": " Json вираз для ключа атрибута", + "attr-topic-key-expression": "Вираз для ключа атрибута в назві теми", + "request-id-expression": "Вираз для id запиту", + "request-id-json-expression": "Json вираз для id запиту", + "request-id-topic-expression": "Вираз для id запиту в назві теми", + "response-topic-expression": "Вираз для теми відповідей", + "value-expression": "Вираз для значення", + "topic": "Тема", + "timeout": "Час очікування в мілісекундах", + "converter-json-required": "Необхідно вказати json конвертер.", + "converter-json-parse": "Неможливо проаналізувати json конвертера.", + "filter-expression": "Вираз для фільтра", + "connect-requests": "Запити на підключення", + "add-connect-request": "Додати запит на підключення", + "disconnect-requests": "Відключення запитів", + "add-disconnect-request": "Додати запит на відключення", + "attribute-requests": "Запити атрибутів", + "add-attribute-request": "Додати запит атрибута", + "attribute-updates": "Оновлення атрибутів", + "add-attribute-update": "Додати оновлення атрибутів", + "server-side-rpc": "Серверна сторона RPC", + "add-server-side-rpc-request": "Додати RPC-запит на стороні сервера", + "device-name-filter": "Фільтр назви пристрою", + "attribute-filter": "Фільтр атрибутів", + "method-filter": "Фільтр методів", + "request-topic-expression": "Вираз для теми запитів", + "response-timeout": "Час очікування відповіді в мілісекундах", + "topic-expression": "Вираз для назви теми", + "client-scope": "Обсяг клієнта", + "add-device": "Додати пристрій", + "opc-server": "Сервери", + "opc-add-server": "Додати сервер", + "opc-add-server-prompt": "Будь ласка, додайте сервер", + "opc-application-name": "Назва програми", + "opc-application-uri": "URI програми", + "opc-scan-period-in-seconds": "Період сканування в секундах", + "opc-security": "Безпека", + "opc-identity": "Ідентифікація", + "opc-keystore": "Сховище ключів", + "opc-type": "Тип", + "opc-keystore-type": "Тип", + "opc-keystore-location": "Розташування *", + "opc-keystore-password": "Пароль", + "opc-keystore-alias": "Псевдонім", + "opc-keystore-key-password": "Пароль для ключа", + "opc-device-node-pattern": "Патерн OPC вузла пристрою", + "opc-device-name-pattern": "Патерн назви пристрою", + "modbus-server": "Сервери/ведені пристрої", + "modbus-add-server": "Додати сервер/ведений пристрій", + "modbus-add-server-prompt": "Будь ласка, додайте сервер/ведений пристрій", + "modbus-transport": "Транспорт", + "modbus-port-name": "Ім'я послідовного порту", + "modbus-encoding": "Кодування", + "modbus-parity": "Паритет", + "modbus-baudrate": "Швидкість передачі даних", + "modbus-databits": "Біти даних", + "modbus-stopbits": "Стоп-біти", + "modbus-databits-range": "Біти даних повинні знаходитися в діапазоні від 7 до 8.", + "modbus-stopbits-range": "Стоп-біти повинні знаходитися в діапазоні від 1 до 2.", + "modbus-unit-id": "Unit ID", + "modbus-unit-id-range": "Unit ID should be in a range from 1 to 247.", + "modbus-device-name": "Ім'я пристрою", + "modbus-poll-period": "Період опитування (мс)", + "modbus-attributes-poll-period": "Період опитування атрибутів (мс)", + "modbus-timeseries-poll-period": "Період опитування телеметрії (мс)", + "modbus-poll-period-range": "Період опитування повинен бути більше 0.", + "modbus-tag": "Тег", + "modbus-function": "Modbus функція", + "modbus-register-address": "Адреса регістру ", + "modbus-register-address-range": "Адреса регістру повинна бути в діапазоні від 0 до 65535.", + "modbus-register-bit-index": "Номер бітів", + "modbus-register-bit-index-range": "Номер бітів повинен знаходитися в діапазоні від 0 до 15.", + "modbus-register-count": "Рахунок регістру", + "modbus-register-count-range": "Рахунок регістру повинен бути більше 0.", + "modbus-byte-order": "Порядок байтів", + + "sync": { + "status": "Статус", + "sync": "Синхронізований", + "not-sync": "Не синхронізований", + "last-sync-time": "Час останньої синхронізації", + "not-available": "Недоступний" + }, + + "export-extensions-configuration": "Експортувати конфігурацію розширень", + "import-extensions-configuration": "Імпортувати конфігурацію розширень", + "import-extensions": "Імпортувати розширення", + "import-extension": "Імпортувати розширення", + "export-extension": "Експортувати розширення", + "file": "Файл розширень", + "invalid-file-error": "Не правильний формат файла" + }, + "fullscreen": { + "expand": "Відкрити у повноекранному режимі", + "exit": "Вийти з повноекранного режиму", + "toggle": "Перемкнути повноекранний режим", + "fullscreen": "Повноекранний режим" + }, + "function": { + "function": "Функція" + }, + "grid": { + "delete-item-title": "Ви впенені, що хочете видалити цей елемент?", + "delete-item-text": "Будьте обережні, після підтвердження, цей елемент і всі пов'язані з ним дані, стануть недоступними.", + "delete-items-title": "Ви впенені, що хочете видалити { count, plural, 1 {1 елемент} other {# елементи} }?", + "delete-items-action-title": "Видалити{ count, plural, 1 {1 елемент} other {# елементи} }", + "delete-items-text": "Будьте обережні, після підтвердження, всі виділені елементи і пов'язані з ними дані, стануть недоступними.", + "add-item-text": "Додати новий елемент", + "no-items-text": "Не знайдено жодного елемента", + "item-details": "Деталі елемента", + "delete-item": "Видалити елементи", + "scroll-to-top": "Перейти угору" + }, + "help": { + "goto-help-page": "Перейти на сторінку довідки" + }, + "home": { + "home": "Домашня сторінка", + "profile": "Профіль", + "logout": "Вийти", + "menu": "Меню", + "avatar": "Аватар", + "open-user-menu": "Відкрити меню користувача" + }, + "import": { + "no-file": "Не вибрано жодного файлу", + "drop-file": "Перетягніть JSON файл, або клацніть, щоб вибрати файл для завантаження.", + "drop-csv-file": "Перетягніть CSV файл, або клацніть, щоб вибрати файл для завантаження." + }, + "integration": { + "integration": "Інтеграція", + "integrations": "Інтеграції", + "select-integration": "Виберіть інтеграцію", + "no-integrations-matching": "Не знайдено жодних інтеграцій, які відповідають '{{entity}}'.", + "integration-required": "Необхідно вказати інтеграцію", + "delete": "Видалити інтеграцію", + "management": "Управління інтеграціями", + "add-integration-text": "Додати нову інтеграцію", + "no-integrations-text": "Не знайдено жодної інтеграції", + "selected-integrations": "{ count, plural, 1 {1 інтеграція} other {# інтеграції} } вибрано", + "delete-integration-title": "Ви впевнені, що хочете видалити інтеграцію '{{integrationName}}'?", + "delete-integration-text": "Будьте обережні, після підтвердження інтеграція та всі пов'язані з нею дані стануть недоступними.", + "delete-integrations-title": "Ви впевнені, що хочете видалити { count, plural, 1 {1 інтеграцію} other {# інтеграції} }?", + "delete-integrations-action-title": "Видалити { count, plural, 1 {1 інтеграцію} other {# інтеграції} }", + "delete-integrations-text": "Будьте обережні, після підтвердження всі вибрані інтеграції будуть видалені, і всі пов'язані з ними дані стануть недоступними.", + "events": "Події", + "add": "Додати інтеграцію", + "integration-details": "Деталі інтеграції", + "details": "Деталі", + "copyId": "Копіювати Id інтеграції", + "idCopiedMessage": "Id інтеграції скопійовано в буфер обміну", + "debug-mode": "Режим налагодження", + "enable-security": "Увімкнути безпеку", + "headers-filter": "Заголовки фільтра", + "header": "Заголовок", + "no-headers-filter": "Немає фільтрів заголовків", + "downlink-url": "Downlink URL", + "application-uri": "URI програми", + "as-id": "AS ID", + "as-id-required": "Необхідно вказати AS ID.", + "as-key": "AS ключ", + "as-key-required": "Необхідно вказати AS ключ.", + "max-time-diff-in-seconds": "Максимальна різниця в часі (секунди)", + "max-time-diff-in-seconds-required": "Необхідно вказати максимальну різницю в часі.", + "name": "Ім'я", + "name-required": "Необхідно вказати ім'я.", + "description": "Опис", + "base-url": "Базова URL-адреса", + "base-url-required": "Необхідно вказати базову URL-адресу", + "security-key": "Ключ захисту", + "http-endpoint": "URL кінцевої точки HTTP", + "copy-http-endpoint-url": "Скопіювати URL-адресу кінцевої точки HTTP", + "http-endpoint-url-copied-message": "URL кінцевої точки HTTP скопійовано в буфер обміну", + "host": "Хост", + "host-required": "Необхідно вказати хост.", + "host-type": "Тип хоста", + "host-type-required": "Необхідно вказати тип хоста.", + "custom-host": "Хост користувача", + "custom-host-required": "Необхідний спеціальний хост.", + "port": "Порт", + "port-required": "Необхідно вказати порт.", + "port-range": "Порт має бути в діапазоні від 1 до 65535.", + "connect-timeout": "Час очікування з'єднання (сек)", + "connect-timeout-required": " Необхідно вказати час з'єднання підключення.", + "connect-timeout-range": "Час очікування з'єднання має бути в діапазоні від 1 до 200.", + "client-id": "ID клієнта", + "clean-session": "Очистити сеанс", + "enable-ssl": "Увімкнути SSL", + "credentials": "Авторизаційні дані", + "credentials-type": "Тип авторизаційних даних", + "credentials-type-required": "Необхідно вказати тип авторизаційних даних.", + "username": "Ім'я користувача", + "username-required": "Необхідно вказати ім'я користувача.", + "password": "Пароль", + "password-required": "Необхідно вказати пароль.", + "ca-cert": "Файл сертифіката CA *", + "private-key": "Файл приватного ключа *", + "private-key-password": "Пароль приватного ключа", + "cert": "Файл сертифіката*", + "no-file": "Не вибрано жодного файлу.", + "drop-file": "Перетягніть файл, або клацніть, щоб вибрати файл для завантаження.", + "topic-filters": "Тематичні фільтри", + "remove-topic-filter": "Видалити фільтр тем", + "add-topic-filter": "Додати фільтр тем", + "add-topic-filter-prompt": "Будь ласка, додайте фільтр тем", + "topic": "Тема", + "mqtt-qos": "QoS", + "mqtt-qos-at-most-once": "Не більше одного разу", + "mqtt-qos-at-least-once": "Принаймні, один раз", + "mqtt-qos-exactly-once": "Точно один раз", + "downlink-topic-pattern": "Downlink topic pattern", + "downlink-topic-pattern-required": "Downlink topic pattern is required.", + "aws-iot-endpoint": "AWS IoT Endpoint", + "aws-iot-endpoint-required": "AWS IoT Endpoint is required.", + "aws-iot-credentials": "Авторизаційні дані AWS IoT", + "application-credentials": "Авторизаційні дані додатків", + "api-key": "API ключ", + "api-key-required": "Необхідно вказати API ключ.", + "auth-token": "Маркер аутентифікації", + "auth-token-required": "Необхідно вказати маркер аутентифікації.", + "region": "Регіон", + "region-required": "Необхідно вказати регіон.", + "application-id": "ID програми", + "application-id-required": "Необхідно вказати ID програми.", + "access-key": "Ключ доступу", + "access-key-required": "Необхідно вказати ключ доступу.", + "connection-parameters": "Параметри підключення", + "service-bus-namespace-name": "Service Bus Namespace Name", + "service-bus-namespace-name-required": "Необхідно вказати Service Bus Namespace Name is required.", + "event-hub-name": "Event Hub Name", + "event-hub-name-required": "Необхідно вказати Event Hub Name is required.", + "sas-key-name": "Назва ключа SAS", + "sas-key-name-required": "Необхідно вказати назву ключа SAS.", + "sas-key": "Ключ SAS", + "sas-key-required": "SAS Key is required.", + "iot-hub-name": "IoT Hub Name (required for downlink)", + "metadata": "Метадані", + "type": "Тип", + "type-required": "Необхідно вказати тип.", + "uplink-converter": "Конвертер передачі даних", + "uplink-converter-required": "Необхідно вказати конвертер передачі даних.", + "downlink-converter": "Downlink data converter", + "type-http": "HTTP", + "type-ocean-connect": "OceanConnect", + "type-sigfox": "SigFox", + "type-thingpark": "ThingPark", + "type-tmobile-iot-cdp": "T-Mobile – IoT CDP", + "type-mqtt": "MQTT", + "type-aws-iot": "AWS IoT", + "type-ibm-watson-iot": "IBM Watson IoT", + "type-ttn": "TheThingsNetwork", + "type-azure-event-hub": "Azure Event Hub", + "type-opc-ua": "OPC-UA", + "type-ffb": "FFB", + "opc-ua-application-name": "Назва програми", + "opc-ua-application-uri": "Application uri", + "opc-ua-scan-period-in-seconds": "Період сканування в секундах", + "opc-ua-scan-period-in-seconds-required": "Необхідно вказати період сканування в секундах", + "opc-ua-timeout": "Час очікування в мілісекундах", + "opc-ua-timeout-required": "Необхідно вказати час очікування в мілісекундах", + "opc-ua-security": "Безпека", + "opc-ua-security-required": "Необхідно задати безпеку", + "opc-ua-identity": "Ідентифікація", + "opc-ua-identity-required": "Необхідно вказати ідентифікацію", + "opc-ua-keystore": "Сховище ключів", + "add-opc-ua-keystore-prompt": "Будь ласка, додайте файл сховища ключів", + "opc-ua-keystore-required": "Необхідно вказати сховище ключів", + "opc-ua-type": "Тип", + "opc-ua-keystore-type":"Тип сховища ключів", + "opc-ua-keystore-type-required":"Необхідно вказати тип", + "opc-ua-keystore-location":"Розташування *", + "opc-ua-keystore-password":"Пароль", + "opc-ua-keystore-password-required":"Необхідно вказати пароль", + "opc-ua-keystore-alias":"Псевдонім", + "opc-ua-keystore-alias-required":"Необхідно вказати псевдонім", + "opc-ua-keystore-key-password":"Пароль ключа", + "opc-ua-keystore-key-password-required":"Необхідно вказати пароль ключа", + "opc-ua-mapping":"Зіставлення", + "add-opc-ua-mapping-prompt": "Будь ласка, додайте зіставлення", + "opc-ua-mapping-type":"Тип зіставлення", + "opc-ua-mapping-type-required":"Необхідно вказати тип зіставлення", + "opc-ua-device-node-pattern":"Шаблон вузла пристрою", + "opc-ua-device-node-pattern-required":"Необхідно вказати шаблон вузла пристрою", + "opc-ua-add-map": "Додати елемент зіставлення", + "subscription-tags": "Теги передплати Теги підписки", + "remove-subscription-tag": "Видалити тег підписки", + "add-subscription-tag": "Додати тег підписки", + "add-subscription-tag-prompt": "Будь ласка, додайте тег підписки", + "key": "Ключ", + "path": "Шлях", + "required": "Необхідно" + }, + "item": { + "selected": "Вибрані" + }, + "js-func": { + "no-return-error": "Функція повинна повертати значення!", + "return-type-mismatch": "Функція повинна повернути значення типу '{{type}}'!", + "tidy": "Tidy" + }, + "key-val": { + "key": "Ключ", + "value": "Значення", + "remove-entry": "Видалити елемент", + "add-entry": "Додати елемент", + "no-data": "Елементи відсутні" + }, + "layout": { + "layout": "Макет", + "manage": "Керування макетами", + "settings": "Налаштування макета", + "color": "Колір", + "main": "Основний", + "right": "Правий", + "select": "Вибрати макет" + }, + "legend": { + "direction": "Розташування елементів легенди", + "position": "Розташування легенди", + "show-max": "Показати максимальне значення", + "show-min": "Показати мінімальне значення ", + "show-avg": "Показати середнє значення", + "show-total": "Показати суму", + "settings": "Налаштування легенди", + "min": "мін", + "max": "макс", + "avg": "середнє", + "total": "Сума" + }, + "login": { + "login": "Вхід", + "request-password-reset": "Запит скидання пароля", + "reset-password": "Скинути пароль", + "create-password": "Створити пароль", + "passwords-mismatch-error": "Введені паролі повинні бути однаковими!", + "password-again": "Введіть пароль ще раз", + "sign-in": "Будь ласка, увійдіть в систему", + "username": "Ім'я користувача (ел. пошта)", + "remember-me": "Запам'ятати мене", + "forgot-password": "Забули пароль?", + "password-reset": "Скидання пароля", + "new-password": "Новий пароль", + "new-password-again": "Повторіть новий пароль", + "password-link-sent-message": "Посилання для скидання пароля було успішно надіслано!", + "email": "Електронна пошта" + }, + "position": { + "top": "Угорі", + "bottom": "Знизу", + "left": "Ліворуч", + "right": "Праворуч" + }, + "profile": { + "profile": "Профіль", + "change-password": "Змінити пароль", + "current-password": "Поточний пароль" + }, + "relation": { + "relations": "Відношення", + "direction": "Напрямок", + "search-direction": { + "FROM": "З", + "TO": "До" + }, + "direction-type": { + "FROM": "з", + "TO": "до" + }, + "from-relations": "Вихідні відношення", + "to-relations": "Вхідні відношення", + "selected-relations": "Вибрано { count, plural, 1 {1 відношення} other {# відношення} }", + "type": "Тип", + "to-entity-type": "До типу сутності", + "to-entity-name": "До імені сутності", + "from-entity-type": "Від типу сутності", + "from-entity-name": "Від імені сутності", + "to-entity": "До сутності", + "from-entity": "Від сутності", + "delete": "Видалити відношення", + "relation-type": "Тип відношення", + "relation-type-required": "Необхідно вказати тип відношення.", + "any-relation-type": "Будь-який тип", + "add": "Додати відношення", + "edit": "Редагувати відношення", + "delete-to-relation-title": "Ви впевнені, що хочете видалити відношення до сутності '{{entityName}}'?", + "delete-to-relation-text": "Будьте обережні, після підтвердження, сутність '{{entityName}}' не буде пов'язана з поточним об'єктом.", + "delete-to-relations-title": "Ви впевнені, що хочете видалити { count, plural, 1 {1 відношення} other {# відношення} }?", + "delete-to-relations-text": "Будьте обережні, після підтвердження, всі вибрані відношення стануть не пов'язані з поточною сутністю.", + "delete-from-relation-title": "Ви впевнені, що хочете видалити зв'язок, який йде від сутності '{{entityName}}'?", + "delete-from-relation-text": "Будьте обережні, після підтвердження поточна сутність буде відв'язана від сутності '{{entityName}}'.", + "delete-from-relations-title": "Ви впевнені, що хочете видалити { count, plural, 1 {1 відношення} other {# відношення} }?", + "delete-from-relations-text": "Будьте обережні, після підтвердження, всі вибрані відношення будуть видалені, а поточна сутність стане не зв'язаною з відповідними сутностями.", + "remove-relation-filter": "Видалити фільтр відношення", + "add-relation-filter": "Додати фільтр відношення", + "any-relation": "Будь-яке відношення", + "relation-filters": "Фільтри відношення", + "additional-info": "Додаткова інформація (JSON)", + "invalid-additional-info": "Не вдалося розібрати JSON з додатковою інформацією ." + }, + "rulechain": { + "rulechain": "Ланцюг правил", + "rulechains": "Ланцюги правил", + "root": "Основний", + "delete": "Видалити ланцюг правил", + "name": "Ім'я", + "name-required": "Необхідно вказати ім'я.", + "description": "Опис", + "add": "Додати ланцюг правил", + "set-root": "Зробити ланцюг правил основним", + "set-root-rulechain-title": "Ви впевнені, що хочете зробити ланцюг правил '{{ruleChainName}}' основним?", + "set-root-rulechain-text": "Після підтвердження ланцюг правил стане основним і буде обробляти всі вхідні транспортні повідомлення.", + "delete-rulechain-title": "Ви впевнені, що хочете видалити ланцюг правил '{{ruleChainName}}'?", + "delete-rulechain-text": "Будьте обережні, після підтвердження ланцюг правил і всі пов'язані з ним дані стануть недоступними.", + "delete-rulechains-title": "Ви впевнені, що хочете видалити { count, plural, 1 {1 ланцюг правил} other {# ланцюги правил} }?", + "delete-rulechains-action-title": "Видалити{ count, plural, 1 {1 ланцюг правил} other {# ланцюги правил} }", + "delete-rulechains-text": "Будьте обережні, після підтвердження, вибрані ланцюги правил і всі пов'язані з ними дані стануть недоступними.", + "add-rulechain-text": "Додати новий ланцюг правил", + "no-rulechains-text": "Ланцюг правил не знайдено", + "rulechain-details": "Деталі ланцюга правил", + "details": "Деталі", + "events": "Події", + "system": "Система", + "import": "Імпортувати ланцюг правил", + "export": "Експортувати ланцюг правил", + "export-failed-error": "Не вдалося експортувати ланцюг правил: {{error}}", + "create-new-rulechain": "Створити новий ланцюг правил", + "rulechain-file": "Файл ланцюга правил", + "invalid-rulechain-file-error": "Неможливо імпортувати ланцюг правил: недійсна структуру даних ланцюга правил.", + "copyId": "Копіювати Id ланцюга правил", + "idCopiedMessage": "Id ланцюга правил скопійовано в буфер обміну", + "select-rulechain": "Вибрати ланцюг правил", + "no-rulechains-matching": "Не знайдено жодних ланцюгів правил, які відповідають '{{entity}}'.", + "rulechain-required": "Необхідно вказати ланцюг правил", + "management": "Управління ланцюгами правил", + "debug-mode": "Режим налагодження" + }, + "rulenode": { + "details": "Деталі", + "events": "Події", + "search": "Пошук вузлів", + "open-node-library": "Відкрити бібліотеку вузлів", + "add": "Додати вузол правил", + "name": "Ім'я", + "name-required": "Необхідно вказати ім'я.", + "type": "Тип", + "description": "Опис", + "delete": "Видалити вузол правил", + "select-all-objects": "Вибрати усі вузли та з'єднання", + "deselect-all-objects": "Зняти виділення з усіх вузлів і з'єднань", + "delete-selected-objects": "Видалити вибрані вузли та з'єднання", + "delete-selected": "Видалити вибране", + "select-all": "Вибрати все", + "copy-selected": "Копіювати вибране", + "deselect-all": "Відмінити вибране", + "rulenode-details": "Деталі вузла правил", + "debug-mode": "Режим налагодження", + "configuration": "Конфігурація", + "link": "Посилання", + "link-details": "Деталі посилання про вузол правил", + "add-link": "Додати посилання", + "link-label": "Мітка посилання", + "link-label-required": "Необхідно вказати мітку посилання.", + "custom-link-label": "Мітка посилання користувача", + "custom-link-label-required": "Необхідно вказати мітку посилання користувача.", + "link-labels": "Мітки посилання", + "link-labels-required": "Необхідно вказати мітки посилання.", + "no-link-labels-found": "Не знайдено жодних міток посилання", + "no-link-label-matching": "Мітка'{{label}}' не знайдена.", + "create-new-link-label": "Створити нову!", + "type-filter": "Filter", + "type-filter-details": "Фільтрувати вхідні повідомлення з заданими умовами", + "type-enrichment": "Насичення", + "type-enrichment-details": "Додати додаткову інформацію до метаданих повідомлень", + "type-transformation": "Трансформація", + "type-transformation-details": "Змінити склад повідомлення та його метадані", + "type-action": "Дія", + "type-action-details": "Виконати задану дію", + "type-analytics": "Аналітика", + "type-analytics-details": "Виконати аналіз потокових або збережених даних", + "type-external": "Зовнішній", + "type-external-details": "Взаємодіє з зовнішньою системою", + "type-rule-chain": "Ланцюг правил", + "type-rule-chain-details": "Перенаправити вхідне повідомлення на вказаний ланцюг правил", + "type-input": "Вхід", + "type-input-details": "Логічний вхід ланцюга правил, перенаправляє вхідні повідомлення на наступний пов'язаний вузол правил", + "type-unknown": "Невідомий", + "type-unknown-details": "Невизначений вузол правил", + "directive-is-not-loaded": "Вказана директива конфігурації '{{directiveName}}' недоступна.", + "ui-resources-load-error": "Не вдалося завантажити UI ресурси.", + "invalid-target-rulechain": "Не вдається визначити цільовий ланцюг правил!", + "test-script-function": "Протестувати скрипт", + "message": "Повідомлення", + "message-type": "Тип повідомлення", + "select-message-type": "Вибрати тип повідомлення", + "message-type-required": "Необхідно вказати тип повідомлення", + "metadata": "Метадані", + "metadata-required": "Записи метаданих не можуть бути порожніми.", + "output": "Вихід", + "test": "Тест", + "help": "Допомога" + }, + "scheduler": { + "scheduler": "Планувальник", + "scheduler-event": "Scheduler event Планування події. Запланувати подію. Подія планувальника", + "select-scheduler-event": "Виберати подію", + "no-scheduler-events-matching": "Не знайдено жодних подій, які відповідають '{{entity}}'.", + "scheduler-event-required": "Необхвдно вказати заплановану подію", + "management": "Управління планувальником", + "scheduler-events": "Планування подій", + "add-scheduler-event": "Додати подію", + "search-scheduler-events": "Пошук події", + "created-time": "Час створення", + "name": "Ім'я", + "type": "Тип", + "assigned_customer": "Призначений клієнт", + "edit-scheduler-event": "Редагувати подію", + "delete-scheduler-event": "Видалити подію", + "no-scheduler-events": "Не знайдено жодних запланованих подій", + "selected-scheduler-events": "{ count, plural, 1 {1 запланована подія} other {# заплановані події} } вибрано", + "delete-scheduler-event-title": "Ви впевнені, що хочете видалити подію '{{schedulerEventName}}'?", + "delete-scheduler-event-text": "Будьте обережні, після підтвердження подія і всі пов'язані з нею дані стануть недоступними.", + "delete-scheduler-events-title": "Ви впевнені, що хочете видалити { count, plural, 1 {1 запланована подія} other {# заплановані події} }?", + "delete-scheduler-events-text": "Будьте обережні, після підтвердження всі вибрані події будуть видалені, і всі пов'язані з ними дані стануть недоступними.", + "create": "Створити подію планувальника", + "edit": "Змінити подію планувальника", + "name-required": "Необхідно задати ім'я", + "configuration": "Конфігурація", + "schedule": "Розклад", + "start": "Початок", + "date": "Дата", + "time": "Час", + "repeat": "Повтор", + "repeats": "Повтори", + "daily": "Щодня", + "weekly": "Щотижня", + "repeats-required": "Потрібно вказати повторення.", + "repeat-on": "Повторити на", + "ends-on": "Завершити на", + "sunday-label": "Нд", + "monday-label": "Пн", + "tuesday-label": "Вт", + "wednesday-label": "Ср", + "thursday-label": "Чт", + "friday-label": "Пт", + "saturday-label": "Сб", + "repeat-on-sunday": "Повторити у неділю", + "repeat-on-monday": "Повторити в понеділок", + "repeat-on-tuesday": "Повторити у вівторок", + "repeat-on-wednesday": "Повторити в середу", + "repeat-on-thursday": "Повторити в червер", + "repeat-on-friday": "Повторити в п'ятницю", + "repeat-on-saturday": "Повторити в суботу", + "event-type": "Тип події", + "select-event-type": "Вибрати тип події", + "event-type-required": "Необхідно вказати типи події.", + "list-mode": "Перегляд списку", + "calendar-mode": "Перегляд календаря", + "calendar-view-type": "Тип перегляду календаря", + "month": "Місяць", + "week": "Тиждень", + "day": "День", + "agenda-week": "Порядок тижня", + "agenda-day": "Порялок дня", + "list-year": "Список року", + "list-month": "Список місяця", + "list-week": "Список тижня", + "list-day": "Список дня", + "today": "Сьогодні", + "navigate-before": "Перейти до", + "navigate-next": "Перейти далі", + "starting-from": "Починаючи з", + "until": "до", + "on": "на", + "sunday": "Неділя", + "monday": "Понеділок", + "tuesday": "Вівторок", + "wednesday": "Середа", + "thursday": "Четвер", + "friday": "П'ятниця", + "saturday": "Субота", + "originator": "Засновник", + "single-entity": "Самостійна сутність", + "group-of-entities": "Група сутностей", + "single-device": "Один пристрій", + "group-of-devices": "Група пристроїв", + "message-body": "Тіло повідомлення", + "target": "Ціль", + "rpc-method": "Метод", + "rpc-method-required": "Необхідно вказати метод", + "rpc-params": "Парами. Парні.", + "select-dashboard-state": "Виберіть стан панелі візуалізації" + }, + "report": { + "report-config": "Конфігурація звіту", + "email-config": "Конфігурація електронної пошти", + "dashboard-state-param": "Значення параметра стану панелі візуалізації", + "base-url": "Базова URL-адреса", + "base-url-required": "Необхідно вказати базову URL-адресу.", + "use-dashboard-timewindow": "Використовуйте вікно часу на панелі інструментів", + "timewindow": "Вікно часу", + "name-pattern": "Шаблон імені звіту", + "name-pattern-required": "Необхідно задати шаблон назви звіту", + "type": "Report type", + "use-current-user-credentials": "Використовувати поточні авторизаційні дані користувача", + "customer-user-credentials": "Авторизаційні дані користувачів", + "customer-user-credentials-required": "Необхідно задати авторизаційні дані користувачів", + "generate-test-report": "Створити звіт про перевірку", + "send-email": "Відправити лист", + "from": "Від", + "from-required": "Необхідно вказати від кого.", + "to": "До", + "to-required": "Необхідно вказати до кого.", + "cc": "Cc", + "bcc": "Bcc", + "subject": "Тема", + "subject-required": "Необхідно вказати тему.", + "body": "Тіло", + "body-required": "Необідно вказати тіло." + }, + "blob-entity": { + "blob-entity": "Blob сутності", + "select-blob-entity": "Вибрати blob сутності", + "no-blob-entities-matching": "Не знайдено жодних сутностей blob, які відповідають '{{entity}}'.", + "blob-entity-required": "Необхідно вказати blob сутності", + "files": "Файли", + "search": "Пошук файлів", + "clear-search": "Очистити пошук", + "no-blob-entities-prompt": "Файлів не знайдено", + "report": "Звіт", + "created-time": "Створено час", + "name": "Ім'я", + "type": "Тип", + "assigned_customer": "Призначений клієнт", + "download-blob-entity": "Завантажити файл", + "delete-blob-entity": "Видалити файл", + "delete-blob-entity-title": "Ви впевнені, що хочете видалити файл '{{blobEntityName}}'?", + "delete-blob-entity-text": "Будьте обережні, після підствердження, дані файлу стануть недоступними." + }, + "timezone": { + "timezone": "Часовий пояс", + "select-timezone": "Виберати часовий пояс ", + "no-timezones-matching": "Не знайдено жодних часових поясів, які відповідають '{{timezone}}'.", + "timezone-required": "Необхідно вказати часовий пояс." + }, + "tenant": { + "tenant": "Власник", + "tenants": "Власники", + "management": "Управління власниками", + "add": "Додати власника", + "admins": "Адміністратори", + "manage-tenant-admins": "Керування адміністраторами власника", + "delete": "Видалити власника", + "add-tenant-text": "Додати нового власника", + "no-tenants-text": "Не знайдено жодного власника", + "tenant-details": "Подробиці про власника", + "delete-tenant-title": "Ви впевнені, що хочете видалити власника'{{tenantTitle}}'?", + "delete-tenant-text": "Будьте обережні, після підтвердження власник і всі пов'язані з ним дані стануть недоступними.", + "delete-tenants-title": "Ви впевнені, що хочете видалити { count, plural, 1 {1 власник} other {# власники} }?", + "delete-tenants-action-title": "Видалити { count, plural, 1 {1 власник} other {# власники} }", + "delete-tenants-text": "Будьте обережні, після підтвердження, усі вибрані власники будуть видалені, і всі пов'язані з ними дані стануть недоступними.", + "title": "Назва", + "title-required": "Необхідно вказати назву.", + "description": "Опис", + "details": "Деталі", + "events": "Події", + "copyId": "Крпіювати Id власника", + "idCopiedMessage": "Id власника скопійовано в буфер обміну", + "select-tenant": "Вибрати власника", + "no-tenants-matching": "Не знайдено жодних власників, які відповідають '{{entity}}'.", + "tenant-required": "Необхідно вказати власника", + "selected-tenants": "{ count, plural, 1 {1 власник} other {# власники} } вибрано", + "search": "Пошук власників" + }, + "timeinterval": { + "seconds-interval": "{ seconds, plural, 1 {1 секунда} other {# секунди} }", + "minutes-interval": "{ minutes, plural, 1 {1 хвилина} other {# хвилини} }", + "hours-interval": "{ hours, plural, 1 {1 година} other {# години} }", + "days-interval": "{ days, plural, 1 {1 день} other {# дні} }", + "days": "Дні", + "hours": "Години", + "minutes": "Хвилини", + "seconds": "Секунди", + "advanced": "Додатково" + }, + "timewindow": { + "days": "{ days, plural, 1 { день } other {# дні } }", + "hours": "{ hours, plural, 0 { годин } 1 {1 година } other {# години } }", + "minutes": "{ minutes, plural, 0 { хвилин } 1 {1 хвилина } other {# хвилини } }", + "seconds": "{ seconds, plural, 0 { секунд } 1 {1 секунда } other {# секунди } }", + "realtime": "Реальний час", + "history": "Історія", + "last-prefix": "Останнє", + "period": "з {{ startTime }} до {{ endTime }}", + "edit": "Редагувати вікно часу", + "date-range": "Проміжок часу", + "last": "Останнє", + "time-period": "Період часу" + }, + "user": { + "user": "Користувач", + "users": "Користувачі", + "customer-users": "Користувачі клієнта", + "tenant-admins": "Адміністратори власників", + "sys-admin": "Системний адміністратор", + "tenant-admin": "Адміністратор власника", + "customer": "Клієнт", + "anonymous": "Анонім", + "add": "Додати користувача", + "delete": "Видалити користувача", + "add-user-text": "Додати нового користувача", + "no-users-text": "Не знайдено жодного користувача", + "user-details": "Подробиці про користувача", + "delete-user-title": "Ви впевнені, що хочете видалити користувача'{{userEmail}}'?", + "delete-user-text": "Будьте обережні, після підтвердження, користувач і всі пов'язані з ним дані стануть недоступними.", + "delete-users-title": "Ви впевнені, що хочете видалити { count, plural, 1 {1 користувача} other {# користувачів} }?", + "delete-users-action-title": "Видалити { count, plural, 1 {1 користувача} other {# користувачів} }", + "delete-users-text": "Будьте обережні, після підтвердження, усіх виділених користувачів буде видалено, і всі пов'язані з ними дані стануть недоступними.", + "activation-email-sent-message": "Повідомлення про активацію успішно надіслано!", + "resend-activation": "Повторно надіслати активацію", + "email": "Електронна пошта", + "email-required": "Необхідно вказати електронну пошту.", + "invalid-email-format": "Недійсний формат електронної пошти.", + "first-name": "Ім'я", + "last-name": "Прізвище", + "description": "Опис", + "default-dashboard": "Стандартна панель візуалізації", + "always-fullscreen": "Завжди в повноекранному режимі", + "select-user": "Вибрати користувача", + "no-users-matching": "Не знайдено жодного користувача, що відповідає '{{entity}}'.", + "user-required": "Необхідно вказати користувача", + "activation-method": "Спосіб активації", + "display-activation-link": "Показати посилання для активації", + "send-activation-mail": "Надіслати активаційного листа", + "activation-link": "Активаційне посилання для користувача", + "activation-link-text": "Для активувації користувача, скористайтеся наступним activation link :", + "copy-activation-link": "Скопіювати активаційне посилання ", + "activation-link-copied-message": "Посилання на активацію користувача було скопійовано в буфер обміну", + "selected-users": "{ count, plural, 1 {1 користувач} other {# користувачі} } вибрано", + "search": "Пошук користувачів", + "details": "Подробиці", + "login-as-tenant-admin": "Увійти як адміністратор власника", + "login-as-customer-user": "Увійти як користувач клієнта" + }, + "value": { + "type": "Тип значення", + "string": "Рядок", + "string-value": "Значення рядка", + "integer": "Ціле", + "integer-value": "Ціле значення", + "invalid-integer-value": "Недійсне ціле значення", + "double": "Подвійне", + "double-value": "Подвійне значення", + "boolean": "Логічне", + "boolean-value": "Логічне значення", + "false": "Помилкове", + "true": "Правдиве", + "long": "Довге" + }, + "widget": { + "widget-library": "Бібліотека віджетів", + "widget-bundle": "Пакет віджетів", + "select-widgets-bundle": "Виберіть пакет віджетів", + "management": "Керування віджетами", + "editor": "Редактор віджетів", + "widget-type-not-found": "Помилка завантаження конфігурації віджетів.
Можливо, пов'язаний з нею\n тип віджета було видалено.", + "widget-type-load-error": "Віджет не вдалося завантажити з наступних причин:", + "remove": "Видалити віджет", + "edit": "Відредагувати віджет", + "remove-widget-title": "Ви впевнені, що хочете видалити віджет '{{widgetTitle}}'?", + "remove-widget-text": "Після підтвердження віджет і всі пов'язані з ним дані стануть недоступними.", + "timeseries": "Телеметрія", + "search-data": "Пошук даних", + "no-data-found": "Даних не знайдено", + "latest-values": "Останні значення", + "rpc": "Керуючий віджет", + "alarm": "Віджет сигнала тривоги", + "static": "Статичний віджет", + "select-widget-type": "Вибрати тип віджета", + "missing-widget-title-error": "Необхідно вказати назву віджета!", + "widget-saved": "Віджет збережено", + "unable-to-save-widget-error": "Неможливо зберегти віджет! Віджет має помилки!", + "save": "Зберегти віджет", + "saveAs": "Зберегти віджет як", + "save-widget-type-as": "Зберегти тип віджета як", + "save-widget-type-as-text": "Введіть новий заголовок віджета та / або виберіть цільові віджети", + "toggle-fullscreen": "Перейти в повноекранний режим", + "run": "Запустити віджет", + "title": "Назва віджета", + "title-required": "Необхідно вказати назву віджета.", + "type": "Тип віджета", + "resources": "Ресурси", + "resource-url": "JavaScript/CSS URL", + "remove-resource": "Видалити ресурс", + "add-resource": "Додати ресурс", + "html": "HTML", + "tidy": "Форматувати", + "css": "CSS", + "settings-schema": "Схема налаштувань", + "datakey-settings-schema": "Схема налаштувань ключів даних", + "javascript": "Javascript", + "remove-widget-type-title": "Ви впевнені, що хочете видалити тип віджета '{{widgetName}}'?", + "remove-widget-type-text": "Будьте обережні, після підтвердження, тип віджета і всі пов'язані з ним дані стануть недоступними.", + "remove-widget-type": "Видалити тип віджета", + "add-widget-type": "Додати новий тип віджета", + "widget-type-load-failed-error": "Не вдалося завантажити тип віджета!", + "widget-template-load-failed-error": "Не вдалося завантажити шаблон віджета!", + "add": "Додати віджет", + "undo": "Скасувати зміни віджета", + "export": "Експртувати віджет", + "export-data": "Експортувати дані віджетів", + "export-to-csv": "Експортувати дані в CSV...", + "export-to-excel": "Експортувати дані в XLS..." + }, + "widget-action": { + "header-button": "Кнопка заголовка віджета", + "open-dashboard-state": "Перейти до нового стану панелі візуалізації", + "update-dashboard-state": "Оновити поточний стан панелі візуалізації", + "open-dashboard": "Перейти до іншої панелі візуалізації", + "custom": "Дії користувачів", + "custom-pretty": "Дії користувачів (з HTML шаблоном)", + "target-dashboard-state": "Цільовий стан панелі візуалізації", + "target-dashboard-state-required": "Необхідно вказати цільовий стан панелі візуалізації", + "set-entity-from-widget": "Встановити сутність із віджета", + "target-dashboard": "Цільова панель візуалізації", + "open-right-layout": "Відкрити мобільний режим панелі візуалізації" + }, + "widgets-bundle": { + "current": "Поточний зв'язок", + "widgets-bundles": "Пакети віджетів", + "add": "Додати пакет віджетів", + "delete": "Видалити пакет віджетів", + "title": "Назва", + "title-required": "Необхідно вказати назву віджета.", + "add-widgets-bundle-text": "Додати новий пакет віджетів", + "no-widgets-bundles-text": "Не знайдено жодних пакетів віджетів", + "empty": "Пакет віджетів порожній", + "details": "Подробиці", + "widgets-bundle-details": "Деталі пакетів віджетів", + "delete-widgets-bundle-title": "Ви впевнені, що хочете видалити пакет віджетів '{{widgetsBundleTitle}}'?", + "delete-widgets-bundle-text": "Будьте обережні, після підтвердження, пакети віджетів і всі пов'язані з ними дані стануть недоступними.", + "delete-widgets-bundles-title": "Ви впевнені, що хочете видалити { count, plural, 1 {пакет віджетів} other {# пакети віджетів} }?", + "delete-widgets-bundles-action-title": "Видалити { count, plural, 1 {1 пакет віджетів} other {# пакет віджетів} }", + "delete-widgets-bundles-text": "Будьте обережні, після підтвердження, всі виділені пакети віджетів і всі пов'язані з ними дані стануть недоступними.", + "no-widgets-bundles-matching": "Не знайдено жодних пакетів віджетів, які відповідають '{{widgetsBundle}}'.", + "widgets-bundle-required": "Необхідно вказати пакет віджетів.", + "system": "Системний", + "import": "Імпортувати пакет віджетів", + "export": "Експортувати пакет віджетів", + "export-failed-error": "Неможливо експортувати пакет віджетів: {{error}}", + "create-new-widgets-bundle": "Створити новий пакет віджетів", + "widgets-bundle-file": "Файл набору віджетів", + "invalid-widgets-bundle-file-error": "Неможливо імпортувати пакет віджетів: недійсна структура даних пакету віджетів." + }, + "widget-config": { + "data": "Дані", + "settings": "Налаштування", + "advanced": "Додатково", + "title": "Назва", + "general-settings": "Загальні налаштування", + "display-title": "Відобразити назву", + "drop-shadow": "Тінь", + "enable-fullscreen": "Увімкнути повноекранний режим", + "enable-data-export": "Увімкнути експорт даних", + "background-color": "Колір фону", + "text-color": "Колір тексту", + "padding": "Відступ", + "margin": "Границі", + "widget-style": "Стиль віджетів", + "title-style": "Стиль заголовка", + "mobile-mode-settings": "Налаштування мобільного режиму", + "order": "Порядок", + "height": "Висота", + "units": "Спеціальний символ після значення", + "decimals": "Кількість цифр після коми", + "timewindow": "Вікно часу", + "use-dashboard-timewindow": "Використати вікно часу на панелі візуалізації", + "display-legend": "Показати легенду", + "datasources": "Джерела даних", + "maximum-datasources": "Максимально { count, plural, 1 {1 дозволене джерело даних.} other {# дозволені джерела даних } }", + "datasource-type": "Тип", + "datasource-parameters": "Параметри", + "remove-datasource": "Видалити джерело даних", + "add-datasource": "Додати джерело даних", + "target-device": "Цільовий пристрій", + "alarm-source": "Джерело сигнала тривоги", + "actions": "Дії", + "action": "Дія", + "add-action": "Додати дію", + "search-actions": "Пошук дії", + "action-source": "Джерело дії", + "action-source-required": "Необхідно вказати джерело дії.", + "action-name": "Ім'я дії", + "action-name-required": "Необхідно вказати ім'я дії.", + "action-name-not-unique": "Дія з такою назвою вже існує.
Назва дії має бути унікальною в межах одного джерела дії.", + "action-icon": "Іконка", + "action-type": "Тип", + "action-type-required": "Необхідно вказати тип дії.", + "edit-action": "Редагувати дію", + "delete-action": "Видалити дію", + "delete-action-title": "Видалити дію віджета", + "delete-action-text": "Ви впевнені, що хочете видалити дію віджета '{{actionName}}'?" + }, + "widget-type": { + "import": "Імпортувати тип віджета", + "export": "Експортувати тип віджета", + "export-failed-error": "Неможливо експортувати тип віджета: {{error}}", + "create-new-widget-type": "Створити новий тип віджета", + "widget-type-file": "Файл типу віджета", + "invalid-widget-type-file-error": "Неможливо імпортувати тип віджету: неправильна структура даних типу віджета." + }, + "white-labeling": { + "white-labeling": "Білий маркування", + "login-white-labeling": "Login White Labeling", + "preview": "Попередній перегляд", + "app-title": "Назва програми", + "favicon": "Іконка веб-сайту", + "favicon-description": "*.ico, *.gif or *.png image with maximum size {{kbSize}} KBytes.", + "favicon-size-error": "Зображення веб-сайту завелике. Максимально дозволений розмір зображення веб-сайту {{kbSize}} KBytes.", + "favicon-type-error": "Недійсний формат файлу зображення веб-сайту. Приймаються лише зображення ICO, GIF або PNG.", + "drop-favicon-image": "Зніміть зображення піктограми веб-сайту або клацніть, щоб вибрати файл для завантаження.", + "no-favicon-image": "Не вибрано жодної іконки", + "logo": "Логотип", + "logo-description": "Будь-яке зображення з максимальним розміром {{kbSize}} KBytes.", + "logo-size-error": "Зображення логотипу занадто велике. Максимально дозволений розмір зображення логотипу{{kbSize}} KBytes.", + "logo-type-error": "Недійсний формат файлу логотипу. Приймаються тільки зображення.", + "drop-logo-image": "Зніміть зображення логотипу або клацніть, щоб вибрати файл для завантаження.", + "no-logo-image": "Не вибрано жожного логотипу", + "logo-height": "Висота логотипу, px", + "primary-palette": "Основна палітра", + "accent-palette": "Палітра акцент", + "customize-palette": "Налаштування", + "edit-palette": "Редагувати палітру", + "save-palette": "Зберегти палітру", + "primary-background": "Первинний фон", + "secondary-background": "Вторинний фон", + "hue1": "HUE 1", + "hue2": "HUE 2", + "hue3": "HUE 3", + "page-background-color": "Колір фону сторінки", + "dark-foreground": "Темний передній план", + "domain-name": "Доменне ім'я" + }, + "icon": { + "icon": "веб-іконка", + "select-icon": "Виберіть веб-іконку", + "material-icons": "Матеріал веб-іконки", + "show-all": "Показати всі веб-іконки" + }, + "custom": { + "widget-action": { + "action-cell-button": "Кнопка дії клітинки", + "row-click": "Клацніть на рядок", + "marker-click": "Клацніть на маркер", + "tooltip-tag-action": "Дії при підказці" + } + }, + "language": { + "language": "Мова", + "locales": { + "fr_FR": "Французька", + "zh_CN": "Китайська", + "en_US": "Англійська", + "it_IT": "Італійська", + "ko_KR": "Корейська", + "ru_RU": "Російська", + "es_ES": "Іспанська", + "ja_JA": "Японська", + "tr_TR": "Турецька", + "de_DE": "Німецька", + "uk_UA": "Українська", + "fa_IR": "Перська", + "cs_CZ": "Чеська" + } + } +} diff --git a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json new file mode 100644 index 0000000000..a683828a8a --- /dev/null +++ b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json @@ -0,0 +1,1620 @@ +{ + "access": { + "unauthorized": "未授权", + "unauthorized-access": "未授权访问", + "unauthorized-access-text": "您需要登陆才能访问这个资源!", + "access-forbidden": "禁止访问", + "access-forbidden-text": "您没有访问此位置的权限
如果您仍希望访问此位置,请尝试使用其他用户登录。", + "refresh-token-expired": "会话已过期", + "refresh-token-failed": "无法刷新会话" + }, + "action": { + "activate": "激活", + "suspend": "暂停", + "save": "保存", + "saveAs": "另存为", + "cancel": "取消", + "ok": "确定", + "delete": "删除", + "add": "添加", + "yes": "是", + "no": "否", + "update": "更新", + "remove": "移除", + "search": "查询", + "clear-search": "清除查询", + "assign": "分配", + "unassign": "取消分配", + "share": "分享", + "make-private": "私有", + "apply": "应用", + "apply-changes": "应用更改", + "edit-mode": "编辑模式", + "enter-edit-mode": "进入编辑模式", + "decline-changes": "撤销更改", + "close": "关闭", + "back": "后退", + "run": "运行", + "sign-in": "登录!", + "edit": "编辑", + "view": "查看", + "create": "创建", + "drag": "拖拽", + "refresh": "刷新", + "undo": "撤销", + "copy": "复制", + "paste": "粘贴", + "copy-reference": "复制引用", + "paste-reference": "粘贴引用", + "import": "导入", + "export": "导出", + "share-via": "通过 {{provider}}分享" + }, + "aggregation": { + "aggregation": "聚合", + "function": "数据聚合功能", + "limit": "最大值", + "group-interval": "分组间隔", + "min": "最少值", + "max": "最大值", + "avg": "平均值", + "sum": "求和", + "count": "计数", + "none": "空" + }, + "admin": { + "general": "常规", + "general-settings": "常规设置", + "outgoing-mail": "发送邮件", + "outgoing-mail-settings": "发送邮件设置", + "system-settings": "系统设置", + "test-mail-sent": "测试邮件发送成功!", + "base-url": "基本URL", + "base-url-required": "基本URL必填。", + "mail-from": "邮件来自", + "mail-from-required": "邮件发件人必填。", + "smtp-protocol": "SMTP协议", + "smtp-host": "SMTP主机", + "smtp-host-required": "SMTP主机必填。", + "smtp-port": "SMTP端口", + "smtp-port-required": "您必须提供一个smtp端口。", + "smtp-port-invalid": "这看起来不是有效的smtp端口。", + "timeout-msec": "超时(ms)", + "timeout-required": "超时必填。", + "timeout-invalid": "这看起来不像有效的超时值。", + "enable-tls": "启用TLS", + "send-test-mail": "发送测试邮件" + }, + "alarm": { + "alarm": "警告", + "alarms": "警告", + "select-alarm": "选择警告", + "no-alarms-matching": "没有找到匹配 '{{entity}}' 的警告", + "alarm-required": "警告必填", + "alarm-status": "警告状态", + "search-status": { + "ANY": "所有", + "ACTIVE": "已激活", + "CLEARED": "已清除", + "ACK": "已应答", + "UNACK": "未应答" + }, + "display-status": { + "ACTIVE_UNACK": "激活未应答", + "ACTIVE_ACK": "激活已应答", + "CLEARED_UNACK": "清除未应答", + "CLEARED_ACK": "清除已应答" + }, + "no-alarms-prompt": "未发现警告", + "created-time": "创建时间", + "type": "类型", + "severity": "严重程度", + "originator": "起因", + "originator-type": "起因类型", + "details": "详情", + "status": "状态", + "alarm-details": "警告详情", + "start-time": "开始时间", + "end-time": "结束时间", + "ack-time": "应答时间", + "clear-time": "创建时间", + "severity-critical": "危险", + "severity-major": "重要", + "severity-minor": "次要", + "severity-warning": "警告", + "severity-indeterminate": "不确定", + "acknowledge": "应答", + "clear": "清除", + "search": "搜索警告", + "selected-alarms": "已选择 { count, plural, 1 {1 警告} other {# 警告} } ", + "no-data": "无数据显示", + "polling-interval": "警告轮询间隔(秒)", + "polling-interval-required": "警告轮询间隔必填。", + "min-polling-interval-message": "轮询间隔至少是1秒。", + "aknowledge-alarms-title": "应答 { count, plural, 1 {1 警告} other {# 警告} }", + "aknowledge-alarms-text": "确定要应答 { count, plural, 1 {1 警告} other {# 警告} }?", + "clear-alarms-title": "清除 { count, plural, 1 {1 警告} other {# 警告} }", + "clear-alarms-text": "确定要清除 { count, plural, 1 {1 警告} other {# 警告} }?" + }, + "alias": { + "add": "添加别名", + "edit": "编辑别名", + "name": "别名", + "name-required": "别名必填", + "duplicate-alias": "别名已经存在。", + "filter-type-single-entity": "单个实体", + "filter-type-entity-list": "实体列表", + "filter-type-entity-name": "实体名称", + "filter-type-state-entity": "实体(仪表板状态)", + "filter-type-state-entity-description": "实体令牌(仪表板状态参数)", + "filter-type-asset-type": "资产类型", + "filter-type-asset-type-description": "类型为 '{{assetType}}' 的资产", + "filter-type-asset-type-and-name-description": "类型为 '{{assetType}}' 且以 '{{prefix}}' 开头的资产", + "filter-type-device-type": "设备类型", + "filter-type-device-type-description": "类型为 '{{deviceType}}' 的设备", + "filter-type-device-type-and-name-description": "类型为 '{{deviceType}}' 且以 '{{prefix}}' 开头的设备", + "filter-type-entity-view-type": "实体视图类型", + "filter-type-entity-view-type-description": "类型为 '{{entityView}}' 的实体视图", + "filter-type-entity-view-type-and-name-description": "类型为 {{entityView}}' 且以 '{{prefix}}' 开头的实体视图", + "filter-type-relations-query": "关系查询", + "filter-type-relations-query-description": "具有 {{relationType}} 关联 {{direction}} {{rootEntity}} 的 {{entities}} ", + "filter-type-asset-search-query": "资产搜索查询", + "filter-type-asset-search-query-description": "类型为 {{assetTypes}} 且具有 {{relationType}} 关联 {{direction}} {{rootEntity}} 的资产", + "filter-type-device-search-query": "设备搜索查询", + "filter-type-device-search-query-description": "类型为 {{deviceTypes}} 且具有 {{relationType}} 关联 {{direction}} {{rootEntity}} 的设备", + "filter-type-entity-view-search-query": "实体视图搜索查询", + "filter-type-entity-view-search-query-description": "类型为 {{entityViewTypes}} 且具有 {{relationType}} 关联 {{direction}} {{rootEntity}} 的实体视图", + "entity-filter": "实体过滤", + "resolve-multiple": "解决为多实体", + "filter-type": "过滤类型", + "filter-type-required": "过滤类型必填。", + "entity-filter-no-entity-matched": "未找到符合指定过滤条件的实体。", + "no-entity-filter-specified": "没有指定实体过滤条件", + "root-state-entity": "使用仪表板状态实体作为根实体", + "root-entity": "根实体", + "state-entity-parameter-name": "状态实体参数名称", + "default-state-entity": "默认状态实体", + "default-entity-parameter-name": "默认", + "max-relation-level": "最大关系层级", + "unlimited-level": "不限层级", + "state-entity": "仪表板状态实体", + "all-entities": "所有实体", + "any-relation": "不限" + }, + "asset": { + "asset": "资产", + "assets": "资产", + "management": "资产管理", + "view-assets": "查看资产", + "add": "添加资产", + "assign-to-customer": "分配给客户", + "assign-asset-to-customer": "将资产分配给客户", + "assign-asset-to-customer-text": "请选择要分配给客户的资产", + "no-assets-text": "未找到资产", + "assign-to-customer-text": "请选择客户以分配资产", + "public": "公开", + "assignedToCustomer": "分配客户", + "make-public": "资产设为公开", + "make-private": "资产设为私有", + "unassign-from-customer": "取消分配客户", + "delete": "删除资产", + "asset-public": "资产公开", + "asset-type": "资产类型", + "asset-type-required": "资产类型必填。", + "select-asset-type": "选择资产类型", + "enter-asset-type": "输入资产类型", + "any-asset": "任何资产", + "no-asset-types-matching": "没有找到匹配 '{{entitySubtype}}' 的资产类型。", + "asset-type-list-empty": "资产类型未选择。", + "asset-types": "资产类型", + "name": "名称", + "name-required": "名称必填。", + "description": "描述", + "type": "类型", + "type-required": "类型必填。", + "details": "详情", + "events": "事件", + "add-asset-text": "添加新资产", + "asset-details": "资产详情", + "assign-assets": "分配资产", + "assign-assets-text": "分配 { count, plural, 1 {1 资产} other {# 资产} } 给客户", + "delete-assets": "删除资产", + "unassign-assets": "取消分配资产", + "unassign-assets-action-title": "从客户处取消分配 { count, plural, 1 {1 资产} other {# 资产} } ", + "assign-new-asset": "分配新资产", + "delete-asset-title": "确定要删除资产 '{{assetName}}'?", + "delete-asset-text": "小心!确认后资产及其所有相关数据将不可恢复。", + "delete-assets-title": "确定要删除 { count, plural, 1 {1 资产} other {# 资产} }?", + "delete-assets-action-title": "删除 { count, plural, 1 {1 资产} other {# 资产} }", + "delete-assets-text": "小心,确认后,所有选定的资产将被删除,所有相关的数据将变得不可恢复。", + "make-public-asset-title": "你确定你想创建公开'{{assetName}}'资产?", + "make-public-asset-text": "确认后,资产及其所有数据将被公开并被他人访问。", + "make-private-asset-title": "你确定你想创建私有 '{{assetName}}' 资产?", + "make-private-asset-text": "确认后,资产及其所有数据将被私有化,无法被他人访问。", + "unassign-asset-title": "您确定要取消对'{{assetName}}'资产的分配吗?", + "unassign-asset-text": "确认后,资产将未分配,客户无法访问。", + "unassign-asset": "未分配资产", + "unassign-assets-title": "您确定要取消分配 { count, plural, 1 {1 资产} other {# 资产} }吗?", + "unassign-assets-text": "确认后,所有选定的资产将被分配,客户无法访问。", + "copyId": "复制资产ID", + "idCopiedMessage": "资产ID已经复制到粘贴板", + "select-asset": "选择资产", + "no-assets-matching": "没有找到匹配 '{{entity}}' 的资产。", + "asset-required": "资产必填", + "name-starts-with": "资产名称以此开头" + }, + "attribute": { + "attributes": "属性", + "latest-telemetry": "最新遥测", + "attributes-scope": "设备属性范围", + "scope-latest-telemetry": "最新遥测", + "scope-client": "客户端属性", + "scope-server": "服务端属性", + "scope-shared": "共享属性", + "add": "添加属性", + "key": "键", + "last-update-time": "最后更新时间", + "key-required": "属性键必填。", + "value": "值", + "value-required": "属性值必填。", + "delete-attributes-title": "您确定要删除 { count, plural, 1 {1 属性} other {# 属性} }吗?", + "delete-attributes-text": "注意,确认后所有选中的属性都会被删除。", + "delete-attributes": "删除属性", + "enter-attribute-value": "输入属性值", + "show-on-widget": "在部件上显示", + "widget-mode": "部件模式", + "next-widget": "下一个部件", + "prev-widget": "上一个部件", + "add-to-dashboard": "添加到仪表板", + "add-widget-to-dashboard": "将部件添加到仪表板", + "selected-attributes": "{ count, plural, 1 {1 属性} other {# 属性} } 被选中", + "selected-telemetry": "{ count, plural, 1 {1 遥测} other {# 遥测} } 被选中" + }, + "audit-log": { + "audit": "审计", + "audit-logs": "审计日志", + "timestamp": "时间戳", + "entity-type": "实体类型", + "entity-name": "实体名称", + "user": "用户", + "type": "类型", + "status": "状态", + "details": "详情", + "type-added": "添加", + "type-deleted": "删除", + "type-updated": "更新", + "type-attributes-updated": "更新属性", + "type-attributes-deleted": "删除属性", + "type-rpc-call": "RPC调用", + "type-credentials-updated": "更新凭证", + "type-assigned-to-customer": "分配给客户", + "type-unassigned-from-customer": "未分配给客户", + "type-activated": "激活", + "type-suspended": "暂停", + "type-credentials-read": "读取凭证", + "type-attributes-read": "读取属性", + "status-success": "成功", + "status-failure": "失败", + "audit-log-details": "审计日志详情", + "no-audit-logs-prompt": "找不到日志", + "action-data": "活动数据", + "failure-details": "失败详情", + "search": "查找审计日志", + "clear-search": "清空查找" + }, + "confirm-on-exit": { + "message": "您有未保存的更改。确定要离开此页吗?", + "html-message": "您有未保存的更改。
确定要离开此页面吗?", + "title": "未保存的更改" + }, + "contact": { + "country": "国家", + "city": "城市", + "state": "州", + "postal-code": "邮政编码", + "postal-code-invalid": "只允许数字。", + "address": "地址", + "address2": "地址2", + "phone": "手机", + "email": "邮箱", + "no-address": "无地址" + }, + "common": { + "username": "用户名", + "password": "密码", + "enter-username": "输入用户名", + "enter-password": "输入密码", + "enter-search": "输入检索条件" + }, + "content-type": { + "json": "Json", + "text": "Text", + "binary": "Binary (Base64)" + }, + "customer": { + "customer": "客户", + "customers": "客户", + "management": "客户管理", + "dashboard": "客户仪表板", + "dashboards": "客户仪表板", + "devices": "客户设备", + "entity-views": "客户实体视图", + "assets": "客户资产", + "public-dashboards": "公共仪表板", + "public-devices": "公共设备", + "public-assets": "公共资产", + "public-entity-views": "公共实体视图", + "add": "添加客户", + "delete": "删除客户", + "manage-customer-users": "管理客户用户", + "manage-customer-devices": "管理客户设备", + "manage-customer-dashboards": "管理客户仪表板", + "manage-public-devices": "管理公共设备", + "manage-public-dashboards": "管理公共仪表板", + "manage-customer-assets": "管理客户资产", + "manage-public-assets": "管理公共资产", + "add-customer-text": "添加新客户", + "no-customers-text": "没有找到客户", + "customer-details": "客户详情", + "delete-customer-title": "您确定要删除客户'{{customerTitle}}'吗?", + "delete-customer-text": "小心!确认后,客户及其所有相关数据将不可恢复。", + "delete-customers-title": "您确定要删除 { count, plural, 1 {1 客户} other {# 客户} }吗?", + "delete-customers-action-title": "删除 { count, plural, 1 {1 客户} other {# 客户} }", + "delete-customers-text": "小心!确认后,所有选定的客户将被删除,所有相关数据将不可恢复。", + "manage-users": "管理用户", + "manage-assets": "管理资产", + "manage-devices": "管理设备", + "manage-dashboards": "管理仪表板", + "title": "标题", + "title-required": "需要标题", + "description": "描述", + "details": "详情", + "events": "事件", + "copyId": "复制客户ID", + "idCopiedMessage": "客户ID已复制到粘贴板", + "select-customer": "选择客户", + "no-customers-matching": "没有找到匹配 '{{entity}}' 的客户。", + "customer-required": "客户是必选项", + "select-default-customer": "选择默认的客户", + "default-customer": "默认客户", + "default-customer-required": "为了调试租户级别上的仪表板,需要默认客户。" + }, + "datetime": { + "date-from": "日期从", + "time-from": "时间从", + "date-to": "日期到", + "time-to": "时间到" + }, + "dashboard": { + "dashboard": "仪表板", + "dashboards": "仪表板库", + "management": "仪表板管理", + "view-dashboards": "查看仪表板", + "add": "添加仪表板", + "assign-dashboard-to-customer": "将仪表板分配给客户", + "assign-dashboard-to-customer-text": "请选择要分配给客户的仪表板", + "assign-to-customer-text": "请选择客户分配仪表板", + "assign-to-customer": "分配给客户", + "unassign-from-customer": "取消分配客户", + "make-public": "仪表板设为公开", + "make-private": "仪表板设为私有", + "manage-assigned-customers": "管理已分配的客户", + "assigned-customers": "已分配的客户", + "assign-to-customers": "将仪表板分配给客户", + "assign-to-customers-text": "请选择客户指定仪表板", + "unassign-from-customers": "客户未分配仪表板", + "unassign-from-customers-text": "请选择从仪表板中取消分配的客户", + "no-dashboards-text": "没有找到仪表板", + "no-widgets": "没有配置部件", + "add-widget": "添加新的部件", + "title": "标题", + "select-widget-title": "选择部件", + "select-widget-subtitle": "可用的部件类型列表", + "delete": "删除仪表板", + "title-required": "需要标题。", + "description": "描述", + "details": "详情", + "dashboard-details": "仪表板详情", + "add-dashboard-text": "添加新的仪表板", + "assign-dashboards": "分配仪表板", + "assign-new-dashboard": "分配新的仪表板", + "assign-dashboards-text": "分配 { count, plural, 1 {1 仪表板} other {# 仪表板} } 给客户", + "unassign-dashboards-action-text": "未分配 { count, plural, 1 {1 仪表板} other {# 仪表板} } 给客户", + "delete-dashboards": "删除仪表板", + "unassign-dashboards": "取消分配仪表板", + "unassign-dashboards-action-title": "从客户处取消分配 { count, plural, 1 {1 仪表板} other {# 仪表板} } ", + "delete-dashboard-title": "您确定要删除仪表板 '{{dashboardTitle}}'吗?", + "delete-dashboard-text": "小心!确认后仪表板及其所有相关数据将不可恢复。", + "delete-dashboards-title": "你确定你要删除 { count, plural, 1 {1 仪表板} other {# 仪表板} }吗?", + "delete-dashboards-action-title": "删除 { count, plural, 1 {1 仪表板} other {# 仪表板} }", + "delete-dashboards-text": "小心!确认后所有选定的仪表板将被删除,所有相关数据将不可恢复。", + "unassign-dashboard-title": "您确定要取消分配仪表板 '{{dashboardTitle}}'吗?", + "unassign-dashboard-text": "确认后,面板将被取消分配,客户将无法访问。", + "unassign-dashboard": "取消分配仪表板", + "unassign-dashboards-title": "您确定要取消分配仪表板 { count, plural, 1 {1 仪表板} other {# 仪表板} } 吗?", + "unassign-dashboards-text": "确认后,所有选定的仪表板将被取消分配,客户将无法访问。", + "public-dashboard-title": "仪表板现已公布", + "public-dashboard-text": "你的仪表板 {{dashboardTitle}} 已被公开,可通过如下 链接访问:", + "public-dashboard-notice": "提示: 不要忘记将相关设备公开以访问其数据。", + "make-private-dashboard-title": "您确定要将仪表板 '{{dashboardTitle}}' 设为私有吗?", + "make-private-dashboard-text": "确认后,仪表板将被设为私有,不能被其他人访问。", + "make-private-dashboard": "仪表板设为私有", + "socialshare-text": "'{{dashboardTitle}}' 由Thingsboard提供支持", + "socialshare-title": "'{{dashboardTitle}}' 由Thingsboard提供支持", + "select-dashboard": "选择仪表板", + "no-dashboards-matching": "找不到符合 '{{entity}}' 的仪表板。", + "dashboard-required": "仪表板必填。", + "select-existing": "选择现有仪表板", + "create-new": "创建新的仪表板", + "new-dashboard-title": "新仪表板标题", + "open-dashboard": "打开仪表板", + "set-background": "设置背景", + "background-color": "背景颜色", + "background-image": "背景图片", + "background-size-mode": "背景大小模式", + "no-image": "无图像选择", + "drop-image": "拖拽图像或单击以选择要上传的文件。", + "settings": "设置", + "columns-count": "列数", + "columns-count-required": "需要列数。", + "min-columns-count-message": "只允许最少10列", + "max-columns-count-message": "只允许最多1000列", + "widgets-margins": "部件间边距", + "horizontal-margin": "水平边距", + "horizontal-margin-required": "需要水平边距值。", + "min-horizontal-margin-message": "只允许0作为最小水平边距值。", + "max-horizontal-margin-message": "只允许50作为最大水平边距值。", + "vertical-margin": "垂直边距", + "vertical-margin-required": "需要垂直边距值。", + "min-vertical-margin-message": "只允许0作为最小垂直边距值。", + "max-vertical-margin-message": "只允许50作为最大垂直边距值。", + "autofill-height": "自动填充布局高度", + "mobile-layout": "移动端布局设置", + "mobile-row-height": "移动端行高距(px)", + "mobile-row-height-required": "移动端行高距必填。", + "min-mobile-row-height-message": "移动端行高距至少5px。", + "max-mobile-row-height-message": "移动端行高距最多200px。", + "display-title": "显示仪表板标题", + "toolbar-always-open": "工具栏常驻", + "title-color": "标题颜色", + "display-dashboards-selection": "显示仪表板选项", + "display-entities-selection": "显示实体选项", + "display-dashboard-timewindow": "显示时间窗口", + "display-dashboard-export": "显示导出", + "import": "导入仪表板", + "export": "导出仪表板", + "export-failed-error": "无法导出仪表板: {{error}}", + "create-new-dashboard": "创建新的仪表板", + "dashboard-file": "仪表板文件", + "invalid-dashboard-file-error": "无法导入仪表板: 仪表板数据结构无效。", + "dashboard-import-missing-aliases-title": "配置导入仪表板使用的别名", + "create-new-widget": "创建新部件", + "import-widget": "导入部件", + "widget-file": "部件文件", + "invalid-widget-file-error": "无法导入窗口部件: 窗口部件数据结构无效。", + "widget-import-missing-aliases-title": "配置导入的窗口部件使用的别名", + "open-toolbar": "打开仪表板工具栏", + "close-toolbar": "关闭工具栏", + "configuration-error": "配置错误", + "alias-resolution-error-title": "仪表板别名配置错误", + "invalid-aliases-config": "无法找到与某些别名过滤器匹配的任何设备。
请联系您的管理员以解决此问题。", + "select-devices": "选择设备", + "assignedToCustomer": "分配给客户", + "public": "公共", + "public-link": "公共链接", + "copy-public-link": "复制公共链接", + "public-link-copied-message": "仪表板的公共链接已被复制到剪贴板", + "manage-states": "仪表板状态管理", + "states": "仪表板状态", + "search-states": "仪表板状态检索", + "selected-states": "{ count, plural, 1 {1 仪表板状态} other {# 仪表板状态} } 选中", + "edit-state": "仪表板状态编辑", + "delete-state": "删除仪表板状态", + "add-state": "添加仪表板状态", + "state": "仪表板状态", + "state-name": "状态名", + "state-name-required": "仪表板状态名必填。", + "state-id": "状态ID", + "state-id-required": "仪表板状态ID必填。", + "state-id-exists": "仪表板状态ID已经存在。", + "is-root-state": "根状态", + "delete-state-title": "删除仪表板状态", + "delete-state-text": "确定要删除仪表板状态 '{{stateName}}' 吗?", + "show-details": "显示详情", + "hide-details": "隐藏详情", + "select-state": "选择目标状态", + "state-controller": "状态控制" + }, + "datakey": { + "settings": "设置", + "advanced": "高级", + "label": "标签", + "color": "颜色", + "units": "单位符号", + "decimals": "小数位数", + "data-generation-func": "数据生成功能", + "use-data-post-processing-func": "使用数据后处理功能", + "configuration": "数据键配置", + "timeseries": "时间序列", + "attributes": "属性", + "alarm": "报警字段", + "timeseries-required": "需要设备时间序列。", + "timeseries-or-attributes-required": "设备时间/属性必填。", + "maximum-timeseries-or-attributes": "最大允许 { count, plural, 1 {1 时间序列/属性} other {# 时间序列/属性} }", + "alarm-fields-required": "警告字段必填。", + "function-types": "函数类型", + "function-types-required": "需要函数类型。", + "maximum-function-types": "至少需要 { count, plural, 1 {1 函数类型} other {# 函数类型} }" + }, + "datasource": { + "type": "数据源类型", + "name": "数据源名称", + "add-datasource-prompt": "请添加数据源" + }, + "details": { + "edit-mode": "编辑模式", + "toggle-edit-mode": "切换编辑模式" + }, + "device": { + "device": "设备", + "device-required": "设备必填", + "devices": "设备", + "management": "设备管理", + "view-devices": "查看设备", + "device-alias": "设备别名", + "aliases": "设备别名", + "no-alias-matching": "'{{alias}}' 没有找到。", + "no-aliases-found": "找不到别名。", + "no-key-matching": "'{{key}}' 没有找到。", + "no-keys-found": "找不到密钥。", + "create-new-alias": "创建一个新的!", + "create-new-key": "创建一个新的!", + "duplicate-alias-error": "找到重复别名 '{{alias}}'。
设备别名必须是唯一的。", + "configure-alias": "配置 '{{alias}}' 别名", + "no-devices-matching": "找不到与 '{{entity}}' 匹配的设备。", + "alias": "别名", + "alias-required": "需要设备别名。", + "remove-alias": "删除设备别名", + "add-alias": "添加设备别名", + "name-starts-with": "名称前缀", + "device-list": "设备列表", + "use-device-name-filter": "使用过滤器", + "device-list-empty": "没有被选中的设备", + "device-name-filter-required": "设备名称过滤器必填。", + "device-name-filter-no-device-matched": "找不到以'{{device}}' 开头的设备。", + "add": "添加设备", + "assign-to-customer": "分配给客户", + "assign-device-to-customer": "将设备分配给客户", + "assign-device-to-customer-text": "请选择要分配给客户的设备", + "make-public": "公开", + "make-private": "私有", + "no-devices-text": "找不到设备", + "assign-to-customer-text": "请选择客户分配设备", + "device-details": "设备详细信息", + "add-device-text": "添加新设备", + "credentials": "凭据", + "manage-credentials": "管理凭据", + "delete": "删除设备", + "assign-devices": "分配设备", + "assign-devices-text": "将{count,plural,1 {1 设备} other {# 设备}}分配给客户", + "delete-devices": "删除设备", + "unassign-from-customer": "取消分配客户", + "unassign-devices": "取消分配设备", + "unassign-devices-action-title": "从客户处取消分配{count,plural,1 {1 设备} other {# 设备}}", + "assign-new-device": "分配新设备", + "make-public-device-title": "您确定要将设备 '{{deviceName}}' 设为公开吗?", + "make-public-device-text": "确认后,设备及其所有数据将被设为公开并可被其他人访问。", + "make-private-device-title": "您确定要将设备 '{{deviceName}}' 设为私有吗?", + "make-private-device-text": "确认后,设备及其所有数据将被设为私有,不被其他人访问。", + "view-credentials": "查看凭据", + "delete-device-title": "您确定要删除设备的{{deviceName}}吗?", + "delete-device-text": "小心!确认后设备及其所有相关数据将不可恢复。", + "delete-devices-title": "您确定要删除{count,plural,1 {1 设备} other {# 设备}} 吗?", + "delete-devices-action-title": "删除 {count,plural,1 {1 设备} other {# 设备}}", + "delete-devices-text": "小心!确认后所有选定的设备将被删除,所有相关数据将不可恢复。", + "unassign-device-title": "您确定要取消分配设备 '{{deviceName}}'?", + "unassign-device-text": "确认后,设备将被取消分配,客户将无法访问。", + "unassign-device": "取消分配设备", + "unassign-devices-title": "您确定要取消分配{count,plural,1 {1 设备} other {# 设备}} 吗?", + "unassign-devices-text": "确认后,所有选定的设备将被取消分配,并且客户将无法访问。", + "device-credentials": "设备凭据", + "credentials-type": "凭据类型", + "access-token": "访问令牌", + "access-token-required": "需要访问令牌", + "access-token-invalid": "访问令牌长度必须为1到20个字符。", + "rsa-key": "RSA公钥", + "rsa-key-required": "需要RSA公钥", + "secret": "密钥", + "secret-required": "密钥必填", + "device-type": "设备类型", + "device-type-required": "设备类型必填。", + "select-device-type": "选择设备类型", + "enter-device-type": "输入设备类型", + "any-device": "任意设备", + "no-device-types-matching": "没有找到符合 '{{entitySubtype}}' 的设备类型。", + "device-type-list-empty": "未选择设备类型", + "device-types": "设备类型", + "name": "名称", + "name-required": "名称必填。", + "description": "说明", + "events": "事件", + "details": "详细信息", + "copyId": "复制设备ID", + "copyAccessToken": "复制访问令牌", + "idCopiedMessage": "设备ID已复制到剪贴板", + "accessTokenCopiedMessage": "设备访问令牌已复制到剪贴板", + "assignedToCustomer": "分配给客户", + "unable-delete-device-alias-title": "无法删除设备别名", + "unable-delete-device-alias-text": "设备别名 '{{deviceAlias}}' 不能够被删除,因为它被下列部件所使用:
{{widgetsList}}", + "is-gateway": "是网关", + "public": "公开", + "device-public": "设备公开", + "select-device": "选择设备" + }, + "dialog": { + "close": "关闭对话框" + }, + "error": { + "unable-to-connect": "无法连接到服务器!请检查您的互联网连接。", + "unhandled-error-code": "未处理的错误代码: {{errorCode}}", + "unknown-error": "未知错误" + }, + "entity": { + "entity": "实体", + "entities": "实体", + "aliases": "实体别名", + "entity-alias": "实体别名", + "unable-delete-entity-alias-title": "无法删除实体别名", + "unable-delete-entity-alias-text": "实体别名 '{{entityAlias}}' 被以下部件使用不能删除:
{{widgetsList}}", + "duplicate-alias-error": "别名 '{{alias}}' 重复。
同一仪表板别名必须唯一。", + "missing-entity-filter-error": "别名 '{{alias}}' 缺少过滤器", + "configure-alias": "别名 '{{alias}}' 配置", + "alias": "别名", + "alias-required": "实体别名必填。", + "remove-alias": "移除实体别名", + "add-alias": "添加实体别名", + "entity-list": "实体列表", + "entity-type": "实体类型", + "entity-types": "实体类型", + "entity-type-list": "实体类型列表", + "any-entity": "任意实体", + "enter-entity-type": "输入实体类型", + "no-entities-matching": "没有找到符合 '{{entity}}' 的实体。", + "no-entity-types-matching": "没有找到符合 '{{entityType}}' 类型的实体。", + "name-starts-with": "名称开始于", + "use-entity-name-filter": "用户过滤", + "entity-list-empty": "没有选择实体。", + "entity-type-list-empty": "没有选择实体类型。", + "entity-name-filter-required": "实体名过滤器必填。", + "entity-name-filter-no-entity-matched": "没有找到以 '{{entity}}' 开头的实体", + "all-subtypes": "所有", + "select-entities": "选择实体", + "no-aliases-found": "没有找到别名", + "no-alias-matching": "没有找到 '{{alias}}'", + "create-new-alias": "创建新别名", + "key": "键", + "key-name": "键名", + "no-keys-found": "没有找到键", + "no-key-matching": "没有找到键 '{{key}}'", + "create-new-key": "创建新键", + "type": "类型", + "type-required": "实体类型必填。", + "type-device": "设备", + "type-devices": "设备", + "list-of-devices": "{ count, plural, 1 {设备} other {# 设备列表} }", + "device-name-starts-with": "以 '{{prefix}}' 开头的设备", + "type-asset": "资产", + "type-assets": "资产", + "list-of-assets": "{ count, plural, 1 {资产} other {# 资产列表} }", + "asset-name-starts-with": "以 '{{prefix}}' 开头的资产", + "type-entity-view": "实体视图", + "type-entity-views": "实体视图", + "list-of-entity-views": "{ count, plural, 1 {实体视图} other {# 实体视图列表} }", + "entity-view-name-starts-with": "以 '{{prefix}}' 开头的实体视图", + "type-rule": "规则", + "type-rules": "规则", + "list-of-rules": "{ count, plural, 1 {规则} other {# 规则列表} }", + "rule-name-starts-with": "以 '{{prefix}}' 开头的规则", + "type-plugin": "插件", + "type-plugins": "插件", + "list-of-plugins": "{ count, plural, 1 {插件} other {# 插件列表} }", + "plugin-name-starts-with": "以 '{{prefix}}' 开头的插件", + "type-tenant": "租户", + "type-tenants": "租户", + "list-of-tenants": "{ count, plural, 1 {租户} other {# 租户列表} }", + "tenant-name-starts-with": "以 '{{prefix}}' 开头的租户", + "type-customer": "客户", + "type-customers": "客户", + "list-of-customers": "{ count, plural, 1 {客户} other {# 客户列表} }", + "customer-name-starts-with": "以 '{{prefix}}' 开头的客户", + "type-user": "用户", + "type-users": "用户", + "list-of-users": "{ count, plural, 1 {用户} other {# 用户列表} }", + "user-name-starts-with": "以 '{{prefix}}' 开头的用户", + "type-dashboard": "仪表板", + "type-dashboards": "仪表板", + "list-of-dashboards": "{ count, plural, 1 {仪表板} other {# 仪表板列表} }", + "dashboard-name-starts-with": "以 '{{prefix}}' 开头的仪表板", + "type-alarm": "警告", + "type-alarms": "警告", + "list-of-alarms": "{ count, plural, 1 {警告} other {# 警告列表} }", + "alarm-name-starts-with": "以 '{{prefix}}' 开头的警告", + "type-rulechain": "规则链", + "type-rulechains": "规则链库", + "list-of-rulechains": "{ count, plural, 1 {一个规则链} other {# 规则链列表} }", + "rulechain-name-starts-with": "规则链前缀名称 '{{prefix}}'", + "type-current-customer": "当前客户", + "search": "实体检索", + "selected-entities": "{ count, plural, 1 {1 实体} other {# 实体} } 被选中", + "entity-name": "实体名", + "details": "实体详情", + "no-entities-prompt": "没有找到实体", + "no-data": "无数据" + }, + "entity-view": { + "entity-view": "实体视图", + "entity-view-required": "实体视图必填。", + "entity-views": "实体视图", + "management": "实体视图管理", + "view-entity-views": "查看实体视图", + "entity-view-alias": "实体视图别名", + "aliases": "实体视图别名", + "no-alias-matching": "'{{alias}}' 没有找到。", + "no-aliases-found": "找不到别名。", + "no-key-matching": "'{{key}}' 没有找到。", + "no-keys-found": "找不到密钥。", + "create-new-alias": "创建一个新的!", + "create-new-key": "创建一个新的!", + "duplicate-alias-error": "找到重复别名 '{{alias}}'。
实体视图别名必须是唯一的。", + "configure-alias": "配置 '{{alias}}' 别名", + "no-devices-matching": "找不到与 '{{entity}}' 匹配的实体视图。", + "alias": "别名", + "alias-required": "需要实体视图别名。", + "remove-alias": "删除实体视图别名", + "add-alias": "添加实体视图别名", + "name-starts-with": "名称前缀", + "entity-view-list": "实体视图列表", + "use-entity-view-name-filter": "使用过滤器", + "entity-view-list-empty": "没有被选中的实体视图", + "entity-view-name-filter-required": "实体视图名称过滤器必填。", + "entity-view-name-filter-no-entity-view-matched": "找不到以'{{entityView}}' 开头的实体视图。", + "add": "添加实体视图", + "assign-to-customer": "分配给客户", + "assign-entity-view-to-customer": "将实体视图分配给客户", + "assign-entity-view-to-customer-text": "请选择要分配给客户的实体视图", + "no-entity-views-text": "找不到实体视图", + "assign-to-customer-text": "请选择客户分配实体视图", + "entity-view-details": "实体视图详细信息", + "add-entity-view-text": "添加新实体视图", + "delete": "删除实体视图", + "assign-entity-views": "分配实体视图", + "assign-entity-views-text": "分配 { count, plural, 1 {1 实体视图} other {# 实体视图} } 给客户", + "delete-entity-views": "删除实体视图", + "unassign-from-customer": "取消分配客户", + "unassign-entity-views": "取消分配实体视图", + "unassign-entity-views-action-title": "从客户处取消分配{count,plural,1 {1 实体视图} other {# 实体视图}}", + "assign-new-entity-view": "分配新实体视图", + "delete-entity-view-title": "确定要删除实体视图 '{{entityViewName}}'?", + "delete-entity-view-text": "小心!确认后实体视图及其所有相关数据将不可恢复。", + "delete-entity-views-title": "确定要删除 { count, plural, 1 {1 实体视图} other {# 实体视图} }?", + "delete-entity-views-action-title": "删除 { count, plural, 1 {1 实体视图} other {# 实体视图} }", + "delete-entity-views-text": "B小心,确认后,所有选定的实体视图将被删除,所有相关的数据将变得不可恢复。", + "unassign-entity-view-title": "您确定要取消对 '{{entityViewName}}'实体视图的分配吗?", + "unassign-entity-view-text": "确认后,实体视图将未分配,客户无法访问。", + "unassign-entity-view": "未分配实体视图", + "unassign-entity-views-title": "您确定要取消分配 { count, plural, 1 {1 实体视图} other {# 实体视图} }吗?", + "unassign-entity-views-text": "确认后,所有选定的实体视图将被分配,客户无法访问。", + "entity-view-type": "实体视图类型", + "entity-view-type-required": "实体视图类型必填。", + "select-entity-view-type": "选择实体视图类型", + "enter-entity-view-type": "输入实体视图类型", + "any-entity-view": "任何实体视图", + "no-entity-view-types-matching": "没有找到匹配 '{{entitySubtype}}' 的实体视图类型。", + "entity-view-type-list-empty": "实体视图类型未选择。", + "entity-view-types": "实体视图类型", + "name": "名称", + "name-required": "名称必填。", + "description": "描述", + "events": "事件", + "details": "详情", + "copyId": "复制实体视图ID", + "assignedToCustomer": "分配给客户", + "unable-entity-view-device-alias-title": "无法删除实体视图别名", + "unable-entity-view-device-alias-text": "实体视图别名 '{{entityViewAlias}}' 不能够被删除,因为它被下列部件所使用:
{{widgetsList}}", + "select-entity-view": "选择实体视图", + "make-public": "实体视图设为公开", + "make-private": "实体视图设为私有", + "start-date": "开始日期", + "start-ts": "开始时间", + "end-date": "结束日期", + "end-ts": "结束时间", + "date-limits": "日期限制", + "client-attributes": "客户端属性", + "shared-attributes": "共享属性", + "server-attributes": "服务端属性", + "timeseries": "时间序列", + "client-attributes-placeholder": "客户端属性", + "shared-attributes-placeholder": "共享属性", + "server-attributes-placeholder": "服务端属性", + "timeseries-placeholder": "时间序列", + "target-entity": "目标实体", + "attributes-propagation": "属性传播", + "attributes-propagation-hint": "每次保存或更新这个实体视图时,实体视图将自动从目标实体复制指定的属性。由于性能原因,目标实体属性不会在每次属性更改时传播到实体视图。您可以通过配置\"copy to view\"规则链中的规则节点,并将\"Post attributes\"和\"attributes Updated\"消息链接到新规则节点,从而启用自动传播。", + "timeseries-data": "时间序列数据", + "timeseries-data-hint": "配置目标实体的时间序列数据键,以便实体视图可以访问这些键。这个时间序列数据是只读的。", + "make-public-entity-view-title": "你确定你想创建公开 '{{entityViewName}}' 实体视图?", + "make-public-entity-view-text": "确认后,实体视图 及其所有数据将被公开并被他人访问。", + "make-private-entity-view-title": "你确定你想创建私有 '{{entityViewName}}' 实体视图?", + "make-private-entity-view-text": "确认后,实体视图及其所有数据将被私有化,无法被他人访问。" + }, + "event": { + "event-type": "事件类型", + "type-error": "错误", + "type-lc-event": "生命周期事件", + "type-stats": "类型统计", + "type-debug-rule-node": "调试", + "type-debug-rule-chain": "调试", + "no-events-prompt": "找不到事件", + "error": "错误", + "alarm": "报警", + "event-time": "事件时间", + "server": "服务器", + "body": "整体", + "method": "方法", + "type": "类型", + "entity": "实体", + "message-id": "消息ID", + "message-type": "消息类型", + "data-type": "数据类型", + "relation-type": "关系类型", + "metadata": "元数据", + "data": "数据", + "event": "事件", + "status": "状态", + "success": "成功", + "failed": "失败", + "messages-processed": "消息处理", + "errors-occurred": "错误发生" + }, + "extension": { + "extensions": "扩展", + "selected-extensions": "{ count, plural, 1 {1 扩展} other {# 扩展} } 被选择", + "type": "类型", + "key": "键名", + "value": "值", + "id": "ID", + "extension-id": "扩展ID", + "extension-type": "扩展类型", + "transformer-json": "JSON *", + "unique-id-required": "当前扩展ID已经存在。", + "delete": "删除扩展", + "add": "添加扩展", + "edit": "编辑扩展", + "delete-extension-title": "确实要删除扩展名'{{extensionId}}'吗?", + "delete-extension-text": "小心,确认后,扩展和所有相关数据将变得不可恢复。", + "delete-extensions-title": "您确定要删除 { count, plural, 1 {1 表达式} other {# 表达式} }吗?", + "delete-extensions-text": "小心,确认后,所有选定的扩展将被删除。", + "converters": "转换器", + "converter-id": "转换器序号", + "configuration": "配置", + "converter-configurations": "转换器的配置", + "token": "安全令牌", + "add-converter": "添加转换器", + "add-config": "添加转换器配置", + "device-name-expression": "设备名称表达式", + "device-type-expression": "设备类型表达式", + "custom": "顾客", + "to-double": "加倍", + "transformer": "转换器", + "json-required": "转换器JSON必填。", + "json-parse": "无法解析转换器JSON。", + "attributes": "属性", + "add-attribute": "添加属性", + "add-map": "添加映射元素", + "timeseries": "时间序列", + "add-timeseries": "添加时间序列", + "field-required": "必填字段", + "brokers": "代理服务器组", + "add-broker": "添加代理服务器", + "host": "主机", + "port": "端口", + "port-range": "端口应该在1到65535的范围内。", + "ssl": "Ssl", + "credentials": "证书", + "username": "用户名", + "password": "密码", + "retry-interval": "以毫秒为单位的重试间隔", + "anonymous": "匿名", + "basic": "Basic", + "pem": "PEM", + "ca-cert": "CA证书文件 *", + "private-key": "私钥文件 *", + "cert": "证书文件 *", + "no-file": "没有选择文件。", + "drop-file": "删除文件或单击以选择要上载的文件。", + "mapping": "映射", + "topic-filter": "主题滤波", + "converter-type": "转换类型", + "converter-json": "Json", + "json-name-expression": "设备名称JSON表达式", + "topic-name-expression": "设备名称主题表达式", + "json-type-expression": "设备类型JSON表达式", + "topic-type-expression": "设备类型主题表达式", + "attribute-key-expression": "属性关键字表达式", + "attr-json-key-expression": "属性键JSON表达式", + "attr-topic-key-expression": "属性关键字主题表达式", + "request-id-expression": "请求ID表达式", + "request-id-json-expression": "请求ID JSON表达式", + "request-id-topic-expression": "请求ID主题表达式", + "response-topic-expression": "响应主题表达式", + "value-expression": "值表达式", + "topic": "主题", + "timeout": "毫秒超时", + "converter-json-required": "转换JSON是必需的。", + "converter-json-parse": "无法解析转换JSON。", + "filter-expression": "过滤表达式", + "connect-requests": "连接请求", + "add-connect-request": "添加连接请求", + "disconnect-requests": "断开请求", + "add-disconnect-request": "添加断开请求", + "attribute-requests": "属性请求", + "add-attribute-request": "添加属性请求", + "attribute-updates": "属性更新", + "add-attribute-update": "添加属性更新", + "server-side-rpc": "服务端RPC", + "add-server-side-rpc-request": "添加服务端RPC请求", + "device-name-filter": "设备名称滤波", + "attribute-filter": "属性滤波", + "method-filter": "方法滤波", + "request-topic-expression": "请求主题表达式", + "response-timeout": "毫秒内响应超时", + "topic-expression": "主题表达", + "client-scope": "客户范围", + "add-device": "添加服务器", + "opc-server": "服务器组", + "opc-add-server": "添加服务器", + "opc-add-server-prompt": "请添加服务器", + "opc-application-name": "应用名称", + "opc-application-uri": "应用URI", + "opc-scan-period-in-seconds": "秒级扫描周期", + "opc-security": "安全性", + "opc-identity": "身份", + "opc-keystore": "密钥库", + "opc-type": "类型", + "opc-keystore-type": "类型", + "opc-keystore-location": "位置 *", + "opc-keystore-password": "密码", + "opc-keystore-alias": "别名", + "opc-keystore-key-password": "密钥密码", + "opc-device-node-pattern": "设备节点模式", + "opc-device-name-pattern": "设备名称模式", + "modbus-server": "Servers/slaves", + "modbus-add-server": "添加 server/slave", + "modbus-add-server-prompt": "请添加 server/slave", + "modbus-transport": "传输", + "modbus-port-name": "串口名称", + "modbus-encoding": "编码", + "modbus-parity": "奇偶性", + "modbus-baudrate": "波特率", + "modbus-databits": "数据位", + "modbus-stopbits": "停止位", + "modbus-databits-range": "数据位应该在7到8的范围内。", + "modbus-stopbits-range": "停止位应该在1到2的范围内。", + "modbus-unit-id": "单位编号", + "modbus-unit-id-range": "单位ID应该在1到247的范围内", + "modbus-device-name": "设备名称", + "modbus-poll-period": "轮询周期 (ms)", + "modbus-attributes-poll-period": "轮询属性周期 (ms)", + "modbus-timeseries-poll-period": "时间序列轮询周期 (ms)", + "modbus-poll-period-range": "轮询周期应为正值。", + "modbus-tag": "标签", + "modbus-function": "函数", + "modbus-register-address": "寄存器地址", + "modbus-register-address-range": "寄存器地址应该在0到65535的范围内。", + "modbus-register-bit-index": "位索引", + "modbus-register-bit-index-range": "位索引应该在0到15的范围内。", + "modbus-register-count": "寄存器计数", + "modbus-register-count-range": "寄存器计数应该是一个正值。", + "modbus-byte-order": "字节顺序", + "sync": { + "status": "状态", + "sync": "同步", + "not-sync": "不同步", + "last-sync-time": "最后同步时间", + "not-available": "无法使用" + }, + "export-extensions-configuration": "导出扩展配置", + "import-extensions-configuration": "导入扩展配置", + "import-extensions": "导入扩展", + "import-extension": "导入扩展", + "export-extension": "导出扩展", + "file": "扩展文件", + "invalid-file-error": "无效的扩展文件" + }, + "fullscreen": { + "expand": "展开到全屏", + "exit": "退出全屏", + "toggle": "切换全屏模式", + "fullscreen": "全屏" + }, + "function": { + "function": "函数" + }, + "grid": { + "delete-item-title": "您确定要删除此项吗?", + "delete-item-text": "注意,确认后此项及其所有相关数据将变得不可恢复。", + "delete-items-title": "你确定你要删除 { count, plural, 1 {1 项} other {# 项} }吗?", + "delete-items-action-title": "删除 { count, plural, 1 {1 项} other {# 项} }", + "delete-items-text": "注意,确认后所有选择的项目将被删除,所有相关数据将不可恢复。", + "add-item-text": "添加新项目", + "no-items-text": "没有找到项目", + "item-details": "项目详细信息", + "delete-item": "删除项目", + "delete-items": "删除项目", + "scroll-to-top": "滚动到顶部" + }, + "help": { + "goto-help-page": "转到帮助页面" + }, + "home": { + "home": "首页", + "profile": "属性", + "logout": "注销", + "menu": "菜单", + "avatar": "头像", + "open-user-menu": "打开用户菜单" + }, + "import": { + "no-file": "没有选择文件", + "drop-file": "拖动一个JSON文件或者单击以选择要上传的文件。" + }, + "item": { + "selected": "选择" + }, + "js-func": { + "no-return-error": "函数必须返回值!", + "return-type-mismatch": "函数必须返回 '{{type}}' 类型的值!", + "tidy": "整洁" + }, + "key-val": { + "key": "键名", + "value": "值", + "remove-entry": "删除条目", + "add-entry": "增加条目", + "no-data": "没有条目" + }, + "layout": { + "layout": "布局", + "manage": "布局管理", + "settings": "布局设置", + "color": "颜色", + "main": "主体", + "right": "右侧", + "select": "选择目标布局" + }, + "legend": { + "position": "图例位置", + "show-max": "显示最大值", + "show-min": "显示最小值", + "show-avg": "显示平均值", + "show-total": "显示总数", + "settings": "图例设置", + "min": "最小值", + "max": "最大值", + "avg": "平均值", + "total": "总数" + }, + "login": { + "login": "登录", + "request-password-reset": "请求密码重置", + "reset-password": "重置密码", + "create-password": "创建密码", + "passwords-mismatch-error": "输入的密码必须相同!", + "password-again": "再次输入密码", + "sign-in": "登录 ", + "username": "用户名(电子邮件)", + "remember-me": "记住我", + "forgot-password": "忘记密码?", + "password-reset": "密码重置", + "new-password": "新密码", + "new-password-again": "再次输入新密码", + "password-link-sent-message": "密码重置链接已成功发送!", + "email": "电子邮件" + }, + "position": { + "top": "顶部", + "bottom": "底部", + "left": "左侧", + "right": "右侧" + }, + "profile": { + "profile": "属性", + "change-password": "更改密码", + "current-password": "当前密码" + }, + "relation": { + "relations": "关联", + "direction": "方向", + "search-direction": { + "FROM": "从", + "TO": "到" + }, + "direction-type": { + "FROM": "从", + "TO": "到" + }, + "from-relations": "向外的关联", + "to-relations": "向内的关联", + "selected-relations": "{ count, plural, 1 {1 关联} other {# 关联} } 被选中", + "type": "类型", + "to-entity-type": "到实体类型", + "to-entity-name": "到实体名称", + "from-entity-type": "从实体类型", + "from-entity-name": "从实体类型", + "to-entity": "到实体", + "from-entity": "从实体", + "delete": "删除关联", + "relation-type": "关联类型", + "relation-type-required": "关联类型必填", + "any-relation-type": "任意类型", + "add": "添加关联", + "edit": "编辑关联", + "delete-to-relation-title": "确定要删除实体 '{{entityName}}' 的关联吗?", + "delete-to-relation-text": "确定删除后实体 '{{entityName}}' 将取消与当前实体的关联关系。", + "delete-to-relations-title": "确定要删除 { count, plural, 1 {1 关联} other {# 关联} }?", + "delete-to-relations-text": "确定删除所有选择的关联关系后,与当前实体对应的所有关联关系将被移除。", + "delete-from-relation-title": "确定要从实体 '{{entityName}}' 删除关联吗?", + "delete-from-relation-text": "确定删除后,当前实体将与实体 '{{entityName}}' 取消关联", + "delete-from-relations-title": "确定删除 { count, plural, 1 {1 关联} other {# 关联} } 吗?", + "delete-from-relations-text": "确定删除所有选择的关联关系后,当前实体将与对应的实体取消关联", + "remove-relation-filter": "移除关联过滤器", + "add-relation-filter": "添加关联过滤器", + "any-relation": "任意关联", + "relation-filters": "关联过滤器", + "additional-info": "附加信息 (JSON)", + "invalid-additional-info": "无法解析附加信息json。" + }, + "rulechain": { + "rulechain": "规则链", + "rulechains": "规则链库", + "root": "根实体", + "delete": "删除规则", + "activate": "激活规则", + "suspend": "暂停规则", + "active": "激活", + "suspended": "暂停", + "name": "名称", + "name-required": "名称必填。", + "description": "描述", + "add": "添加规则", + "set-root": "创建规则链根", + "set-root-rulechain-title": "您确定要生成规则链'{{RuleChainName}}'根吗?", + "set-root-rulechain-text": "确认之后,规则链将变为根规格链,并将处理所有传入的传输消息。", + "delete-rulechain-title": " 确实要删除规则链'{{ruleChainName}}'吗?", + "delete-rulechain-text": "小心,在确认规则链和所有相关数据将变得不可恢复。", + "delete-rulechains-title": "确实要删除{count, plural, 1 { 1 规则链}其他{# 规则链库}}吗?", + "delete-rulechains-action-title": "删除 { count, plural, 1 {1 规则链} other {# 规则链库} }", + "delete-rulechains-text": "小心,确认后,所有选定的规则链将被删除,所有相关的数据将变得不可恢复。", + "add-rulechain-text": "添加新的规则链", + "no-rulechains-text": "规则链没有发现", + "rulechain-details": "规则链详情", + "details": "详情", + "events": "事件", + "system": "系统", + "import": "导入规则", + "export": "导出规则", + "export-failed-error": "无法导出规则:{{error}}", + "create-new-rulechain": "创建新的规则链", + "rulechain-file": "规则链文件", + "invalid-rulechain-file-error": "不能导入规则链:无效的规则链数据格式。", + "copyId": "复制规则链ID", + "idCopiedMessage": "规则ID已经复制到粘贴板", + "select-rulechain": "选择规则链", + "no-rulechains-matching": "没有发现匹配'{{entity}}'的规则链。", + "rulechain-required": "规则链必填", + "management": "规则集管理", + "debug-mode": "调试模式" + }, + "rulenode": { + "details": "详情", + "events": "事件", + "search": "搜索节点", + "open-node-library": "打开节点库", + "add": "添加规则节点", + "name": "名称", + "name-required": "名称必填。", + "type": "类型", + "description": "描述", + "delete": "删除规则节点", + "select-all-objects": "选择所有节点和连接", + "deselect-all-objects": "取消选择所有节点和连接", + "delete-selected-objects": "删除选定的节点和连接", + "delete-selected": "删除选定", + "select-all": "选择全部", + "copy-selected": "选择副本", + "deselect-all": "取消选择", + "rulenode-details": "规则节点详情", + "debug-mode": "调试模式", + "configuration": "配置", + "link": "链接", + "link-details": "规则节点链接详情", + "add-link": "添加链接", + "link-label": "链接标签", + "link-label-required": "链接标签必填", + "custom-link-label": "自定义链接标签", + "custom-link-label-required": "自定义链接标签必填", + "type-filter": "滤波器", + "type-filter-details": "使用配置条件过滤传入消息", + "type-enrichment": "属性集", + "type-enrichment-details": "向消息元数据中添加附加信息", + "type-transformation": "变换", + "type-transformation-details": "更改消息有效载荷和元数据", + "type-action": "动作", + "type-action-details": "执行特别动作", + "type-external": "外部的", + "type-external-details": "与外部系统交互", + "type-rule-chain": "规则链", + "type-rule-chain-details": "将传入消息转发到指定的规则链", + "type-input": "输入", + "type-input-details": "规则链的逻辑输入,将传入消息转发到下一个相关规则节点", + "directive-is-not-loaded": "定义的配置指令 '{{directiveName}}' 不可用。", + "ui-resources-load-error": "加载配置UI资源失败。", + "invalid-target-rulechain": "无法解析目标规则链!", + "test-script-function": "测试脚本功能", + "message": "消息", + "message-type": "消息类型", + "message-type-required": "消息类型必填", + "metadata": "元数据", + "metadata-required": "元数据项不能为空。", + "output": "输出", + "test": "测试", + "help": "帮助" + }, + "tenant": { + "tenant": "租户", + "tenants": "租户", + "management": "租户管理", + "add": "添加租户", + "admins": "管理员", + "manage-tenant-admins": "管理租户管理员", + "delete": "删除租户", + "add-tenant-text": "添加新租户", + "no-tenants-text": "没有找到租户", + "tenant-details": "租客详情", + "delete-tenant-title": "您确定要删除租户'{{tenantTitle}}'吗?", + "delete-tenant-text": "小心!确认后,租户和所有相关数据将不可恢复。", + "delete-tenants-title": "您确定要删除 {count,plural,1 {1 租户} other {# 租户}} 吗?", + "delete-tenants-action-title": "删除 { count, plural, 1 {1 租户} other {# 租户} }", + "delete-tenants-text": "小心!确认后,所有选定的租户将被删除,所有相关数据将不可恢复。", + "title": "标题", + "title-required": "标题必填。", + "description": "描述", + "details": "详情", + "events": "事件", + "copyId": "复制租户ID", + "idCopiedMessage": "租户ID已经复制到粘贴板", + "select-tenant": "选择租户", + "no-tenants-matching": "没有找到符合 '{{entity}}' 的租户", + "tenant-required": "租户必填" + }, + "timeinterval": { + "seconds-interval": "{ seconds, plural, 1 {1 秒} other {# 秒} }", + "minutes-interval": "{ minutes, plural, 1 {1 分} other {# 分} }", + "hours-interval": "{ hours, plural, 1 {1 小时} other {# 小时} }", + "days-interval": "{ days, plural, 1 {1 天} other {# 天} }", + "days": "天", + "hours": "时", + "minutes": "分", + "seconds": "秒", + "advanced": "高级" + }, + "timewindow": { + "days": "{ days, plural, 1 { 天 } other {# 天 } }", + "hours": "{ hours, plural, 0 { 小时 } 1 {1 小时 } other {# 小时 } }", + "minutes": "{ minutes, plural, 0 { 分 } 1 {1 分 } other {# 分 } }", + "seconds": "{ seconds, plural, 0 { 秒 } 1 {1 秒 } other {# 秒 } }", + "realtime": "实时", + "history": "历史", + "last-prefix": "最后", + "period": "从 {{ startTime }} 到 {{ endTime }}", + "edit": "编辑时间窗口", + "date-range": "日期范围", + "last": "最后", + "time-period": "时间段" + }, + "user": { + "user": "用户", + "users": "用户", + "customer-users": "客户用户", + "tenant-admins": "租户管理员", + "sys-admin": "系统管理员", + "tenant-admin": "租户管理员", + "customer": "客户", + "anonymous": "匿名", + "add": "添加用户", + "delete": "删除用户", + "add-user-text": "添加新用户", + "no-users-text": "找不到用户", + "user-details": "用户详细信息", + "delete-user-title": "您确定要删除用户 '{{userEmail}}' 吗?", + "delete-user-text": "小心!确认后,用户和所有相关数据将不可恢复。", + "delete-users-title": "你确定你要删除 { count, plural, 1 {1 用户} other {# 用户} } 吗?", + "delete-users-action-title": "删除 { count, plural, 1 {1 用户} other {# 用户} }", + "delete-users-text": "小心!确认后,所有选定的用户将被删除,所有相关数据将不可恢复。", + "activation-email-sent-message": "激活电子邮件已成功发送!", + "resend-activation": "重新发送激活", + "email": "电子邮件", + "email-required": "电子邮件必填。", + "invalid-email-format": "无效的邮件格式。", + "first-name": "名字", + "last-name": "姓", + "description": "描述", + "default-dashboard": "默认面板", + "always-fullscreen": "始终全屏", + "select-user": "选择用户", + "no-users-matching": "没有找到符合 '{{entity}}' 的用户。", + "user-required": "用户必填", + "activation-method": "激活方式", + "display-activation-link": "显示激活链接", + "send-activation-mail": "发送激活邮件", + "activation-link": "用户激活链接", + "activation-link-text": "使用该链接 激活 激活用户:", + "copy-activation-link": "复制用户激活链接", + "activation-link-copied-message": "用户激活链接已经复制到粘贴板", + "details": "详细信息" + }, + "value": { + "type": "值类型", + "string": "字符串", + "string-value": "字符串值", + "integer": "数字", + "integer-value": "数字值", + "invalid-integer-value": "整数值无效", + "double": "双精度小数", + "double-value": "双精度小数值", + "boolean": "布尔", + "boolean-value": "布尔值", + "false": "假", + "true": "真", + "long": "Long" + }, + "widget": { + "widget-library": "部件库", + "widget-bundle": "部件包", + "select-widgets-bundle": "选择部件包", + "management": "管理部件", + "editor": "部件编辑器", + "widget-type-not-found": "加载部件配置出错。
可能关联的\n 部件已经删除了。", + "widget-type-load-error": "由于以下错误未加载小部件:", + "remove": "删除部件", + "edit": "编辑部件", + "remove-widget-title": "确实要删除 '{{widgetTitle}}'部件吗?", + "remove-widget-text": "确认后,控件和所有相关数据将变得不可恢复。", + "timeseries": "时间序列", + "search-data": "搜索数据", + "no-data-found": "没有找到数据", + "latest-values": "最新值", + "rpc": "控件部件", + "alarm": "警告部件", + "static": "静态部件", + "select-widget-type": "选择窗口部件类型", + "missing-widget-title-error": "部件标题必须指定!", + "widget-saved": "部件已保存", + "unable-to-save-widget-error": "无法保存部件!控件有错误!", + "save": "保存部件", + "saveAs": "部件另存为", + "save-widget-type-as": "部件类型另存为", + "save-widget-type-as-text": "请输入新的部件标题或选择目标部件包", + "toggle-fullscreen": "切换全屏", + "run": "运行部件", + "title": "部件标题", + "title-required": "需要部件标题。", + "type": "部件类型", + "resources": "资源", + "resource-url": "JavaScript/CSS URL", + "remove-resource": "删除资源", + "add-resource": "添加资源", + "html": "HTML", + "tidy": "整理", + "css": "CSS", + "settings-schema": "设置模式", + "datakey-settings-schema": "数据键设置模式", + "javascript": "Javascript", + "remove-widget-type-title": "您确定要删除部件类型 '{{widgetName}}'吗?", + "remove-widget-type-text": "确认后,窗口部件类型和所有相关数据将不可恢复。", + "remove-widget-type": "删除部件类型", + "add-widget-type": "添加新的部件类型", + "widget-type-load-failed-error": "无法加载部件类型!", + "widget-template-load-failed-error": "无法加载部件模板!", + "add": "添加部件", + "undo": "撤消部件更改", + "export": "导出部件" + }, + "widget-action": { + "header-button": "部件顶部按钮", + "open-dashboard-state": "切换到新仪表板状态", + "update-dashboard-state": "更新当前仪表板状态", + "open-dashboard": "切换到另一个仪表板", + "custom": "自定义动作", + "target-dashboard-state": "目标仪表板状态", + "target-dashboard-state-required": "目标仪表板状态必填", + "set-entity-from-widget": "从部件中设置实体", + "target-dashboard": "目标仪表板", + "open-right-layout": "打开右侧布局 (移动端视图)" + }, + "widgets-bundle": { + "current": "当前包", + "widgets-bundles": "部件包", + "add": "添加部件包", + "delete": "删除部件包", + "title": "标题", + "title-required": "标题必填。", + "add-widgets-bundle-text": "添加新的部件包", + "no-widgets-bundles-text": "找不到部件包", + "empty": "部件包是空的", + "details": "详情", + "widgets-bundle-details": "部件包详细信息", + "delete-widgets-bundle-title": "您确定要删除部件包 '{{widgetsBundleTitle}}'吗?", + "delete-widgets-bundle-text": "小心!确认后,部件包和所有相关数据将不可恢复。", + "delete-widgets-bundles-title": "你确定你要删除 { count, plural, 1 {1 部件包} other {# 部件包} } 吗?", + "delete-widgets-bundles-action-title": "删除 { count, plural, 1 {1 部件包} other {# 部件包} }", + "delete-widgets-bundles-text": "小心!确认后,所有选定的部件包将被删除,所有相关数据将不可恢复。", + "no-widgets-bundles-matching": "没有找到与 '{{widgetsBundle}}' 匹配的部件包。", + "widgets-bundle-required": "需要部件包。", + "system": "系统", + "import": "导入部件包", + "export": "导出部件包", + "export-failed-error": "无法导出部件包: {{error}}", + "create-new-widgets-bundle": "创建新的部件包", + "widgets-bundle-file": "部件包文件", + "invalid-widgets-bundle-file-error": "无法导入部件包:无效的部件包数据结构。" + }, + "widget-config": { + "data": "数据", + "settings": "设置", + "advanced": "高级", + "title": "标题", + "general-settings": "常规设置", + "display-title": "显示标题", + "drop-shadow": "阴影", + "enable-fullscreen": "启用全屏", + "background-color": "背景颜色", + "text-color": "文字颜色", + "padding": "填充", + "margin": "边缘", + "widget-style": "部件风格", + "title-style": "标题风格", + "mobile-mode-settings": "移动端设置", + "order": "顺序", + "height": "高度", + "units": "特殊符号展示值", + "decimals": "浮点数后的位数", + "timewindow": "时间窗口", + "use-dashboard-timewindow": "使用仪表板的时间窗口", + "display-legend": "显示图例", + "datasources": "数据源", + "maximum-datasources": "最大允许 { count, plural, 1 {1 数据} other {# 数据} }", + "datasource-type": "类型", + "datasource-parameters": "参数", + "remove-datasource": "移除数据源", + "add-datasource": "添加数据源", + "target-device": "目标设备", + "alarm-source": "警告源", + "actions": "动作", + "action": "动作", + "add-action": "添加动作", + "search-actions": "动作检索", + "action-source": "动作源", + "action-source-required": "动作源必填", + "action-name": "动作名称", + "action-name-required": "动作名称必填。", + "action-name-not-unique": "动作名称已经存在。
统一动作源的动作名称必须唯一。", + "action-icon": "图标", + "action-type": "类型", + "action-type-required": "类型必填", + "edit-action": "编辑动作", + "delete-action": "删除动作", + "delete-action-title": "删除部件动作", + "delete-action-text": "确定要删除部件动作 '{{actionName}}' 吗?" + }, + "widget-type": { + "import": "导入部件类型", + "export": "导出部件类型", + "export-failed-error": "无法导出部件类型: {{error}}", + "create-new-widget-type": "创建新的部件类型", + "widget-type-file": "部件类型文件", + "invalid-widget-type-file-error": "无法导入部件类型:无效的部件类型数据结构。" + }, + "widgets": { + "date-range-navigator": { + "localizationMap": { + "Sun": "周日", + "Mon": "周一", + "Tue": "周二", + "Wed": "周三", + "Thu": "周四", + "Fri": "周五", + "Sat": "周六", + "Jan": "1月", + "Feb": "2月", + "Mar": "3月", + "Apr": "4月", + "May": "5月", + "Jun": "6月", + "Jul": "7月", + "Aug": "8月", + "Sep": "9月", + "Oct": "10月", + "Nov": "11月", + "Dec": "12月", + "January": "一月", + "February": "二月", + "March": "三月", + "April": "四月", + "June": "六月", + "July": "七月", + "August": "八月", + "September": "九月", + "October": "十月", + "November": "十一月", + "December": "十二月", + "Custom Date Range": "自定义日期范围", + "Date Range Template": "日期范围模板", + "Today": "今天", + "Yesterday": "昨天", + "This Week": "本星期", + "Last Week": "上个星期", + "This Month": "这个月", + "Last Month": "上个月", + "Year": "年", + "This Year": "今年", + "Last Year": "去年", + "Date picker": "日期选择器", + "Hour": "小时", + "Day": "天", + "Week": "周", + "2 weeks": "2周", + "Month": "月", + "3 months": "3个月", + "6 months": "6个月", + "Custom interval": "自定义间隔", + "Interval": "间隔", + "Step size": "步长", + "Ok": "Ok" + } + } + }, + "icon": { + "icon": "图标", + "select-icon": "选择图标", + "material-icons": "素材图标", + "show-all": "显示所有图标" + }, + "custom": { + "widget-action": { + "action-cell-button": "动作单元格按钮", + "row-click": "点击行", + "marker-click": "点击标记", + "polygon-click": "单击多边形", + "tooltip-tag-action": "提示标签动作" + } + }, + "language": { + "language": "语言", + "locales": { + "de_DE": "德语", + "en_US": "英语", + "fr_FR": "法国", + "ko_KR": "韩语", + "zh_CN": "汉语", + "ru_RU": "俄语", + "es_ES": "西班牙语", + "it_IT": "意大利", + "ja_JA": "日本", + "tr_TR": "土耳其", + "fa_IR": "波斯语", + "uk_UA": "乌克兰", + "cs_CZ": "在捷克" + } + } +} diff --git a/ui-ngx/src/assets/logo_title_white.svg b/ui-ngx/src/assets/logo_title_white.svg new file mode 100644 index 0000000000..3e6d570b47 --- /dev/null +++ b/ui-ngx/src/assets/logo_title_white.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/assets/logo_white.svg b/ui-ngx/src/assets/logo_white.svg new file mode 100644 index 0000000000..52a38c9a3a --- /dev/null +++ b/ui-ngx/src/assets/logo_white.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/ui-ngx/src/assets/mdi.svg b/ui-ngx/src/assets/mdi.svg new file mode 100644 index 0000000000..75e8ffea7a --- /dev/null +++ b/ui-ngx/src/assets/mdi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ui-ngx/src/browserslist b/ui-ngx/src/browserslist new file mode 100644 index 0000000000..37371cb04b --- /dev/null +++ b/ui-ngx/src/browserslist @@ -0,0 +1,11 @@ +# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers +# For additional information regarding the format and rule options, please see: +# https://github.com/browserslist/browserslist#queries +# +# For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed + +> 0.5% +last 2 versions +Firefox ESR +not dead +not IE 9-11 \ No newline at end of file diff --git a/ui-ngx/src/environments/environment.prod.ts b/ui-ngx/src/environments/environment.prod.ts new file mode 100644 index 0000000000..f9af1787b2 --- /dev/null +++ b/ui-ngx/src/environments/environment.prod.ts @@ -0,0 +1,25 @@ +/// +/// Copyright © 2016-2019 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 const environment = { + appTitle: 'ThingsBoard', + production: true, +// @ts-ignore + tbVersion: TB_VERSION, +// @ts-ignore + supportedLangs: SUPPORTED_LANGS, + defaultLang: 'en_US' +}; diff --git a/ui-ngx/src/environments/environment.ts b/ui-ngx/src/environments/environment.ts new file mode 100644 index 0000000000..d23f2df7d2 --- /dev/null +++ b/ui-ngx/src/environments/environment.ts @@ -0,0 +1,38 @@ +/// +/// Copyright © 2016-2019 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. +/// + +// This file can be replaced during build by using the `fileReplacements` array. +// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. +// The list of file replacements can be found in `angular.json`. + +export const environment = { + appTitle: 'ThingsBoard', + production: false, +// @ts-ignore + tbVersion: TB_VERSION, +// @ts-ignore + supportedLangs: SUPPORTED_LANGS, + defaultLang: 'en_US' +}; + +/* + * For easier debugging in development mode, you can import the following file + * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. + * + * This import should be commented out in production mode because it will have a negative impact + * on performance if an error is thrown. + */ +// import 'zone.js/dist/zone-error'; // Included with Angular CLI. diff --git a/ui-ngx/src/index.html b/ui-ngx/src/index.html new file mode 100644 index 0000000000..3664026d0e --- /dev/null +++ b/ui-ngx/src/index.html @@ -0,0 +1,31 @@ + + + + + + ThingsBoard + + + + + + + + + diff --git a/ui-ngx/src/karma.conf.js b/ui-ngx/src/karma.conf.js new file mode 100644 index 0000000000..637bf8fbff --- /dev/null +++ b/ui-ngx/src/karma.conf.js @@ -0,0 +1,47 @@ +/* + * Copyright © 2016-2019 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. + */ +// Karma configuration file, see link for more information +// https://karma-runner.github.io/1.0/config/configuration-file.html + +module.exports = function (config) { + config.set({ + basePath: '', + frameworks: ['jasmine', '@angular-devkit/build-angular'], + plugins: [ + require('karma-jasmine'), + require('karma-chrome-launcher'), + require('karma-jasmine-html-reporter'), + require('karma-coverage-istanbul-reporter'), + require('@angular-devkit/build-angular/plugins/karma') + ], + client: { + clearContext: false // leave Jasmine Spec Runner output visible in browser + }, + coverageIstanbulReporter: { + dir: require('path').join(__dirname, '../coverage/tb-license-server'), + reports: ['html', 'lcovonly', 'text-summary'], + fixWebpackSourcePaths: true + }, + reporters: ['progress', 'kjhtml'], + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: true, + browsers: ['Chrome'], + singleRun: false, + restartOnFileChange: true + }); +}; diff --git a/ui-ngx/src/main.ts b/ui-ngx/src/main.ts new file mode 100644 index 0000000000..f8673a520d --- /dev/null +++ b/ui-ngx/src/main.ts @@ -0,0 +1,30 @@ +/// +/// Copyright © 2016-2019 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 'hammerjs'; + +import { enableProdMode } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; +import { environment } from './environments/environment'; + +if (environment.production) { + enableProdMode(); +} + +platformBrowserDynamic().bootstrapModule(AppModule) + .catch(err => console.error(err)); diff --git a/ui-ngx/src/polyfills.ts b/ui-ngx/src/polyfills.ts new file mode 100644 index 0000000000..d98a1aedb9 --- /dev/null +++ b/ui-ngx/src/polyfills.ts @@ -0,0 +1,79 @@ +/// +/// Copyright © 2016-2019 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. +/// + +/** + * This file includes polyfills needed by Angular and is loaded before the app. + * You can add your own extra polyfills to this file. + * + * This file is divided into 2 sections: + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main + * file. + * + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that + * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), + * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. + * + * Learn more in https://angular.io/guide/browser-support + */ + +/*************************************************************************************************** + * BROWSER POLYFILLS + */ + +/** IE10 and IE11 requires the following for NgClass support on SVG elements */ +// import 'classlist.js'; // Run `npm install --save classlist.js`. + +/** + * Web Animations `@angular/platform-browser/animations` + * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. + * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). + */ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + +/** + * By default, zone.js will patch all possible macroTask and DomEvents + * user can disable parts of macroTask/DomEvents patch by setting following flags + * because those flags need to be set before `zone.js` being loaded, and webpack + * will put import in the top of bundle, so user need to create a separate file + * in this directory (for example: zone-flags.ts), and put the following flags + * into that file, and then add the following code before importing zone.js. + * import './zone-flags.ts'; + * + * The flags allowed in zone-flags.ts are listed here. + * + * The following flags will work for all browsers. + * + * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame + * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick + * (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames + * + * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js + * with the following flag, it will bypass `zone.js` patch for IE/Edge + * + * (window as any).__Zone_enable_cross_context_check = true; + * + */ + +/*************************************************************************************************** + * Zone JS is required by default for Angular itself. + */ +import 'zone.js/dist/zone'; // Included with Angular CLI. +import 'core-js/es/array'; + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ diff --git a/ui-ngx/src/scss/animations.scss b/ui-ngx/src/scss/animations.scss new file mode 100644 index 0000000000..428bd79d55 --- /dev/null +++ b/ui-ngx/src/scss/animations.scss @@ -0,0 +1,48 @@ +/** + * Copyright © 2016-2019 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 "~compass-sass-mixins/lib/animate"; + +@keyframes tbMoveFromTopFade { + from { + opacity: 0; + + transform: translate(0, -100%); + } +} + +@keyframes tbMoveToTopFade { + to { + opacity: 0; + + transform: translate(0, -100%); + } +} + +@keyframes tbMoveFromBottomFade { + from { + opacity: 0; + + transform: translate(0, 100%); + } +} + +@keyframes tbMoveToBottomFade { + to { + opacity: 0; + + transform: translate(0, 150%); + } +} diff --git a/ui-ngx/src/scss/constants.scss b/ui-ngx/src/scss/constants.scss new file mode 100644 index 0000000000..f1cb13f310 --- /dev/null +++ b/ui-ngx/src/scss/constants.scss @@ -0,0 +1,29 @@ +/** + * Copyright © 2016-2019 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. + */ +$mat-xs: "screen and (max-width: 599px)"; +$mat-sm: "screen and (min-width: 600px) and (max-width: 959px)"; +$mat-md: "screen and (min-width: 960px) and (max-width: 1279px)"; +$mat-lg: "screen and (min-width: 1280px) and (max-width: 1919px)"; +$mat-xl: "screen and (min-width: 1920px) and (max-width: 5000px)"; +$mat-lt-sm: "screen and (max-width: 599px)"; +$mat-lt-md: "screen and (max-width: 959px)"; +$mat-lt-lg: "screen and (max-width: 1279px)"; +$mat-lt-xl: "screen and (max-width: 1919px)"; +$mat-gt-xs: "screen and (min-width: 600px)"; +$mat-gt-sm: "screen and (min-width: 960px)"; +$mat-gt-md: "screen and (min-width: 1280px)"; +$mat-gt-xmd: "screen and (min-width: 1600px)"; +$mat-gt-xl: "screen and (min-width: 1920px)"; diff --git a/ui-ngx/src/styles.scss b/ui-ngx/src/styles.scss new file mode 100644 index 0000000000..e2ea5e36c9 --- /dev/null +++ b/ui-ngx/src/styles.scss @@ -0,0 +1,314 @@ +/** + * Copyright © 2016-2019 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. + */ +/* You can add global styles to this file, and also import other style files */ + +@import '~material-design-icons/iconfont/material-icons.css'; +@import '~typeface-roboto/index.css'; +@import '~font-awesome/css/font-awesome.min.css'; +@import 'theme.scss'; +@import './scss/constants'; +@import './scss/animations'; + +body, html { + height: 100%; + min-height: 100%; + position: relative; + -webkit-tap-highlight-color: rgba(0,0,0,0); + -webkit-touch-callout: none; + + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +body { + margin: 0; + padding: 0; + background-color: rgb(250,250,250); +} + +tb-root { + margin: 0; + width: 100%; + min-height: 100%; + height: 100%; + display: flex; + flex-direction: row; + box-sizing: border-box; +} + +/*************** + * TYPE DEFAULTS + ***************/ + +body, +button, +html, +input, +select, +textarea, +td, +th { + font-family: Roboto, "Helvetica Neue", sans-serif; + font-size: 16px; +} + +body { + line-height: normal; +} + +a { + font-weight: 400; + color: #106cc8; + text-decoration: none; + border-bottom: 1px solid rgba(64, 84, 178, .25); + + transition: border-bottom .35s; +} + +a:hover, +a:focus { + border-bottom: 1px solid #4054b2; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + margin-top: 1rem; + margin-bottom: 1rem; +} + +h1 { + font-size: 3.4rem; + font-weight: 400; + line-height: 4rem; +} + +h2 { + font-size: 2.4rem; + font-weight: 400; + line-height: 3.2rem; +} + +h3 { + font-size: 2rem; + font-weight: 500; + letter-spacing: .005em; +} + +h4 { + font-size: 1.6rem; + font-weight: 400; + line-height: 2.4rem; + letter-spacing: .01em; +} + +p { + margin: .8em 0 1.6em; + font-size: 1.6rem; + font-weight: 400; + line-height: 1.6em; + letter-spacing: .01em; +} + +strong { + font-weight: 500; +} + +blockquote { + padding-left: 16px; + margin-left: 0; + font-style: italic; + border-left: 3px solid rgba(0, 0, 0, .12); +} + +fieldset { + padding: 0; + margin: 0; + border: none; +} + +section.tb-header-buttons { + position: absolute; + top: 86px; + right: 0; + z-index: 3; + pointer-events: none; + + @media #{$mat-gt-sm} { + top: 86px; + } + + .tb-btn-header { + margin: 6px 8px; + position: relative !important; + display: inline-block !important; + animation: tbMoveFromTopFade .3s ease both; + + &.tb-hide { + animation: tbMoveToTopFade .3s ease both; + } + } +} + +.tb-details-buttons { + button { + margin: 6px 8px; + } +} + +label { + &.tb-title { + padding-bottom: 15px; + font-size: 13px; + font-weight: 400; + color: #666; + pointer-events: none; + + &.no-padding { + padding-bottom: 0; + } + + &.tb-required::after { + font-size: 13px; + color: rgba(0, 0, 0, .54); + vertical-align: top; + content: " *"; + } + + &.tb-error { + color: rgb(221, 44, 0); + + &.tb-required::after { + color: rgb(221, 44, 0); + } + } + } + &.tb-small { + font-size: 12px; + color: rgba(0, 0, 0, .54); + pointer-events: none; + } +} + +pre.tb-highlight { + display: block; + padding: 15px; + margin: 20px 0; + overflow-x: auto; + background-color: #f7f7f7; + + code { + box-sizing: border-box; + display: inline-block; + padding: 0; + font-family: monospace; + font-size: 16px; + font-weight: 700; + color: #303030; + vertical-align: bottom; + } +} + +.ace_editor { + font-size: 16px !important; +} + +.tb-timewindow-panel { + overflow: hidden; + background: #fff; + border-radius: 4px; + box-shadow: + 0 7px 8px -4px rgba(0, 0, 0, .2), + 0 13px 19px 2px rgba(0, 0, 0, .14), + 0 5px 24px 4px rgba(0, 0, 0, .12); +} + +.tb-panel-actions { + margin-bottom: 0; + padding: 8px 8px 8px 16px; + .mat-button+.mat-button, + .mat-button+.mat-raised-button, + .mat-raised-button+.mat-button, + .mat-raised-button+.mat-raised-button { + margin-left: 8px; + } +} + +.tb-container { + position: relative; + padding: 10px 0; + margin-top: 32px; +} + +.tb-fullscreen { + position: fixed !important; + top: 0; + left: 0; + width: 100% !important; + height: 100% !important; +} + +.tb-fullscreen-parent { + background: #fff; +} + +mat-label { + &.tb-title { + font-size: 13px; + font-weight: 400; + color: #666; + pointer-events: none; + + &.no-padding { + padding-bottom: 0; + } + + &.tb-required::after { + font-size: 13px; + color: rgba(0, 0, 0, .54); + vertical-align: top; + content: " *"; + } + + &.tb-error { + color: rgb(221, 44, 0); + + &.tb-required::after { + color: rgb(221, 44, 0); + } + } + } +} + +.tb-error-messages { + height: 24px; //30px + margin-top: -6px; +} + +.tb-error-message { + transition: all .3s cubic-bezier(.55, 0, .55, .2); + padding: 10px 0 0 10px; + overflow: hidden; + font-size: 12px; + line-height: 14px; + color: rgb(221, 44, 0); +} diff --git a/ui-ngx/src/test.ts b/ui-ngx/src/test.ts new file mode 100644 index 0000000000..f02fc6d056 --- /dev/null +++ b/ui-ngx/src/test.ts @@ -0,0 +1,36 @@ +/// +/// Copyright © 2016-2019 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. +/// + +// This file is required by karma.conf.js and loads recursively all the .spec and framework files + +import 'zone.js/dist/zone-testing'; +import { getTestBed } from '@angular/core/testing'; +import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting +} from '@angular/platform-browser-dynamic/testing'; + +declare const require: any; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting() +); +// Then we find all the tests. +const context = require.context('./', true, /\.spec\.ts$/); +// And load the modules. +context.keys().map(context); diff --git a/ui-ngx/src/theme.scss b/ui-ngx/src/theme.scss new file mode 100644 index 0000000000..d0e4c17146 --- /dev/null +++ b/ui-ngx/src/theme.scss @@ -0,0 +1,557 @@ +/** + * Copyright © 2016-2019 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 '~@angular/material/theming'; +@import '~@mat-datetimepicker/core/datetimepicker/datetimepicker-theme.scss'; +@import './scss/constants'; + +@include mat-core(); + +$tb-primary-color: #305680; +$tb-secondary-color: #527dad; +$tb-hue3-color: #a7c1de; + +$tb-dark-primary-color: #9fa8da; + +$tb-mat-indigo: ( + 50: #e8eaf6, + 100: #c5cae9, + 200: #9fa8da, + 300: #7986cb, + 400: #5c6bc0, + 500: $tb-primary-color, + 600: $tb-secondary-color, + 700: #303f9f, + 800: #283593, + 900: #1a237e, + A100: $tb-hue3-color, + A200: #536dfe, + A400: #3d5afe, + A700: #304ffe, + contrast: ( + 50: $dark-primary-text, + 100: $dark-primary-text, + 200: $dark-primary-text, + 300: $light-primary-text, + 400: $light-primary-text, + 500: $light-primary-text, + 600: $light-primary-text, + 700: $light-primary-text, + 800: $light-primary-text, + 900: $light-primary-text, + A100: $dark-primary-text, + A200: $light-primary-text, + A400: $light-primary-text, + A700: $light-primary-text, + ) +); + +$tb-primary: mat-palette($tb-mat-indigo); +$tb-accent: mat-palette($mat-deep-orange); + +// Background palette for light themes. +$tb-light-theme-background: ( + status-bar: map_get($mat-grey, 300), + app-bar: map_get($mat-grey, 100), + background: #eee, // map_get($mat-grey, 50), + hover: rgba(black, 0.04), + card: white, + dialog: white, + disabled-button: rgba(black, 0.12), + raised-button: white, + focused-button: $dark-focused, + selected-button: map_get($mat-grey, 300), + selected-disabled-button: map_get($mat-grey, 400), + disabled-button-toggle: map_get($mat-grey, 200), + unselected-chip: map_get($mat-grey, 300), + disabled-list-option: map_get($mat-grey, 200), +); + +@function get-tb-light-theme($primary, $accent, $warn: mat-palette($mat-red)) { + @return ( + primary: $primary, + accent: $accent, + warn: $warn, + is-dark: false, + foreground: $mat-light-theme-foreground, + background: $tb-light-theme-background, + ); +} + +$tb-theme: get-tb-light-theme( + $tb-primary, + $tb-accent +); + +$primary: mat-color($tb-primary); +$accent: mat-color($tb-accent); + +$tb-dark-mat-indigo: ( + 50: #e8eaf6, + 100: #c5cae9, + 200: #9fa8da, + 300: #7986cb, + 400: #5c6bc0, + 500: $tb-dark-primary-color, + 600: $tb-secondary-color, + 700: #303f9f, + 800: #283593, + 900: #1a237e, + A100: $tb-hue3-color, + A200: #536dfe, + A400: #3d5afe, + A700: #304ffe, + contrast: ( + 50: $dark-primary-text, + 100: $dark-primary-text, + 200: $dark-primary-text, + 300: $dark-primary-text, + 400: $dark-primary-text, + 500: map_get($tb-mat-indigo, 900), + 600: $light-primary-text, + 700: $light-primary-text, + 800: $light-primary-text, + 900: $light-primary-text, + A100: $dark-primary-text, + A200: $dark-primary-text, + A400: $dark-primary-text, + A700: $dark-primary-text, + ) +); + +$tb-dark-primary: mat-palette($tb-dark-mat-indigo); + +$tb-dark-theme-background: ( + status-bar: black, + app-bar: map_get($tb-mat-indigo, 900), + background: map_get($tb-mat-indigo, 800), + hover: rgba(white, 0.04), + card: map_get($tb-mat-indigo, 800), + dialog: map_get($tb-mat-indigo, 800), + disabled-button: rgba(white, 0.12), + raised-button: map-get($tb-mat-indigo, 50), + focused-button: $light-focused, + selected-button: map_get($tb-mat-indigo, 900), + selected-disabled-button: map_get($tb-mat-indigo, 800), + disabled-button-toggle: black, + unselected-chip: map_get($tb-mat-indigo, 700), + disabled-list-option: black, +); + +@function get-tb-dark-theme($primary, $accent, $warn: mat-palette($mat-red)) { + @return ( + primary: $primary, + accent: $accent, + warn: $warn, + is-dark: true, + foreground: $mat-dark-theme-foreground, + background: $tb-dark-theme-background, + ); +} + +$tb-dark-theme: get-tb-dark-theme( + $tb-dark-primary, + $tb-accent +); + +.tb-default { + @include angular-material-theme($tb-theme); + @include mat-datetimepicker-theme($tb-theme); +} + +.tb-dark { + @include angular-material-theme($tb-dark-theme); +} + +.tb-default, .tb-dark { + + /********************************* + * MATERIAL DESIGN CUSTOMIZATIONS + ********************************/ + + .mat-tooltip { + white-space: pre-line; + } + + button { + pointer-events: all; + } + + button:not(.mat-menu-item):not(.mat-sort-header-button) { + text-transform: uppercase; + } + + button.mat-menu-item { + font-size: 15px; + } + + button.mat-fab.mat-fab-bottom-right { + top: auto; + right: 20px; + bottom: 20px; + left: auto; + position: absolute; + } + + .layout-padding, .layout-padding > * { + @media #{$mat-lt-md} { + padding: 4px; + } + @media #{$mat-gt-sm} { + padding: 8px; + } + } + + .mat-padding { + padding: 8px; + @media #{$mat-gt-sm} { + padding: 16px; + } + } + + .mat-content { + position: relative; + overflow: auto; + } + + .layout-wrap { + flex-wrap: wrap; + } + + mat-form-field.mat-block { + display: block; + } + + mat-toolbar.mat-table-toolbar { + background: #fff; + padding: 0 24px; + .mat-toolbar-tools { + padding: 0; + & > button.mat-icon-button:last-child { + margin-right: -12px; + } + } + } + + mat-toolbar.mat-table-toolbar, .mat-cell { + button.mat-icon-button { + mat-icon { + color: rgba(0, 0, 0, .54); + } + } + } + + mat-toolbar.mat-primary { + button.mat-icon-button { + mat-icon { + color: white; + } + } + } + + + .mat-row { + transition: background-color .2s; + &:hover:not(.tb-current-entity) { + background-color: #f4f4f4; + } + &.tb-current-entity { + background-color: #e9e9e9; + } + } + + .mat-row:not(.mat-row-select), .mat-header-row:not(.mat-row-select) { + mat-cell:first-child, mat-footer-cell:first-child, mat-header-cell:first-child { + padding: 0 12px; + } + mat-cell:nth-child(n+2):nth-last-child(n+2), mat-footer-cell:nth-child(n+2):nth-last-child(n+2), mat-header-cell:nth-child(n+2):nth-last-child(n+2) { + padding: 0 28px 0 0; + } + } + + .mat-row.mat-row-select, .mat-header-row.mat-row-select { + mat-cell:first-child, mat-footer-cell:first-child, mat-header-cell:first-child { + width: 30px; + padding: 0 0 0 12px; + } + mat-cell:nth-child(2), mat-footer-cell:nth-child(2), mat-header-cell:nth-child(2) { + padding: 0 12px; + } + mat-cell:nth-child(n+3):nth-last-child(n+2), mat-footer-cell:nth-child(n+3):nth-last-child(n+2), mat-header-cell:nth-child(n+3):nth-last-child(n+2) { + padding: 0 28px 0 0; + } + &.mat-selected:not(.tb-current-entity) { + background-color: #ededed; + } + } + + .mat-header-cell { + white-space: nowrap; + } + + .mat-cell, .mat-header-cell { + min-width: 80px; + &:last-child { + padding: 0 12px 0 0; + } + &.mat-column-select { + min-width: 30px; + max-width: 30px; + width: 30px; + padding: 0 0 0 12px; + } + &.mat-column-actions { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } + + .mat-cell, .mat-footer-cell { + font-size: 13px; + } + + .mat-cell, .mat-footer-cell, .mat-header-cell { + // fix for ie11 'align-items: center' + height: 20px; + } + + .mat-toolbar-tools { + font-size: 20px; + letter-spacing: .005em; + box-sizing: border-box; + font-weight: 400; + display: flex; + align-items: center; + flex-direction: row; + width: 100%; + height: 64px; + max-height: 64px; + padding: 0 16px; + margin: 0; + } + + .mat-icon { + vertical-align: middle; + &.tb-mat-32 { + width: 32px; + height: 32px; + font-size: 32px; + svg { + width: 24px; + height: 24px; + transform: scale(1.33); + } + } + &.tb-mat-96 { + width: 96px; + height: 96px; + font-size: 96px; + svg { + width: 24px; + height: 24px; + transform: scale(4); + } + } + } + + .mat-icon-button { + &.tb-mat-32 { + width: 32px; + height: 32px; + line-height: 32px; + } + &.tb-mat-96 { + width: 96px; + height: 96px; + line-height: 96px; + } + } + + .mat-snack-bar-container { + position: absolute; + background: none; + box-shadow: none; + margin: 0; + padding: 0; + border: none; + border-radius: inherit; + max-width: inherit; + min-width: inherit; + pointer-events: none; + display: flex; + } + + .mat-snack-bar-handset { + .mat-snack-bar-container { + position: relative !important; + width: 100% !important; + top: 0 !important; + left: 0 !important; + height: inherit !important; + tb-snack-bar-component { + width: 100%; + } + } + } + + .mat-drawer-side { + border: none; + } + + .mat-drawer-inner-container { + display: flex; + flex-direction: column; + overflow: hidden; + } + + mat-drawer.tb-details-drawer { + z-index: 59 !important; + width: 100% !important; + max-width: 100% !important; + @media #{$mat-gt-sm} { + width: 80% !important; + } + @media #{$mat-gt-md} { + width: 65% !important; + } + } + + .mat-card-subtitle, .mat-card-content { + font-size: 16px; + } + + .mat-toolbar > button:first-child { + margin-left: -8px; + } + + .mat-toolbar > button:last-child { + margin-right: -8px; + } + + .mat-toolbar { + line-height: normal; + } + + mat-toolbar *, mat-toolbar :after, mat-toolbar :before { + box-sizing: border-box; + } + + .mat-button, .mat-flat-button, .mat-stroked-button, .mat-raised-button { + &:not(.mat-icon-button) { + @media #{$mat-lt-md} { + padding: 0 6px; + min-width: 88px; + } + mat-icon { + margin-right: 5px; + } + } + } + + .tb-dialog { + .mat-dialog-container { + padding: 0; + > *:first-child, form { + max-width: 100%; + min-width: 100%; + display: flex; + flex-direction: column; + overflow: auto; + height: 100%; + } + .mat-dialog-content { + margin: 0; + padding: 24px; + } + .mat-dialog-actions { + margin-bottom: 0; + padding: 8px 8px 8px 16px; + } + } + } + + .tb-fullscreen-dialog-gt-sm { + @media #{$mat-gt-sm} { + min-height: 100%; + min-width: 100%; + max-width: none !important; + position: absolute !important; + top: 0; + bottom: 0; + left: 0; + right: 0; + .mat-dialog-container { + > *:first-child, form { + min-width: 100% !important; + } + .mat-dialog-content { + max-height: 100%; + } + } + } + } + + .tb-fullscreen-dialog { + @media #{$mat-lt-md} { + min-height: 100%; + min-width: 100%; + max-width: none !important; + position: absolute !important; + top: 0; + bottom: 0; + left: 0; + right: 0; + .mat-dialog-container { + > *:first-child, form { + min-width: 100% !important; + } + .mat-dialog-content { + max-height: 100%; + } + } + } + } + + .tb-absolute-fill { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + } + + span.no-data-found { + position: relative; + display: flex; + height: calc(100% - 57px); + text-transform: uppercase; + } + + + mat-tab-group.tb-headless { + margin-top: -50px; + } + + .mat-tab-label { + text-transform: uppercase; + } + + .tb-primary-background { + background-color: $primary; + } + +} + diff --git a/ui-ngx/src/thingsboard.ico b/ui-ngx/src/thingsboard.ico new file mode 100644 index 0000000000000000000000000000000000000000..8564792b7552b1f660b92688cf38194dce71ec5d GIT binary patch literal 4286 zcmb_fOK1~O6n!yD1hsXexG1J8b(NW+3k&|b776~Qb7MMT$v(4ZByipDP~BpJ_5<|Z$bm(19ja(iFiz4zSn-n;J;Awu|TZ58xi zT%Rh$bRonX2)aZEg!+pg*n)iQxPA`u%{KKem47m>Z-f1XZ5n%3_ObQ>dl=$C@=4R^ zk@1qIzR$Ih-LWm>xNPUP9oN>t{sVG@`-1AErLRU@zKAbzsL5U8NIXu+*x(zq+R`FoKA)@HqwJD;?)?jZS8J{}I7r-9b)SLODsoiazM);=Xo z%~IR7TR!7f@u{gB6*lz&`@4&uL%pw({ZK=FHMvdwnQdyj&_7sX<25n$2Qp5hwr%OX zz$v&`We&>RgukMVwl?uzRyTi%M^#NS2e&?cA@*ju&(b#GdxZmRYHZ`J6w|HtDehZd zlauUEnraa-ysv)C`vNwZ1IK&yCC0NY?Q($p6kp`v$(^TpU@X?~?Qe*QeJ%M8HT30L zAdPx}FOOTfCvmpy_QhKsYoYv91HQg;{z@BbY;5K+u&!Hcl{lKm*JXc8-#WVd#sDxv zj6>R8ckeo@zMtbbyHjsP{ymTym$|>t&x7kF{H=HVKHHx+1NG#WB(aw z{FbquV&o6vt^|p7eQ>527tp?Wc^~rgu?KvYG%^TYudNFV=FG2n{ zjXC({rnB-Y#IGg?3S@_6L-2yE}w_Uc;maLD-OB|H5F0XEl% zoY$wV-BRJ>UEK-1VeFOjD!bh0$(?uKJ%0OwdBMN(W|47&v@us*(zwfSCUDw<{ze?= zw+heKs5t=RBZS6MgHewI-znaCqkA^Um}MV#U%aE5k^8yr<-oZmyIeQJ-2Xo{-FvtL z2C(-wsBqmi%wP`Bs_Y|q$H`{*CvrRHCkkSQ Date: Fri, 9 Aug 2019 19:13:18 +0300 Subject: [PATCH 005/133] Base modules structure. Base services. Login and Home module implementation. --- ui-ngx/angular.json | 2 +- ui-ngx/package-lock.json | 6 - ui-ngx/package.json | 1 - ui-ngx/src/app/app-routing.module.ts | 36 ++ ui-ngx/src/app/app.component.html | 20 + ui-ngx/src/app/app.component.scss | 15 + ui-ngx/src/app/app.component.ts | 62 +++ ui-ngx/src/app/app.module.ts | 12 +- ui-ngx/src/app/core/auth/auth.actions.ts | 50 +++ ui-ngx/src/app/core/auth/auth.models.ts | 31 ++ ui-ngx/src/app/core/auth/auth.reducer.ts | 53 +++ ui-ngx/src/app/core/auth/auth.selectors.ts | 72 ++++ ui-ngx/src/app/core/auth/auth.service.spec.ts | 28 ++ ui-ngx/src/app/core/auth/auth.service.ts | 403 ++++++++++++++++++ ui-ngx/src/app/core/core.module.ts | 100 +++++ ui-ngx/src/app/core/core.state.ts | 65 +++ ui-ngx/src/app/core/guards/auth.guard.ts | 113 +++++ .../app/core/guards/confirm-on-exit.guard.ts | 68 +++ ui-ngx/src/app/core/http/http-utils.ts | 28 ++ ui-ngx/src/app/core/http/user.service.ts | 71 +++ .../interceptors/global-http-interceptor.ts | 269 ++++++++++++ .../core/interceptors/interceptor-config.ts | 21 + .../interceptors/interceptor-http-params.ts | 27 ++ .../src/app/core/interceptors/load.actions.ts | 32 ++ .../src/app/core/interceptors/load.models.ts | 19 + .../src/app/core/interceptors/load.reducer.ts | 38 ++ .../app/core/interceptors/load.selectors.ts | 34 ++ .../local-storage/local-storage.service.ts | 87 ++++ .../app/core/meta-reducers/debug.reducer.ts | 33 ++ .../init-state-from-local-storage.reducer.ts | 32 ++ .../core/notification/notification.actions.ts | 31 ++ .../core/notification/notification.effects.ts | 47 ++ .../core/notification/notification.models.ts | 33 ++ .../core/notification/notification.reducer.ts | 34 ++ .../notification/notification.selectors.ts | 29 ++ ui-ngx/src/app/core/operator/enterZone.ts | 44 ++ .../src/app/core/services/dialog.service.ts | 70 +++ .../dialog/alert-dialog.component.html | 23 + .../dialog/alert-dialog.component.scss | 20 + .../services/dialog/alert-dialog.component.ts | 34 ++ .../dialog/confirm-dialog.component.html | 24 ++ .../dialog/confirm-dialog.component.scss | 20 + .../dialog/confirm-dialog.component.ts | 35 ++ ui-ngx/src/app/core/services/menu.models.ts | 39 ++ ui-ngx/src/app/core/services/menu.service.ts | 344 +++++++++++++++ .../app/core/services/notification.service.ts | 43 ++ ui-ngx/src/app/core/services/time.service.ts | 123 ++++++ ui-ngx/src/app/core/services/title.service.ts | 55 +++ .../src/app/core/services/window.service.ts | 71 +++ .../src/app/core/settings/settings.actions.ts | 30 ++ .../src/app/core/settings/settings.effects.ts | 88 ++++ .../src/app/core/settings/settings.models.ts | 20 + .../src/app/core/settings/settings.reducer.ts | 34 ++ .../app/core/settings/settings.selectors.ts | 34 ++ .../src/app/core/settings/settings.utils.ts | 57 +++ .../translate/missing-translate-handler.ts | 23 + .../translate/translate-default-compiler.ts | 67 +++ ui-ngx/src/app/core/utils.ts | 86 ++++ .../app/modules/home/home-routing.module.ts | 45 ++ .../src/app/modules/home/home.component.html | 56 +++ .../src/app/modules/home/home.component.scss | 66 +++ ui-ngx/src/app/modules/home/home.component.ts | 114 +++++ ui-ngx/src/app/modules/home/home.module.ts | 41 ++ .../home/menu/menu-link.component.html | 23 + .../home/menu/menu-link.component.scss | 18 + .../modules/home/menu/menu-link.component.ts | 35 ++ .../home/menu/menu-toggle.component.html | 31 ++ .../home/menu/menu-toggle.component.scss | 18 + .../home/menu/menu-toggle.component.ts | 47 ++ .../home/menu/side-menu.component.html | 23 + .../home/menu/side-menu.component.scss | 114 +++++ .../modules/home/menu/side-menu.component.ts | 35 ++ .../home-links/home-links-routing.module.ts | 42 ++ .../home-links/home-links.component.html | 37 ++ .../home-links/home-links.component.scss | 74 ++++ .../pages/home-links/home-links.component.ts | 69 +++ .../pages/home-links/home-links.module.ts | 35 ++ .../modules/home/pages/home-pages.module.ts | 36 ++ .../app/modules/login/login-routing.module.ts | 69 +++ ui-ngx/src/app/modules/login/login.module.ts | 40 ++ .../login/create-password.component.html | 56 +++ .../login/create-password.component.scss | 29 ++ .../pages/login/create-password.component.ts | 76 ++++ .../login/pages/login/login.component.html | 61 +++ .../login/pages/login/login.component.scss | 32 ++ .../login/pages/login/login.component.ts | 54 +++ .../reset-password-request.component.html | 54 +++ .../reset-password-request.component.scss | 29 ++ .../login/reset-password-request.component.ts | 57 +++ .../pages/login/reset-password.component.html | 56 +++ .../pages/login/reset-password.component.scss | 29 ++ .../pages/login/reset-password.component.ts | 76 ++++ .../components/breadcrumb.component.html | 39 ++ .../components/breadcrumb.component.scss | 59 +++ .../shared/components/breadcrumb.component.ts | 82 ++++ .../src/app/shared/components/breadcrumb.ts | 26 ++ .../shared/components/footer.component.html | 20 + .../shared/components/footer.component.scss | 28 ++ .../app/shared/components/footer.component.ts | 28 ++ .../shared/components/fullscreen.directive.ts | 99 +++++ .../app/shared/components/help.component.html | 24 ++ .../app/shared/components/help.component.ts | 40 ++ .../app/shared/components/logo.component.html | 19 + .../app/shared/components/logo.component.scss | 29 ++ .../app/shared/components/logo.component.ts | 32 ++ .../app/shared/components/page.component.ts | 56 +++ .../components/snack-bar-component.html | 26 ++ .../components/snack-bar-component.scss | 43 ++ .../shared/components/tb-anchor.component.ts | 25 ++ .../components/tb-checkbox.component.html | 24 ++ .../components/tb-checkbox.component.ts | 74 ++++ .../app/shared/components/toast.directive.ts | 154 +++++++ .../components/user-menu.component.html | 41 ++ .../components/user-menu.component.scss | 50 +++ .../shared/components/user-menu.component.ts | 116 +++++ .../src/app/shared/models/authority.enum.ts | 23 + ui-ngx/src/app/shared/models/base-data.ts | 26 ++ ui-ngx/src/app/shared/models/constants.ts | 109 +++++ .../app/shared/models/contact-based.model.ts | 28 ++ .../src/app/shared/models/customer.model.ts | 25 ++ .../app/shared/models/entity-type.models.ts | 116 +++++ .../src/app/shared/models/id/customer-id.ts | 26 ++ ui-ngx/src/app/shared/models/id/entity-id.ts | 22 + ui-ngx/src/app/shared/models/id/has-uuid.ts | 21 + ui-ngx/src/app/shared/models/id/tenant-id.ts | 26 ++ ui-ngx/src/app/shared/models/id/user-id.ts | 26 ++ ui-ngx/src/app/shared/models/login.models.ts | 30 ++ .../src/app/shared/models/page/page-data.ts | 33 ++ .../src/app/shared/models/page/page-link.ts | 96 +++++ .../src/app/shared/models/page/sort-order.ts | 26 ++ .../src/app/shared/models/settings.models.ts | 35 ++ ui-ngx/src/app/shared/models/tenant.model.ts | 25 ++ .../src/app/shared/models/time/time.models.ts | 315 ++++++++++++++ ui-ngx/src/app/shared/models/user.model.ts | 56 +++ ui-ngx/src/app/shared/pipe/nospace.pipe.ts | 28 ++ ui-ngx/src/app/shared/shared.module.ts | 224 ++++++++++ .../assets/locale/locale.constant-en_US.json | 12 +- ui-ngx/src/theme.scss | 18 +- 138 files changed, 7517 insertions(+), 26 deletions(-) create mode 100644 ui-ngx/src/app/app-routing.module.ts create mode 100644 ui-ngx/src/app/app.component.html create mode 100644 ui-ngx/src/app/app.component.scss create mode 100644 ui-ngx/src/app/app.component.ts create mode 100644 ui-ngx/src/app/core/auth/auth.actions.ts create mode 100644 ui-ngx/src/app/core/auth/auth.models.ts create mode 100644 ui-ngx/src/app/core/auth/auth.reducer.ts create mode 100644 ui-ngx/src/app/core/auth/auth.selectors.ts create mode 100644 ui-ngx/src/app/core/auth/auth.service.spec.ts create mode 100644 ui-ngx/src/app/core/auth/auth.service.ts create mode 100644 ui-ngx/src/app/core/core.module.ts create mode 100644 ui-ngx/src/app/core/core.state.ts create mode 100644 ui-ngx/src/app/core/guards/auth.guard.ts create mode 100644 ui-ngx/src/app/core/guards/confirm-on-exit.guard.ts create mode 100644 ui-ngx/src/app/core/http/http-utils.ts create mode 100644 ui-ngx/src/app/core/http/user.service.ts create mode 100644 ui-ngx/src/app/core/interceptors/global-http-interceptor.ts create mode 100644 ui-ngx/src/app/core/interceptors/interceptor-config.ts create mode 100644 ui-ngx/src/app/core/interceptors/interceptor-http-params.ts create mode 100644 ui-ngx/src/app/core/interceptors/load.actions.ts create mode 100644 ui-ngx/src/app/core/interceptors/load.models.ts create mode 100644 ui-ngx/src/app/core/interceptors/load.reducer.ts create mode 100644 ui-ngx/src/app/core/interceptors/load.selectors.ts create mode 100644 ui-ngx/src/app/core/local-storage/local-storage.service.ts create mode 100644 ui-ngx/src/app/core/meta-reducers/debug.reducer.ts create mode 100644 ui-ngx/src/app/core/meta-reducers/init-state-from-local-storage.reducer.ts create mode 100644 ui-ngx/src/app/core/notification/notification.actions.ts create mode 100644 ui-ngx/src/app/core/notification/notification.effects.ts create mode 100644 ui-ngx/src/app/core/notification/notification.models.ts create mode 100644 ui-ngx/src/app/core/notification/notification.reducer.ts create mode 100644 ui-ngx/src/app/core/notification/notification.selectors.ts create mode 100644 ui-ngx/src/app/core/operator/enterZone.ts create mode 100644 ui-ngx/src/app/core/services/dialog.service.ts create mode 100644 ui-ngx/src/app/core/services/dialog/alert-dialog.component.html create mode 100644 ui-ngx/src/app/core/services/dialog/alert-dialog.component.scss create mode 100644 ui-ngx/src/app/core/services/dialog/alert-dialog.component.ts create mode 100644 ui-ngx/src/app/core/services/dialog/confirm-dialog.component.html create mode 100644 ui-ngx/src/app/core/services/dialog/confirm-dialog.component.scss create mode 100644 ui-ngx/src/app/core/services/dialog/confirm-dialog.component.ts create mode 100644 ui-ngx/src/app/core/services/menu.models.ts create mode 100644 ui-ngx/src/app/core/services/menu.service.ts create mode 100644 ui-ngx/src/app/core/services/notification.service.ts create mode 100644 ui-ngx/src/app/core/services/time.service.ts create mode 100644 ui-ngx/src/app/core/services/title.service.ts create mode 100644 ui-ngx/src/app/core/services/window.service.ts create mode 100644 ui-ngx/src/app/core/settings/settings.actions.ts create mode 100644 ui-ngx/src/app/core/settings/settings.effects.ts create mode 100644 ui-ngx/src/app/core/settings/settings.models.ts create mode 100644 ui-ngx/src/app/core/settings/settings.reducer.ts create mode 100644 ui-ngx/src/app/core/settings/settings.selectors.ts create mode 100644 ui-ngx/src/app/core/settings/settings.utils.ts create mode 100644 ui-ngx/src/app/core/translate/missing-translate-handler.ts create mode 100644 ui-ngx/src/app/core/translate/translate-default-compiler.ts create mode 100644 ui-ngx/src/app/core/utils.ts create mode 100644 ui-ngx/src/app/modules/home/home-routing.module.ts create mode 100644 ui-ngx/src/app/modules/home/home.component.html create mode 100644 ui-ngx/src/app/modules/home/home.component.scss create mode 100644 ui-ngx/src/app/modules/home/home.component.ts create mode 100644 ui-ngx/src/app/modules/home/home.module.ts create mode 100644 ui-ngx/src/app/modules/home/menu/menu-link.component.html create mode 100644 ui-ngx/src/app/modules/home/menu/menu-link.component.scss create mode 100644 ui-ngx/src/app/modules/home/menu/menu-link.component.ts create mode 100644 ui-ngx/src/app/modules/home/menu/menu-toggle.component.html create mode 100644 ui-ngx/src/app/modules/home/menu/menu-toggle.component.scss create mode 100644 ui-ngx/src/app/modules/home/menu/menu-toggle.component.ts create mode 100644 ui-ngx/src/app/modules/home/menu/side-menu.component.html create mode 100644 ui-ngx/src/app/modules/home/menu/side-menu.component.scss create mode 100644 ui-ngx/src/app/modules/home/menu/side-menu.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/home-links/home-links-routing.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/home-links/home-links.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/home-links/home-links.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/home-links/home-links.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/home-links/home-links.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/home-pages.module.ts create mode 100644 ui-ngx/src/app/modules/login/login-routing.module.ts create mode 100644 ui-ngx/src/app/modules/login/login.module.ts create mode 100644 ui-ngx/src/app/modules/login/pages/login/create-password.component.html create mode 100644 ui-ngx/src/app/modules/login/pages/login/create-password.component.scss create mode 100644 ui-ngx/src/app/modules/login/pages/login/create-password.component.ts create mode 100644 ui-ngx/src/app/modules/login/pages/login/login.component.html create mode 100644 ui-ngx/src/app/modules/login/pages/login/login.component.scss create mode 100644 ui-ngx/src/app/modules/login/pages/login/login.component.ts create mode 100644 ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.html create mode 100644 ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.scss create mode 100644 ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.ts create mode 100644 ui-ngx/src/app/modules/login/pages/login/reset-password.component.html create mode 100644 ui-ngx/src/app/modules/login/pages/login/reset-password.component.scss create mode 100644 ui-ngx/src/app/modules/login/pages/login/reset-password.component.ts create mode 100644 ui-ngx/src/app/shared/components/breadcrumb.component.html create mode 100644 ui-ngx/src/app/shared/components/breadcrumb.component.scss create mode 100644 ui-ngx/src/app/shared/components/breadcrumb.component.ts create mode 100644 ui-ngx/src/app/shared/components/breadcrumb.ts create mode 100644 ui-ngx/src/app/shared/components/footer.component.html create mode 100644 ui-ngx/src/app/shared/components/footer.component.scss create mode 100644 ui-ngx/src/app/shared/components/footer.component.ts create mode 100644 ui-ngx/src/app/shared/components/fullscreen.directive.ts create mode 100644 ui-ngx/src/app/shared/components/help.component.html create mode 100644 ui-ngx/src/app/shared/components/help.component.ts create mode 100644 ui-ngx/src/app/shared/components/logo.component.html create mode 100644 ui-ngx/src/app/shared/components/logo.component.scss create mode 100644 ui-ngx/src/app/shared/components/logo.component.ts create mode 100644 ui-ngx/src/app/shared/components/page.component.ts create mode 100644 ui-ngx/src/app/shared/components/snack-bar-component.html create mode 100644 ui-ngx/src/app/shared/components/snack-bar-component.scss create mode 100644 ui-ngx/src/app/shared/components/tb-anchor.component.ts create mode 100644 ui-ngx/src/app/shared/components/tb-checkbox.component.html create mode 100644 ui-ngx/src/app/shared/components/tb-checkbox.component.ts create mode 100644 ui-ngx/src/app/shared/components/toast.directive.ts create mode 100644 ui-ngx/src/app/shared/components/user-menu.component.html create mode 100644 ui-ngx/src/app/shared/components/user-menu.component.scss create mode 100644 ui-ngx/src/app/shared/components/user-menu.component.ts create mode 100644 ui-ngx/src/app/shared/models/authority.enum.ts create mode 100644 ui-ngx/src/app/shared/models/base-data.ts create mode 100644 ui-ngx/src/app/shared/models/constants.ts create mode 100644 ui-ngx/src/app/shared/models/contact-based.model.ts create mode 100644 ui-ngx/src/app/shared/models/customer.model.ts create mode 100644 ui-ngx/src/app/shared/models/entity-type.models.ts create mode 100644 ui-ngx/src/app/shared/models/id/customer-id.ts create mode 100644 ui-ngx/src/app/shared/models/id/entity-id.ts create mode 100644 ui-ngx/src/app/shared/models/id/has-uuid.ts create mode 100644 ui-ngx/src/app/shared/models/id/tenant-id.ts create mode 100644 ui-ngx/src/app/shared/models/id/user-id.ts create mode 100644 ui-ngx/src/app/shared/models/login.models.ts create mode 100644 ui-ngx/src/app/shared/models/page/page-data.ts create mode 100644 ui-ngx/src/app/shared/models/page/page-link.ts create mode 100644 ui-ngx/src/app/shared/models/page/sort-order.ts create mode 100644 ui-ngx/src/app/shared/models/settings.models.ts create mode 100644 ui-ngx/src/app/shared/models/tenant.model.ts create mode 100644 ui-ngx/src/app/shared/models/time/time.models.ts create mode 100644 ui-ngx/src/app/shared/models/user.model.ts create mode 100644 ui-ngx/src/app/shared/pipe/nospace.pipe.ts create mode 100644 ui-ngx/src/app/shared/shared.module.ts diff --git a/ui-ngx/angular.json b/ui-ngx/angular.json index ac6c5d2f4e..0eab7d353c 100644 --- a/ui-ngx/angular.json +++ b/ui-ngx/angular.json @@ -86,7 +86,7 @@ } }, "serve": { - "builder": "@angular-builders/dev-server:generic", + "builder": "@angular-builders/custom-webpack:dev-server", "options": { "browserTarget": "thingsboard:build", "proxyConfig": "proxy.conf.json" diff --git a/ui-ngx/package-lock.json b/ui-ngx/package-lock.json index 37ca17e5fc..5500e96dfb 100644 --- a/ui-ngx/package-lock.json +++ b/ui-ngx/package-lock.json @@ -14,12 +14,6 @@ "webpack-merge": "^4.2.1" } }, - "@angular-builders/dev-server": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@angular-builders/dev-server/-/dev-server-7.3.1.tgz", - "integrity": "sha512-rFr0NyFcwTb4RkkboYQN5JeR9ZraOkfUrQYljMSe/O01MM3SJvE8LYJbsyMwGtp71Rc8T6JrpdxaNEeYCV/4PA==", - "dev": true - }, "@angular-devkit/architect": { "version": "0.802.0", "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.802.0.tgz", diff --git a/ui-ngx/package.json b/ui-ngx/package.json index c3fe04638d..378f6e3f6d 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -47,7 +47,6 @@ }, "devDependencies": { "@angular-builders/custom-webpack": "^8.1.0", - "@angular-builders/dev-server": "^7.3.1", "@angular-devkit/build-angular": "^0.802.0", "@angular/cli": "~8.2.0", "@angular/compiler-cli": "~8.2.0", diff --git a/ui-ngx/src/app/app-routing.module.ts b/ui-ngx/src/app/app-routing.module.ts new file mode 100644 index 0000000000..02f99e1c56 --- /dev/null +++ b/ui-ngx/src/app/app-routing.module.ts @@ -0,0 +1,36 @@ +/// +/// Copyright © 2016-2019 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 { Routes, RouterModule } from '@angular/router'; + +const routes: Routes = [ + { path: '', + redirectTo: 'home', + pathMatch: 'full', + data: { + breadcrumb: { + skip: true + } + } + } +]; + +@NgModule({ + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule] +}) +export class AppRoutingModule { } diff --git a/ui-ngx/src/app/app.component.html b/ui-ngx/src/app/app.component.html new file mode 100644 index 0000000000..0cd4586f74 --- /dev/null +++ b/ui-ngx/src/app/app.component.html @@ -0,0 +1,20 @@ + + + + diff --git a/ui-ngx/src/app/app.component.scss b/ui-ngx/src/app/app.component.scss new file mode 100644 index 0000000000..e2b008f382 --- /dev/null +++ b/ui-ngx/src/app/app.component.scss @@ -0,0 +1,15 @@ +/** + * Copyright © 2016-2019 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. + */ diff --git a/ui-ngx/src/app/app.component.ts b/ui-ngx/src/app/app.component.ts new file mode 100644 index 0000000000..19a06c3d0b --- /dev/null +++ b/ui-ngx/src/app/app.component.ts @@ -0,0 +1,62 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, OnInit } from '@angular/core'; + +import { environment as env } from '@env/environment'; + +import { TranslateService } from '@ngx-translate/core'; +import { Store } from '@ngrx/store'; +import { AppState } from './core/core.state'; +import { LocalStorageService } from './core/local-storage/local-storage.service'; +import { DomSanitizer } from '@angular/platform-browser'; +import { MatIconRegistry } from '@angular/material'; + +@Component({ + selector: 'tb-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.scss'] +}) +export class AppComponent implements OnInit { + + constructor(private store: Store, + private storageService: LocalStorageService, + private translate: TranslateService, + private matIconRegistry: MatIconRegistry, + private domSanitizer: DomSanitizer) { + + console.log(`ThingsBoard Version: ${env.tbVersion}`); + + this.matIconRegistry.addSvgIconSetInNamespace('mdi', + this.domSanitizer.bypassSecurityTrustResourceUrl('./assets/mdi.svg')); + + this.storageService.testLocalStorage(); + + this.setupTranslate(); + } + + setupTranslate() { + console.log(`Supported Langs: ${env.supportedLangs}`); + this.translate.addLangs(env.supportedLangs); + console.log(`Default Lang: ${env.defaultLang}`); + this.translate.setDefaultLang(env.defaultLang); + } + + ngOnInit() { + } + +} + diff --git a/ui-ngx/src/app/app.module.ts b/ui-ngx/src/app/app.module.ts index aa76e9b7a0..1a1e6ab20b 100644 --- a/ui-ngx/src/app/app.module.ts +++ b/ui-ngx/src/app/app.module.ts @@ -18,26 +18,26 @@ import { BrowserModule } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { NgModule } from '@angular/core'; -/* import { AppRoutingModule } from './app-routing.module'; +import { AppRoutingModule } from './app-routing.module'; import { CoreModule } from './core/core.module'; import { LoginModule } from './modules/login/login.module'; import { HomeModule } from './modules/home/home.module'; -import { AppComponent } from './app.component'; */ +import { AppComponent } from './app.component'; @NgModule({ declarations: [ - /* AppComponent */ + AppComponent ], imports: [ - /* BrowserModule, + BrowserModule, BrowserAnimationsModule, AppRoutingModule, CoreModule, LoginModule, - HomeModule */ + HomeModule ], providers: [], - bootstrap: [/*AppComponent*/] + bootstrap: [AppComponent] }) export class AppModule { } diff --git a/ui-ngx/src/app/core/auth/auth.actions.ts b/ui-ngx/src/app/core/auth/auth.actions.ts new file mode 100644 index 0000000000..d5a30c0856 --- /dev/null +++ b/ui-ngx/src/app/core/auth/auth.actions.ts @@ -0,0 +1,50 @@ +/// +/// Copyright © 2016-2019 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 { Action } from '@ngrx/store'; +import { AuthUser, User } from '../../shared/models/user.model'; +import { AuthPayload } from '@core/auth/auth.models'; + +export enum AuthActionTypes { + AUTHENTICATED = '[Auth] Authenticated', + UNAUTHENTICATED = '[Auth] Unauthenticated', + LOAD_USER = '[Auth] Load User', + UPDATE_USER_DETAILS = '[Auth] Update User Details' +} + +export class ActionAuthAuthenticated implements Action { + readonly type = AuthActionTypes.AUTHENTICATED; + + constructor(readonly payload: AuthPayload) {} +} + +export class ActionAuthUnauthenticated implements Action { + readonly type = AuthActionTypes.UNAUTHENTICATED; +} + +export class ActionAuthLoadUser implements Action { + readonly type = AuthActionTypes.LOAD_USER; + + constructor(readonly payload: { isUserLoaded: boolean }) {} +} + +export class ActionAuthUpdateUserDetails implements Action { + readonly type = AuthActionTypes.UPDATE_USER_DETAILS; + + constructor(readonly payload: { userDetails: User }) {} +} + +export type AuthActions = ActionAuthAuthenticated | ActionAuthUnauthenticated | ActionAuthLoadUser | ActionAuthUpdateUserDetails; diff --git a/ui-ngx/src/app/core/auth/auth.models.ts b/ui-ngx/src/app/core/auth/auth.models.ts new file mode 100644 index 0000000000..21daba10ea --- /dev/null +++ b/ui-ngx/src/app/core/auth/auth.models.ts @@ -0,0 +1,31 @@ +/// +/// Copyright © 2016-2019 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 { AuthUser, User } from '../../shared/models/user.model'; + +export interface AuthPayload { + authUser: AuthUser; + userDetails: User; + userTokenAccessEnabled: boolean; +} + +export interface AuthState { + isAuthenticated: boolean; + isUserLoaded: boolean; + authUser: AuthUser; + userDetails: User; + userTokenAccessEnabled: boolean; +} diff --git a/ui-ngx/src/app/core/auth/auth.reducer.ts b/ui-ngx/src/app/core/auth/auth.reducer.ts new file mode 100644 index 0000000000..b82f76f9c4 --- /dev/null +++ b/ui-ngx/src/app/core/auth/auth.reducer.ts @@ -0,0 +1,53 @@ +/// +/// Copyright © 2016-2019 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 { AuthPayload, AuthState } from './auth.models'; +import { AuthActions, AuthActionTypes } from './auth.actions'; + +const emptyUserAuthState: AuthPayload = { + authUser: null, + userDetails: null, + userTokenAccessEnabled: false +}; + +export const initialState: AuthState = { + isAuthenticated: false, + isUserLoaded: false, + ...emptyUserAuthState +}; + +export function authReducer( + state: AuthState = initialState, + action: AuthActions +): AuthState { + switch (action.type) { + case AuthActionTypes.AUTHENTICATED: + return { ...state, isAuthenticated: true, ...action.payload }; + + case AuthActionTypes.UNAUTHENTICATED: + return { ...state, isAuthenticated: false, ...emptyUserAuthState }; + + case AuthActionTypes.LOAD_USER: + return { ...state, ...action.payload, isAuthenticated: action.payload.isUserLoaded ? state.isAuthenticated : false, + ...action.payload.isUserLoaded ? {} : emptyUserAuthState }; + + case AuthActionTypes.UPDATE_USER_DETAILS: + return { ...state, ...action.payload}; + + default: + return state; + } +} diff --git a/ui-ngx/src/app/core/auth/auth.selectors.ts b/ui-ngx/src/app/core/auth/auth.selectors.ts new file mode 100644 index 0000000000..7e024d333f --- /dev/null +++ b/ui-ngx/src/app/core/auth/auth.selectors.ts @@ -0,0 +1,72 @@ +/// +/// Copyright © 2016-2019 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 { createFeatureSelector, createSelector, select, Store } from '@ngrx/store'; + +import { AppState } from '../core.state'; +import { AuthState } from './auth.models'; +import { take } from 'rxjs/operators'; +import { AuthUser } from '@shared/models/user.model'; + +export const selectAuthState = createFeatureSelector( + 'auth' +); + +export const selectAuth = createSelector( + selectAuthState, + (state: AuthState) => state +); + +export const selectIsAuthenticated = createSelector( + selectAuthState, + (state: AuthState) => state.isAuthenticated +); + +export const selectIsUserLoaded = createSelector( + selectAuthState, + (state: AuthState) => state.isUserLoaded +); + +export const selectAuthUser = createSelector( + selectAuthState, + (state: AuthState) => state.authUser +); + +export const selectUserDetails = createSelector( + selectAuthState, + (state: AuthState) => state.userDetails +); + +export const selectUserTokenAccessEnabled = createSelector( + selectAuthState, + (state: AuthState) => state.userTokenAccessEnabled +); + +export function getCurrentAuthState(store: Store): AuthState { + let state: AuthState; + store.pipe(select(selectAuth), take(1)).subscribe( + val => state = val + ); + return state; +} + +export function getCurrentAuthUser(store: Store): AuthUser { + let authUser: AuthUser; + store.pipe(select(selectAuthUser), take(1)).subscribe( + val => authUser = val + ); + return authUser; +} diff --git a/ui-ngx/src/app/core/auth/auth.service.spec.ts b/ui-ngx/src/app/core/auth/auth.service.spec.ts new file mode 100644 index 0000000000..e3168f0560 --- /dev/null +++ b/ui-ngx/src/app/core/auth/auth.service.spec.ts @@ -0,0 +1,28 @@ +/// +/// Copyright © 2016-2019 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 { TestBed } from '@angular/core/testing'; + +import { AuthService } from './auth.service'; + +describe('AuthService', () => { + beforeEach(() => TestBed.configureTestingModule({})); + + it('should be created', () => { + const service: AuthService = TestBed.get(AuthService); + expect(service).toBeTruthy(); + }); +}); diff --git a/ui-ngx/src/app/core/auth/auth.service.ts b/ui-ngx/src/app/core/auth/auth.service.ts new file mode 100644 index 0000000000..2df91a6416 --- /dev/null +++ b/ui-ngx/src/app/core/auth/auth.service.ts @@ -0,0 +1,403 @@ +/// +/// Copyright © 2016-2019 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 {Injectable, NgZone} from '@angular/core'; +import {JwtHelperService} from '@auth0/angular-jwt'; +import {HttpClient} from '@angular/common/http'; + +import {combineLatest, forkJoin, Observable, of} from 'rxjs'; +import {distinctUntilChanged, filter, map, skip, tap} from 'rxjs/operators'; + +import {LoginRequest, LoginResponse} from '../../shared/models/login.models'; +import {ActivatedRoute, Router, UrlTree} from '@angular/router'; +import {defaultHttpOptions} from '../http/http-utils'; +import {ReplaySubject} from 'rxjs/internal/ReplaySubject'; +import {UserService} from '../http/user.service'; +import {select, Store} from '@ngrx/store'; +import {AppState} from '../core.state'; +import {ActionAuthAuthenticated, ActionAuthLoadUser, ActionAuthUnauthenticated} from './auth.actions'; +import {getCurrentAuthUser, selectIsAuthenticated, selectIsUserLoaded} from './auth.selectors'; +import {Authority} from '../../shared/models/authority.enum'; +import {ActionSettingsChangeLanguage} from '@app/core/settings/settings.actions'; +import {AuthPayload} from '@core/auth/auth.models'; +import {TranslateService} from '@ngx-translate/core'; +import {AuthUser} from '@shared/models/user.model'; +import {TimeService} from '@core/services/time.service'; + +@Injectable({ + providedIn: 'root' +}) +export class AuthService { + + constructor( + private store: Store, + private http: HttpClient, + private userService: UserService, + private timeService: TimeService, + private router: Router, + private route: ActivatedRoute, + private zone: NgZone, + private translate: TranslateService + ) { + combineLatest( + this.store.pipe(select(selectIsAuthenticated)), + this.store.pipe(select(selectIsUserLoaded)) + ).pipe( + map(results => ({isAuthenticated: results[0], isUserLoaded: results[1]})), + distinctUntilChanged(), + filter((data) => data.isUserLoaded ), + skip(1), + ).subscribe((data) => { + this.gotoDefaultPlace(data.isAuthenticated); + }); + this.reloadUser(); + } + + redirectUrl: string; + + private refreshTokenSubject: ReplaySubject = null; + private jwtHelper = new JwtHelperService(); + + private static _storeGet(key) { + return localStorage.getItem(key); + } + + private static isTokenValid(prefix) { + const clientExpiration = AuthService._storeGet(prefix + '_expiration'); + return clientExpiration && Number(clientExpiration) > (new Date().valueOf() + 2000); + } + + public static isJwtTokenValid() { + return AuthService.isTokenValid('jwt_token'); + } + + private static clearTokenData() { + localStorage.removeItem('jwt_token'); + localStorage.removeItem('jwt_token_expiration'); + localStorage.removeItem('refresh_token'); + localStorage.removeItem('refresh_token_expiration'); + } + + public static getJwtToken() { + return AuthService._storeGet('jwt_token'); + } + + public reloadUser() { + this.loadUser(true).subscribe( + (authPayload) => { + this.notifyAuthenticated(authPayload); + this.notifyUserLoaded(true); + }, + () => { + this.notifyUnauthenticated(); + this.notifyUserLoaded(true); + } + ); + } + + + public login(loginRequest: LoginRequest): Observable { + return this.http.post('/api/auth/login', loginRequest, defaultHttpOptions()).pipe( + tap((loginResponse: LoginResponse) => { + this.setUserFromJwtToken(loginResponse.token, loginResponse.refreshToken, true); + } + )); + } + + public sendResetPasswordLink(email: string) { + return this.http.post('/api/noauth/resetPasswordByEmail', + {email}, defaultHttpOptions()); + } + + public activate(activateToken: string, password: string): Observable { + return this.http.post('/api/noauth/activate', {activateToken, password}, defaultHttpOptions()).pipe( + tap((loginResponse: LoginResponse) => { + this.setUserFromJwtToken(loginResponse.token, loginResponse.refreshToken, true); + } + )); + } + + public resetPassword(resetToken: string, password: string): Observable { + return this.http.post('/api/noauth/resetPassword', {resetToken, password}, defaultHttpOptions()).pipe( + tap((loginResponse: LoginResponse) => { + this.setUserFromJwtToken(loginResponse.token, loginResponse.refreshToken, true); + } + )); + } + + public changePassword(currentPassword: string, newPassword: string) { + return this.http.post('/api/auth/changePassword', + {currentPassword, newPassword}, defaultHttpOptions()); + } + + public activateByEmailCode(emailCode: string): Observable { + return this.http.post(`/api/noauth/activateByEmailCode?emailCode=${emailCode}`, + null, defaultHttpOptions()); + } + + public resendEmailActivation(email: string) { + return this.http.post(`/api/noauth/resendEmailActivation?email=${email}`, + null, defaultHttpOptions()); + } + + public loginAsUser(userId: string) { + return this.http.get(`/api/user/${userId}/token`, defaultHttpOptions()).pipe( + tap((loginResponse: LoginResponse) => { + this.setUserFromJwtToken(loginResponse.token, loginResponse.refreshToken, true); + } + )); + } + + public logout(captureLastUrl: boolean = false) { + if (captureLastUrl) { + this.redirectUrl = this.router.url; + } + this.clearJwtToken(); + } + + private notifyUserLoaded(isUserLoaded: boolean) { + this.store.dispatch(new ActionAuthLoadUser({isUserLoaded})); + } + + public gotoDefaultPlace(isAuthenticated: boolean) { + const url = this.defaultUrl(isAuthenticated); + this.zone.run(() => { + this.router.navigateByUrl(url); + }); + } + + public defaultUrl(isAuthenticated: boolean): UrlTree { + if (isAuthenticated) { + if (this.redirectUrl) { + const redirectUrl = this.redirectUrl; + this.redirectUrl = null; + return this.router.parseUrl(redirectUrl); + } else { + + // TODO: + + return this.router.parseUrl('home'); + } + } else { + return this.router.parseUrl('login'); + } + } + + private loadUser(doTokenRefresh): Observable { + const authUser = getCurrentAuthUser(this.store); + if (!authUser) { + return this.procceedJwtTokenValidate(doTokenRefresh); + } else { + return of({} as AuthPayload); + } + } + + private procceedJwtTokenValidate(doTokenRefresh: boolean): Observable { + const loadUserSubject = new ReplaySubject(); + this.validateJwtToken(doTokenRefresh).subscribe( + () => { + let authPayload = {} as AuthPayload; + const jwtToken = AuthService._storeGet('jwt_token'); + authPayload.authUser = this.jwtHelper.decodeToken(jwtToken); + if (authPayload.authUser && authPayload.authUser.scopes && authPayload.authUser.scopes.length) { + authPayload.authUser.authority = Authority[authPayload.authUser.scopes[0]]; + } else if (authPayload.authUser) { + authPayload.authUser.authority = Authority.ANONYMOUS; + } + const sysParamsObservable = this.loadSystemParams(authPayload.authUser); + if (authPayload.authUser.isPublic) { + + // TODO: + + } else if (authPayload.authUser.userId) { + this.userService.getUser(authPayload.authUser.userId).subscribe( + (user) => { + sysParamsObservable.subscribe( + (sysParams) => { + authPayload = {...authPayload, ...sysParams}; + authPayload.userDetails = user; + let userLang; + if (authPayload.userDetails.additionalInfo && authPayload.userDetails.additionalInfo.lang) { + userLang = authPayload.userDetails.additionalInfo.lang; + } else { + userLang = null; + } + this.notifyUserLang(userLang); + loadUserSubject.next(authPayload); + loadUserSubject.complete(); + }, + (err) => { + loadUserSubject.error(err); + this.logout(); + }); + }, + (err) => { + loadUserSubject.error(err); + this.logout(); + } + ); + } else { + loadUserSubject.error(null); + } + }, + (err) => { + loadUserSubject.error(err); + } + ); + return loadUserSubject; + } + + private loadIsUserTokenAccessEnabled(authUser: AuthUser): Observable { + if (authUser.authority === Authority.SYS_ADMIN || + authUser.authority === Authority.TENANT_ADMIN) { + return this.http.get('/api/user/tokenAccessEnabled', defaultHttpOptions()); + } else { + return of(false); + } + } + + private loadSystemParams(authUser: AuthUser): Observable { + const sources: Array> = [this.loadIsUserTokenAccessEnabled(authUser), + this.timeService.loadMaxDatapointsLimit()]; + return forkJoin(sources) + .pipe(map((data) => { + const userTokenAccessEnabled: boolean = data[0]; + return {userTokenAccessEnabled}; + })); + } + + public refreshJwtToken(): Observable { + let response: Observable = this.refreshTokenSubject; + if (this.refreshTokenSubject === null) { + this.refreshTokenSubject = new ReplaySubject(1); + response = this.refreshTokenSubject; + const refreshToken = AuthService._storeGet('refresh_token'); + const refreshTokenValid = AuthService.isTokenValid('refresh_token'); + this.setUserFromJwtToken(null, null, false); + if (!refreshTokenValid) { + this.refreshTokenSubject.error(new Error(this.translate.instant('access.refresh-token-expired'))); + this.refreshTokenSubject = null; + } else { + const refreshTokenRequest = { + refreshToken + }; + const refreshObservable = this.http.post('/api/auth/token', refreshTokenRequest, defaultHttpOptions()); + refreshObservable.subscribe((loginResponse: LoginResponse) => { + this.setUserFromJwtToken(loginResponse.token, loginResponse.refreshToken, false); + this.refreshTokenSubject.next(loginResponse); + this.refreshTokenSubject.complete(); + this.refreshTokenSubject = null; + }, () => { + this.clearJwtToken(); + this.refreshTokenSubject.error(new Error(this.translate.instant('access.refresh-token-failed'))); + this.refreshTokenSubject = null; + }); + } + } + return response; + } + + private validateJwtToken(doRefresh): Observable { + const subject = new ReplaySubject(); + if (!AuthService.isTokenValid('jwt_token')) { + if (doRefresh) { + this.refreshJwtToken().subscribe( + () => { + subject.next(); + subject.complete(); + }, + (err) => { + subject.error(err); + } + ); + } else { + this.clearJwtToken(); + subject.error(null); + } + } else { + subject.next(); + subject.complete(); + } + return subject; + } + + public refreshTokenPending() { + return this.refreshTokenSubject !== null; + } + + public setUserFromJwtToken(jwtToken, refreshToken, notify) { + if (!jwtToken) { + AuthService.clearTokenData(); + if (notify) { + this.notifyUnauthenticated(); + } + } else { + this.updateAndValidateToken(jwtToken, 'jwt_token', true); + this.updateAndValidateToken(refreshToken, 'refresh_token', true); + if (notify) { + this.notifyUserLoaded(false); + this.loadUser(false).subscribe( + (authPayload) => { + this.notifyUserLoaded(true); + this.notifyAuthenticated(authPayload); + }, + () => { + this.notifyUserLoaded(true); + this.notifyUnauthenticated(); + } + ); + } else { + this.loadUser(false); + } + } + } + + private notifyUnauthenticated() { + this.store.dispatch(new ActionAuthUnauthenticated()); + } + + private notifyAuthenticated(authPayload: AuthPayload) { + this.store.dispatch(new ActionAuthAuthenticated(authPayload)); + } + + private notifyUserLang(userLang: string) { + this.store.dispatch(new ActionSettingsChangeLanguage({userLang})); + } + + private updateAndValidateToken(token, prefix, notify) { + let valid = false; + const tokenData = this.jwtHelper.decodeToken(token); + const issuedAt = tokenData.iat; + const expTime = tokenData.exp; + if (issuedAt && expTime) { + const ttl = expTime - issuedAt; + if (ttl > 0) { + const clientExpiration = new Date().valueOf() + ttl * 1000; + localStorage.setItem(prefix, token); + localStorage.setItem(prefix + '_expiration', '' + clientExpiration); + valid = true; + } + } + if (!valid && notify) { + this.notifyUnauthenticated(); + } + } + + private clearJwtToken() { + this.setUserFromJwtToken(null, null, true); + } + +} diff --git a/ui-ngx/src/app/core/core.module.ts b/ui-ngx/src/app/core/core.module.ts new file mode 100644 index 0000000000..d07c7e7383 --- /dev/null +++ b/ui-ngx/src/app/core/core.module.ts @@ -0,0 +1,100 @@ +/// +/// Copyright © 2016-2019 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 { CommonModule } from '@angular/common'; +import { HTTP_INTERCEPTORS, HttpClient, HttpClientModule } from '@angular/common/http'; +import { StoreModule } from '@ngrx/store'; +import { EffectsModule } from '@ngrx/effects'; +import { StoreDevtoolsModule } from '@ngrx/store-devtools'; +import { GlobalHttpInterceptor } from './interceptors/global-http-interceptor'; +import { effects, metaReducers, reducers } from './core.state'; +import { environment as env } from '@env/environment'; + +import { + MissingTranslationHandler, + TranslateCompiler, + TranslateLoader, + TranslateModule +} from '@ngx-translate/core'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; +import { TbMissingTranslationHandler } from './translate/missing-translate-handler'; +import { MatButtonModule, MatDialogModule, MatSnackBarModule } from '@angular/material'; +import { ConfirmDialogComponent } from '@core/services/dialog/confirm-dialog.component'; +import { FlexLayoutModule } from '@angular/flex-layout'; +import { TranslateDefaultCompiler } from '@core/translate/translate-default-compiler'; +import { AlertDialogComponent } from '@core/services/dialog/alert-dialog.component'; +import { WINDOW_PROVIDERS } from '@core/services/window.service'; + +export function HttpLoaderFactory(http: HttpClient) { + return new TranslateHttpLoader(http, './assets/locale/locale.constant-', '.json'); +} + +@NgModule({ + entryComponents: [ + ConfirmDialogComponent, + AlertDialogComponent + ], + declarations: [ + ConfirmDialogComponent, + AlertDialogComponent + ], + imports: [ + CommonModule, + HttpClientModule, + FlexLayoutModule.withConfig({addFlexToParent: false}), + MatDialogModule, + MatButtonModule, + MatSnackBarModule, + + // ngx-translate + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [HttpClient] + }, + missingTranslationHandler: { + provide: MissingTranslationHandler, + useClass: TbMissingTranslationHandler + }, + compiler: { + provide: TranslateCompiler, + useClass: TranslateDefaultCompiler + } + }), + + // ngrx + StoreModule.forRoot(reducers, { metaReducers }), + EffectsModule.forRoot(effects), + env.production + ? [] + : StoreDevtoolsModule.instrument({ + name: env.appTitle + }) + ], + providers: [ + { + provide: HTTP_INTERCEPTORS, + useClass: GlobalHttpInterceptor, + multi: true + }, + WINDOW_PROVIDERS + ], + exports: [] +}) +export class CoreModule { +} diff --git a/ui-ngx/src/app/core/core.state.ts b/ui-ngx/src/app/core/core.state.ts new file mode 100644 index 0000000000..4057216083 --- /dev/null +++ b/ui-ngx/src/app/core/core.state.ts @@ -0,0 +1,65 @@ +/// +/// Copyright © 2016-2019 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 { + ActionReducerMap, + MetaReducer, Store +} from '@ngrx/store'; +import { storeFreeze } from 'ngrx-store-freeze'; + +import { environment as env} from '@env/environment'; + +import { initStateFromLocalStorage } from './meta-reducers/init-state-from-local-storage.reducer'; +import { debug } from './meta-reducers/debug.reducer'; +import { LoadState } from './interceptors/load.models'; +import { loadReducer } from './interceptors/load.reducer'; +import { AuthState } from './auth/auth.models'; +import { authReducer } from './auth/auth.reducer'; +import { settingsReducer } from '@app/core/settings/settings.reducer'; +import { SettingsState } from '@app/core/settings/settings.models'; +import { Type } from '@angular/core'; +import { SettingsEffects } from '@app/core/settings/settings.effects'; +import { NotificationState } from '@app/core/notification/notification.models'; +import { notificationReducer } from '@app/core/notification/notification.reducer'; +import { NotificationEffects } from '@app/core/notification/notification.effects'; +import { take } from 'rxjs/operators'; + +export const reducers: ActionReducerMap = { + load: loadReducer, + auth: authReducer, + settings: settingsReducer, + notification: notificationReducer +}; + +export const metaReducers: MetaReducer[] = [ + initStateFromLocalStorage +]; +if (!env.production) { + metaReducers.unshift(storeFreeze); + metaReducers.unshift(debug); +} + +export const effects: Type[] = [ + SettingsEffects, + NotificationEffects +]; + +export interface AppState { + load: LoadState; + auth: AuthState; + settings: SettingsState; + notification: NotificationState; +} diff --git a/ui-ngx/src/app/core/guards/auth.guard.ts b/ui-ngx/src/app/core/guards/auth.guard.ts new file mode 100644 index 0000000000..4ba3ca7612 --- /dev/null +++ b/ui-ngx/src/app/core/guards/auth.guard.ts @@ -0,0 +1,113 @@ +/// +/// Copyright © 2016-2019 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 { Injectable, NgZone } from '@angular/core'; +import { + ActivatedRouteSnapshot, + CanActivate, + CanActivateChild, + RouterStateSnapshot +} from '@angular/router'; +import { AuthService } from '../auth/auth.service'; +import { select, Store } from '@ngrx/store'; +import { AppState } from '../core.state'; +import { selectAuth } from '../auth/auth.selectors'; +import { catchError, map, skipWhile, take } from 'rxjs/operators'; +import { AuthState } from '../auth/auth.models'; +import { Observable, of } from 'rxjs'; +import { enterZone } from '@core/operator/enterZone'; +import { Authority } from '@shared/models/authority.enum'; +import { DialogService } from '@core/services/dialog.service'; +import { TranslateService } from '@ngx-translate/core'; + +@Injectable({ + providedIn: 'root' +}) +export class AuthGuard implements CanActivate, CanActivateChild { + + constructor(private store: Store, + private authService: AuthService, + private dialogService: DialogService, + private translate: TranslateService, + private zone: NgZone) {} + + getAuthState(): Observable { + return this.store.pipe( + select(selectAuth), + skipWhile((authState) => !authState || !authState.isUserLoaded), + take(1), + enterZone(this.zone) + ); + } + + canActivate(next: ActivatedRouteSnapshot, + state: RouterStateSnapshot) { + + return this.getAuthState().pipe( + map((authState) => { + const url: string = state.url; + + let lastChild = state.root; + while (lastChild.children.length) { + lastChild = lastChild.children[0]; + } + const data = lastChild.data || {}; + const isPublic = data.module === 'public'; + + if (!authState.isAuthenticated) { + if (!isPublic) { + this.authService.redirectUrl = url; + // this.authService.gotoDefaultPlace(false); + return this.authService.defaultUrl(false); + } else { + return true; + } + } else { + if (url === '/login') { + // this.authService.gotoDefaultPlace(true); + return this.authService.defaultUrl(true); + } else { + const authority = Authority[authState.authUser.authority]; + if (data.auth && data.auth.indexOf(authority) === -1) { + this.dialogService.confirm( + this.translate.instant('access.access-forbidden'), + this.translate.instant('access.access-forbidden-text'), + this.translate.instant('action.cancel'), + this.translate.instant('action.sign-in'), + true + ).subscribe((res) => { + if (res) { + this.authService.logout(); + } + } + ); + return false; + } else { + return true; + } + } + } + }), + catchError((err => { console.error(err); return of(false); } )) + ); + } + + canActivateChild( + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot) { + return this.canActivate(route, state); + } +} diff --git a/ui-ngx/src/app/core/guards/confirm-on-exit.guard.ts b/ui-ngx/src/app/core/guards/confirm-on-exit.guard.ts new file mode 100644 index 0000000000..ecf827d91c --- /dev/null +++ b/ui-ngx/src/app/core/guards/confirm-on-exit.guard.ts @@ -0,0 +1,68 @@ +/// +/// Copyright © 2016-2019 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 { Injectable } from '@angular/core'; +import { + ActivatedRouteSnapshot, + CanDeactivate, + RouterStateSnapshot +} from '@angular/router'; +import { FormGroup } from '@angular/forms'; +import { select, Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { AuthState } from '@core/auth/auth.models'; +import { selectAuth } from '@core/auth/auth.selectors'; +import { take } from 'rxjs/operators'; +import { DialogService } from '@core/services/dialog.service'; +import { TranslateService } from '@ngx-translate/core'; + +export interface HasConfirmForm { + confirmForm(): FormGroup; +} + +@Injectable({ + providedIn: 'root' +}) +export class ConfirmOnExitGuard implements CanDeactivate { + + constructor(private store: Store, + private dialogService: DialogService, + private translate: TranslateService) { } + + canDeactivate(component: HasConfirmForm, + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot) { + + + let auth: AuthState = null; + this.store.pipe(select(selectAuth), take(1)).subscribe( + (authState: AuthState) => { + auth = authState; + } + ); + + if (component.confirmForm && auth && auth.isAuthenticated) { + const confirmForm = component.confirmForm(); + if (confirmForm && confirmForm.dirty) { + return this.dialogService.confirm( + this.translate.instant('confirm-on-exit.title'), + this.translate.instant('confirm-on-exit.html-message') + ); + } + } + return true; + } +} diff --git a/ui-ngx/src/app/core/http/http-utils.ts b/ui-ngx/src/app/core/http/http-utils.ts new file mode 100644 index 0000000000..7a5b38cf7c --- /dev/null +++ b/ui-ngx/src/app/core/http/http-utils.ts @@ -0,0 +1,28 @@ +/// +/// Copyright © 2016-2019 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 { InterceptorHttpParams } from '../interceptors/interceptor-http-params'; +import { HttpHeaders } from '@angular/common/http'; +import { InterceptorConfig } from '../interceptors/interceptor-config'; + +export function defaultHttpOptions(ignoreLoading: boolean = false, + ignoreErrors: boolean = false, + resendRequest: boolean = false) { + return { + headers: new HttpHeaders({'Content-Type': 'application/json'}), + params: new InterceptorHttpParams(new InterceptorConfig(ignoreLoading, ignoreErrors, resendRequest)) + }; +} diff --git a/ui-ngx/src/app/core/http/user.service.ts b/ui-ngx/src/app/core/http/user.service.ts new file mode 100644 index 0000000000..1e031289fd --- /dev/null +++ b/ui-ngx/src/app/core/http/user.service.ts @@ -0,0 +1,71 @@ +/// +/// Copyright © 2016-2019 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 { Injectable } from '@angular/core'; +import { defaultHttpOptions } from './http-utils'; +import { User } from '../../shared/models/user.model'; +import { Observable } from 'rxjs/index'; +import { HttpClient, HttpResponse } from '@angular/common/http'; +import { AdminSettings } from '@shared/models/settings.models'; +import { PageLink } from '@shared/models/page/page-link'; +import { PageData } from '@shared/models/page/page-data'; + +@Injectable({ + providedIn: 'root' +}) +export class UserService { + + constructor( + private http: HttpClient + ) { } + + public getTenantAdmins(tenantId: string, pageLink: PageLink, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + return this.http.get>(`/api/tenant/${tenantId}/users${pageLink.toQuery()}`, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public getCustomerUsers(customerId: string, pageLink: PageLink, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + return this.http.get>(`/api/customer/${customerId}/users${pageLink.toQuery()}`, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public getUser(userId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.get(`/api/user/${userId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public saveUser(user: User, sendActivationMail: boolean = false, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + let url = '/api/user'; + url += '?sendActivationMail=' + sendActivationMail; + return this.http.post(url, user, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public deleteUser(userId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { + return this.http.delete(`/api/user/${userId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public getActivationLink(userId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.get(`/api/user/${userId}/activationLink`, + {...{responseType: 'text'}, ...defaultHttpOptions(ignoreLoading, ignoreErrors)}); + } + + public sendActivationEmail(email: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { + return this.http.post(`/api/user/sendActivationMail?email=${email}`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + +} diff --git a/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts b/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts new file mode 100644 index 0000000000..3b87d3f6ed --- /dev/null +++ b/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts @@ -0,0 +1,269 @@ +/// +/// Copyright © 2016-2019 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 { + HttpErrorResponse, + HttpEvent, + HttpHandler, + HttpInterceptor, + HttpRequest, + HttpResponseBase +} from '@angular/common/http'; +import { Observable } from 'rxjs/internal/Observable'; +import { Injectable } from '@angular/core'; +import { AuthService } from '../auth/auth.service'; +import { Constants } from '../../shared/models/constants'; +import { InterceptorHttpParams } from './interceptor-http-params'; +import {catchError, delay, switchMap, tap, map, mergeMap} from 'rxjs/operators'; +import { throwError } from 'rxjs/internal/observable/throwError'; +import { of } from 'rxjs/internal/observable/of'; +import { InterceptorConfig } from './interceptor-config'; +import { Store } from '@ngrx/store'; +import { AppState } from '../core.state'; +import { ActionLoadFinish, ActionLoadStart } from './load.actions'; +import { ActionNotificationShow } from '@app/core/notification/notification.actions'; +import { DialogService } from '@core/services/dialog.service'; +import { TranslateService } from '@ngx-translate/core'; + +let tmpHeaders = {}; + +@Injectable() +export class GlobalHttpInterceptor implements HttpInterceptor { + + private AUTH_SCHEME = 'Bearer '; + private AUTH_HEADER_NAME = 'X-Authorization'; + + private internalUrlPrefixes = [ + '/api/auth/token' + ]; + + constructor(private store: Store, + private dialogService: DialogService, + private translate: TranslateService, + private authService: AuthService) { + } + + intercept(req: HttpRequest, next: HttpHandler): Observable> { + if (req.url.startsWith('/api/')) { + const config = this.getInterceptorConfig(req); + const isLoading = !this.isInternalUrlPrefix(req.url); + this.updateLoadingState(config, isLoading); + if (this.isTokenBasedAuthEntryPoint(req.url)) { + if (!AuthService.getJwtToken() && !this.authService.refreshTokenPending()) { + return this.handleResponseError(req, next, new HttpErrorResponse({error: {message: 'Unauthorized!'}, status: 401})); + } else if (!AuthService.isJwtTokenValid()) { + return this.handleResponseError(req, next, new HttpErrorResponse({error: {refreshTokenPending: true}})); + } else { + return this.jwtIntercept(req, next); + } + } else { + return this.handleRequest(req, next); + } + } else { + return next.handle(req); + } + } + + private jwtIntercept(req: HttpRequest, next: HttpHandler): Observable> { + const newReq = this.updateAuthorizationHeader(req); + if (newReq) { + return this.handleRequest(newReq, next); + } else { + return this.handleRequestError(req, new Error('Could not get JWT token from store.')); + } + } + + private handleRequest(req: HttpRequest, next: HttpHandler): Observable> { + return next.handle(req).pipe( + tap((event: HttpEvent) => { + if (event instanceof HttpResponseBase) { + this.handleResponse(req, event as HttpResponseBase); + } + }), + catchError((err) => { + const errorResponse = err as HttpErrorResponse; + return this.handleResponseError(req, next, errorResponse); + })); + } + + private handleRequestError(req: HttpRequest, err): Observable> { + const config = this.getInterceptorConfig(req); + if (req.url.startsWith('/api/')) { + this.updateLoadingState(config, false); + } + return throwError(err); + } + + private handleResponse(req: HttpRequest, response: HttpResponseBase) { + const config = this.getInterceptorConfig(req); + if (req.url.startsWith('/api/')) { + this.updateLoadingState(config, false); + } + } + + private handleResponseError(req: HttpRequest, next: HttpHandler, errorResponse: HttpErrorResponse): Observable> { + const config = this.getInterceptorConfig(req); + if (req.url.startsWith('/api/')) { + this.updateLoadingState(config, false); + } + let unhandled = false; + const ignoreErrors = config.ignoreErrors; + const resendRequest = config.resendRequest; + const errorCode = errorResponse.error ? errorResponse.error.errorCode : null; + if (errorResponse.error.refreshTokenPending || errorResponse.status === 401) { + if (errorResponse.error.refreshTokenPending || errorCode && errorCode === Constants.serverErrorCode.jwtTokenExpired) { + return this.refreshTokenAndRetry(req, next); + } else { + unhandled = true; + } + } else if (errorResponse.status === 429) { + if (resendRequest) { + return this.retryRequest(req, next); + } + } else if (errorResponse.status === 403) { + if (!ignoreErrors) { + this.permissionDenied(); + } + } else if (errorResponse.status === 0 || errorResponse.status === -1) { + this.showError('Unable to connect'); + } else if (!req.url.startsWith('/api/plugins/rpc')) { + if (errorResponse.status === 404) { + if (!ignoreErrors) { + this.showError(req.method + ': ' + req.url + '
' + + errorResponse.status + ': ' + errorResponse.statusText); + } + } else { + unhandled = true; + } + } + + if (unhandled && !ignoreErrors) { + let error = null; + if (req.responseType === 'text') { + try { + error = errorResponse.error ? JSON.parse(errorResponse.error) : null; + } catch (e) {} + } else { + error = errorResponse.error; + } + if (error && !error.message) { + this.showError(this.prepareMessageFromData(error)); + } else if (error && error.message) { + this.showError(error.message, error.timeout ? error.timeout : 0); + } else { + this.showError('Unhandled error code ' + (error ? error.status : '\'Unknown\'')); + } + } + return throwError(errorResponse); + } + + private prepareMessageFromData(data) { + if (typeof data === 'object' && data.constructor === ArrayBuffer) { + const msg = String.fromCharCode.apply(null, new Uint8Array(data)); + try { + const msgObj = JSON.parse(msg); + if (msgObj.message) { + return msgObj.message; + } else { + return msg; + } + } catch (e) { + return msg; + } + } else { + return data; + } + } + + private retryRequest(req: HttpRequest, next: HttpHandler): Observable> { + const thisTimeout = 1000 + Math.random() * 3000; + return of(null).pipe( + delay(thisTimeout), + mergeMap(() => { + return this.jwtIntercept(req, next); + } + )); + } + + private refreshTokenAndRetry(req: HttpRequest, next: HttpHandler): Observable> { + return this.authService.refreshJwtToken().pipe(switchMap(() => { + return this.jwtIntercept(req, next); + }), + catchError((err: Error) => { + this.authService.logout(true); + const message = err ? err.message : 'Unauthorized!'; + return this.handleResponseError(req, next, new HttpErrorResponse({error: {message, timeout: 200}, status: 401})); + })); + } + + private updateAuthorizationHeader(req: HttpRequest): HttpRequest { + const jwtToken = AuthService.getJwtToken(); + if (jwtToken) { + req = req.clone({ + setHeaders: (tmpHeaders = {}, + tmpHeaders[this.AUTH_HEADER_NAME] = '' + this.AUTH_SCHEME + jwtToken, + tmpHeaders) + }); + return req; + } else { + return null; + } + } + + private isInternalUrlPrefix(url): boolean { + for (const index in this.internalUrlPrefixes) { + if (url.startsWith(this.internalUrlPrefixes[index])) { + return true; + } + } + return false; + } + + private isTokenBasedAuthEntryPoint(url): boolean { + return url.startsWith('/api/') && + !url.startsWith(Constants.entryPoints.login) && + !url.startsWith(Constants.entryPoints.tokenRefresh) && + !url.startsWith(Constants.entryPoints.nonTokenBased); + } + + private updateLoadingState(config: InterceptorConfig, isLoading: boolean) { + if (!config.ignoreLoading) { + this.store.dispatch(isLoading ? new ActionLoadStart() : new ActionLoadFinish()); + } + } + + private getInterceptorConfig(req: HttpRequest): InterceptorConfig { + if (req.params && req.params instanceof InterceptorHttpParams) { + return (req.params as InterceptorHttpParams).interceptorConfig; + } else { + return new InterceptorConfig(false, false); + } + } + + private permissionDenied() { + this.dialogService.alert( + this.translate.instant('access.permission-denied'), + this.translate.instant('access.permission-denied-text'), + this.translate.instant('action.close') + ); + } + + private showError(error: string, timeout: number = 0) { + setTimeout(() => { + this.store.dispatch(new ActionNotificationShow({message: error, type: 'error'})); + }, timeout); + } +} diff --git a/ui-ngx/src/app/core/interceptors/interceptor-config.ts b/ui-ngx/src/app/core/interceptors/interceptor-config.ts new file mode 100644 index 0000000000..ebe4d56453 --- /dev/null +++ b/ui-ngx/src/app/core/interceptors/interceptor-config.ts @@ -0,0 +1,21 @@ +/// +/// Copyright © 2016-2019 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 class InterceptorConfig { + constructor(public ignoreLoading: boolean = false, + public ignoreErrors: boolean = false, + public resendRequest: boolean = false) {} +} diff --git a/ui-ngx/src/app/core/interceptors/interceptor-http-params.ts b/ui-ngx/src/app/core/interceptors/interceptor-http-params.ts new file mode 100644 index 0000000000..d9e665a0fe --- /dev/null +++ b/ui-ngx/src/app/core/interceptors/interceptor-http-params.ts @@ -0,0 +1,27 @@ +/// +/// Copyright © 2016-2019 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 { HttpParams } from '@angular/common/http'; +import { InterceptorConfig } from './interceptor-config'; + +export class InterceptorHttpParams extends HttpParams { + constructor( + public interceptorConfig: InterceptorConfig, + params?: { [param: string]: string | string[] } + ) { + super({ fromObject: params }); + } +} diff --git a/ui-ngx/src/app/core/interceptors/load.actions.ts b/ui-ngx/src/app/core/interceptors/load.actions.ts new file mode 100644 index 0000000000..4f7387cb55 --- /dev/null +++ b/ui-ngx/src/app/core/interceptors/load.actions.ts @@ -0,0 +1,32 @@ +/// +/// Copyright © 2016-2019 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 { Action } from '@ngrx/store'; + +export enum LoadActionTypes { + START_LOAD = '[Load] Start', + FINISH_LOAD = '[Load] Finish' +} + +export class ActionLoadStart implements Action { + readonly type = LoadActionTypes.START_LOAD; +} + +export class ActionLoadFinish implements Action { + readonly type = LoadActionTypes.FINISH_LOAD; +} + +export type LoadActions = ActionLoadStart | ActionLoadFinish; diff --git a/ui-ngx/src/app/core/interceptors/load.models.ts b/ui-ngx/src/app/core/interceptors/load.models.ts new file mode 100644 index 0000000000..91fe7c9368 --- /dev/null +++ b/ui-ngx/src/app/core/interceptors/load.models.ts @@ -0,0 +1,19 @@ +/// +/// Copyright © 2016-2019 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 interface LoadState { + isLoading: boolean; +} diff --git a/ui-ngx/src/app/core/interceptors/load.reducer.ts b/ui-ngx/src/app/core/interceptors/load.reducer.ts new file mode 100644 index 0000000000..0a855fcb79 --- /dev/null +++ b/ui-ngx/src/app/core/interceptors/load.reducer.ts @@ -0,0 +1,38 @@ +/// +/// Copyright © 2016-2019 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 { LoadState } from './load.models'; +import { LoadActions, LoadActionTypes } from './load.actions'; + +export const initialState: LoadState = { + isLoading: false +}; + +export function loadReducer( + state: LoadState = initialState, + action: LoadActions +): LoadState { + switch (action.type) { + case LoadActionTypes.START_LOAD: + return { ...state, isLoading: true }; + + case LoadActionTypes.FINISH_LOAD: + return { ...state, isLoading: false }; + + default: + return state; + } +} diff --git a/ui-ngx/src/app/core/interceptors/load.selectors.ts b/ui-ngx/src/app/core/interceptors/load.selectors.ts new file mode 100644 index 0000000000..a34063c465 --- /dev/null +++ b/ui-ngx/src/app/core/interceptors/load.selectors.ts @@ -0,0 +1,34 @@ +/// +/// Copyright © 2016-2019 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 { createFeatureSelector, createSelector } from '@ngrx/store'; + +import { AppState } from '../core.state'; +import { LoadState } from './load.models'; + +export const selectLoadState = createFeatureSelector( + 'load' +); + +export const selectLoad = createSelector( + selectLoadState, + (state: LoadState) => state +); + +export const selectIsLoading = createSelector( + selectLoadState, + (state: LoadState) => state.isLoading +); diff --git a/ui-ngx/src/app/core/local-storage/local-storage.service.ts b/ui-ngx/src/app/core/local-storage/local-storage.service.ts new file mode 100644 index 0000000000..0c0bff21fc --- /dev/null +++ b/ui-ngx/src/app/core/local-storage/local-storage.service.ts @@ -0,0 +1,87 @@ +/// +/// Copyright © 2016-2019 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 { Injectable } from '@angular/core'; + +const APP_PREFIX = 'TB-'; + +@Injectable( + { + providedIn: 'root' + } +) +export class LocalStorageService { + constructor() {} + + static loadInitialState() { + return Object.keys(localStorage).reduce((state: any, storageKey) => { + if (storageKey.includes(APP_PREFIX)) { + const stateKeys = storageKey + .replace(APP_PREFIX, '') + .toLowerCase() + .split('.') + .map(key => + key + .split('-') + .map( + (token, index) => + index === 0 + ? token + : token.charAt(0).toUpperCase() + token.slice(1) + ) + .join('') + ); + let currentStateRef = state; + stateKeys.forEach((key, index) => { + if (index === stateKeys.length - 1) { + currentStateRef[key] = JSON.parse(localStorage.getItem(storageKey)); + return; + } + currentStateRef[key] = currentStateRef[key] || {}; + currentStateRef = currentStateRef[key]; + }); + } + return state; + }, {}); + } + + setItem(key: string, value: any) { + localStorage.setItem(`${APP_PREFIX}${key}`, JSON.stringify(value)); + } + + getItem(key: string) { + return JSON.parse(localStorage.getItem(`${APP_PREFIX}${key}`)); + } + + removeItem(key: string) { + localStorage.removeItem(`${APP_PREFIX}${key}`); + } + /** Tests that localStorage exists, can be written to, and read from. */ + testLocalStorage() { + const testValue = 'testValue'; + const testKey = 'testKey'; + let retrievedValue: string; + const errorMessage = 'localStorage did not return expected value'; + + this.setItem(testKey, testValue); + retrievedValue = this.getItem(testKey); + this.removeItem(testKey); + + if (retrievedValue !== testValue) { + throw new Error(errorMessage); + } + } +} diff --git a/ui-ngx/src/app/core/meta-reducers/debug.reducer.ts b/ui-ngx/src/app/core/meta-reducers/debug.reducer.ts new file mode 100644 index 0000000000..0d65341903 --- /dev/null +++ b/ui-ngx/src/app/core/meta-reducers/debug.reducer.ts @@ -0,0 +1,33 @@ +/// +/// Copyright © 2016-2019 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 { ActionReducer } from '@ngrx/store'; + +import { AppState } from '../core.state'; + +export function debug( + reducer: ActionReducer +): ActionReducer { + return (state, action) => { + const newState = reducer(state, action); + console.log(`[DEBUG] action: ${action.type}`, { + payload: (action as any).payload, + oldState: state, + newState + }); + return newState; + }; +} diff --git a/ui-ngx/src/app/core/meta-reducers/init-state-from-local-storage.reducer.ts b/ui-ngx/src/app/core/meta-reducers/init-state-from-local-storage.reducer.ts new file mode 100644 index 0000000000..3091491a99 --- /dev/null +++ b/ui-ngx/src/app/core/meta-reducers/init-state-from-local-storage.reducer.ts @@ -0,0 +1,32 @@ +/// +/// Copyright © 2016-2019 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 { ActionReducer, INIT, UPDATE } from '@ngrx/store'; + +import { LocalStorageService } from '../local-storage/local-storage.service'; +import { AppState } from '../core.state'; + +export function initStateFromLocalStorage( + reducer: ActionReducer +): ActionReducer { + return (state, action) => { + const newState = reducer(state, action); + if ([INIT.toString(), UPDATE.toString()].includes(action.type)) { + return { ...newState, ...LocalStorageService.loadInitialState() }; + } + return newState; + }; +} diff --git a/ui-ngx/src/app/core/notification/notification.actions.ts b/ui-ngx/src/app/core/notification/notification.actions.ts new file mode 100644 index 0000000000..aa1fb865af --- /dev/null +++ b/ui-ngx/src/app/core/notification/notification.actions.ts @@ -0,0 +1,31 @@ +/// +/// Copyright © 2016-2019 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 { Action } from '@ngrx/store'; +import { NotificationMessage } from '@app/core/notification/notification.models'; + +export enum NotificationActionTypes { + SHOW_NOTIFICATION = '[Notification] Show' +} + +export class ActionNotificationShow implements Action { + readonly type = NotificationActionTypes.SHOW_NOTIFICATION; + + constructor(readonly notification: NotificationMessage ) {} +} + +export type NotificationActions = + | ActionNotificationShow; diff --git a/ui-ngx/src/app/core/notification/notification.effects.ts b/ui-ngx/src/app/core/notification/notification.effects.ts new file mode 100644 index 0000000000..dd687cc8be --- /dev/null +++ b/ui-ngx/src/app/core/notification/notification.effects.ts @@ -0,0 +1,47 @@ +/// +/// Copyright © 2016-2019 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 { Injectable } from '@angular/core'; +import { Actions, Effect, ofType } from '@ngrx/effects'; +import { + map, +} from 'rxjs/operators'; + +import { + NotificationActions, + NotificationActionTypes +} from '@app/core/notification/notification.actions'; +import { NotificationService } from '@app/core/services/notification.service'; + +@Injectable() +export class NotificationEffects { + constructor( + private actions$: Actions, + private notificationService: NotificationService + ) { + } + + @Effect({dispatch: false}) + dispatchNotification = this.actions$.pipe( + ofType( + NotificationActionTypes.SHOW_NOTIFICATION, + ), + map(({ notification }) => { + this.notificationService.dispatchNotification(notification); + }) + ); + +} diff --git a/ui-ngx/src/app/core/notification/notification.models.ts b/ui-ngx/src/app/core/notification/notification.models.ts new file mode 100644 index 0000000000..f0fc4135c3 --- /dev/null +++ b/ui-ngx/src/app/core/notification/notification.models.ts @@ -0,0 +1,33 @@ +/// +/// Copyright © 2016-2019 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 interface NotificationState { + notification: NotificationMessage; +} + +export declare type NotificationType = 'info' | 'success' | 'error'; +export declare type NotificationHorizontalPosition = 'start' | 'center' | 'end' | 'left' | 'right'; +export declare type NotificationVerticalPosition = 'top' | 'bottom'; + +export class NotificationMessage { + message: string; + type: NotificationType; + target?: string; + duration?: number; + horizontalPosition?: NotificationHorizontalPosition; + verticalPosition?: NotificationVerticalPosition; +} diff --git a/ui-ngx/src/app/core/notification/notification.reducer.ts b/ui-ngx/src/app/core/notification/notification.reducer.ts new file mode 100644 index 0000000000..81c0387fa2 --- /dev/null +++ b/ui-ngx/src/app/core/notification/notification.reducer.ts @@ -0,0 +1,34 @@ +/// +/// Copyright © 2016-2019 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 { NotificationState } from './notification.models'; +import { NotificationActions, NotificationActionTypes } from './notification.actions'; + +export const initialState: NotificationState = { + notification: null +}; + +export function notificationReducer( + state: NotificationState = initialState, + action: NotificationActions +): NotificationState { + switch (action.type) { + case NotificationActionTypes.SHOW_NOTIFICATION: + return { ...state, notification: action.notification }; + default: + return state; + } +} diff --git a/ui-ngx/src/app/core/notification/notification.selectors.ts b/ui-ngx/src/app/core/notification/notification.selectors.ts new file mode 100644 index 0000000000..b075ae3b39 --- /dev/null +++ b/ui-ngx/src/app/core/notification/notification.selectors.ts @@ -0,0 +1,29 @@ +/// +/// Copyright © 2016-2019 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 { createFeatureSelector, createSelector } from '@ngrx/store'; + +import { NotificationState } from './notification.models'; +import { AppState } from '@app/core/core.state'; + +export const selectNotificationState = createFeatureSelector( + 'notification' +); + +export const selectNotification = createSelector( + selectNotificationState, + (state: NotificationState) => state +); diff --git a/ui-ngx/src/app/core/operator/enterZone.ts b/ui-ngx/src/app/core/operator/enterZone.ts new file mode 100644 index 0000000000..e55367f41c --- /dev/null +++ b/ui-ngx/src/app/core/operator/enterZone.ts @@ -0,0 +1,44 @@ +/// +/// Copyright © 2016-2019 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 { MonoTypeOperatorFunction, Observable, Operator, Subscriber } from 'rxjs'; + +export type EnterZoneSignature = (zone: { run: (fn: any) => any }) => Observable; + +export function enterZone(zone: { run: (fn: any) => any }): MonoTypeOperatorFunction { + return (source: Observable) => { + return source.lift(new EnterZoneOperator(zone)); + }; +} + +export class EnterZoneOperator implements Operator { + constructor(private zone: { run: (fn: any) => any }) { } + + call(subscriber: Subscriber, source: any): any { + return source._subscribe(new EnterZoneSubscriber(subscriber, this.zone)); + } +} + +class EnterZoneSubscriber extends Subscriber { + constructor(destination: Subscriber, private zone: { run: (fn: any) => any }) { + super(destination); + } + + protected _next(value: T) { + this.zone.run(() => this.destination.next(value)); + } +} diff --git a/ui-ngx/src/app/core/services/dialog.service.ts b/ui-ngx/src/app/core/services/dialog.service.ts new file mode 100644 index 0000000000..955e062dce --- /dev/null +++ b/ui-ngx/src/app/core/services/dialog.service.ts @@ -0,0 +1,70 @@ +/// +/// Copyright © 2016-2019 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 { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { MatDialog, MatDialogConfig } from '@angular/material'; +import { ConfirmDialogComponent } from '@core/services/dialog/confirm-dialog.component'; +import { TranslateService } from '@ngx-translate/core'; +import { AlertDialogComponent } from '@core/services/dialog/alert-dialog.component'; + +@Injectable( + { + providedIn: 'root' + } +) +export class DialogService { + + constructor( + private translate: TranslateService, + public dialog: MatDialog + ) { + } + + confirm(title: string, message: string, cancel: string = null, ok: string = null, fullscreen: boolean = false): Observable { + const dialogConfig: MatDialogConfig = { + disableClose: true, + data: { + title, + message, + cancel: cancel || this.translate.instant('action.cancel'), + ok: ok || this.translate.instant('action.ok') + } + }; + if (fullscreen) { + dialogConfig.panelClass = ['tb-fullscreen-dialog']; + } + const dialogRef = this.dialog.open(ConfirmDialogComponent, dialogConfig); + return dialogRef.afterClosed(); + } + + alert(title: string, message: string, ok: string = null, fullscreen: boolean = false): Observable { + const dialogConfig: MatDialogConfig = { + disableClose: true, + data: { + title, + message, + ok: ok || this.translate.instant('action.ok') + } + }; + if (fullscreen) { + dialogConfig.panelClass = ['tb-fullscreen-dialog']; + } + const dialogRef = this.dialog.open(AlertDialogComponent, dialogConfig); + return dialogRef.afterClosed(); + } + +} diff --git a/ui-ngx/src/app/core/services/dialog/alert-dialog.component.html b/ui-ngx/src/app/core/services/dialog/alert-dialog.component.html new file mode 100644 index 0000000000..aa959330c0 --- /dev/null +++ b/ui-ngx/src/app/core/services/dialog/alert-dialog.component.html @@ -0,0 +1,23 @@ + +

{{data.title}}

+
+
+
+ +
diff --git a/ui-ngx/src/app/core/services/dialog/alert-dialog.component.scss b/ui-ngx/src/app/core/services/dialog/alert-dialog.component.scss new file mode 100644 index 0000000000..c15dc6cd28 --- /dev/null +++ b/ui-ngx/src/app/core/services/dialog/alert-dialog.component.scss @@ -0,0 +1,20 @@ +/** + * Copyright © 2016-2019 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 { + .mat-dialog-content { + padding: 0 24px 24px; + } +} diff --git a/ui-ngx/src/app/core/services/dialog/alert-dialog.component.ts b/ui-ngx/src/app/core/services/dialog/alert-dialog.component.ts new file mode 100644 index 0000000000..b005609bfd --- /dev/null +++ b/ui-ngx/src/app/core/services/dialog/alert-dialog.component.ts @@ -0,0 +1,34 @@ +/// +/// Copyright © 2016-2019 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 } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; + +export interface AlertDialogData { + title: string; + message: string; + ok: string; +} + +@Component({ + selector: 'tb-alert-dialog', + templateUrl: './alert-dialog.component.html', + styleUrls: ['./alert-dialog.component.scss'] +}) +export class AlertDialogComponent { + constructor(public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: AlertDialogData) {} +} diff --git a/ui-ngx/src/app/core/services/dialog/confirm-dialog.component.html b/ui-ngx/src/app/core/services/dialog/confirm-dialog.component.html new file mode 100644 index 0000000000..7e7fd5a5d2 --- /dev/null +++ b/ui-ngx/src/app/core/services/dialog/confirm-dialog.component.html @@ -0,0 +1,24 @@ + +

{{data.title}}

+
+
+
+ + +
diff --git a/ui-ngx/src/app/core/services/dialog/confirm-dialog.component.scss b/ui-ngx/src/app/core/services/dialog/confirm-dialog.component.scss new file mode 100644 index 0000000000..c15dc6cd28 --- /dev/null +++ b/ui-ngx/src/app/core/services/dialog/confirm-dialog.component.scss @@ -0,0 +1,20 @@ +/** + * Copyright © 2016-2019 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 { + .mat-dialog-content { + padding: 0 24px 24px; + } +} diff --git a/ui-ngx/src/app/core/services/dialog/confirm-dialog.component.ts b/ui-ngx/src/app/core/services/dialog/confirm-dialog.component.ts new file mode 100644 index 0000000000..13c384624a --- /dev/null +++ b/ui-ngx/src/app/core/services/dialog/confirm-dialog.component.ts @@ -0,0 +1,35 @@ +/// +/// Copyright © 2016-2019 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 } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; + +export interface ConfirmDialogData { + title: string; + message: string; + cancel: string; + ok: string; +} + +@Component({ + selector: 'tb-confirm-dialog', + templateUrl: './confirm-dialog.component.html', + styleUrls: ['./confirm-dialog.component.scss'] +}) +export class ConfirmDialogComponent { + constructor(public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: ConfirmDialogData) {} +} diff --git a/ui-ngx/src/app/core/services/menu.models.ts b/ui-ngx/src/app/core/services/menu.models.ts new file mode 100644 index 0000000000..eb822d7833 --- /dev/null +++ b/ui-ngx/src/app/core/services/menu.models.ts @@ -0,0 +1,39 @@ +/// +/// Copyright © 2016-2019 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 declare type MenuSectionType = 'link' | 'toggle'; + +export class MenuSection { + name: string; + type: MenuSectionType; + path: string; + icon: string; + isMdiIcon?: boolean; + height?: string; + pages?: Array; +} + +export class HomeSection { + name: string; + places: Array; +} + +export class 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 new file mode 100644 index 0000000000..463513052e --- /dev/null +++ b/ui-ngx/src/app/core/services/menu.service.ts @@ -0,0 +1,344 @@ +/// +/// Copyright © 2016-2019 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 { Injectable } from '@angular/core'; +import { AuthService } from '../auth/auth.service'; +import { select, Store } from '@ngrx/store'; +import { AppState } from '../core.state'; +import { selectAuthUser, selectIsAuthenticated } from '../auth/auth.selectors'; +import { take } from 'rxjs/operators'; +import { HomeSection, MenuSection } from '@core/services/menu.models'; +import { BehaviorSubject, Observable, Subject } from 'rxjs'; +import { Authority } from '@shared/models/authority.enum'; +import {AuthUser} from '@shared/models/user.model'; + +@Injectable({ + providedIn: 'root' +}) +export class MenuService { + + menuSections$: Subject> = new BehaviorSubject>([]); + homeSections$: Subject> = new BehaviorSubject>([]); + + constructor(private store: Store, private authService: AuthService) { + this.store.pipe(select(selectIsAuthenticated)).subscribe( + (authenticated: boolean) => { + if (authenticated) { + this.buildMenu(); + } + } + ); + } + + private buildMenu() { + this.store.pipe(select(selectAuthUser), take(1)).subscribe( + (authUser: AuthUser) => { + if (authUser) { + let menuSections: Array; + let homeSections: Array; + switch (authUser.authority) { + case Authority.SYS_ADMIN: + menuSections = this.buildSysAdminMenu(authUser); + homeSections = this.buildSysAdminHome(authUser); + break; + case Authority.TENANT_ADMIN: + menuSections = this.buildTenantAdminMenu(authUser); + homeSections = this.buildTenantAdminHome(authUser); + break; + case Authority.CUSTOMER_USER: + menuSections = this.buildCustomerUserMenu(authUser); + homeSections = this.buildCustomerUserHome(authUser); + break; + } + this.menuSections$.next(menuSections); + this.homeSections$.next(homeSections); + } + } + ); + } + + private buildSysAdminMenu(authUser: any): Array { + const sections: Array = []; + sections.push( + { + name: 'home.home', + type: 'link', + path: '/home', + icon: 'home' + }, + { + name: 'tenant.tenants', + type: 'link', + path: '/tenants', + icon: 'supervisor_account' + }, + { + name: 'widget.widget-library', + type: 'link', + path: '/widgets-bundles', + icon: 'now_widgets' + }, + { + name: 'admin.system-settings', + type: 'toggle', + path: '/settings', + height: '120px', + icon: 'settings', + pages: [ + { + name: 'admin.general', + type: 'link', + path: '/settings/general', + icon: 'settings_applications' + }, + { + name: 'admin.outgoing-mail', + type: 'link', + path: '/settings/outgoing-mail', + icon: 'mail' + }, + { + name: 'admin.security-settings', + type: 'link', + path: '/settings/security-settings', + icon: 'security' + } + ] + } + ); + return sections; + } + + private buildSysAdminHome(authUser: any): Array { + const homeSections: Array = []; + homeSections.push( + { + name: 'tenant.management', + places: [ + { + name: 'tenant.tenants', + icon: 'supervisor_account', + path: '/tenants' + } + ] + }, + { + name: 'widget.management', + places: [ + { + name: 'widget.widget-library', + icon: 'now_widgets', + path: '/widgets-bundles' + } + ] + }, + { + name: 'admin.system-settings', + places: [ + { + name: 'admin.general', + icon: 'settings_applications', + path: '/settings/general' + }, + { + name: 'admin.outgoing-mail', + icon: 'mail', + path: '/settings/outgoing-mail' + }, + { + name: 'admin.security-settings', + icon: 'security', + path: '/settings/security-settings' + } + ] + } + ); + return homeSections; + } + + private buildTenantAdminMenu(authUser: any): Array { + const sections: Array = []; + sections.push( + { + name: 'home.home', + type: 'link', + path: '/home', + icon: 'home' + }, + { + name: 'rulechain.rulechains', + type: 'link', + path: '/ruleChains', + icon: 'settings_ethernet' + }, + { + name: 'customer.customers', + type: 'link', + path: '/customers', + icon: 'supervisor_account' + }, + { + name: 'asset.assets', + type: 'link', + path: '/assets', + icon: 'domain' + }, + { + name: 'device.devices', + type: 'link', + path: '/devices', + icon: 'devices_other' + }, + { + name: 'entity-view.entity-views', + type: 'link', + path: '/entityViews', + icon: 'view_quilt' + }, + { + name: 'widget.widget-library', + type: 'link', + path: '/widgets-bundles', + icon: 'now_widgets' + }, + { + name: 'dashboard.dashboards', + type: 'link', + path: '/dashboards', + icon: 'dashboards' + }, + { + name: 'audit-log.audit-logs', + type: 'link', + path: '/auditLogs', + icon: 'track_changes' + } + ); + return sections; + } + + private buildTenantAdminHome(authUser: any): Array { + const homeSections: Array = []; + homeSections.push( + { + name: 'rulechain.management', + places: [ + { + name: 'rulechain.rulechains', + icon: 'settings_ethernet', + path: '/ruleChains' + } + ] + }, + { + name: 'customer.management', + places: [ + { + name: 'customer.customers', + icon: 'supervisor_account', + path: '/customers' + } + ] + }, + { + name: 'asset.management', + places: [ + { + name: 'asset.assets', + icon: 'domain', + path: '/assets' + } + ] + }, + { + name: 'device.management', + places: [ + { + name: 'device.devices', + icon: 'devices_other', + path: '/devices' + } + ] + }, + { + name: 'entity-view.management', + places: [ + { + name: 'entity-view.entity-views', + icon: 'view_quilt', + path: '/entityViews' + } + ] + }, + { + name: 'dashboard.management', + places: [ + { + name: 'widget.widget-library', + icon: 'now_widgets', + path: '/widgets-bundles' + }, + { + name: 'dashboard.dashboards', + icon: 'dashboard', + path: '/dashboards' + } + ] + }, + { + name: 'audit-log.audit', + places: [ + { + name: 'audit-log.audit-logs', + icon: 'track_changes', + path: '/auditLogs' + } + ] + } + ); + return homeSections; + } + + private buildCustomerUserMenu(authUser: any): Array { + const sections: Array = []; + sections.push( + { + name: 'home.home', + type: 'link', + path: '/home', + icon: 'home' + } + ); + // TODO: + return sections; + } + + private buildCustomerUserHome(authUser: any): Array { + const homeSections: Array = []; + // TODO: + return homeSections; + } + + public menuSections(): Observable> { + return this.menuSections$; + } + + public homeSections(): Observable> { + return this.homeSections$; + } + +} + diff --git a/ui-ngx/src/app/core/services/notification.service.ts b/ui-ngx/src/app/core/services/notification.service.ts new file mode 100644 index 0000000000..fed93cf93a --- /dev/null +++ b/ui-ngx/src/app/core/services/notification.service.ts @@ -0,0 +1,43 @@ +/// +/// Copyright © 2016-2019 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 { Injectable } from '@angular/core'; +import { NotificationMessage } from '@app/core/notification/notification.models'; +import { BehaviorSubject, Observable, Subject } from 'rxjs'; + + +@Injectable( + { + providedIn: 'root' + } +) +export class NotificationService { + + private notificationSubject: Subject = new Subject(); + + constructor( + ) { + } + + dispatchNotification(notification: NotificationMessage) { + this.notificationSubject.next(notification); + } + + getNotification(): Observable { + return this.notificationSubject; + } + +} diff --git a/ui-ngx/src/app/core/services/time.service.ts b/ui-ngx/src/app/core/services/time.service.ts new file mode 100644 index 0000000000..070356abbc --- /dev/null +++ b/ui-ngx/src/app/core/services/time.service.ts @@ -0,0 +1,123 @@ +/// +/// Copyright © 2016-2019 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 { Injectable } from '@angular/core'; +import { DAY, defaultTimeIntervals, SECOND } from '@shared/models/time/time.models'; +import {HttpClient} from '@angular/common/http'; +import {Observable} from 'rxjs'; +import {defaultHttpOptions} from '@core/http/http-utils'; +import {map} from 'rxjs/operators'; + +export interface TimeInterval { + name: string; + translateParams: {[key: string]: any}; + value: number; +} + +const MIN_INTERVAL = SECOND; +const MAX_INTERVAL = 365 * 20 * DAY; + +const MIN_LIMIT = 10; + +const MAX_DATAPOINTS_LIMIT = 500; + +@Injectable({ + providedIn: 'root' +}) +export class TimeService { + + private maxDatapointsLimit = MAX_DATAPOINTS_LIMIT; + + constructor( + private http: HttpClient + ) {} + + public loadMaxDatapointsLimit(): Observable { + return this.http.get('/api/dashboard/maxDatapointsLimit', + defaultHttpOptions(true)).pipe( + map( (limit) => { + this.maxDatapointsLimit = limit; + if (!this.maxDatapointsLimit || this.maxDatapointsLimit <= MIN_LIMIT) { + this.maxDatapointsLimit = MIN_LIMIT + 1; + } + return this.maxDatapointsLimit; + }) + ); + } + + public matchesExistingInterval(min: number, max: number, intervalMs: number): boolean { + const intervals = this.getIntervals(min, max); + return intervals.findIndex(interval => interval.value === intervalMs) > -1; + } + + public getIntervals(min: number, max: number): Array { + min = this.boundMinInterval(min); + max = this.boundMaxInterval(max); + return defaultTimeIntervals.filter((interval) => interval.value >= min && interval.value <= max); + } + + public boundMinInterval(min: number): number { + return this.toBound(min, MIN_INTERVAL, MAX_INTERVAL, MIN_INTERVAL); + } + + public boundMaxInterval(max: number): number { + return this.toBound(max, MIN_INTERVAL, MAX_INTERVAL, MAX_INTERVAL); + } + + public boundToPredefinedInterval(min: number, max: number, intervalMs: number): number { + const intervals = this.getIntervals(min, max); + let minDelta = MAX_INTERVAL; + const boundedInterval = intervalMs || min; + let matchedInterval: TimeInterval = intervals[0]; + intervals.forEach((interval) => { + const delta = Math.abs(interval.value - boundedInterval); + if (delta < minDelta) { + matchedInterval = interval; + minDelta = delta; + } + }); + return matchedInterval.value; + } + + public getMaxDatapointsLimit(): number { + return this.maxDatapointsLimit; + } + + public getMinDatapointsLimit(): number { + return MIN_LIMIT; + } + + public minIntervalLimit(timewindowMs: number): number { + const min = timewindowMs / 500; + return this.boundMinInterval(min); + } + + public maxIntervalLimit(timewindowMs: number): number { + const max = timewindowMs / MIN_LIMIT; + return this.boundMaxInterval(max); + } + + private toBound(value: number, min: number, max: number, defValue: number): number { + if (typeof value !== 'undefined') { + value = Math.max(value, min); + value = Math.min(value, max); + return value; + } else { + return defValue; + } + } + +} diff --git a/ui-ngx/src/app/core/services/title.service.ts b/ui-ngx/src/app/core/services/title.service.ts new file mode 100644 index 0000000000..9e7a501e0f --- /dev/null +++ b/ui-ngx/src/app/core/services/title.service.ts @@ -0,0 +1,55 @@ +/// +/// Copyright © 2016-2019 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 { Title } from '@angular/platform-browser'; +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot } from '@angular/router'; +import { TranslateService } from '@ngx-translate/core'; +import { filter } from 'rxjs/operators'; + +import { environment as env } from '@env/environment'; + +@Injectable({ + providedIn: 'root' +}) +export class TitleService { + constructor( + private translate: TranslateService, + private title: Title + ) {} + + setTitle( + snapshot: ActivatedRouteSnapshot, + lazyTranslate?: TranslateService + ) { + let lastChild = snapshot; + while (lastChild.children.length) { + lastChild = lastChild.children[0]; + } + const { title } = lastChild.data; + const translate = lazyTranslate || this.translate; + if (title) { + translate + .get(title) + .pipe(filter(translatedTitle => translatedTitle !== title)) + .subscribe(translatedTitle => + this.title.setTitle(`${env.appTitle} | ${translatedTitle}`) + ); + } else { + this.title.setTitle(env.appTitle); + } + } +} diff --git a/ui-ngx/src/app/core/services/window.service.ts b/ui-ngx/src/app/core/services/window.service.ts new file mode 100644 index 0000000000..2309b4ec94 --- /dev/null +++ b/ui-ngx/src/app/core/services/window.service.ts @@ -0,0 +1,71 @@ +/// +/// Copyright © 2016-2019 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 { isPlatformBrowser } from '@angular/common'; +import { ClassProvider, FactoryProvider, InjectionToken, PLATFORM_ID } from '@angular/core'; + +/* Create a new injection token for injecting the window into a component. */ +export const WINDOW = new InjectionToken('WindowToken'); + +/* Define abstract class for obtaining reference to the global window object. */ +export abstract class WindowRef { + + get nativeWindow(): Window | object { + throw new Error('Not implemented.'); + } + +} + +/* Define class that implements the abstract class and returns the native window object. */ +export class BrowserWindowRef extends WindowRef { + + constructor() { + super(); + } + + get nativeWindow(): Window | object { + return window; + } + +} + +/* Create an factory function that returns the native window object. */ +export function windowFactory(browserWindowRef: BrowserWindowRef, platformId: object): Window | object { + if (isPlatformBrowser(platformId)) { + return browserWindowRef.nativeWindow; + } + return new Object(); +} + +/* Create a injectable provider for the WindowRef token that uses the BrowserWindowRef class. */ +export const browserWindowProvider: ClassProvider = { + provide: WindowRef, + useClass: BrowserWindowRef +}; + +/* Create an injectable provider that uses the windowFactory function for returning the native window object. */ +export const windowProvider: FactoryProvider = { + provide: WINDOW, + useFactory: windowFactory, + deps: [ WindowRef, PLATFORM_ID ] +}; + +/* Create an array of providers. */ +export const WINDOW_PROVIDERS = [ + browserWindowProvider, + windowProvider +]; diff --git a/ui-ngx/src/app/core/settings/settings.actions.ts b/ui-ngx/src/app/core/settings/settings.actions.ts new file mode 100644 index 0000000000..3358dc572d --- /dev/null +++ b/ui-ngx/src/app/core/settings/settings.actions.ts @@ -0,0 +1,30 @@ +/// +/// Copyright © 2016-2019 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 { Action } from '@ngrx/store'; + +export enum SettingsActionTypes { + CHANGE_LANGUAGE = '[Settings] Change Language' +} + +export class ActionSettingsChangeLanguage implements Action { + readonly type = SettingsActionTypes.CHANGE_LANGUAGE; + + constructor(readonly payload: { userLang: string }) {} +} + +export type SettingsActions = + | ActionSettingsChangeLanguage; diff --git a/ui-ngx/src/app/core/settings/settings.effects.ts b/ui-ngx/src/app/core/settings/settings.effects.ts new file mode 100644 index 0000000000..e0b4993719 --- /dev/null +++ b/ui-ngx/src/app/core/settings/settings.effects.ts @@ -0,0 +1,88 @@ +/// +/// Copyright © 2016-2019 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 { ActivationEnd, Router } from '@angular/router'; +import { Injectable } from '@angular/core'; +import { select, Store } from '@ngrx/store'; +import { Actions, Effect, ofType } from '@ngrx/effects'; +import { TranslateService } from '@ngx-translate/core'; +import { merge } from 'rxjs'; +import { + tap, + withLatestFrom, + map, + distinctUntilChanged, + filter +} from 'rxjs/operators'; + +import { + SettingsActionTypes, + SettingsActions, +} from './settings.actions'; +import { + selectSettingsState +} from './settings.selectors'; +import { AppState } from '@app/core/core.state'; +import { LocalStorageService } from '@app/core/local-storage/local-storage.service'; +import { TitleService } from '@app/core/services/title.service'; +import { updateUserLang } from '@app/core/settings/settings.utils'; + +export const SETTINGS_KEY = 'SETTINGS'; + +@Injectable() +export class SettingsEffects { + constructor( + private actions$: Actions, + private store: Store, + private router: Router, + private localStorageService: LocalStorageService, + private titleService: TitleService, + private translate: TranslateService + ) { + } + + @Effect({dispatch: false}) + persistSettings = this.actions$.pipe( + ofType( + SettingsActionTypes.CHANGE_LANGUAGE, + ), + withLatestFrom(this.store.pipe(select(selectSettingsState))), + tap(([action, settings]) => + this.localStorageService.setItem(SETTINGS_KEY, settings) + ) + ); + + @Effect({dispatch: false}) + setTranslateServiceLanguage = this.store.pipe( + select(selectSettingsState), + map(settings => settings.userLang), + distinctUntilChanged(), + tap(userLang => updateUserLang(this.translate, userLang)) + ); + + @Effect({dispatch: false}) + setTitle = merge( + this.actions$.pipe(ofType(SettingsActionTypes.CHANGE_LANGUAGE)), + this.router.events.pipe(filter(event => event instanceof ActivationEnd)) + ).pipe( + tap(() => { + this.titleService.setTitle( + this.router.routerState.snapshot.root, + this.translate + ); + }) + ); +} diff --git a/ui-ngx/src/app/core/settings/settings.models.ts b/ui-ngx/src/app/core/settings/settings.models.ts new file mode 100644 index 0000000000..f7fc433711 --- /dev/null +++ b/ui-ngx/src/app/core/settings/settings.models.ts @@ -0,0 +1,20 @@ +/// +/// Copyright © 2016-2019 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 interface SettingsState { + userLang: string; +} diff --git a/ui-ngx/src/app/core/settings/settings.reducer.ts b/ui-ngx/src/app/core/settings/settings.reducer.ts new file mode 100644 index 0000000000..aa6f4ae982 --- /dev/null +++ b/ui-ngx/src/app/core/settings/settings.reducer.ts @@ -0,0 +1,34 @@ +/// +/// Copyright © 2016-2019 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 { SettingsState } from './settings.models'; +import { SettingsActions, SettingsActionTypes } from './settings.actions'; + +export const initialState: SettingsState = { + userLang: null +}; + +export function settingsReducer( + state: SettingsState = initialState, + action: SettingsActions +): SettingsState { + switch (action.type) { + case SettingsActionTypes.CHANGE_LANGUAGE: + return { ...state, ...action.payload }; + default: + return state; + } +} diff --git a/ui-ngx/src/app/core/settings/settings.selectors.ts b/ui-ngx/src/app/core/settings/settings.selectors.ts new file mode 100644 index 0000000000..09ff660433 --- /dev/null +++ b/ui-ngx/src/app/core/settings/settings.selectors.ts @@ -0,0 +1,34 @@ +/// +/// Copyright © 2016-2019 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 { createFeatureSelector, createSelector } from '@ngrx/store'; + +import { SettingsState } from './settings.models'; +import { AppState } from '@app/core/core.state'; + +export const selectSettingsState = createFeatureSelector( + 'settings' +); + +export const selectSettings = createSelector( + selectSettingsState, + (state: SettingsState) => state +); + +export const selectUserLang = createSelector( + selectSettings, + (state: SettingsState) => state.userLang +); diff --git a/ui-ngx/src/app/core/settings/settings.utils.ts b/ui-ngx/src/app/core/settings/settings.utils.ts new file mode 100644 index 0000000000..1f7a24d7d5 --- /dev/null +++ b/ui-ngx/src/app/core/settings/settings.utils.ts @@ -0,0 +1,57 @@ +/// +/// Copyright © 2016-2019 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 { environment } from '@env/environment'; +import { TranslateService } from '@ngx-translate/core'; + +export function updateUserLang(translate: TranslateService, userLang: string) { + let targetLang = userLang; + console.log(`User lang: ${targetLang}`); + if (!targetLang) { + targetLang = translate.getBrowserCultureLang(); + console.log(`Fallback to browser lang: ${targetLang}`); + } + const detectedSupportedLang = detectSupportedLang(targetLang); + console.log(`Detected supported lang: ${detectedSupportedLang}`); + translate.use(detectedSupportedLang); +} + +function detectSupportedLang(targetLang: string): string { + const langTag = (targetLang || '').split('-').join('_'); + if (langTag.length) { + if (environment.supportedLangs.indexOf(langTag) > -1) { + return langTag; + } else { + const parts = langTag.split('_'); + let lang; + if (parts.length === 2) { + lang = parts[0]; + } else { + lang = langTag; + } + const foundLangs = environment.supportedLangs.filter( + (supportedLang: string) => { + const supportedLangParts = supportedLang.split('_'); + return supportedLangParts[0] === lang; + } + ); + if (foundLangs.length) { + return foundLangs[0]; + } + } + } + return environment.defaultLang; +} diff --git a/ui-ngx/src/app/core/translate/missing-translate-handler.ts b/ui-ngx/src/app/core/translate/missing-translate-handler.ts new file mode 100644 index 0000000000..71cf578b2b --- /dev/null +++ b/ui-ngx/src/app/core/translate/missing-translate-handler.ts @@ -0,0 +1,23 @@ +/// +/// Copyright © 2016-2019 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 {MissingTranslationHandler, MissingTranslationHandlerParams} from '@ngx-translate/core'; + +export class TbMissingTranslationHandler implements MissingTranslationHandler { + handle(params: MissingTranslationHandlerParams) { + console.warn('Translation for ' + params.key + ' doesn\'t exist'); + } +} diff --git a/ui-ngx/src/app/core/translate/translate-default-compiler.ts b/ui-ngx/src/app/core/translate/translate-default-compiler.ts new file mode 100644 index 0000000000..3e92f3b64c --- /dev/null +++ b/ui-ngx/src/app/core/translate/translate-default-compiler.ts @@ -0,0 +1,67 @@ +/// +/// Copyright © 2016-2019 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 { + MESSAGE_FORMAT_CONFIG, MessageFormatConfig, + TranslateMessageFormatCompiler +} from 'ngx-translate-messageformat-compiler'; +import { Inject, Optional } from '@angular/core'; +const parse = require('messageformat-parser').parse; + +export class TranslateDefaultCompiler extends TranslateMessageFormatCompiler { + + constructor( + @Optional() + @Inject(MESSAGE_FORMAT_CONFIG) + config?: MessageFormatConfig + ) { + super(config); + } + + public compile(value: string, lang: string): (params: any) => string { + return this.defaultCompile(value, lang); + } + + public compileTranslations(translations: any, lang: string): any { + return this.defaultCompile(translations, lang); + } + + private defaultCompile(src: any, lang: string): any { + if (typeof src !== 'object') { + if (this.checkIsPlural(src)) { + return super.compile(src, lang); + } else { + return src; + } + } else { + const result = {}; + for (const key of Object.keys(src)) { + result[key] = this.defaultCompile(src[key], lang); + } + return result; + } + } + + private checkIsPlural(src: string): boolean { + const tokens: any[] = parse(src.replace(/\{\{/g, '{').replace(/\}\}/g, '}'), + {cardinal: [], ordinal: []}); + const res = tokens.filter( + (value) => typeof value !== 'string' && value.type === 'plural' + ); + return res.length > 0; + } + +} diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts new file mode 100644 index 0000000000..b25fa860e9 --- /dev/null +++ b/ui-ngx/src/app/core/utils.ts @@ -0,0 +1,86 @@ +/// +/// Copyright © 2016-2019 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 { BehaviorSubject, Observable, Subject } from 'rxjs'; +import { finalize, share } from 'rxjs/operators'; + +export function onParentScrollOrWindowResize(el: Node): Observable { + const scrollSubject = new Subject(); + const scrollParentNodes = scrollParents(el); + const eventListenerObject: EventListenerObject = { + handleEvent(evt: Event) { + scrollSubject.next(evt); + } + }; + scrollParentNodes.forEach((scrollParentNode) => { + scrollParentNode.addEventListener('scroll', eventListenerObject); + }); + window.addEventListener('resize', eventListenerObject); + const shared = scrollSubject.pipe( + finalize(() => { + scrollParentNodes.forEach((scrollParentNode) => { + scrollParentNode.removeEventListener('scroll', eventListenerObject); + }); + window.removeEventListener('resize', eventListenerObject); + }), + share() + ); + return shared; +} + +const scrollRegex = /(auto|scroll)/; + +function parentNodes(node: Node, nodes: Node[]): Node[] { + if (node.parentNode === null) { + return nodes; + } + return parentNodes(node.parentNode, nodes.concat([node])); +} + +function style(el: Element, prop: string): string { + return getComputedStyle(el, null).getPropertyValue(prop); +} + +function overflow(el: Element): string { + return style(el, 'overflow') + style(el, 'overflow-y') + style(el, 'overflow-x'); +} + +function isScrollNode(node: Node): boolean { + if (node instanceof Element) { + return scrollRegex.test(overflow(node)); + } else { + return false; + } +} + +function scrollParents(node: Node): Node[] { + if (!(node instanceof HTMLElement || node instanceof SVGElement)) { + return []; + } + const scrollParentNodes = []; + const nodeParents = parentNodes(node, []); + nodeParents.forEach((nodeParent) => { + if (isScrollNode(nodeParent)) { + scrollParentNodes.push(nodeParent); + } + }); + if (document.scrollingElement) { + scrollParentNodes.push(document.scrollingElement); + } else if (document.documentElement) { + scrollParentNodes.push(document.documentElement); + } + return scrollParentNodes; +} diff --git a/ui-ngx/src/app/modules/home/home-routing.module.ts b/ui-ngx/src/app/modules/home/home-routing.module.ts new file mode 100644 index 0000000000..d2a6332c8c --- /dev/null +++ b/ui-ngx/src/app/modules/home/home-routing.module.ts @@ -0,0 +1,45 @@ +/// +/// Copyright © 2016-2019 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 { Routes, RouterModule } from '@angular/router'; + +import { HomeComponent } from './home.component'; +import { AuthGuard } from '@core/guards/auth.guard'; +import { StoreModule } from '@ngrx/store'; + +const routes: Routes = [ + { path: '', + component: HomeComponent, + data: { + title: 'home.home', + breadcrumb: { + skip: true + } + }, + canActivate: [AuthGuard], + canActivateChild: [AuthGuard], + loadChildren: './pages/home-pages.module#HomePagesModule' + } +]; + +@NgModule({ + imports: [ + StoreModule, + RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class HomeRoutingModule { } diff --git a/ui-ngx/src/app/modules/home/home.component.html b/ui-ngx/src/app/modules/home/home.component.html new file mode 100644 index 0000000000..c4d387464e --- /dev/null +++ b/ui-ngx/src/app/modules/home/home.component.html @@ -0,0 +1,56 @@ + + + +
+ +
+ +
+
+
+ + + +
+ +
+ + +
+
+ + +
+ + +
+ +
+
+
+
diff --git a/ui-ngx/src/app/modules/home/home.component.scss b/ui-ngx/src/app/modules/home/home.component.scss new file mode 100644 index 0000000000..7d26343517 --- /dev/null +++ b/ui-ngx/src/app/modules/home/home.component.scss @@ -0,0 +1,66 @@ +/** + * Copyright © 2016-2019 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 { + display: flex; + width: 100%; + height: 100%; + mat-sidenav-container { + flex: 1; + } + mat-sidenav.tb-site-sidenav { + width: 250px; + @media (max-width:456px) { + width: calc(100% - 56px); + } + .tb-nav-header { + z-index: 2; + flex-shrink: 0; + white-space: nowrap; + .tb-nav-header-toolbar { + min-height: 64px; + height: inherit; + z-index: 2; + flex-shrink: 0; + white-space: nowrap; + border-bottom: 1px solid rgba(0, 0, 0, .12); + & > div { + height: 64px; + .tb-logo-title { + width: auto; + height: 36px; + margin: auto; + } + } + } + } + .tb-side-menu-toolbar { + overflow-y: auto; + height: inherit; + padding: 0; + } + } + .tb-primary-toolbar { + z-index: 2; + h1 { + font-size: 24px !important; + font-weight: 400 !important; + } + } + .tb-main-content { + overflow: auto; + position: relative; + } +} diff --git a/ui-ngx/src/app/modules/home/home.component.ts b/ui-ngx/src/app/modules/home/home.component.ts new file mode 100644 index 0000000000..13462e42e2 --- /dev/null +++ b/ui-ngx/src/app/modules/home/home.component.ts @@ -0,0 +1,114 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, OnInit, ViewChild } from '@angular/core'; +import { Observable } from 'rxjs'; +import { select, Store } from '@ngrx/store'; +import { map, mergeMap, take } from 'rxjs/operators'; + +import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; +import { User } from '@shared/models/user.model'; +import { PageComponent } from '@shared/components/page.component'; +import { AppState } from '@core/core.state'; +import { AuthService } from '@core/auth/auth.service'; +import { UserService } from '@core/http/user.service'; +import { MenuService } from '@core/services/menu.service'; +import { selectAuthUser, selectUserDetails } from '@core/auth/auth.selectors'; +import { MediaBreakpoints } from '@shared/models/constants'; +import { ActionNotificationShow } from '@core/notification/notification.actions'; +import { Router } from '@angular/router'; +import * as screenfull from 'screenfull'; +import { MatSidenav } from '@angular/material'; + +@Component({ + selector: 'tb-home', + templateUrl: './home.component.html', + styleUrls: ['./home.component.scss'] +}) +export class HomeComponent extends PageComponent implements OnInit { + + sidenavMode = 'side'; + sidenavOpened = true; + + logo = require('../../../assets/logo_title_white.svg'); + + @ViewChild('sidenav', {static: false}) + sidenav: MatSidenav; + + // @ts-ignore + fullscreenEnabled = screenfull.enabled; + + authUser$: Observable; + userDetails$: Observable; + userDetailsString: Observable; + testUser1$: Observable; + testUser2$: Observable; + testUser3$: Observable; + + constructor(protected store: Store, + private authService: AuthService, + private router: Router, + private userService: UserService, private menuService: MenuService, + public breakpointObserver: BreakpointObserver) { + super(store); + } + + ngOnInit() { + + this.authUser$ = this.store.pipe(select(selectAuthUser)); + this.userDetails$ = this.store.pipe(select(selectUserDetails)); + this.userDetailsString = this.userDetails$.pipe(map((user: User) => { + return JSON.stringify(user); + })); + + const isGtSm = this.breakpointObserver.isMatched(MediaBreakpoints['gt-sm']); + this.sidenavMode = isGtSm ? 'side' : 'over'; + this.sidenavOpened = isGtSm; + + this.breakpointObserver + .observe(MediaBreakpoints['gt-sm']) + .subscribe((state: BreakpointState) => { + if (state.matches) { + this.sidenavMode = 'side'; + this.sidenavOpened = true; + } else { + this.sidenavMode = 'over'; + this.sidenavOpened = false; + } + } + ); + } + + sidenavClicked() { + if (this.sidenavMode === 'over') { + this.sidenav.toggle(); + } + } + + toggleFullscreen() { + // @ts-ignore + if (screenfull.enabled) { + // @ts-ignore + screenfull.toggle(); + } + } + + isFullscreen() { + // @ts-ignore + return screenfull.isFullscreen; + } + +} diff --git a/ui-ngx/src/app/modules/home/home.module.ts b/ui-ngx/src/app/modules/home/home.module.ts new file mode 100644 index 0000000000..2db95ff74b --- /dev/null +++ b/ui-ngx/src/app/modules/home/home.module.ts @@ -0,0 +1,41 @@ +/// +/// Copyright © 2016-2019 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 { CommonModule } from '@angular/common'; + +import { HomeRoutingModule } from './home-routing.module'; +import { HomeComponent } from './home.component'; +import { SharedModule } from '@app/shared/shared.module'; +import { MenuLinkComponent } from '@modules/home/menu/menu-link.component'; +import { MenuToggleComponent } from '@modules/home/menu/menu-toggle.component'; +import { SideMenuComponent } from '@modules/home/menu/side-menu.component'; + +@NgModule({ + declarations: + [ + HomeComponent, + MenuLinkComponent, + MenuToggleComponent, + SideMenuComponent + ], + imports: [ + CommonModule, + SharedModule, + HomeRoutingModule + ] +}) +export class HomeModule { } 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 new file mode 100644 index 0000000000..c229a57c39 --- /dev/null +++ b/ui-ngx/src/app/modules/home/menu/menu-link.component.html @@ -0,0 +1,23 @@ + + diff --git a/ui-ngx/src/app/modules/home/menu/menu-link.component.scss b/ui-ngx/src/app/modules/home/menu/menu-link.component.scss new file mode 100644 index 0000000000..dfbd362f33 --- /dev/null +++ b/ui-ngx/src/app/modules/home/menu/menu-link.component.scss @@ -0,0 +1,18 @@ +/** + * Copyright © 2016-2019 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 { + +} diff --git a/ui-ngx/src/app/modules/home/menu/menu-link.component.ts b/ui-ngx/src/app/modules/home/menu/menu-link.component.ts new file mode 100644 index 0000000000..82ba35f4aa --- /dev/null +++ b/ui-ngx/src/app/modules/home/menu/menu-link.component.ts @@ -0,0 +1,35 @@ +/// +/// Copyright © 2016-2019 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, Input, OnInit, ViewEncapsulation } from '@angular/core'; +import { MenuSection } from '@core/services/menu.models'; + +@Component({ + selector: 'tb-menu-link', + templateUrl: './menu-link.component.html', + styleUrls: ['./menu-link.component.scss'] +}) +export class MenuLinkComponent implements OnInit { + + @Input() section: MenuSection; + + constructor() { + } + + ngOnInit() { + } + +} 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 new file mode 100644 index 0000000000..6444eaf8fb --- /dev/null +++ b/ui-ngx/src/app/modules/home/menu/menu-toggle.component.html @@ -0,0 +1,31 @@ + + +
    +
  • + +
  • +
diff --git a/ui-ngx/src/app/modules/home/menu/menu-toggle.component.scss b/ui-ngx/src/app/modules/home/menu/menu-toggle.component.scss new file mode 100644 index 0000000000..dfbd362f33 --- /dev/null +++ b/ui-ngx/src/app/modules/home/menu/menu-toggle.component.scss @@ -0,0 +1,18 @@ +/** + * Copyright © 2016-2019 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 { + +} diff --git a/ui-ngx/src/app/modules/home/menu/menu-toggle.component.ts b/ui-ngx/src/app/modules/home/menu/menu-toggle.component.ts new file mode 100644 index 0000000000..76c4002d8b --- /dev/null +++ b/ui-ngx/src/app/modules/home/menu/menu-toggle.component.ts @@ -0,0 +1,47 @@ +/// +/// Copyright © 2016-2019 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, Input, OnInit, ViewEncapsulation } from '@angular/core'; +import { MenuSection } from '@core/services/menu.models'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'tb-menu-toggle', + templateUrl: './menu-toggle.component.html', + styleUrls: ['./menu-toggle.component.scss'] +}) +export class MenuToggleComponent implements OnInit { + + @Input() section: MenuSection; + + constructor(private router: Router) { + } + + ngOnInit() { + } + + sectionActive(): boolean { + return this.router.isActive(this.section.path, false); + } + + sectionHeight(): string { + if (this.router.isActive(this.section.path, false)) { + return this.section.height; + } else { + return '0px'; + } + } +} diff --git a/ui-ngx/src/app/modules/home/menu/side-menu.component.html b/ui-ngx/src/app/modules/home/menu/side-menu.component.html new file mode 100644 index 0000000000..cbf8e27240 --- /dev/null +++ b/ui-ngx/src/app/modules/home/menu/side-menu.component.html @@ -0,0 +1,23 @@ + +
    +
  • + + +
  • +
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 new file mode 100644 index 0000000000..53100c456e --- /dev/null +++ b/ui-ngx/src/app/modules/home/menu/side-menu.component.scss @@ -0,0 +1,114 @@ +/** + * Copyright © 2016-2019 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 { + width: 100%; +} + +:host ::ng-deep { + + .tb-side-menu, + .tb-side-menu ul { + padding: 0; + margin-top: 0; + list-style: none; + } + + .tb-side-menu > li { + border-bottom: 1px solid rgba(0, 0, 0, .12); + } + + button { + display: flex; + width: 100%; + max-height: 40px; + padding: 0 16px; + margin: 0; + overflow: hidden; + line-height: 40px; + color: inherit; + text-align: left; + text-decoration: none; + text-overflow: ellipsis; + white-space: nowrap; + cursor: pointer; + border-radius: 0; + &:hover { + background-color: rgba(255,255,255,0.08); + } + + .mat-button-wrapper { + width: 100%; + span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } + } + + button.tb-active { + font-weight: 500; + background-color: rgba(255, 255, 255, .15); + } + + span.tb-toggle-icon { + padding-top: 12px; + padding-bottom: 12px; + } + + mat-icon { + margin-right: 8px; + margin-left: 0; + } + + .tb-menu-toggle-list button { + padding: 0 16px 0 32px; + font-weight: 500; + text-transform: none; + text-rendering: optimizeLegibility; + } + + .tb-button-toggle .tb-toggle-icon { + display: inline-block; + width: 15px; + margin: auto 0 auto auto; + background-size: 100% auto; + + transition: transform .3s, ease-in-out; + } + + .tb-button-toggle .tb-toggle-icon.tb-toggled { + transform: rotateZ(180deg); + } + + .tb-menu-toggle-list { + position: relative; + z-index: 1; + overflow: hidden; + + transition: .75s cubic-bezier(.35, 0, .25, 1); + + transition-property: height; + + button { + padding: 0 16px 0 32px; + font-weight: 500; + text-transform: none; + text-rendering: optimizeLegibility; + } + } + +} 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 new file mode 100644 index 0000000000..60b1952432 --- /dev/null +++ b/ui-ngx/src/app/modules/home/menu/side-menu.component.ts @@ -0,0 +1,35 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, OnInit } from '@angular/core'; +import { MenuService } from '@core/services/menu.service'; + +@Component({ + selector: 'tb-side-menu', + templateUrl: './side-menu.component.html', + styleUrls: ['./side-menu.component.scss'] +}) +export class SideMenuComponent implements OnInit { + + menuSections$ = this.menuService.menuSections(); + + constructor(private menuService: MenuService) { + } + + ngOnInit() { + } + +} 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 new file mode 100644 index 0000000000..1227f9619b --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/home-links/home-links-routing.module.ts @@ -0,0 +1,42 @@ +/// +/// Copyright © 2016-2019 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 {HomeLinksComponent} from './home-links.component'; +import {Authority} from '@shared/models/authority.enum'; + +const routes: Routes = [ + { + path: 'home', + component: HomeLinksComponent, + data: { + auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], + title: 'home.home', + breadcrumb: { + label: 'home.home', + icon: 'home' + } + } + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class HomeLinksRoutingModule { } 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 new file mode 100644 index 0000000000..43724776db --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/home-links/home-links.component.html @@ -0,0 +1,37 @@ + + + + + + {{section.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 new file mode 100644 index 0000000000..6d103ff1a9 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/home-links/home-links.component.scss @@ -0,0 +1,74 @@ +/** + * Copyright © 2016-2019 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 ::ng-deep { + .tb-home-links { + .mat-headline { + font-size: 20px; + @media #{$mat-gt-xmd} { + font-size: 24px; + } + } + mat-card { + padding: 0; + margin: 8px; + mat-card-title { + margin: 0; + padding: 24px 16px 16px; + } + mat-card-title+mat-card-content { + padding-top: 0; + } + mat-card-content { + padding: 16px; + } + } + button.tb-card-button { + width: 100%; + height: 100%; + max-width: 240px; + .mat-button-wrapper { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + mat-icon { + margin: auto; + } + span { + height: 18px; + min-height: 18px; + max-height: 18px; + padding: 0 0 20px 0; + margin: auto; + font-size: 18px; + font-weight: 400; + line-height: 18px; + white-space: normal; + } + } + &.mat-raised-button.mat-primary { + .mat-ripple-element { + opacity: 0.3; + background-color: rgba(255, 255, 255, 0.3); + } + } + } + } +} + diff --git a/ui-ngx/src/app/modules/home/pages/home-links/home-links.component.ts b/ui-ngx/src/app/modules/home/pages/home-links/home-links.component.ts new file mode 100644 index 0000000000..f8f538f52b --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/home-links/home-links.component.ts @@ -0,0 +1,69 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, OnInit } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { PageComponent } from '@shared/components/page.component'; +import { MenuService } from '@core/services/menu.service'; +import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; +import { MediaBreakpoints } from '@shared/models/constants'; +import { HomeSection } from '@core/services/menu.models'; + +@Component({ + selector: 'tb-home-links', + templateUrl: './home-links.component.html', + styleUrls: ['./home-links.component.scss'] +}) +export class HomeLinksComponent implements OnInit { + + homeSections$ = this.menuService.homeSections(); + + cols = 2; + + constructor(private menuService: MenuService, + public breakpointObserver: BreakpointObserver) { + } + + ngOnInit() { + this.updateColumnCount(); + this.breakpointObserver + .observe([MediaBreakpoints.lg, MediaBreakpoints['gt-lg']]) + .subscribe((state: BreakpointState) => this.updateColumnCount()); + } + + private updateColumnCount() { + this.cols = 2; + if (this.breakpointObserver.isMatched(MediaBreakpoints.lg)) { + this.cols = 3; + } + if (this.breakpointObserver.isMatched(MediaBreakpoints['gt-lg'])) { + this.cols = 4; + } + } + + sectionColspan(section: HomeSection): number { + if (this.breakpointObserver.isMatched(MediaBreakpoints['gt-sm'])) { + let colspan = this.cols; + if (section && section.places && section.places.length <= colspan) { + colspan = section.places.length; + } + return colspan; + } else { + return 2; + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/home-links/home-links.module.ts b/ui-ngx/src/app/modules/home/pages/home-links/home-links.module.ts new file mode 100644 index 0000000000..66e11f093b --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/home-links/home-links.module.ts @@ -0,0 +1,35 @@ +/// +/// Copyright © 2016-2019 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 { CommonModule } from '@angular/common'; + +import { HomeLinksRoutingModule } from './home-links-routing.module'; +import { HomeLinksComponent} from './home-links.component'; +import { SharedModule } from '@app/shared/shared.module'; + +@NgModule({ + declarations: + [ + HomeLinksComponent + ], + imports: [ + CommonModule, + SharedModule, + HomeLinksRoutingModule + ] +}) +export class HomeLinksModule { } 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 new file mode 100644 index 0000000000..cbd37fcc65 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/home-pages.module.ts @@ -0,0 +1,36 @@ +/// +/// Copyright © 2016-2019 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 { AdminModule } from './admin/admin.module'; +import { HomeLinksModule } from './home-links/home-links.module'; +// import { ProfileModule } from './profile/profile.module'; +// import { CustomerModule } from '@modules/home/pages/customer/customer.module'; +// import { AuditLogModule } from '@modules/home/pages/audit-log/audit-log.module'; +// import { UserModule } from '@modules/home/pages/user/user.module'; + +@NgModule({ + exports: [ +// AdminModule, + HomeLinksModule, +// ProfileModule, +// CustomerModule, +// AuditLogModule, +// UserModule + ] +}) +export class HomePagesModule { } diff --git a/ui-ngx/src/app/modules/login/login-routing.module.ts b/ui-ngx/src/app/modules/login/login-routing.module.ts new file mode 100644 index 0000000000..3af7f4dd1d --- /dev/null +++ b/ui-ngx/src/app/modules/login/login-routing.module.ts @@ -0,0 +1,69 @@ +/// +/// Copyright © 2016-2019 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 { Routes, RouterModule } from '@angular/router'; + +import { LoginComponent } from './pages/login/login.component'; +import { AuthGuard } from '../../core/guards/auth.guard'; +import { ResetPasswordRequestComponent } from '@modules/login/pages/login/reset-password-request.component'; +import { ResetPasswordComponent } from '@modules/login/pages/login/reset-password.component'; +import { CreatePasswordComponent } from '@modules/login/pages/login/create-password.component'; + +const routes: Routes = [ + { + path: 'login', + component: LoginComponent, + data: { + title: 'login.login', + module: 'public' + }, + canActivate: [AuthGuard] + }, + { + path: 'login/resetPasswordRequest', + component: ResetPasswordRequestComponent, + data: { + title: 'login.request-password-reset', + module: 'public' + }, + canActivate: [AuthGuard] + }, + { + path: 'login/resetPassword', + component: ResetPasswordComponent, + data: { + title: 'login.reset-password', + module: 'public' + }, + canActivate: [AuthGuard] + }, + { + path: 'login/createPassword', + component: CreatePasswordComponent, + data: { + title: 'login.create-password', + module: 'public' + }, + canActivate: [AuthGuard] + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class LoginRoutingModule { } diff --git a/ui-ngx/src/app/modules/login/login.module.ts b/ui-ngx/src/app/modules/login/login.module.ts new file mode 100644 index 0000000000..5ed9e66eb6 --- /dev/null +++ b/ui-ngx/src/app/modules/login/login.module.ts @@ -0,0 +1,40 @@ +/// +/// Copyright © 2016-2019 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 { CommonModule } from '@angular/common'; + +import { LoginRoutingModule } from './login-routing.module'; +import { LoginComponent } from './pages/login/login.component'; +import { SharedModule } from '@app/shared/shared.module'; +import { ResetPasswordRequestComponent } from '@modules/login/pages/login/reset-password-request.component'; +import { ResetPasswordComponent } from '@modules/login/pages/login/reset-password.component'; +import { CreatePasswordComponent } from '@modules/login/pages/login/create-password.component'; + +@NgModule({ + declarations: [ + LoginComponent, + ResetPasswordRequestComponent, + ResetPasswordComponent, + CreatePasswordComponent + ], + imports: [ + CommonModule, + SharedModule, + LoginRoutingModule + ] +}) +export class LoginModule { } diff --git a/ui-ngx/src/app/modules/login/pages/login/create-password.component.html b/ui-ngx/src/app/modules/login/pages/login/create-password.component.html new file mode 100644 index 0000000000..739bb9cf4e --- /dev/null +++ b/ui-ngx/src/app/modules/login/pages/login/create-password.component.html @@ -0,0 +1,56 @@ + +
+ + + login.create-password + + + + + +
+
+
+ + + common.password + + lock + + + login.password-again + + lock + +
+ + +
+
+
+
+
+
+
diff --git a/ui-ngx/src/app/modules/login/pages/login/create-password.component.scss b/ui-ngx/src/app/modules/login/pages/login/create-password.component.scss new file mode 100644 index 0000000000..8f10478802 --- /dev/null +++ b/ui-ngx/src/app/modules/login/pages/login/create-password.component.scss @@ -0,0 +1,29 @@ +/** + * Copyright © 2016-2019 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 { + display: flex; + flex: 1 1 0%; + .tb-create-password-content { + background-color: #eee; + .tb-create-password-card { + @media #{$mat-gt-sm} { + width: 450px !important; + } + } + } +} diff --git a/ui-ngx/src/app/modules/login/pages/login/create-password.component.ts b/ui-ngx/src/app/modules/login/pages/login/create-password.component.ts new file mode 100644 index 0000000000..2e471db8a3 --- /dev/null +++ b/ui-ngx/src/app/modules/login/pages/login/create-password.component.ts @@ -0,0 +1,76 @@ +/// +/// Copyright © 2016-2019 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, OnDestroy, OnInit } from '@angular/core'; +import { AuthService } from '../../../../core/auth/auth.service'; +import { LoginRequest } from '../../../../shared/models/login.models'; +import { Store } from '@ngrx/store'; +import { AppState } from '../../../../core/core.state'; +import { PageComponent } from '../../../../shared/components/page.component'; +import { FormBuilder } from '@angular/forms'; +import { ActionNotificationShow } from '@core/notification/notification.actions'; +import { TranslateService } from '@ngx-translate/core'; +import { ActivatedRoute, ActivatedRouteSnapshot } from '@angular/router'; +import { Observable, Subscription } from 'rxjs'; +import { map } from 'rxjs/operators'; + +@Component({ + selector: 'tb-create-password', + templateUrl: './create-password.component.html', + styleUrls: ['./create-password.component.scss'] +}) +export class CreatePasswordComponent extends PageComponent implements OnInit, OnDestroy { + + activateToken = ''; + sub: Subscription; + + createPassword = this.fb.group({ + password: [''], + password2: [''] + }); + + constructor(protected store: Store, + private route: ActivatedRoute, + private authService: AuthService, + private translate: TranslateService, + public fb: FormBuilder) { + super(store); + } + + ngOnInit() { + this.sub = this.route + .queryParams + .subscribe(params => { + this.activateToken = params.activateToken || ''; + }); + } + + ngOnDestroy(): void { + super.ngOnDestroy(); + this.sub.unsubscribe(); + } + + onCreatePassword() { + if (this.createPassword.get('password').value !== this.createPassword.get('password2').value) { + this.store.dispatch(new ActionNotificationShow({ message: this.translate.instant('login.passwords-mismatch-error'), + type: 'error' })); + } else { + this.authService.activate( + this.activateToken, + this.createPassword.get('password').value).subscribe(); + } + } +} diff --git a/ui-ngx/src/app/modules/login/pages/login/login.component.html b/ui-ngx/src/app/modules/login/pages/login/login.component.html new file mode 100644 index 0000000000..5d9a1b7e5d --- /dev/null +++ b/ui-ngx/src/app/modules/login/pages/login/login.component.html @@ -0,0 +1,61 @@ + + diff --git a/ui-ngx/src/app/modules/login/pages/login/login.component.scss b/ui-ngx/src/app/modules/login/pages/login/login.component.scss new file mode 100644 index 0000000000..ea75abdce6 --- /dev/null +++ b/ui-ngx/src/app/modules/login/pages/login/login.component.scss @@ -0,0 +1,32 @@ +/** + * Copyright © 2016-2019 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 { + display: flex; + flex: 1 1 0%; + .tb-login-content { + margin-top: 36px; + margin-bottom: 76px; + background-color: rgb(250,250,250); + .tb-login-form { + @media #{$mat-gt-sm} { + width: 550px !important; + } + } + } +} + diff --git a/ui-ngx/src/app/modules/login/pages/login/login.component.ts b/ui-ngx/src/app/modules/login/pages/login/login.component.ts new file mode 100644 index 0000000000..ca75c1f4b8 --- /dev/null +++ b/ui-ngx/src/app/modules/login/pages/login/login.component.ts @@ -0,0 +1,54 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, OnInit } from '@angular/core'; +import { AuthService } from '../../../../core/auth/auth.service'; +import { LoginRequest } from '../../../../shared/models/login.models'; +import { Store } from '@ngrx/store'; +import { AppState } from '../../../../core/core.state'; +import { PageComponent } from '../../../../shared/components/page.component'; +import { FormBuilder } from '@angular/forms'; + +@Component({ + selector: 'tb-login', + templateUrl: './login.component.html', + styleUrls: ['./login.component.scss'] +}) +export class LoginComponent extends PageComponent implements OnInit { + + loginFormGroup = this.fb.group(new LoginRequest('', '')); + + constructor(protected store: Store, + private authService: AuthService, + public fb: FormBuilder) { + super(store); + } + + ngOnInit() { + } + + login(): void { + if (this.loginFormGroup.valid) { + this.authService.login(this.loginFormGroup.value).subscribe(); + } else { + Object.keys(this.loginFormGroup.controls).forEach(field => { + const control = this.loginFormGroup.get(field); + control.markAsTouched({onlySelf: true}); + }); + } + } + +} diff --git a/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.html b/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.html new file mode 100644 index 0000000000..e1f5e85036 --- /dev/null +++ b/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.html @@ -0,0 +1,54 @@ + +
+ + + login.request-password-reset + + + + + +
+
+
+ + + login.email + + email + + {{ 'user.invalid-email-format' | translate }} + + +
+ + +
+
+
+
+
+
+
diff --git a/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.scss b/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.scss new file mode 100644 index 0000000000..5f9c8b0258 --- /dev/null +++ b/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.scss @@ -0,0 +1,29 @@ +/** + * Copyright © 2016-2019 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 { + display: flex; + flex: 1 1 0%; + .tb-request-password-reset-content { + background-color: #eee; + .tb-request-password-reset-card { + @media #{$mat-gt-sm} { + width: 450px !important; + } + } + } +} diff --git a/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.ts b/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.ts new file mode 100644 index 0000000000..b8ac67df3f --- /dev/null +++ b/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.ts @@ -0,0 +1,57 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, OnInit } from '@angular/core'; +import { AuthService } from '../../../../core/auth/auth.service'; +import { LoginRequest } from '../../../../shared/models/login.models'; +import { Store } from '@ngrx/store'; +import { AppState } from '../../../../core/core.state'; +import { PageComponent } from '../../../../shared/components/page.component'; +import { FormBuilder } from '@angular/forms'; +import { ActionNotificationShow } from '@core/notification/notification.actions'; +import { TranslateService } from '@ngx-translate/core'; + +@Component({ + selector: 'tb-reset-password-request', + templateUrl: './reset-password-request.component.html', + styleUrls: ['./reset-password-request.component.scss'] +}) +export class ResetPasswordRequestComponent extends PageComponent implements OnInit { + + requestPasswordRequest = this.fb.group({ + email: [''] + }); + + constructor(protected store: Store, + private authService: AuthService, + private translate: TranslateService, + public fb: FormBuilder) { + super(store); + } + + ngOnInit() { + } + + sendResetPasswordLink() { + this.authService.sendResetPasswordLink(this.requestPasswordRequest.get('email').value).subscribe( + () => { + this.store.dispatch(new ActionNotificationShow({ message: this.translate.instant('login.password-link-sent-message'), + type: 'success' })); + } + ); + } + +} diff --git a/ui-ngx/src/app/modules/login/pages/login/reset-password.component.html b/ui-ngx/src/app/modules/login/pages/login/reset-password.component.html new file mode 100644 index 0000000000..6e7ddcee49 --- /dev/null +++ b/ui-ngx/src/app/modules/login/pages/login/reset-password.component.html @@ -0,0 +1,56 @@ + +
+ + + login.password-reset + + + + + +
+
+
+ + + login.new-password + + lock + + + login.new-password-again + + lock + +
+ + +
+
+
+
+
+
+
diff --git a/ui-ngx/src/app/modules/login/pages/login/reset-password.component.scss b/ui-ngx/src/app/modules/login/pages/login/reset-password.component.scss new file mode 100644 index 0000000000..4bcec045e9 --- /dev/null +++ b/ui-ngx/src/app/modules/login/pages/login/reset-password.component.scss @@ -0,0 +1,29 @@ +/** + * Copyright © 2016-2019 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 { + display: flex; + flex: 1 1 0%; + .tb-reset-password-content { + background-color: #eee; + .tb-reset-password-card { + @media #{$mat-gt-sm} { + width: 450px !important; + } + } + } +} diff --git a/ui-ngx/src/app/modules/login/pages/login/reset-password.component.ts b/ui-ngx/src/app/modules/login/pages/login/reset-password.component.ts new file mode 100644 index 0000000000..6d97bbe2dd --- /dev/null +++ b/ui-ngx/src/app/modules/login/pages/login/reset-password.component.ts @@ -0,0 +1,76 @@ +/// +/// Copyright © 2016-2019 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, OnDestroy, OnInit } from '@angular/core'; +import { AuthService } from '../../../../core/auth/auth.service'; +import { LoginRequest } from '../../../../shared/models/login.models'; +import { Store } from '@ngrx/store'; +import { AppState } from '../../../../core/core.state'; +import { PageComponent } from '../../../../shared/components/page.component'; +import { FormBuilder } from '@angular/forms'; +import { ActionNotificationShow } from '@core/notification/notification.actions'; +import { TranslateService } from '@ngx-translate/core'; +import { ActivatedRoute } from '@angular/router'; +import { Observable, Subscription } from 'rxjs'; +import { map } from 'rxjs/operators'; + +@Component({ + selector: 'tb-reset-password', + templateUrl: './reset-password.component.html', + styleUrls: ['./reset-password.component.scss'] +}) +export class ResetPasswordComponent extends PageComponent implements OnInit, OnDestroy { + + resetToken = ''; + sub: Subscription; + + resetPassword = this.fb.group({ + newPassword: [''], + newPassword2: [''] + }); + + constructor(protected store: Store, + private route: ActivatedRoute, + private authService: AuthService, + private translate: TranslateService, + public fb: FormBuilder) { + super(store); + } + + ngOnInit() { + this.sub = this.route + .queryParams + .subscribe(params => { + this.resetToken = params.resetToken || ''; + }); + } + + ngOnDestroy(): void { + super.ngOnDestroy(); + this.sub.unsubscribe(); + } + + onResetPassword() { + if (this.resetPassword.get('newPassword').value !== this.resetPassword.get('newPassword2').value) { + this.store.dispatch(new ActionNotificationShow({ message: this.translate.instant('login.passwords-mismatch-error'), + type: 'error' })); + } else { + this.authService.resetPassword( + this.resetToken, + this.resetPassword.get('newPassword').value).subscribe(); + } + } +} diff --git a/ui-ngx/src/app/shared/components/breadcrumb.component.html b/ui-ngx/src/app/shared/components/breadcrumb.component.html new file mode 100644 index 0000000000..2a2de92b2b --- /dev/null +++ b/ui-ngx/src/app/shared/components/breadcrumb.component.html @@ -0,0 +1,39 @@ + +
+

{{ (lastBreadcrumb$ | async).label | translate }}

+ + + + + + {{ breadcrumb.icon }} + + {{ breadcrumb.label | translate }} + + + + + + {{ breadcrumb.icon }} + + {{ breadcrumb.label | translate }} + + > + +
diff --git a/ui-ngx/src/app/shared/components/breadcrumb.component.scss b/ui-ngx/src/app/shared/components/breadcrumb.component.scss new file mode 100644 index 0000000000..36ad8fcd91 --- /dev/null +++ b/ui-ngx/src/app/shared/components/breadcrumb.component.scss @@ -0,0 +1,59 @@ +/** + * Copyright © 2016-2019 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 { + display: flex; + flex-direction: row; + align-items: center; + min-width: 0; + flex: 1; + + .tb-breadcrumb { + font-size: 18px !important; + font-weight: 400 !important; + + h1, + a, + span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + h1 { + font-size: 24px !important; + font-weight: 400 !important; + } + + a { + border: none; + opacity: .75; + transition: opacity .35s; + color: inherit; + text-decoration: none; + } + + a:hover, + a:focus { + text-decoration: none !important; + border: none; + opacity: 1; + } + + .divider { + padding: 0 30px; + } + } +} diff --git a/ui-ngx/src/app/shared/components/breadcrumb.component.ts b/ui-ngx/src/app/shared/components/breadcrumb.component.ts new file mode 100644 index 0000000000..3e7fb18adc --- /dev/null +++ b/ui-ngx/src/app/shared/components/breadcrumb.component.ts @@ -0,0 +1,82 @@ +/// +/// Copyright © 2016-2019 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, OnDestroy, OnInit } from '@angular/core'; +import { BehaviorSubject, Subject } from 'rxjs'; +import { BreadCrumb } from './breadcrumb'; +import { ActivatedRoute, ActivatedRouteSnapshot, NavigationEnd, Router } from '@angular/router'; +import { distinctUntilChanged, filter, map } from 'rxjs/operators'; + +@Component({ + selector: '[tb-breadcrumb]', + templateUrl: './breadcrumb.component.html', + styleUrls: ['./breadcrumb.component.scss'] +}) +export class BreadcrumbComponent implements OnInit, OnDestroy { + + breadcrumbs$: Subject> = new BehaviorSubject>(this.buildBreadCrumbs(this.activatedRoute.snapshot)); + + routerEventsSubscription = this.router.events.pipe( + filter((event) => event instanceof NavigationEnd ), + distinctUntilChanged(), + map( () => this.buildBreadCrumbs(this.activatedRoute.snapshot) ) + ).subscribe(breadcrumns => this.breadcrumbs$.next(breadcrumns) ); + + lastBreadcrumb$ = this.breadcrumbs$.pipe( + map( breadcrumbs => breadcrumbs[breadcrumbs.length - 1]) + ); + + constructor(private router: Router, + private activatedRoute: ActivatedRoute) { + } + + ngOnInit(): void { + } + + ngOnDestroy(): void { + if (this.routerEventsSubscription) { + this.routerEventsSubscription.unsubscribe(); + } + } + + + buildBreadCrumbs(route: ActivatedRouteSnapshot, breadcrumbs: Array = []): Array { + let newBreadcrumbs = breadcrumbs; + if (route.routeConfig && route.routeConfig.data) { + const breadcrumbData = route.routeConfig.data.breadcrumb; + if (breadcrumbData && !breadcrumbData.skip) { + const label = breadcrumbData.label || 'home.home'; + const icon = breadcrumbData.icon || 'home'; + const isMdiIcon = icon.startsWith('mdi:'); + const link = [ '/' + route.url.join('') ]; + const queryParams = route.queryParams; + const breadcrumb = { + label, + icon, + isMdiIcon, + link, + queryParams + }; + newBreadcrumbs = [...breadcrumbs, breadcrumb]; + } + } + if (route.firstChild) { + return this.buildBreadCrumbs(route.firstChild, newBreadcrumbs); + } + return newBreadcrumbs; + } + +} diff --git a/ui-ngx/src/app/shared/components/breadcrumb.ts b/ui-ngx/src/app/shared/components/breadcrumb.ts new file mode 100644 index 0000000000..0986bb4715 --- /dev/null +++ b/ui-ngx/src/app/shared/components/breadcrumb.ts @@ -0,0 +1,26 @@ +/// +/// Copyright © 2016-2019 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 { Params } from '@angular/router'; + +export interface BreadCrumb { + label: string; + icon: string; + isMdiIcon: boolean; + link: any[]; + queryParams: Params; +} + diff --git a/ui-ngx/src/app/shared/components/footer.component.html b/ui-ngx/src/app/shared/components/footer.component.html new file mode 100644 index 0000000000..828d60e573 --- /dev/null +++ b/ui-ngx/src/app/shared/components/footer.component.html @@ -0,0 +1,20 @@ + + diff --git a/ui-ngx/src/app/shared/components/footer.component.scss b/ui-ngx/src/app/shared/components/footer.component.scss new file mode 100644 index 0000000000..8962a13327 --- /dev/null +++ b/ui-ngx/src/app/shared/components/footer.component.scss @@ -0,0 +1,28 @@ +/** + * Copyright © 2016-2019 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. + */ +.footer-text { + position: absolute; + width: 100%; + bottom: 20px; + margin: 0; + left: 0; + line-height: 20px; + text-align: center; + small { + font-size: 14px; + color: #98a6ad; + } +} diff --git a/ui-ngx/src/app/shared/components/footer.component.ts b/ui-ngx/src/app/shared/components/footer.component.ts new file mode 100644 index 0000000000..3bebc95a2f --- /dev/null +++ b/ui-ngx/src/app/shared/components/footer.component.ts @@ -0,0 +1,28 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; + +@Component({ + selector: 'tb-footer', + templateUrl: './footer.component.html', + styleUrls: ['./footer.component.scss'] +}) +export class FooterComponent { + + year = new Date().getFullYear(); + +} diff --git a/ui-ngx/src/app/shared/components/fullscreen.directive.ts b/ui-ngx/src/app/shared/components/fullscreen.directive.ts new file mode 100644 index 0000000000..a738ad4654 --- /dev/null +++ b/ui-ngx/src/app/shared/components/fullscreen.directive.ts @@ -0,0 +1,99 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + Directive, + ElementRef, + EventEmitter, + Input, + Output, + ViewContainerRef +} from '@angular/core'; +import { Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; +import { ComponentPortal } from '@angular/cdk/portal'; +import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; + +@Directive({ + selector: '[tb-fullscreen]' +}) +export class FullscreenDirective { + + fullscreenValue = false; + + private overlayRef: OverlayRef; + private parentElement: HTMLElement; + + @Input() + set fullscreen(fullscreen: boolean) { + if (this.fullscreenValue !== fullscreen) { + this.fullscreenValue = fullscreen; + if (this.fullscreenValue) { + this.enterFullscreen(); + } else { + this.exitFullscreen(); + } + } + } + + @Output() + fullscreenChanged = new EventEmitter(); + + constructor(public elementRef: ElementRef, + private viewContainerRef: ViewContainerRef, + private overlay: Overlay) { + + } + + enterFullscreen() { + this.parentElement = this.elementRef.nativeElement.parentElement; + this.parentElement.removeChild(this.elementRef.nativeElement); + this.elementRef.nativeElement.classList.add('tb-fullscreen'); + const position = this.overlay.position(); + const config = new OverlayConfig({ + hasBackdrop: false, + panelClass: 'tb-fullscreen-parent' + }); + config.minWidth = '100%'; + config.minHeight = '100%'; + config.positionStrategy = position.global().top('0%').left('0%') + .right('0%').bottom('0%'); + + this.overlayRef = this.overlay.create(config); + this.overlayRef.attach(new EmptyPortal()); + this.overlayRef.overlayElement.append( this.elementRef.nativeElement ); + this.fullscreenChanged.emit(true); + } + + exitFullscreen() { + if (this.parentElement) { + this.overlayRef.overlayElement.removeChild( this.elementRef.nativeElement ); + this.parentElement.append( this.elementRef.nativeElement ); + this.parentElement = null; + } + this.elementRef.nativeElement.classList.remove('tb-fullscreen'); + this.overlayRef.dispose(); + this.fullscreenChanged.emit(false); + } + +} + +class EmptyPortal extends ComponentPortal { + + constructor() { + super(TbAnchorComponent); + } + +} diff --git a/ui-ngx/src/app/shared/components/help.component.html b/ui-ngx/src/app/shared/components/help.component.html new file mode 100644 index 0000000000..2f319e097c --- /dev/null +++ b/ui-ngx/src/app/shared/components/help.component.html @@ -0,0 +1,24 @@ + + diff --git a/ui-ngx/src/app/shared/components/help.component.ts b/ui-ngx/src/app/shared/components/help.component.ts new file mode 100644 index 0000000000..0bc85834d4 --- /dev/null +++ b/ui-ngx/src/app/shared/components/help.component.ts @@ -0,0 +1,40 @@ +/// +/// Copyright © 2016-2019 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, Input } from '@angular/core'; +import { HelpLinks } from '@shared/models/constants'; + +@Component({ + selector: '[tb-help]', + templateUrl: './help.component.html' +}) +export class HelpComponent { + + // tslint:disable-next-line:no-input-rename + @Input('tb-help') helpLinkId: string; + + gotoHelpPage(): void { + let helpUrl = HelpLinks.linksMap[this.helpLinkId]; + if (!helpUrl && this.helpLinkId && + (this.helpLinkId.startsWith('http://') || this.helpLinkId.startsWith('https://'))) { + helpUrl = this.helpLinkId; + } + if (helpUrl) { + window.open(helpUrl, '_blank'); + } + } + +} diff --git a/ui-ngx/src/app/shared/components/logo.component.html b/ui-ngx/src/app/shared/components/logo.component.html new file mode 100644 index 0000000000..e734b23b34 --- /dev/null +++ b/ui-ngx/src/app/shared/components/logo.component.html @@ -0,0 +1,19 @@ + + diff --git a/ui-ngx/src/app/shared/components/logo.component.scss b/ui-ngx/src/app/shared/components/logo.component.scss new file mode 100644 index 0000000000..c72149fa1e --- /dev/null +++ b/ui-ngx/src/app/shared/components/logo.component.scss @@ -0,0 +1,29 @@ +/** + * Copyright © 2016-2019 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-context(.login-logo) { + img.tb-logo-title { + width: 280px; + height: 60px; + text-decoration: none; + cursor: pointer; + border: none; + transform: none; + + &:focus { + outline: 0; + } + } +} diff --git a/ui-ngx/src/app/shared/components/logo.component.ts b/ui-ngx/src/app/shared/components/logo.component.ts new file mode 100644 index 0000000000..40b91925fa --- /dev/null +++ b/ui-ngx/src/app/shared/components/logo.component.ts @@ -0,0 +1,32 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; + +@Component({ + selector: 'tb-logo', + templateUrl: './logo.component.html', + styleUrls: ['./logo.component.scss'] +}) +export class LogoComponent { + + logo = require('../../../assets/logo_title_white.svg'); + + gotoThingsboard(): void { + window.open('https://thingsboard.io', '_blank'); + } + +} diff --git a/ui-ngx/src/app/shared/components/page.component.ts b/ui-ngx/src/app/shared/components/page.component.ts new file mode 100644 index 0000000000..3ed33e3e5f --- /dev/null +++ b/ui-ngx/src/app/shared/components/page.component.ts @@ -0,0 +1,56 @@ +/// +/// Copyright © 2016-2019 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 { OnDestroy } from '@angular/core'; +import { select, Store } from '@ngrx/store'; +import { AppState } from '../../core/core.state'; +import { Observable, Subscription } from 'rxjs'; +import { selectIsLoading } from '../../core/interceptors/load.selectors'; +import { delay } from 'rxjs/operators'; +import { AbstractControl } from '@angular/forms'; + +export abstract class PageComponent implements OnDestroy { + + isLoading$: Observable; + loadingSubscription: Subscription; + disabledOnLoadFormControls: Array = []; + + protected constructor(protected store: Store) { + this.isLoading$ = this.store.pipe(delay(0), select(selectIsLoading), delay(100)); + } + + protected registerDisableOnLoadFormControl(control: AbstractControl) { + this.disabledOnLoadFormControls.push(control); + if (!this.loadingSubscription) { + this.loadingSubscription = this.isLoading$.subscribe((isLoading) => { + for (const formControl of this.disabledOnLoadFormControls) { + if (isLoading) { + formControl.disable({emitEvent: false}); + } else { + formControl.enable({emitEvent: false}); + } + } + }); + } + } + + ngOnDestroy(): void { + if (this.loadingSubscription) { + this.loadingSubscription.unsubscribe(); + } + } + +} diff --git a/ui-ngx/src/app/shared/components/snack-bar-component.html b/ui-ngx/src/app/shared/components/snack-bar-component.html new file mode 100644 index 0000000000..4dabc33d75 --- /dev/null +++ b/ui-ngx/src/app/shared/components/snack-bar-component.html @@ -0,0 +1,26 @@ + +
+
+ +
diff --git a/ui-ngx/src/app/shared/components/snack-bar-component.scss b/ui-ngx/src/app/shared/components/snack-bar-component.scss new file mode 100644 index 0000000000..32926b5748 --- /dev/null +++ b/ui-ngx/src/app/shared/components/snack-bar-component.scss @@ -0,0 +1,43 @@ +/** + * Copyright © 2016-2019 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 { + display: inline-block; + pointer-events: all; + .tb-toast { + box-shadow: 0px 3px 5px -1px rgba(0, 0, 0, 0.2), 0px 6px 10px 0px rgba(0, 0, 0, 0.14), 0px 1px 18px 0px rgba(0, 0, 0, 0.12); + color: #fff; + font-size: 18px; + border-radius: 4px; + padding: 0px 18px; + margin: 8px; + .toast-text { + padding: 0px 6px; + width: 100%; + } + button { + margin: 6px 0px 6px 12px; + } + &.info-toast { + background: #323232; + } + &.error-toast { + background: #800000; + } + &.success-toast { + background: #008000; + } + } +} diff --git a/ui-ngx/src/app/shared/components/tb-anchor.component.ts b/ui-ngx/src/app/shared/components/tb-anchor.component.ts new file mode 100644 index 0000000000..39f4a8b27a --- /dev/null +++ b/ui-ngx/src/app/shared/components/tb-anchor.component.ts @@ -0,0 +1,25 @@ +/// +/// Copyright © 2016-2019 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, ViewContainerRef } from '@angular/core'; + +@Component({ + selector: 'tb-anchor', + template: '' +}) +export class TbAnchorComponent { + constructor(public viewContainerRef: ViewContainerRef) { } +} diff --git a/ui-ngx/src/app/shared/components/tb-checkbox.component.html b/ui-ngx/src/app/shared/components/tb-checkbox.component.html new file mode 100644 index 0000000000..d668c08ace --- /dev/null +++ b/ui-ngx/src/app/shared/components/tb-checkbox.component.html @@ -0,0 +1,24 @@ + + + + diff --git a/ui-ngx/src/app/shared/components/tb-checkbox.component.ts b/ui-ngx/src/app/shared/components/tb-checkbox.component.ts new file mode 100644 index 0000000000..744ad821a6 --- /dev/null +++ b/ui-ngx/src/app/shared/components/tb-checkbox.component.ts @@ -0,0 +1,74 @@ +/// +/// Copyright © 2016-2019 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, forwardRef, Input, Output } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; + +@Component({ + selector: 'tb-checkbox', + templateUrl: './tb-checkbox.component.html', + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => TbCheckboxComponent), + multi: true + } + ] +}) +export class TbCheckboxComponent implements ControlValueAccessor { + + innerValue: boolean; + + @Input() disabled: boolean; + @Input() trueValue: any = true; + @Input() falseValue: any = false; + @Output() valueChange = new EventEmitter(); + + private propagateChange = (_: any) => {}; + + onHostChange(ev) { + this.propagateChange(ev.checked ? this.trueValue : this.falseValue); + } + + modelChange($event) { + if ($event) { + this.innerValue = true; + this.valueChange.emit(this.trueValue); + } else { + this.innerValue = false; + this.valueChange.emit(this.falseValue); + } + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + writeValue(obj: any): void { + if (obj === this.trueValue) { + this.innerValue = true; + } else { + this.innerValue = false; + } + } +} diff --git a/ui-ngx/src/app/shared/components/toast.directive.ts b/ui-ngx/src/app/shared/components/toast.directive.ts new file mode 100644 index 0000000000..1d6c9ed24d --- /dev/null +++ b/ui-ngx/src/app/shared/components/toast.directive.ts @@ -0,0 +1,154 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + AfterViewInit, + Component, + Directive, + ElementRef, + Inject, Input, + OnDestroy, + ViewContainerRef +} from '@angular/core'; +import { + MAT_SNACK_BAR_DATA, + MatSnackBar, + MatSnackBarConfig, + MatSnackBarRef +} from '@angular/material'; +import { NotificationMessage } from '@app/core/notification/notification.models'; +import { onParentScrollOrWindowResize } from '@app/core/utils'; +import { Subscription } from 'rxjs'; +import { NotificationService } from '@app/core/services/notification.service'; +import { BreakpointObserver } from '@angular/cdk/layout'; +import { MediaBreakpoints } from '@shared/models/constants'; + +@Directive({ + selector: '[tb-toast]' +}) +export class ToastDirective implements AfterViewInit, OnDestroy { + + @Input() + toastTarget = 'root'; + + private notificationSubscription: Subscription = null; + + constructor(public elementRef: ElementRef, + public viewContainerRef: ViewContainerRef, + private notificationService: NotificationService, + public snackBar: MatSnackBar, + private breakpointObserver: BreakpointObserver) { + } + + ngAfterViewInit(): void { + const toastComponent = this; + + this.notificationSubscription = this.notificationService.getNotification().subscribe( + (notificationMessage) => { + if (notificationMessage && notificationMessage.message) { + const target = notificationMessage.target || 'root'; + if (this.toastTarget === target) { + const data = { + parent: this.elementRef, + notification: notificationMessage + }; + const isGtSm = this.breakpointObserver.isMatched(MediaBreakpoints['gt-sm']); + const config: MatSnackBarConfig = { + horizontalPosition: notificationMessage.horizontalPosition || 'left', + verticalPosition: !isGtSm ? 'bottom' : (notificationMessage.verticalPosition || 'top'), + viewContainerRef: toastComponent.viewContainerRef, + duration: notificationMessage.duration, + data + }; + this.snackBar.openFromComponent(TbSnackBarComponent, config); + } + } + } + ); + } + + ngOnDestroy(): void { + if (this.notificationSubscription) { + this.notificationSubscription.unsubscribe(); + } + } +} + +@Component({ + selector: 'tb-snack-bar-component', + templateUrl: 'snack-bar-component.html', + styleUrls: ['snack-bar-component.scss'] +}) +export class TbSnackBarComponent implements AfterViewInit, OnDestroy { + private parentEl: HTMLElement; + private snackBarContainerEl: HTMLElement; + private parentScrollSubscription: Subscription = null; + public notification: NotificationMessage; + constructor(@Inject(MAT_SNACK_BAR_DATA) public data: any, private elementRef: ElementRef, + public snackBarRef: MatSnackBarRef) { + this.notification = data.notification; + } + + ngAfterViewInit() { + this.parentEl = this.data.parent.nativeElement; + this.snackBarContainerEl = this.elementRef.nativeElement.parentNode; + this.snackBarContainerEl.style.position = 'absolute'; + this.updateContainerRect(); + this.updatePosition(this.snackBarRef.containerInstance.snackBarConfig); + const snackBarComponent = this; + this.parentScrollSubscription = onParentScrollOrWindowResize(this.parentEl).subscribe(() => { + snackBarComponent.updateContainerRect(); + }); + } + + updatePosition(config: MatSnackBarConfig) { + const isRtl = config.direction === 'rtl'; + const isLeft = (config.horizontalPosition === 'left' || + (config.horizontalPosition === 'start' && !isRtl) || + (config.horizontalPosition === 'end' && isRtl)); + const isRight = !isLeft && config.horizontalPosition !== 'center'; + if (isLeft) { + this.snackBarContainerEl.style.justifyContent = 'flex-start'; + } else if (isRight) { + this.snackBarContainerEl.style.justifyContent = 'flex-end'; + } else { + this.snackBarContainerEl.style.justifyContent = 'center'; + } + if (config.verticalPosition === 'top') { + this.snackBarContainerEl.style.alignItems = 'flex-start'; + } else { + this.snackBarContainerEl.style.alignItems = 'flex-end'; + } + } + + ngOnDestroy() { + if (this.parentScrollSubscription) { + this.parentScrollSubscription.unsubscribe(); + } + } + + updateContainerRect() { + const viewportOffset = this.parentEl.getBoundingClientRect(); + this.snackBarContainerEl.style.top = viewportOffset.top + 'px'; + this.snackBarContainerEl.style.left = viewportOffset.left + 'px'; + this.snackBarContainerEl.style.width = viewportOffset.width + 'px'; + this.snackBarContainerEl.style.height = viewportOffset.height + 'px'; + } + + action(): void { + this.snackBarRef.dismissWithAction(); + } +} diff --git a/ui-ngx/src/app/shared/components/user-menu.component.html b/ui-ngx/src/app/shared/components/user-menu.component.html new file mode 100644 index 0000000000..4404091f1c --- /dev/null +++ b/ui-ngx/src/app/shared/components/user-menu.component.html @@ -0,0 +1,41 @@ + +
+ + + +
+ + +
+
+
diff --git a/ui-ngx/src/app/shared/components/user-menu.component.scss b/ui-ngx/src/app/shared/components/user-menu.component.scss new file mode 100644 index 0000000000..6db7757a8a --- /dev/null +++ b/ui-ngx/src/app/shared/components/user-menu.component.scss @@ -0,0 +1,50 @@ +/** + * Copyright © 2016-2019 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 { + div.tb-user-info { + line-height: 1.5; + + span { + text-align: left; + text-transform: none; + } + + span.tb-user-display-name { + font-size: .8rem; + font-weight: 300; + letter-spacing: .008em; + } + + span.tb-user-authority { + font-size: .8rem; + font-weight: 300; + letter-spacing: .005em; + opacity: .8; + } + + } + + mat-icon.tb-mini-avatar { + width: 36px; + height: 36px; + margin: auto 8px; + font-size: 36px; + } +} + +.tb-user-menu-items { + min-width: 256px; +} diff --git a/ui-ngx/src/app/shared/components/user-menu.component.ts b/ui-ngx/src/app/shared/components/user-menu.component.ts new file mode 100644 index 0000000000..7e41e7d230 --- /dev/null +++ b/ui-ngx/src/app/shared/components/user-menu.component.ts @@ -0,0 +1,116 @@ +/// +/// Copyright © 2016-2019 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, Input, OnDestroy, OnInit } from '@angular/core'; +import { User } from '@shared/models/user.model'; +import { Authority } from '@shared/models/authority.enum'; +import { select, Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { selectAuthUser, selectUserDetails } from '@core/auth/auth.selectors'; +import { map } from 'rxjs/operators'; +import { AuthService } from '@core/auth/auth.service'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'tb-user-menu', + templateUrl: './user-menu.component.html', + styleUrls: ['./user-menu.component.scss'] +}) +export class UserMenuComponent implements OnInit, OnDestroy { + + @Input() displayUserInfo: boolean; + + authorities = Authority; + + authority$ = this.store.pipe( + select(selectAuthUser), + map((authUser) => authUser ? authUser.authority : Authority.ANONYMOUS) + ); + + authorityName$ = this.store.pipe( + select(selectUserDetails), + map((user) => this.getAuthorityName(user)) + ); + + userDisplayName$ = this.store.pipe( + select(selectUserDetails), + map((user) => this.getUserDisplayName(user)) + ); + + constructor(private store: Store, + private router: Router, + private authService: AuthService) { + } + + ngOnInit(): void { + } + + ngOnDestroy(): void { + } + + getAuthorityName(user: User): string { + let name = null; + if (user) { + const authority = user.authority; + switch (authority) { + case Authority.SYS_ADMIN: + name = 'user.sys-admin'; + break; + case Authority.TENANT_ADMIN: + name = 'user.tenant-admin'; + break; + case Authority.CUSTOMER_USER: + name = 'user.customer'; + break; + } + } + return name; + } + + getUserDisplayName(user: User): string { + let name = ''; + if (user) { + if ((user.firstName && user.firstName.length > 0) || + (user.lastName && user.lastName.length > 0)) { + if (user.firstName) { + name += user.firstName; + } + if (user.lastName) { + if (name.length > 0) { + name += ' '; + } + name += user.lastName; + } + } else { + name = user.email; + } + } + return name; + } + + openProfile(): void { + this.router.navigate(['profile']); + } + + openCustomerProfile(): void { + this.router.navigate(['customerProfile']); + } + + logout(): void { + this.authService.logout(); + } + +} diff --git a/ui-ngx/src/app/shared/models/authority.enum.ts b/ui-ngx/src/app/shared/models/authority.enum.ts new file mode 100644 index 0000000000..568135a237 --- /dev/null +++ b/ui-ngx/src/app/shared/models/authority.enum.ts @@ -0,0 +1,23 @@ +/// +/// Copyright © 2016-2019 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 enum Authority { + SYS_ADMIN = 'SYS_ADMIN', + TENANT_ADMIN = 'TENANT_ADMIN', + CUSTOMER_USER = 'CUSTOMER_USER', + REFRESH_TOKEN = 'REFRESH_TOKEN', + ANONYMOUS = 'ANONYMOUS' +} diff --git a/ui-ngx/src/app/shared/models/base-data.ts b/ui-ngx/src/app/shared/models/base-data.ts new file mode 100644 index 0000000000..945f75a80c --- /dev/null +++ b/ui-ngx/src/app/shared/models/base-data.ts @@ -0,0 +1,26 @@ +/// +/// Copyright © 2016-2019 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 { EntityId } from '@shared/models/id/entity-id'; +import { HasUUID } from '@shared/models/id/has-uuid'; + +export declare type HasId = EntityId | HasUUID; + +export interface BaseData { + createdTime?: number; + id?: T; + name?: string; +} diff --git a/ui-ngx/src/app/shared/models/constants.ts b/ui-ngx/src/app/shared/models/constants.ts new file mode 100644 index 0000000000..b57950bef6 --- /dev/null +++ b/ui-ngx/src/app/shared/models/constants.ts @@ -0,0 +1,109 @@ +/// +/// Copyright © 2016-2019 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 const Constants = { + serverErrorCode: { + general: 2, + authentication: 10, + jwtTokenExpired: 11, + tenantTrialExpired: 12, + permissionDenied: 20, + invalidArguments: 30, + badRequestParams: 31, + itemNotFound: 32, + tooManyRequests: 33, + tooManyUpdates: 34 + }, + entryPoints: { + login: '/api/auth/login', + tokenRefresh: '/api/auth/token', + nonTokenBased: '/api/noauth' + } +}; + +export const MediaBreakpoints = { + xs: 'screen and (max-width: 599px)', + sm: 'screen and (min-width: 600px) and (max-width: 959px)', + md: 'screen and (min-width: 960px) and (max-width: 1279px)', + lg: 'screen and (min-width: 1280px) and (max-width: 1919px)', + xl: 'screen and (min-width: 1920px) and (max-width: 5000px)', + 'lt-sm': 'screen and (max-width: 599px)', + 'lt-md': 'screen and (max-width: 959px)', + 'lt-lg': 'screen and (max-width: 1279px)', + 'lt-xl': 'screen and (max-width: 1919px)', + 'gt-xs': 'screen and (min-width: 600px)', + 'gt-sm': 'screen and (min-width: 960px)', + 'gt-md': 'screen and (min-width: 1280px)', + 'gt-lg': 'screen and (min-width: 1920px)', + 'gt-xl': 'screen and (min-width: 5001px)' +}; + +const helpBaseUrl = 'https://thingsboard.io'; + +export const HelpLinks = { + linksMap: { + outgoingMailSettings: helpBaseUrl + '/docs/user-guide/ui/mail-settings', + securitySettings: helpBaseUrl + '/docs/user-guide/ui/security-settings', + tenants: helpBaseUrl + '/docs/user-guide/ui/tenants', + customers: helpBaseUrl + '/docs/user-guide/customers', + users: helpBaseUrl + '/docs/user-guide/ui/users' + } +}; + +export interface ValueTypeData { + name: string; + icon: string; +} + +export enum ValueType { + STRING = 'STRING', + INTEGER = 'INTEGER', + DOUBLE = 'DOUBLE', + BOOLEAN = 'BOOLEAN' +} + +export const valueTypesMap = new Map( + [ + [ + ValueType.STRING, + { + name: 'value.string', + icon: 'mdi:format-text' + } + ], + [ + ValueType.INTEGER, + { + name: 'value.integer', + icon: 'mdi:numeric' + } + ], + [ + ValueType.DOUBLE, + { + name: 'value.double', + icon: 'mdi:numeric' + } + ], + [ + ValueType.BOOLEAN, + { + name: 'value.boolean', + icon: 'mdi:checkbox-marked-outline' + } + ] + ] +); diff --git a/ui-ngx/src/app/shared/models/contact-based.model.ts b/ui-ngx/src/app/shared/models/contact-based.model.ts new file mode 100644 index 0000000000..40b519426c --- /dev/null +++ b/ui-ngx/src/app/shared/models/contact-based.model.ts @@ -0,0 +1,28 @@ +/// +/// Copyright © 2016-2019 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 { BaseData, HasId } from './base-data'; + +export interface ContactBased extends BaseData { + country: string; + state: string; + city: string; + address: string; + address2: string; + zip: string; + phone: string; + email: string; +} diff --git a/ui-ngx/src/app/shared/models/customer.model.ts b/ui-ngx/src/app/shared/models/customer.model.ts new file mode 100644 index 0000000000..d7da52711d --- /dev/null +++ b/ui-ngx/src/app/shared/models/customer.model.ts @@ -0,0 +1,25 @@ +/// +/// Copyright © 2016-2019 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 { CustomerId } from '@shared/models/id/customer-id'; +import { ContactBased } from '@shared/models/contact-based.model'; +import {TenantId} from './id/tenant-id'; + +export interface Customer extends ContactBased { + tenantId: TenantId; + title: string; + additionalInfo?: any; +} diff --git a/ui-ngx/src/app/shared/models/entity-type.models.ts b/ui-ngx/src/app/shared/models/entity-type.models.ts new file mode 100644 index 0000000000..36747d3380 --- /dev/null +++ b/ui-ngx/src/app/shared/models/entity-type.models.ts @@ -0,0 +1,116 @@ +/// +/// Copyright © 2016-2019 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 enum EntityType { + TENANT = 'TENANT', + CUSTOMER = 'CUSTOMER', + USER = 'USER', + DASHBOARD = 'DASHBOARD', + ASSET = 'ASSET', + DEVICE = 'DEVICE', + ALARM = 'ALARM', + RULE_CHAIN = 'RULE_CHAIN', + RULE_NODE = 'RULE_NODE', + ENTITY_VIEW = 'ENTITY_VIEW', + WIDGETS_BUNDLE = 'WIDGETS_BUNDLE', + WIDGET_TYPE = 'WIDGET_TYPE' +} + +export interface EntityTypeTranslation { + type: string; + typePlural: string; + list: string; + nameStartsWith: string; + details: string; + add: string; + noEntities: string; + selectedEntities: string; + search: string; +} + +export interface EntityTypeResource { + helpLinkId: string; +} + +export const entityTypeTranslations = new Map( + [ + [ + EntityType.TENANT, + { + type: 'entity.type-tenant', + typePlural: 'entity.type-tenants', + list: 'entity.list-of-tenants', + nameStartsWith: 'entity.tenant-name-starts-with', + details: 'tenant.tenant-details', + add: 'tenant.add', + noEntities: 'tenant.no-tenants-text', + search: 'tenant.search', + selectedEntities: 'tenant.selected-tenants' + } + ], + [ + EntityType.CUSTOMER, + { + type: 'entity.type-customer', + typePlural: 'entity.type-customers', + list: 'entity.list-of-customers', + nameStartsWith: 'entity.customer-name-starts-with', + details: 'customer.customer-details', + add: 'customer.add', + noEntities: 'customer.no-customers-text', + search: 'customer.search', + selectedEntities: 'customer.selected-customers' + } + ], + [ + EntityType.USER, + { + type: 'entity.type-user', + typePlural: 'entity.type-users', + list: 'entity.list-of-users', + nameStartsWith: 'entity.user-name-starts-with', + details: 'user.user-details', + add: 'user.add', + noEntities: 'user.no-users-text', + search: 'user.search', + selectedEntities: 'user.selected-users' + } + ] + ] +); + +export const entityTypeResources = new Map( + [ + [ + EntityType.TENANT, + { + helpLinkId: 'tenants' + } + ], + [ + EntityType.CUSTOMER, + { + helpLinkId: 'customers' + } + ], + [ + EntityType.USER, + { + helpLinkId: 'users' + } + ] + ] +); diff --git a/ui-ngx/src/app/shared/models/id/customer-id.ts b/ui-ngx/src/app/shared/models/id/customer-id.ts new file mode 100644 index 0000000000..363ea3fecb --- /dev/null +++ b/ui-ngx/src/app/shared/models/id/customer-id.ts @@ -0,0 +1,26 @@ +/// +/// Copyright © 2016-2019 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 { EntityId } from './entity-id'; +import { EntityType } from '@shared/models/entity-type.models'; + +export class CustomerId implements EntityId { + entityType = EntityType.CUSTOMER; + id: string; + constructor(id: string) { + this.id = id; + } +} diff --git a/ui-ngx/src/app/shared/models/id/entity-id.ts b/ui-ngx/src/app/shared/models/id/entity-id.ts new file mode 100644 index 0000000000..55bbd2f0c1 --- /dev/null +++ b/ui-ngx/src/app/shared/models/id/entity-id.ts @@ -0,0 +1,22 @@ +/// +/// Copyright © 2016-2019 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 { EntityType } from '@shared/models/entity-type.models'; +import { HasUUID } from '@shared/models/id/has-uuid'; + +export interface EntityId extends HasUUID { + entityType: EntityType; +} diff --git a/ui-ngx/src/app/shared/models/id/has-uuid.ts b/ui-ngx/src/app/shared/models/id/has-uuid.ts new file mode 100644 index 0000000000..681c67bc4a --- /dev/null +++ b/ui-ngx/src/app/shared/models/id/has-uuid.ts @@ -0,0 +1,21 @@ +/// +/// Copyright © 2016-2019 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 const NULL_UUID = '13814000-1dd2-11b2-8080-808080808080'; + +export interface HasUUID { + id: string; +} diff --git a/ui-ngx/src/app/shared/models/id/tenant-id.ts b/ui-ngx/src/app/shared/models/id/tenant-id.ts new file mode 100644 index 0000000000..60ae02147b --- /dev/null +++ b/ui-ngx/src/app/shared/models/id/tenant-id.ts @@ -0,0 +1,26 @@ +/// +/// Copyright © 2016-2019 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 { EntityId } from './entity-id'; +import { EntityType } from '@shared/models/entity-type.models'; + +export class TenantId implements EntityId { + entityType = EntityType.TENANT; + id: string; + constructor(id: string) { + this.id = id; + } +} diff --git a/ui-ngx/src/app/shared/models/id/user-id.ts b/ui-ngx/src/app/shared/models/id/user-id.ts new file mode 100644 index 0000000000..7a7c2fd1f2 --- /dev/null +++ b/ui-ngx/src/app/shared/models/id/user-id.ts @@ -0,0 +1,26 @@ +/// +/// Copyright © 2016-2019 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 { EntityId } from './entity-id'; +import { EntityType } from '@shared/models/entity-type.models'; + +export class UserId implements EntityId { + entityType = EntityType.USER; + id: string; + constructor(id: string) { + this.id = id; + } +} diff --git a/ui-ngx/src/app/shared/models/login.models.ts b/ui-ngx/src/app/shared/models/login.models.ts new file mode 100644 index 0000000000..9d11359f5a --- /dev/null +++ b/ui-ngx/src/app/shared/models/login.models.ts @@ -0,0 +1,30 @@ +/// +/// Copyright © 2016-2019 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 class LoginRequest { + username: string; + password: string; + + constructor(username: string, password: string) { + this.username = username; + this.password = password; + } +} + +export class LoginResponse { + token: string; + refreshToken: string; +} diff --git a/ui-ngx/src/app/shared/models/page/page-data.ts b/ui-ngx/src/app/shared/models/page/page-data.ts new file mode 100644 index 0000000000..76d1438d4e --- /dev/null +++ b/ui-ngx/src/app/shared/models/page/page-data.ts @@ -0,0 +1,33 @@ +/// +/// Copyright © 2016-2019 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 { BaseData, HasId } from '@shared/models/base-data'; + +export interface PageData> { + data: Array; + totalPages: number; + totalElements: number; + hasNext: boolean; +} + +export function emptyPageData>(): PageData { + return { + data: [], + totalPages: 0, + totalElements: 0, + hasNext: false + } as PageData; +} diff --git a/ui-ngx/src/app/shared/models/page/page-link.ts b/ui-ngx/src/app/shared/models/page/page-link.ts new file mode 100644 index 0000000000..e2cf956b2b --- /dev/null +++ b/ui-ngx/src/app/shared/models/page/page-link.ts @@ -0,0 +1,96 @@ +/// +/// Copyright © 2016-2019 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 { Direction, SortOrder } from '@shared/models/page/sort-order'; + +export class PageLink { + + textSearch: string; + pageSize: number; + page: number; + sortOrder: SortOrder; + + constructor(pageSize: number, page: number = 0, textSearch: string = null, sortOrder: SortOrder = null) { + this.textSearch = textSearch; + this.pageSize = pageSize; + this.page = page; + this.sortOrder = sortOrder; + } + + public nextPageLink(): PageLink { + return new PageLink(this.pageSize, this.page + 1, this.textSearch, this.sortOrder); + } + + public toQuery(): string { + let query = `?pageSize=${this.pageSize}&page=${this.page}`; + if (this.textSearch && this.textSearch.length) { + query += `&textSearch=${this.textSearch}`; + } + if (this.sortOrder) { + query += `&sortProperty=${this.sortOrder.property}&sortOrder=${this.sortOrder.direction}`; + } + return query; + } + + public sort(item1: any, item2: any): number { + if (this.sortOrder) { + const property = this.sortOrder.property; + const item1Value = item1[property]; + const item2Value = item2[property]; + let result = 0; + if (item1Value !== item2Value) { + if (typeof item1Value === 'number' && typeof item2Value === 'number') { + result = item1Value - item2Value; + } else if (typeof item1Value === 'string' && typeof item2Value === 'string') { + result = item1Value.localeCompare(item2Value); + } else if (typeof item1Value !== typeof item2Value) { + result = 1; + } + } + return this.sortOrder.direction === Direction.ASC ? result : result * -1; + } + return 0; + } + +} + +export class TimePageLink extends PageLink { + + startTime: number; + endTime: number; + + constructor(pageSize: number, page: number = 0, textSearch: string = null, sortOrder: SortOrder = null, + startTime: number = null, endTime: number = null) { + super(pageSize, page, textSearch, sortOrder); + this.startTime = startTime; + this.endTime = endTime; + } + + public nextPageLink(): TimePageLink { + return new TimePageLink(this.pageSize, this.page + 1, this.textSearch, this.sortOrder, this.startTime, this.endTime); + } + + public toQuery(): string { + let query = super.toQuery(); + if (this.startTime) { + query += `&startTime=${this.startTime}`; + } + if (this.endTime) { + query += `&endTime=${this.endTime}`; + } + return query; + } +} diff --git a/ui-ngx/src/app/shared/models/page/sort-order.ts b/ui-ngx/src/app/shared/models/page/sort-order.ts new file mode 100644 index 0000000000..b51791ca7d --- /dev/null +++ b/ui-ngx/src/app/shared/models/page/sort-order.ts @@ -0,0 +1,26 @@ +/// +/// Copyright © 2016-2019 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 interface SortOrder { + property: string; + direction: Direction; +} + +export enum Direction { + ASC = 'ASC', + DESC = 'DESC' +} diff --git a/ui-ngx/src/app/shared/models/settings.models.ts b/ui-ngx/src/app/shared/models/settings.models.ts new file mode 100644 index 0000000000..6af535d257 --- /dev/null +++ b/ui-ngx/src/app/shared/models/settings.models.ts @@ -0,0 +1,35 @@ +/// +/// Copyright © 2016-2019 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 const smtpPortPattern: RegExp = /^([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/; + +export interface AdminSettings { + key: string; + jsonValue: T; +} + +export declare type SmtpProtocol = 'smtp' | 'smtps'; + +export interface MailServerSettings { + mailFrom: string; + smtpProtocol: SmtpProtocol; + smtpHost: string; + smtpPort: number; + timeout: number; + enableTls: boolean; + username: string; + password: string; +} diff --git a/ui-ngx/src/app/shared/models/tenant.model.ts b/ui-ngx/src/app/shared/models/tenant.model.ts new file mode 100644 index 0000000000..060103b5af --- /dev/null +++ b/ui-ngx/src/app/shared/models/tenant.model.ts @@ -0,0 +1,25 @@ +/// +/// Copyright © 2016-2019 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 { CustomerId } from '@shared/models/id/customer-id'; +import { ContactBased } from '@shared/models/contact-based.model'; +import {TenantId} from './id/tenant-id'; + +export interface Tenant extends ContactBased { + title: string; + region: string; + additionalInfo?: any; +} diff --git a/ui-ngx/src/app/shared/models/time/time.models.ts b/ui-ngx/src/app/shared/models/time/time.models.ts new file mode 100644 index 0000000000..4958082d4f --- /dev/null +++ b/ui-ngx/src/app/shared/models/time/time.models.ts @@ -0,0 +1,315 @@ +/// +/// Copyright © 2016-2019 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 { TimeService } from '@core/services/time.service'; + +export const SECOND = 1000; +export const MINUTE = 60 * SECOND; +export const HOUR = 60 * MINUTE; +export const DAY = 24 * HOUR; + +export enum TimewindowType { + REALTIME, + HISTORY +} + +export enum HistoryWindowType { + LAST_INTERVAL, + FIXED +} + +export class Timewindow { + + displayValue?: string; + selectedTab?: TimewindowType; + realtime?: IntervalWindow; + history?: HistoryWindow; + aggregation?: Aggregation; + + public static historyInterval(timewindowMs: number): Timewindow { + const timewindow = new Timewindow(); + timewindow.history = new HistoryWindow(); + timewindow.history.timewindowMs = timewindowMs; + return timewindow; + } + + public static defaultTimewindow(timeService: TimeService): Timewindow { + const currentTime = new Date().getTime(); + const timewindow = new Timewindow(); + timewindow.displayValue = ''; + timewindow.selectedTab = TimewindowType.REALTIME; + timewindow.realtime = new IntervalWindow(); + timewindow.realtime.interval = SECOND; + timewindow.realtime.timewindowMs = MINUTE; + timewindow.history = new HistoryWindow(); + timewindow.history.historyType = HistoryWindowType.LAST_INTERVAL; + timewindow.history.interval = SECOND; + timewindow.history.timewindowMs = MINUTE; + timewindow.history.fixedTimewindow = new FixedWindow(); + timewindow.history.fixedTimewindow.startTimeMs = currentTime - DAY; + timewindow.history.fixedTimewindow.endTimeMs = currentTime; + timewindow.aggregation = new Aggregation(); + timewindow.aggregation.type = AggregationType.AVG; + timewindow.aggregation.limit = Math.floor(timeService.getMaxDatapointsLimit() / 2); + return timewindow; + } + + public static initModelFromDefaultTimewindow(value: Timewindow, timeService: TimeService): Timewindow { + const model = Timewindow.defaultTimewindow(timeService); + if (value) { + if (value.realtime) { + model.selectedTab = TimewindowType.REALTIME; + if (typeof value.realtime.interval !== 'undefined') { + model.realtime.interval = value.realtime.interval; + } + model.realtime.timewindowMs = value.realtime.timewindowMs; + } else { + model.selectedTab = TimewindowType.HISTORY; + if (typeof value.history.interval !== 'undefined') { + model.history.interval = value.history.interval; + } + if (typeof value.history.timewindowMs !== 'undefined') { + model.history.historyType = HistoryWindowType.LAST_INTERVAL; + model.history.timewindowMs = value.history.timewindowMs; + } else { + model.history.historyType = HistoryWindowType.FIXED; + model.history.fixedTimewindow.startTimeMs = value.history.fixedTimewindow.startTimeMs; + model.history.fixedTimewindow.endTimeMs = value.history.fixedTimewindow.endTimeMs; + } + } + if (value.aggregation) { + if (value.aggregation.type) { + model.aggregation.type = value.aggregation.type; + } + model.aggregation.limit = value.aggregation.limit || Math.floor(timeService.getMaxDatapointsLimit() / 2); + } + } + return model; + } + + public clone(): Timewindow { + const cloned = new Timewindow(); + cloned.displayValue = this.displayValue; + cloned.selectedTab = this.selectedTab; + cloned.realtime = this.realtime ? this.realtime.clone() : null; + cloned.history = this.history ? this.history.clone() : null; + cloned.aggregation = this.aggregation ? this.aggregation.clone() : null; + return cloned; + } + + public cloneSelectedTimewindow(): Timewindow { + const cloned = new Timewindow(); + if (typeof this.selectedTab !== 'undefined') { + if (this.selectedTab === TimewindowType.REALTIME) { + cloned.realtime = this.realtime ? this.realtime.clone() : null; + } else if (this.selectedTab === TimewindowType.HISTORY) { + cloned.history = this.history ? this.history.cloneSelectedTimewindow() : null; + } + } + cloned.aggregation = this.aggregation ? this.aggregation.clone() : null; + return cloned; + } + +} + +export class IntervalWindow { + interval?: number; + timewindowMs?: number; + + public clone(): IntervalWindow { + const cloned = new IntervalWindow(); + cloned.interval = this.interval; + cloned.timewindowMs = this.timewindowMs; + return cloned; + } +} + +export class FixedWindow { + startTimeMs: number; + endTimeMs: number; + + public clone(): FixedWindow { + const cloned = new FixedWindow(); + cloned.startTimeMs = this.startTimeMs; + cloned.endTimeMs = this.endTimeMs; + return cloned; + } +} + +export class HistoryWindow extends IntervalWindow { + historyType?: HistoryWindowType; + fixedTimewindow?: FixedWindow; + + public clone(): HistoryWindow { + const cloned = new HistoryWindow(); + cloned.historyType = this.historyType; + if (this.fixedTimewindow) { + cloned.fixedTimewindow = this.fixedTimewindow.clone(); + } + cloned.interval = this.interval; + cloned.timewindowMs = this.timewindowMs; + return cloned; + } + + public cloneSelectedTimewindow(): HistoryWindow { + const cloned = new HistoryWindow(); + if (typeof this.historyType !== 'undefined') { + cloned.interval = this.interval; + if (this.historyType === HistoryWindowType.LAST_INTERVAL) { + cloned.timewindowMs = this.timewindowMs; + } else if (this.historyType === HistoryWindowType.FIXED) { + cloned.fixedTimewindow = this.fixedTimewindow ? this.fixedTimewindow.clone() : null; + } + } + return cloned; + } +} + +export class Aggregation { + type: AggregationType; + limit: number; + + public clone(): Aggregation { + const cloned = new Aggregation(); + cloned.type = this.type; + cloned.limit = this.limit; + return cloned; + } +} + +export enum AggregationType { + MIN = 'MIN', + MAX = 'MAX', + AVG = 'AVG', + SUM = 'SUM', + COUNT = 'COUNT', + NONE = 'NONE' +} + +export const aggregationTranslations = new Map( + [ + [AggregationType.MIN, 'aggregation.min'], + [AggregationType.MAX, 'aggregation.max'], + [AggregationType.AVG, 'aggregation.avg'], + [AggregationType.SUM, 'aggregation.sum'], + [AggregationType.COUNT, 'aggregation.count'], + [AggregationType.NONE, 'aggregation.none'], + ] +); + +export interface TimeInterval { + name: string; + translateParams: {[key: string]: any}; + value: number; +} + +export const defaultTimeIntervals = new Array( + { + name: 'timeinterval.seconds-interval', + translateParams: {seconds: 1}, + value: 1 * SECOND + }, + { + name: 'timeinterval.seconds-interval', + translateParams: {seconds: 5}, + value: 5 * SECOND + }, + { + name: 'timeinterval.seconds-interval', + translateParams: {seconds: 10}, + value: 10 * SECOND + }, + { + name: 'timeinterval.seconds-interval', + translateParams: {seconds: 15}, + value: 15 * SECOND + }, + { + name: 'timeinterval.seconds-interval', + translateParams: {seconds: 30}, + value: 30 * SECOND + }, + { + name: 'timeinterval.minutes-interval', + translateParams: {minutes: 1}, + value: 1 * MINUTE + }, + { + name: 'timeinterval.minutes-interval', + translateParams: {minutes: 2}, + value: 2 * MINUTE + }, + { + name: 'timeinterval.minutes-interval', + translateParams: {minutes: 5}, + value: 5 * MINUTE + }, + { + name: 'timeinterval.minutes-interval', + translateParams: {minutes: 10}, + value: 10 * MINUTE + }, + { + name: 'timeinterval.minutes-interval', + translateParams: {minutes: 15}, + value: 15 * MINUTE + }, + { + name: 'timeinterval.minutes-interval', + translateParams: {minutes: 30}, + value: 30 * MINUTE + }, + { + name: 'timeinterval.hours-interval', + translateParams: {hours: 1}, + value: 1 * HOUR + }, + { + name: 'timeinterval.hours-interval', + translateParams: {hours: 2}, + value: 2 * HOUR + }, + { + name: 'timeinterval.hours-interval', + translateParams: {hours: 5}, + value: 5 * HOUR + }, + { + name: 'timeinterval.hours-interval', + translateParams: {hours: 10}, + value: 10 * HOUR + }, + { + name: 'timeinterval.hours-interval', + translateParams: {hours: 12}, + value: 12 * HOUR + }, + { + name: 'timeinterval.days-interval', + translateParams: {days: 1}, + value: 1 * DAY + }, + { + name: 'timeinterval.days-interval', + translateParams: {days: 7}, + value: 7 * DAY + }, + { + name: 'timeinterval.days-interval', + translateParams: {days: 30}, + value: 30 * DAY + } +); diff --git a/ui-ngx/src/app/shared/models/user.model.ts b/ui-ngx/src/app/shared/models/user.model.ts new file mode 100644 index 0000000000..86ed38a416 --- /dev/null +++ b/ui-ngx/src/app/shared/models/user.model.ts @@ -0,0 +1,56 @@ +/// +/// Copyright © 2016-2019 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 { BaseData } from './base-data'; +import { UserId } from './id/user-id'; +import { CustomerId } from './id/customer-id'; +import { Authority } from './authority.enum'; +import {TenantId} from './id/tenant-id'; + +export interface User extends BaseData { + tenantId: TenantId; + customerId: CustomerId; + email: string; + authority: Authority; + firstName: string; + lastName: string; + additionalInfo: any; +} + +export enum ActivationMethod { + DISPLAY_ACTIVATION_LINK, + SEND_ACTIVATION_MAIL +} + +export const activationMethodTranslations = new Map( + [ + [ActivationMethod.DISPLAY_ACTIVATION_LINK, 'user.display-activation-link'], + [ActivationMethod.SEND_ACTIVATION_MAIL, 'user.send-activation-mail'] + ] +); + +export interface AuthUser { + sub: string; + scopes: string[]; + userId: string; + firstName: string; + lastName: string; + enabled: boolean; + tenantId: string; + customerId: string; + isPublic: boolean; + authority: Authority; +} diff --git a/ui-ngx/src/app/shared/pipe/nospace.pipe.ts b/ui-ngx/src/app/shared/pipe/nospace.pipe.ts new file mode 100644 index 0000000000..62d049947f --- /dev/null +++ b/ui-ngx/src/app/shared/pipe/nospace.pipe.ts @@ -0,0 +1,28 @@ +/// +/// Copyright © 2016-2019 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 { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'nospace' +}) +export class NospacePipe implements PipeTransform { + + transform(value: string, args?: any): string { + return (!value) ? '' : value.replace(/ /g, ''); + } + +} diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts new file mode 100644 index 0000000000..0106a0422c --- /dev/null +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -0,0 +1,224 @@ +/// +/// Copyright © 2016-2019 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 { CommonModule, DatePipe } from '@angular/common'; +import { FooterComponent } from './components/footer.component'; +import { LogoComponent } from './components/logo.component'; +import { ToastDirective, TbSnackBarComponent } from './components/toast.directive'; +import { BreadcrumbComponent } from '@app/shared/components/breadcrumb.component'; + +import { + MatButtonModule, + MatCheckboxModule, + MatIconModule, + MatCardModule, + MatProgressBarModule, + MatInputModule, + MatSnackBarModule, + MatSidenavModule, + MatToolbarModule, + MatMenuModule, + MatGridListModule, + MatDialogModule, + MatSelectModule, + MatTooltipModule, + MatTableModule, + MatPaginatorModule, + MatSortModule, + MatProgressSpinnerModule, + MatDividerModule, + MatTabsModule, + MatRadioModule, + MatSlideToggleModule, + MatDatepickerModule, + MatSliderModule, + MatExpansionModule, + MatStepperModule, MatAutocompleteModule +} from '@angular/material'; +import { MatDatetimepickerModule, MatNativeDatetimeModule } from '@mat-datetimepicker/core'; +import { FlexLayoutModule } from '@angular/flex-layout'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { RouterModule } from '@angular/router'; +import { UserMenuComponent } from '@shared/components/user-menu.component'; +import { NospacePipe } from './pipe/nospace.pipe'; +import { TranslateModule } from '@ngx-translate/core'; +import { TbCheckboxComponent } from '@shared/components/tb-checkbox.component'; +import { HelpComponent } from '@shared/components/help.component'; +// import { EntitiesTableComponent } from '@shared/components/entity/entities-table.component'; +// import { AddEntityDialogComponent } from '@shared/components/entity/add-entity-dialog.component'; +// import { DetailsPanelComponent } from '@shared/components/details-panel.component'; +// import { EntityDetailsPanelComponent } from '@shared/components/entity/entity-details-panel.component'; +import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; +// import { ContactComponent } from '@shared/components/contact.component'; +// import { AuditLogDetailsDialogComponent } from '@shared/components/audit-log/audit-log-details-dialog.component'; +// import { AuditLogTableComponent } from '@shared/components/audit-log/audit-log-table.component'; +// import { MillisecondsToTimeStringPipe } from '@shared/pipe/milliseconds-to-time-string.pipe'; +// import { TimewindowComponent } from '@shared/components/time/timewindow.component'; +import { OverlayModule } from '@angular/cdk/overlay'; +// import { TimewindowPanelComponent } from '@shared/components/time/timewindow-panel.component'; +// import { TimeintervalComponent } from '@shared/components/time/timeinterval.component'; +// import { DatetimePeriodComponent } from '@shared/components/time/datetime-period.component'; +// import { EnumToArrayPipe } from '@shared/pipe/enum-to-array.pipe'; +import { ClipboardModule } from 'ngx-clipboard'; +// import { ValueInputComponent } from '@shared/components/value-input.component'; +// import { IntervalCountPipe } from '@shared/pipe/interval-count.pipe'; +import { FullscreenDirective } from '@shared/components/fullscreen.directive'; + +@NgModule({ + providers: [ + DatePipe, +// MillisecondsToTimeStringPipe, +// EnumToArrayPipe, +// IntervalCountPipe, + ], + entryComponents: [ + TbSnackBarComponent, + TbAnchorComponent, +// AddEntityDialogComponent, +// AuditLogDetailsDialogComponent, +// TimewindowPanelComponent, + ], + declarations: [ + FooterComponent, + LogoComponent, + ToastDirective, + FullscreenDirective, + TbAnchorComponent, + HelpComponent, + TbCheckboxComponent, + TbSnackBarComponent, + BreadcrumbComponent, + UserMenuComponent, +// EntitiesTableComponent, +// AddEntityDialogComponent, +// DetailsPanelComponent, +// EntityDetailsPanelComponent, +// ContactComponent, +// AuditLogTableComponent, +// AuditLogDetailsDialogComponent, +// TimewindowComponent, +// TimewindowPanelComponent, +// TimeintervalComponent, +// DatetimePeriodComponent, +// ValueInputComponent, + NospacePipe, +// MillisecondsToTimeStringPipe, +// EnumToArrayPipe, +// IntervalCountPipe + ], + imports: [ + CommonModule, + RouterModule, + TranslateModule, + MatButtonModule, + MatCheckboxModule, + MatIconModule, + MatCardModule, + MatProgressBarModule, + MatInputModule, + MatSnackBarModule, + MatSidenavModule, + MatToolbarModule, + MatMenuModule, + MatGridListModule, + MatDialogModule, + MatSelectModule, + MatTooltipModule, + MatTableModule, + MatPaginatorModule, + MatSortModule, + MatProgressSpinnerModule, + MatDividerModule, + MatTabsModule, + MatRadioModule, + MatSlideToggleModule, + MatDatepickerModule, + MatNativeDatetimeModule, + MatDatetimepickerModule, + MatSliderModule, + MatExpansionModule, + MatStepperModule, + MatAutocompleteModule, + ClipboardModule, + FlexLayoutModule.withConfig({addFlexToParent: false}), + FormsModule, + ReactiveFormsModule, + OverlayModule + ], + exports: [ + FooterComponent, + LogoComponent, + ToastDirective, + FullscreenDirective, + TbAnchorComponent, + HelpComponent, + TbCheckboxComponent, + BreadcrumbComponent, + UserMenuComponent, +// EntitiesTableComponent, +// AddEntityDialogComponent, +// DetailsPanelComponent, +// EntityDetailsPanelComponent, +// ContactComponent, +// AuditLogTableComponent, +// TimewindowComponent, +// TimewindowPanelComponent, +// TimeintervalComponent, +// DatetimePeriodComponent, +// ValueInputComponent, + MatButtonModule, + MatCheckboxModule, + MatIconModule, + MatCardModule, + MatProgressBarModule, + MatInputModule, + MatSnackBarModule, + MatSidenavModule, + MatToolbarModule, + MatMenuModule, + MatGridListModule, + MatDialogModule, + MatSelectModule, + MatTooltipModule, + MatTableModule, + MatPaginatorModule, + MatSortModule, + MatProgressSpinnerModule, + MatDividerModule, + MatTabsModule, + MatRadioModule, + MatSlideToggleModule, + MatDatepickerModule, + MatNativeDatetimeModule, + MatDatetimepickerModule, + MatSliderModule, + MatExpansionModule, + MatStepperModule, + MatAutocompleteModule, + ClipboardModule, + FlexLayoutModule, + FormsModule, + ReactiveFormsModule, + OverlayModule, + NospacePipe, +// MillisecondsToTimeStringPipe, +// EnumToArrayPipe, +// IntervalCountPipe, + TranslateModule + ] +}) +export class SharedModule { } 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 00b3ca21e0..88884094c8 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -407,7 +407,9 @@ "customer-required": "Customer is required", "select-default-customer": "Select default customer", "default-customer": "Default customer", - "default-customer-required": "Default customer is required in order to debug dashboard on Tenant level" + "default-customer-required": "Default customer is required in order to debug dashboard on Tenant level", + "search": "Search customers", + "selected-customers": "{ count, plural, 1 {1 customer} other {# customers} } selected" }, "datetime": { "date-from": "Date from", @@ -1385,7 +1387,9 @@ "idCopiedMessage": "Tenant Id has been copied to clipboard", "select-tenant": "Select tenant", "no-tenants-matching": "No tenants matching '{{entity}}' were found.", - "tenant-required": "Tenant is required" + "tenant-required": "Tenant is required", + "search": "Search tenants", + "selected-tenants": "{ count, plural, 1 {1 tenant} other {# tenants} } selected" }, "timeinterval": { "seconds-interval": "{ seconds, plural, 1 {1 second} other {# seconds} }", @@ -1453,7 +1457,9 @@ "activation-link-copied-message": "User activation link has been copied to clipboard", "details": "Details", "login-as-tenant-admin": "Login as Tenant Admin", - "login-as-customer-user": "Login as Customer User" + "login-as-customer-user": "Login as Customer User", + "search": "Search users", + "selected-users": "{ count, plural, 1 {1 user} other {# users} } selected" }, "value": { "type": "Value type", diff --git a/ui-ngx/src/theme.scss b/ui-ngx/src/theme.scss index d0e4c17146..33b7b11673 100644 --- a/ui-ngx/src/theme.scss +++ b/ui-ngx/src/theme.scss @@ -107,7 +107,7 @@ $tb-dark-mat-indigo: ( 500: $tb-dark-primary-color, 600: $tb-secondary-color, 700: #303f9f, - 800: #283593, + 800: $tb-primary-color, 900: #1a237e, A100: $tb-hue3-color, A200: #536dfe, @@ -135,18 +135,18 @@ $tb-dark-primary: mat-palette($tb-dark-mat-indigo); $tb-dark-theme-background: ( status-bar: black, - app-bar: map_get($tb-mat-indigo, 900), - background: map_get($tb-mat-indigo, 800), + app-bar: map_get($tb-dark-mat-indigo, 900), + background: map_get($tb-dark-mat-indigo, 800), hover: rgba(white, 0.04), - card: map_get($tb-mat-indigo, 800), - dialog: map_get($tb-mat-indigo, 800), + card: map_get($tb-dark-mat-indigo, 800), + dialog: map_get($tb-dark-mat-indigo, 800), disabled-button: rgba(white, 0.12), - raised-button: map-get($tb-mat-indigo, 50), + raised-button: map-get($tb-dark-mat-indigo, 50), focused-button: $light-focused, - selected-button: map_get($tb-mat-indigo, 900), - selected-disabled-button: map_get($tb-mat-indigo, 800), + selected-button: map_get($tb-dark-mat-indigo, 900), + selected-disabled-button: map_get($tb-dark-mat-indigo, 800), disabled-button-toggle: black, - unselected-chip: map_get($tb-mat-indigo, 700), + unselected-chip: map_get($tb-dark-mat-indigo, 700), disabled-list-option: black, ); From 08e8c92970519a41d673f38f0bd7ffaa6ff470da Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 12 Aug 2019 19:34:23 +0300 Subject: [PATCH 006/133] Tenants and tenant admins pages. --- ui-ngx/src/app/core/http/admin.service.ts | 55 +++ ui-ngx/src/app/core/http/customer.service.ts | 52 +++ ui-ngx/src/app/core/http/dashboard.service.ts | 77 ++++ ui-ngx/src/app/core/http/tenant.service.ts | 50 +++ ui-ngx/src/app/core/services/menu.service.ts | 69 +++- .../translate/translate-default-compiler.ts | 10 +- .../home/menu/side-menu.component.scss | 2 +- .../home/pages/admin/admin-routing.module.ts | 89 +++++ .../modules/home/pages/admin/admin.module.ts | 39 ++ .../admin/general-settings.component.html | 47 +++ .../admin/general-settings.component.scss | 18 + .../pages/admin/general-settings.component.ts | 77 ++++ .../pages/admin/mail-server.component.html | 104 +++++ .../pages/admin/mail-server.component.scss | 18 + .../home/pages/admin/mail-server.component.ts | 101 +++++ .../admin/security-settings.component.html | 119 ++++++ .../admin/security-settings.component.scss | 23 ++ .../admin/security-settings.component.ts | 86 +++++ .../home/pages/admin/settings-card.scss | 25 ++ .../modules/home/pages/home-pages.module.ts | 14 +- .../change-password-dialog.component.html | 63 +++ .../change-password-dialog.component.scss | 17 + .../change-password-dialog.component.ts | 68 ++++ .../pages/profile/profile-routing.module.ts | 69 ++++ .../home/pages/profile/profile.component.html | 77 ++++ .../home/pages/profile/profile.component.scss | 32 ++ .../home/pages/profile/profile.component.ts | 123 ++++++ .../home/pages/profile/profile.module.ts | 38 ++ .../pages/tenant/tenant-routing.module.ts | 80 ++++ .../home/pages/tenant/tenant.component.html | 61 +++ .../home/pages/tenant/tenant.component.ts | 76 ++++ .../home/pages/tenant/tenant.module.ts | 36 ++ .../tenant/tenants-table-config.resolver.ts | 105 +++++ .../activation-link-dialog.component.html | 59 +++ .../user/activation-link-dialog.component.ts | 64 ++++ .../pages/user/add-user-dialog.component.html | 59 +++ .../pages/user/add-user-dialog.component.ts | 114 ++++++ .../home/pages/user/user-routing.module.ts | 28 ++ .../home/pages/user/user.component.html | 90 +++++ .../home/pages/user/user.component.scss | 35 ++ .../modules/home/pages/user/user.component.ts | 82 ++++ .../modules/home/pages/user/user.module.ts | 42 ++ .../pages/user/users-table-config.resolver.ts | 230 +++++++++++ .../shared/components/contact.component.html | 63 +++ .../shared/components/contact.component.ts | 34 ++ .../app/shared/components/contact.models.ts | 291 ++++++++++++++ .../dashboard-autocomplete.component.html | 46 +++ .../dashboard-autocomplete.component.ts | 226 +++++++++++ .../components/details-panel.component.html | 56 +++ .../components/details-panel.component.scss | 53 +++ .../components/details-panel.component.ts | 80 ++++ .../entity/add-entity-dialog.component.html | 51 +++ .../entity/add-entity-dialog.component.scss | 17 + .../entity/add-entity-dialog.component.ts | 103 +++++ .../entity/contact-based.component.ts | 84 ++++ .../entity/entities-table-config.models.ts | 137 +++++++ .../entity/entities-table.component.html | 182 +++++++++ .../entity/entities-table.component.scss | 41 ++ .../entity/entities-table.component.ts | 358 ++++++++++++++++++ .../entity/entity-component.models.ts | 28 ++ .../entity-details-panel.component.html | 43 +++ .../entity-details-panel.component.scss | 31 ++ .../entity/entity-details-panel.component.ts | 165 ++++++++ .../entity/entity-table-header.component.ts | 36 ++ .../components/entity/entity.component.ts | 110 ++++++ .../time/datetime-period.component.html | 47 +++ .../time/datetime-period.component.scss | 26 ++ .../time/datetime-period.component.ts | 137 +++++++ .../time/timeinterval.component.html | 54 +++ .../time/timeinterval.component.scss | 43 +++ .../components/time/timeinterval.component.ts | 277 ++++++++++++++ .../time/timewindow-panel.component.html | 127 +++++++ .../time/timewindow-panel.component.scss | 63 +++ .../time/timewindow-panel.component.ts | 203 ++++++++++ .../components/time/timewindow.component.html | 43 +++ .../components/time/timewindow.component.scss | 26 ++ .../components/time/timewindow.component.ts | 275 ++++++++++++++ .../src/app/shared/models/customer.model.ts | 6 + .../src/app/shared/models/dashboard.models.ts | 35 ++ .../models/datasource/entity-datasource.ts | 113 ++++++ .../src/app/shared/models/id/dashboard-id.ts | 26 ++ .../src/app/shared/models/settings.models.ts | 17 + .../src/app/shared/pipe/enum-to-array.pipe.ts | 27 ++ ui-ngx/src/app/shared/pipe/highlight.pipe.ts | 28 ++ .../pipe/milliseconds-to-time-string.pipe.ts | 58 +++ ui-ngx/src/app/shared/shared.module.ts | 84 ++-- .../assets/locale/locale.constant-en_US.json | 3 + .../assets/locale/locale.constant-es_ES.json | 2 +- .../assets/locale/locale.constant-fr_FR.json | 130 +++---- .../assets/locale/locale.constant-ru_RU.json | 6 +- .../assets/locale/locale.constant-tr_TR.json | 26 +- .../assets/locale/locale.constant-uk_UA.json | 6 +- .../assets/locale/locale.constant-zh_CN.json | 16 +- 93 files changed, 6717 insertions(+), 145 deletions(-) create mode 100644 ui-ngx/src/app/core/http/admin.service.ts create mode 100644 ui-ngx/src/app/core/http/customer.service.ts create mode 100644 ui-ngx/src/app/core/http/dashboard.service.ts create mode 100644 ui-ngx/src/app/core/http/tenant.service.ts create mode 100644 ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/admin/admin.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/admin/general-settings.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/admin/general-settings.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/admin/general-settings.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/admin/mail-server.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/admin/mail-server.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/admin/mail-server.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/admin/security-settings.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/admin/security-settings.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/admin/security-settings.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/admin/settings-card.scss create mode 100644 ui-ngx/src/app/modules/home/pages/profile/change-password-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/profile/change-password-dialog.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/profile/change-password-dialog.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/profile/profile-routing.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/profile/profile.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/profile/profile.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/profile/profile.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/profile/profile.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/tenant/tenant-routing.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/tenant/tenant.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/tenant/tenant.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/tenant/tenants-table-config.resolver.ts create mode 100644 ui-ngx/src/app/modules/home/pages/user/activation-link-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/user/activation-link-dialog.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/user/user-routing.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/user/user.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/user/user.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/user/user.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/user/user.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/user/users-table-config.resolver.ts create mode 100644 ui-ngx/src/app/shared/components/contact.component.html create mode 100644 ui-ngx/src/app/shared/components/contact.component.ts create mode 100644 ui-ngx/src/app/shared/components/contact.models.ts create mode 100644 ui-ngx/src/app/shared/components/dashboard-autocomplete.component.html create mode 100644 ui-ngx/src/app/shared/components/dashboard-autocomplete.component.ts create mode 100644 ui-ngx/src/app/shared/components/details-panel.component.html create mode 100644 ui-ngx/src/app/shared/components/details-panel.component.scss create mode 100644 ui-ngx/src/app/shared/components/details-panel.component.ts create mode 100644 ui-ngx/src/app/shared/components/entity/add-entity-dialog.component.html create mode 100644 ui-ngx/src/app/shared/components/entity/add-entity-dialog.component.scss create mode 100644 ui-ngx/src/app/shared/components/entity/add-entity-dialog.component.ts create mode 100644 ui-ngx/src/app/shared/components/entity/contact-based.component.ts create mode 100644 ui-ngx/src/app/shared/components/entity/entities-table-config.models.ts create mode 100644 ui-ngx/src/app/shared/components/entity/entities-table.component.html create mode 100644 ui-ngx/src/app/shared/components/entity/entities-table.component.scss create mode 100644 ui-ngx/src/app/shared/components/entity/entities-table.component.ts create mode 100644 ui-ngx/src/app/shared/components/entity/entity-component.models.ts create mode 100644 ui-ngx/src/app/shared/components/entity/entity-details-panel.component.html create mode 100644 ui-ngx/src/app/shared/components/entity/entity-details-panel.component.scss create mode 100644 ui-ngx/src/app/shared/components/entity/entity-details-panel.component.ts create mode 100644 ui-ngx/src/app/shared/components/entity/entity-table-header.component.ts create mode 100644 ui-ngx/src/app/shared/components/entity/entity.component.ts create mode 100644 ui-ngx/src/app/shared/components/time/datetime-period.component.html create mode 100644 ui-ngx/src/app/shared/components/time/datetime-period.component.scss create mode 100644 ui-ngx/src/app/shared/components/time/datetime-period.component.ts create mode 100644 ui-ngx/src/app/shared/components/time/timeinterval.component.html create mode 100644 ui-ngx/src/app/shared/components/time/timeinterval.component.scss create mode 100644 ui-ngx/src/app/shared/components/time/timeinterval.component.ts create mode 100644 ui-ngx/src/app/shared/components/time/timewindow-panel.component.html create mode 100644 ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss create mode 100644 ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts create mode 100644 ui-ngx/src/app/shared/components/time/timewindow.component.html create mode 100644 ui-ngx/src/app/shared/components/time/timewindow.component.scss create mode 100644 ui-ngx/src/app/shared/components/time/timewindow.component.ts create mode 100644 ui-ngx/src/app/shared/models/dashboard.models.ts create mode 100644 ui-ngx/src/app/shared/models/datasource/entity-datasource.ts create mode 100644 ui-ngx/src/app/shared/models/id/dashboard-id.ts create mode 100644 ui-ngx/src/app/shared/pipe/enum-to-array.pipe.ts create mode 100644 ui-ngx/src/app/shared/pipe/highlight.pipe.ts create mode 100644 ui-ngx/src/app/shared/pipe/milliseconds-to-time-string.pipe.ts diff --git a/ui-ngx/src/app/core/http/admin.service.ts b/ui-ngx/src/app/core/http/admin.service.ts new file mode 100644 index 0000000000..589fc4b45f --- /dev/null +++ b/ui-ngx/src/app/core/http/admin.service.ts @@ -0,0 +1,55 @@ +/// +/// Copyright © 2016-2019 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 { Injectable } from '@angular/core'; +import { defaultHttpOptions } from './http-utils'; +import { Observable } from 'rxjs/index'; +import { HttpClient } from '@angular/common/http'; +import {AdminSettings, MailServerSettings, SecuritySettings} from '@shared/models/settings.models'; + +@Injectable({ + providedIn: 'root' +}) +export class AdminService { + + constructor( + private http: HttpClient + ) { } + + public getAdminSettings(key: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + return this.http.get>(`/api/admin/settings/${key}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public saveAdminSettings(adminSettings: AdminSettings, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + return this.http.post>('/api/admin/settings', adminSettings, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public sendTestMail(adminSettings: AdminSettings, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.post('/api/admin/settings/testMail', adminSettings, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public getSecuritySettings(ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.get(`/api/admin/securitySettings`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public saveSecuritySettings(securitySettings: SecuritySettings, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.post('/api/admin/securitySettings', securitySettings, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } +} diff --git a/ui-ngx/src/app/core/http/customer.service.ts b/ui-ngx/src/app/core/http/customer.service.ts new file mode 100644 index 0000000000..c84502cc38 --- /dev/null +++ b/ui-ngx/src/app/core/http/customer.service.ts @@ -0,0 +1,52 @@ +/// +/// Copyright © 2016-2019 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 { Injectable } from '@angular/core'; +import { defaultHttpOptions } from './http-utils'; +import { Observable } from 'rxjs/index'; +import { HttpClient } from '@angular/common/http'; +import { PageLink } from '@shared/models/page/page-link'; +import { PageData } from '@shared/models/page/page-data'; +import { Customer } from '@shared/models/customer.model'; + +@Injectable({ + providedIn: 'root' +}) +export class CustomerService { + + constructor( + private http: HttpClient + ) { } + + public getCustomers(tenantId: string, pageLink: PageLink, ignoreErrors: boolean = false, + ignoreLoading: boolean = false): Observable> { + return this.http.get>(`/api/tenant/${tenantId}/customers${pageLink.toQuery()}`, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public getCustomer(customerId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.get(`/api/customer/${customerId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public saveCustomer(customer: Customer, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.post('/api/customer', customer, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public deleteCustomer(customerId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { + return this.http.delete(`/api/customer/${customerId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + +} diff --git a/ui-ngx/src/app/core/http/dashboard.service.ts b/ui-ngx/src/app/core/http/dashboard.service.ts new file mode 100644 index 0000000000..3419f6ad8a --- /dev/null +++ b/ui-ngx/src/app/core/http/dashboard.service.ts @@ -0,0 +1,77 @@ +/// +/// Copyright © 2016-2019 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 { Injectable } from '@angular/core'; +import { defaultHttpOptions } from './http-utils'; +import { Observable } from 'rxjs/index'; +import { HttpClient } from '@angular/common/http'; +import { PageLink } from '@shared/models/page/page-link'; +import { PageData } from '@shared/models/page/page-data'; +import { Tenant } from '@shared/models/tenant.model'; +import {DashboardInfo, Dashboard} from '@shared/models/dashboard.models'; +import {map} from 'rxjs/operators'; + +@Injectable({ + providedIn: 'root' +}) +export class DashboardService { + + constructor( + private http: HttpClient + ) { } + + public getTenantDashboards(pageLink: PageLink, ignoreErrors: boolean = false, + ignoreLoading: boolean = false): Observable> { + return this.http.get>(`/api/tenant/dashboards${pageLink.toQuery()}`, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public getTenantDashboardsByTenantId(tenantId: string, pageLink: PageLink, ignoreErrors: boolean = false, + ignoreLoading: boolean = false): Observable> { + return this.http.get>(`/api/tenant/${tenantId}/dashboards${pageLink.toQuery()}`, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public getCustomerDashboards(customerId: string, pageLink: PageLink, ignoreErrors: boolean = false, + ignoreLoading: boolean = false): Observable> { + return this.http.get>(`/api/customer/${customerId}/dashboards${pageLink.toQuery()}`, + defaultHttpOptions(ignoreLoading, ignoreErrors)).pipe( + map( dashboards => { + dashboards.data = dashboards.data.filter(dashboard => { + return dashboard.title.toUpperCase().includes(pageLink.textSearch.toUpperCase()); + }); + return dashboards; + } + )); + } + + public getDashboard(dashboardId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.get(`/api/dashboard/${dashboardId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public getDashboardInfo(dashboardId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.get(`/api/dashboard/info/${dashboardId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public saveDashboard(dashboard: Dashboard, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.post('/api/dashboard', dashboard, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public deleteDashboard(dashboardId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { + return this.http.delete(`/api/dashboard/${dashboardId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + +} diff --git a/ui-ngx/src/app/core/http/tenant.service.ts b/ui-ngx/src/app/core/http/tenant.service.ts new file mode 100644 index 0000000000..7574720ca0 --- /dev/null +++ b/ui-ngx/src/app/core/http/tenant.service.ts @@ -0,0 +1,50 @@ +/// +/// Copyright © 2016-2019 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 { Injectable } from '@angular/core'; +import { defaultHttpOptions } from './http-utils'; +import { Observable } from 'rxjs/index'; +import { HttpClient } from '@angular/common/http'; +import { PageLink } from '@shared/models/page/page-link'; +import { PageData } from '@shared/models/page/page-data'; +import { Tenant } from '@shared/models/tenant.model'; + +@Injectable({ + providedIn: 'root' +}) +export class TenantService { + + constructor( + private http: HttpClient + ) { } + + public getTenants(pageLink: PageLink, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + return this.http.get>(`/api/tenants${pageLink.toQuery()}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public getTenant(tenantId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.get(`/api/tenant/${tenantId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public saveTenant(tenant: Tenant, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.post('/api/tenant', tenant, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public deleteTenant(tenantId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { + return this.http.delete(`/api/tenant/${tenantId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + +} diff --git a/ui-ngx/src/app/core/services/menu.service.ts b/ui-ngx/src/app/core/services/menu.service.ts index 463513052e..817d024f50 100644 --- a/ui-ngx/src/app/core/services/menu.service.ts +++ b/ui-ngx/src/app/core/services/menu.service.ts @@ -320,15 +320,78 @@ export class MenuService { type: 'link', path: '/home', icon: 'home' + }, + { + name: 'asset.assets', + type: 'link', + path: '/assets', + icon: 'domain' + }, + { + name: 'device.devices', + type: 'link', + path: '/devices', + icon: 'devices_other' + }, + { + name: 'entity-view.entity-views', + type: 'link', + path: '/entityViews', + icon: 'view_quilt' + }, + { + name: 'dashboard.dashboards', + type: 'link', + path: '/dashboards', + icon: 'dashboard' } ); - // TODO: return sections; } private buildCustomerUserHome(authUser: any): Array { - const homeSections: Array = []; - // TODO: + const homeSections: Array = [ + { + name: 'asset.view-assets', + places: [ + { + name: 'asset.assets', + icon: 'domain', + path: '/assets' + } + ] + }, + { + name: 'device.view-devices', + places: [ + { + name: 'device.devices', + icon: 'devices_other', + path: '/devices' + } + ] + }, + { + name: 'entity-view.management', + places: [ + { + name: 'entity-view.entity-views', + icon: 'view_quilt', + path: '/entityViews' + } + ] + }, + { + name: 'dashboard.view-dashboards', + places: [ + { + name: 'dashboard.dashboards', + icon: 'dashboard', + path: '/dashboards' + } + ] + } + ]; return homeSections; } diff --git a/ui-ngx/src/app/core/translate/translate-default-compiler.ts b/ui-ngx/src/app/core/translate/translate-default-compiler.ts index 3e92f3b64c..5e30a0b588 100644 --- a/ui-ngx/src/app/core/translate/translate-default-compiler.ts +++ b/ui-ngx/src/app/core/translate/translate-default-compiler.ts @@ -56,8 +56,14 @@ export class TranslateDefaultCompiler extends TranslateMessageFormatCompiler { } private checkIsPlural(src: string): boolean { - const tokens: any[] = parse(src.replace(/\{\{/g, '{').replace(/\}\}/g, '}'), - {cardinal: [], ordinal: []}); + let tokens: any[]; + try { + tokens = parse(src.replace(/\{\{/g, '{').replace(/\}\}/g, '}'), + {cardinal: [], ordinal: []}); + } catch (e) { + console.warn(`Failed to parse source: ${src}`); + console.error(e); + } const res = tokens.filter( (value) => typeof value !== 'string' && value.type === 'plural' ); 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 53100c456e..09f4463029 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 @@ -106,7 +106,7 @@ button { padding: 0 16px 0 32px; font-weight: 500; - text-transform: none; + text-transform: none !important; text-rendering: optimizeLegibility; } } 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 new file mode 100644 index 0000000000..0b36672d72 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts @@ -0,0 +1,89 @@ +/// +/// Copyright © 2016-2019 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 { Routes, RouterModule } from '@angular/router'; + +import { MailServerComponent } from '@modules/home/pages/admin/mail-server.component'; +import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard'; +import { Authority } from '@shared/models/authority.enum'; +import {GeneralSettingsComponent} from "@modules/home/pages/admin/general-settings.component"; +import {SecuritySettingsComponent} from "@modules/home/pages/admin/security-settings.component"; + +const routes: Routes = [ + { + path: 'settings', + data: { + auth: [Authority.SYS_ADMIN], + breadcrumb: { + label: 'admin.system-settings', + icon: 'settings' + } + }, + children: [ + { + path: '', + redirectTo: 'general', + pathMatch: 'full' + }, + { + path: 'general', + component: GeneralSettingsComponent, + canDeactivate: [ConfirmOnExitGuard], + data: { + auth: [Authority.SYS_ADMIN], + title: 'admin.general-settings', + breadcrumb: { + label: 'admin.general', + icon: 'settings_applications' + } + } + }, + { + path: 'outgoing-mail', + component: MailServerComponent, + canDeactivate: [ConfirmOnExitGuard], + data: { + auth: [Authority.SYS_ADMIN], + title: 'admin.outgoing-mail-settings', + breadcrumb: { + label: 'admin.outgoing-mail', + icon: 'mail' + } + } + }, + { + path: 'security-settings', + component: SecuritySettingsComponent, + canDeactivate: [ConfirmOnExitGuard], + data: { + auth: [Authority.SYS_ADMIN], + title: 'admin.security-settings', + breadcrumb: { + label: 'admin.security-settings', + icon: 'security' + } + } + } + ] + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class AdminRoutingModule { } diff --git a/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts b/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts new file mode 100644 index 0000000000..18dc45c68c --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts @@ -0,0 +1,39 @@ +/// +/// Copyright © 2016-2019 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 { CommonModule } from '@angular/common'; + +import { AdminRoutingModule } from './admin-routing.module'; +import { SharedModule } from '@app/shared/shared.module'; +import { MailServerComponent } from '@modules/home/pages/admin/mail-server.component'; +import {GeneralSettingsComponent} from "@modules/home/pages/admin/general-settings.component"; +import {SecuritySettingsComponent} from "@modules/home/pages/admin/security-settings.component"; + +@NgModule({ + declarations: + [ + GeneralSettingsComponent, + MailServerComponent, + SecuritySettingsComponent + ], + imports: [ + CommonModule, + SharedModule, + AdminRoutingModule + ] +}) +export class AdminModule { } diff --git a/ui-ngx/src/app/modules/home/pages/admin/general-settings.component.html b/ui-ngx/src/app/modules/home/pages/admin/general-settings.component.html new file mode 100644 index 0000000000..aa6ea9421a --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/general-settings.component.html @@ -0,0 +1,47 @@ + +
+ + +
+ admin.general-settings +
+
+ + +
+ +
+
+ + admin.base-url + + + {{ 'admin.base-url-required' | translate }} + + +
+ +
+
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/admin/general-settings.component.scss b/ui-ngx/src/app/modules/home/pages/admin/general-settings.component.scss new file mode 100644 index 0000000000..dfbd362f33 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/general-settings.component.scss @@ -0,0 +1,18 @@ +/** + * Copyright © 2016-2019 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 { + +} diff --git a/ui-ngx/src/app/modules/home/pages/admin/general-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/general-settings.component.ts new file mode 100644 index 0000000000..a7d7264115 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/general-settings.component.ts @@ -0,0 +1,77 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, OnInit } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { PageComponent } from '@shared/components/page.component'; +import { Router } from '@angular/router'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import {AdminSettings, GeneralSettings} from '@shared/models/settings.models'; +import { AdminService } from '@core/http/admin.service'; +import { ActionNotificationShow } from '@core/notification/notification.actions'; +import { TranslateService } from '@ngx-translate/core'; +import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard'; + +@Component({ + selector: 'tb-general-settings', + templateUrl: './general-settings.component.html', + styleUrls: ['./general-settings.component.scss', './settings-card.scss'] +}) +export class GeneralSettingsComponent extends PageComponent implements OnInit, HasConfirmForm { + + generalSettings: FormGroup; + adminSettings: AdminSettings; + + constructor(protected store: Store, + private router: Router, + private adminService: AdminService, + private translate: TranslateService, + public fb: FormBuilder) { + super(store); + } + + ngOnInit() { + this.buildGeneralServerSettingsForm(); + this.adminService.getAdminSettings('general').subscribe( + (adminSettings) => { + this.adminSettings = adminSettings; + this.generalSettings.reset(this.adminSettings.jsonValue); + } + ); + } + + buildGeneralServerSettingsForm() { + this.generalSettings = this.fb.group({ + baseUrl: ['', [Validators.required]] + }); + } + + save(): void { + this.adminSettings.jsonValue = {...this.adminSettings.jsonValue, ...this.generalSettings.value}; + this.adminService.saveAdminSettings(this.adminSettings).subscribe( + (adminSettings) => { + this.adminSettings = adminSettings; + this.generalSettings.reset(this.adminSettings.jsonValue); + } + ); + } + + confirmForm(): FormGroup { + return this.generalSettings; + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/admin/mail-server.component.html b/ui-ngx/src/app/modules/home/pages/admin/mail-server.component.html new file mode 100644 index 0000000000..88fe171c96 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/mail-server.component.html @@ -0,0 +1,104 @@ + +
+ + +
+ admin.outgoing-mail-settings + +
+
+
+ + +
+ +
+
+ + admin.mail-from + + + {{ 'admin.mail-from-required' | translate }} + + + + admin.smtp-protocol + + + {{protocol.toUpperCase()}} + + + +
+ + admin.smtp-host + + + {{ 'admin.smtp-host-required' | translate }} + + + + admin.smtp-port + + {{smtpPortInput.value?.length || 0}}/5 + + {{ 'admin.smtp-port-required' | translate }} + + + {{ 'admin.smtp-port-invalid' | translate }} + + +
+ + admin.timeout-msec + + {{timeoutInput.value?.length || 0}}/6 + + {{ 'admin.timeout-required' | translate }} + + + {{ 'admin.timeout-invalid' | translate }} + + + + {{ 'admin.enable-tls' | translate }} + + + common.username + + + + common.password + + +
+ + +
+
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/admin/mail-server.component.scss b/ui-ngx/src/app/modules/home/pages/admin/mail-server.component.scss new file mode 100644 index 0000000000..dfbd362f33 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/mail-server.component.scss @@ -0,0 +1,18 @@ +/** + * Copyright © 2016-2019 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 { + +} diff --git a/ui-ngx/src/app/modules/home/pages/admin/mail-server.component.ts b/ui-ngx/src/app/modules/home/pages/admin/mail-server.component.ts new file mode 100644 index 0000000000..e661cb1a78 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/mail-server.component.ts @@ -0,0 +1,101 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, OnInit } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { PageComponent } from '@shared/components/page.component'; +import { Router } from '@angular/router'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { AdminSettings, MailServerSettings, smtpPortPattern } from '@shared/models/settings.models'; +import { AdminService } from '@core/http/admin.service'; +import { ActionNotificationShow } from '@core/notification/notification.actions'; +import { TranslateService } from '@ngx-translate/core'; +import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard'; + +@Component({ + selector: 'tb-mail-server', + templateUrl: './mail-server.component.html', + styleUrls: ['./mail-server.component.scss', './settings-card.scss'] +}) +export class MailServerComponent extends PageComponent implements OnInit, HasConfirmForm { + + mailSettings: FormGroup; + adminSettings: AdminSettings; + smtpProtocols = ['smtp', 'smtps']; + + constructor(protected store: Store, + private router: Router, + private adminService: AdminService, + private translate: TranslateService, + public fb: FormBuilder) { + super(store); + } + + ngOnInit() { + this.buildMailServerSettingsForm(); + this.adminService.getAdminSettings('mail').subscribe( + (adminSettings) => { + this.adminSettings = adminSettings; + this.mailSettings.reset(this.adminSettings.jsonValue); + } + ); + } + + buildMailServerSettingsForm() { + this.mailSettings = this.fb.group({ + mailFrom: ['', [Validators.required]], + smtpProtocol: ['smtp'], + smtpHost: ['localhost', [Validators.required]], + smtpPort: ['25', [Validators.required, + Validators.pattern(smtpPortPattern), + Validators.maxLength(5)]], + timeout: ['10000', [Validators.required, + Validators.pattern(/^[0-9]{1,6}$/), + Validators.maxLength(6)]], + enableTls: ['false'], + username: [''], + password: [''] + }); + this.registerDisableOnLoadFormControl(this.mailSettings.get('smtpProtocol')); + this.registerDisableOnLoadFormControl(this.mailSettings.get('enableTls')); + } + + sendTestMail(): void { + this.adminSettings.jsonValue = {...this.adminSettings.jsonValue, ...this.mailSettings.value}; + this.adminService.sendTestMail(this.adminSettings).subscribe( + () => { + this.store.dispatch(new ActionNotificationShow({ message: this.translate.instant('admin.test-mail-sent'), + type: 'success' })); + } + ); + } + + save(): void { + this.adminSettings.jsonValue = {...this.adminSettings.jsonValue, ...this.mailSettings.value}; + this.adminService.saveAdminSettings(this.adminSettings).subscribe( + (adminSettings) => { + this.adminSettings = adminSettings; + this.mailSettings.reset(this.adminSettings.jsonValue); + } + ); + } + + confirmForm(): FormGroup { + return this.mailSettings; + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/admin/security-settings.component.html b/ui-ngx/src/app/modules/home/pages/admin/security-settings.component.html new file mode 100644 index 0000000000..1c925b0f64 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/security-settings.component.html @@ -0,0 +1,119 @@ + +
+ + +
+ admin.security-settings + +
+
+
+ + +
+ +
+
+ + + +
admin.password-policy
+
+
+
+ + admin.minimum-password-length + + + {{ 'admin.minimum-password-length-required' | translate }} + + + {{ 'admin.minimum-password-length-range' | translate }} + + + {{ 'admin.minimum-password-length-range' | translate }} + + + + admin.minimum-uppercase-letters + + + {{ 'admin.minimum-uppercase-letters-range' | translate }} + + + + admin.minimum-lowercase-letters + + + {{ 'admin.minimum-lowercase-letters-range' | translate }} + + + + admin.minimum-digits + + + {{ 'admin.minimum-digits-range' | translate }} + + + + admin.minimum-special-characters + + + {{ 'admin.minimum-special-characters-range' | translate }} + + + + admin.password-expiration-period-days + + + {{ 'admin.password-expiration-period-days-range' | translate }} + + +
+
+
+ +
+
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/admin/security-settings.component.scss b/ui-ngx/src/app/modules/home/pages/admin/security-settings.component.scss new file mode 100644 index 0000000000..e686efdff5 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/security-settings.component.scss @@ -0,0 +1,23 @@ +/** + * Copyright © 2016-2019 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 { + mat-expansion-panel { + margin-bottom: 16px; + } + .tb-panel-title { + + } +} diff --git a/ui-ngx/src/app/modules/home/pages/admin/security-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/security-settings.component.ts new file mode 100644 index 0000000000..fbd83506c4 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/security-settings.component.ts @@ -0,0 +1,86 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, OnInit } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { PageComponent } from '@shared/components/page.component'; +import { Router } from '@angular/router'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { SecuritySettings} from '@shared/models/settings.models'; +import { AdminService } from '@core/http/admin.service'; +import { ActionNotificationShow } from '@core/notification/notification.actions'; +import { TranslateService } from '@ngx-translate/core'; +import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard'; + +@Component({ + selector: 'tb-security-settings', + templateUrl: './security-settings.component.html', + styleUrls: ['./security-settings.component.scss', './settings-card.scss'] +}) +export class SecuritySettingsComponent extends PageComponent implements OnInit, HasConfirmForm { + + securitySettingsFormGroup: FormGroup; + securitySettings: SecuritySettings; + + constructor(protected store: Store, + private router: Router, + private adminService: AdminService, + private translate: TranslateService, + public fb: FormBuilder) { + super(store); + } + + ngOnInit() { + this.buildSecuritySettingsForm(); + this.adminService.getSecuritySettings().subscribe( + (securitySettings) => { + this.securitySettings = securitySettings; + this.securitySettingsFormGroup.reset(this.securitySettings); + } + ); + } + + buildSecuritySettingsForm() { + this.securitySettingsFormGroup = this.fb.group({ + passwordPolicy: this.fb.group( + { + minimumLength: [null, [Validators.required, Validators.min(5), Validators.max(50)]], + minimumUppercaseLetters: [null, Validators.min(0)], + minimumLowercaseLetters: [null, Validators.min(0)], + minimumDigits: [null, Validators.min(0)], + minimumSpecialCharacters: [null, Validators.min(0)], + passwordExpirationPeriodDays: [null, Validators.min(0)] + } + ) + }); + } + + save(): void { + this.securitySettings = {...this.securitySettings, ...this.securitySettingsFormGroup.value}; + this.adminService.saveSecuritySettings(this.securitySettings).subscribe( + (securitySettings) => { + this.securitySettings = securitySettings; + this.securitySettingsFormGroup.reset(this.securitySettings); + } + ); + } + + confirmForm(): FormGroup { + return this.securitySettingsFormGroup; + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/admin/settings-card.scss b/ui-ngx/src/app/modules/home/pages/admin/settings-card.scss new file mode 100644 index 0000000000..840f0b031e --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/settings-card.scss @@ -0,0 +1,25 @@ +/** + * Copyright © 2016-2019 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 { + mat-card.settings-card { + margin: 8px; + @media #{$mat-gt-sm} { + width: 60%; + } + } +} 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 cbd37fcc65..08cf06cf2d 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 @@ -16,21 +16,23 @@ import { NgModule } from '@angular/core'; -// import { AdminModule } from './admin/admin.module'; +import { AdminModule } from './admin/admin.module'; import { HomeLinksModule } from './home-links/home-links.module'; -// import { ProfileModule } from './profile/profile.module'; +import { ProfileModule } from './profile/profile.module'; +import { TenantModule } from '@modules/home/pages/tenant/tenant.module'; // import { CustomerModule } from '@modules/home/pages/customer/customer.module'; // import { AuditLogModule } from '@modules/home/pages/audit-log/audit-log.module'; -// import { UserModule } from '@modules/home/pages/user/user.module'; +import { UserModule } from '@modules/home/pages/user/user.module'; @NgModule({ exports: [ -// AdminModule, + AdminModule, HomeLinksModule, -// ProfileModule, + ProfileModule, + TenantModule, // CustomerModule, // AuditLogModule, -// UserModule + UserModule ] }) export class HomePagesModule { } diff --git a/ui-ngx/src/app/modules/home/pages/profile/change-password-dialog.component.html b/ui-ngx/src/app/modules/home/pages/profile/change-password-dialog.component.html new file mode 100644 index 0000000000..3fd4bce498 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/profile/change-password-dialog.component.html @@ -0,0 +1,63 @@ + +
+ +

profile.change-password

+ + +
+ + +
+
+ + profile.current-password + + lock + + + login.new-password + + lock + + + login.new-password-again + + lock + +
+
+ + + +
+
diff --git a/ui-ngx/src/app/modules/home/pages/profile/change-password-dialog.component.scss b/ui-ngx/src/app/modules/home/pages/profile/change-password-dialog.component.scss new file mode 100644 index 0000000000..bb18c2c7b6 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/profile/change-password-dialog.component.scss @@ -0,0 +1,17 @@ +/** + * Copyright © 2016-2019 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 { +} diff --git a/ui-ngx/src/app/modules/home/pages/profile/change-password-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/profile/change-password-dialog.component.ts new file mode 100644 index 0000000000..451ce03cae --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/profile/change-password-dialog.component.ts @@ -0,0 +1,68 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, OnInit } from '@angular/core'; +import { MatDialogRef } from '@angular/material'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { ActionNotificationShow } from '@core/notification/notification.actions'; +import { TranslateService } from '@ngx-translate/core'; +import { AuthService } from '@core/auth/auth.service'; + +@Component({ + selector: 'tb-change-password-dialog', + templateUrl: './change-password-dialog.component.html', + styleUrls: ['./change-password-dialog.component.scss'] +}) +export class ChangePasswordDialogComponent extends PageComponent implements OnInit { + + changePassword: FormGroup; + + constructor(protected store: Store, + private translate: TranslateService, + private authService: AuthService, + public dialogRef: MatDialogRef, + public fb: FormBuilder) { + super(store); + } + + ngOnInit(): void { + this.buildChangePasswordForm(); + } + + buildChangePasswordForm() { + this.changePassword = this.fb.group({ + currentPassword: [''], + newPassword: [''], + newPassword2: [''] + }); + } + + onChangePassword(): void { + if (this.changePassword.get('newPassword').value !== this.changePassword.get('newPassword2').value) { + this.store.dispatch(new ActionNotificationShow({ message: this.translate.instant('login.passwords-mismatch-error'), + type: 'error' })); + } else { + this.authService.changePassword( + this.changePassword.get('currentPassword').value, + this.changePassword.get('newPassword').value).subscribe(() => { + this.dialogRef.close(true); + }); + } + } +} 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 new file mode 100644 index 0000000000..4989a280e4 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/profile/profile-routing.module.ts @@ -0,0 +1,69 @@ +/// +/// Copyright © 2016-2019 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 {Injectable, NgModule} from '@angular/core'; +import {Resolve, RouterModule, Routes} from '@angular/router'; + +import {ProfileComponent} from './profile.component'; +import {ConfirmOnExitGuard} from '@core/guards/confirm-on-exit.guard'; +import {Authority} from '@shared/models/authority.enum'; +import {User} from '@shared/models/user.model'; +import {Store} from '@ngrx/store'; +import {AppState} from '@core/core.state'; +import {UserService} from '@core/http/user.service'; +import {getCurrentAuthUser} from '@core/auth/auth.selectors'; +import {Observable} from 'rxjs'; + +@Injectable() +export class UserProfileResolver implements Resolve { + + constructor(private store: Store, + private userService: UserService) { + } + + resolve(): Observable { + const userId = getCurrentAuthUser(this.store).userId; + return this.userService.getUser(userId); + } +} + +const routes: Routes = [ + { + path: 'profile', + component: ProfileComponent, + canDeactivate: [ConfirmOnExitGuard], + data: { + auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], + title: 'profile.profile', + breadcrumb: { + label: 'profile.profile', + icon: 'account_circle' + } + }, + resolve: { + user: UserProfileResolver + } + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], + providers: [ + UserProfileResolver + ] +}) +export class ProfileRoutingModule { } diff --git a/ui-ngx/src/app/modules/home/pages/profile/profile.component.html b/ui-ngx/src/app/modules/home/pages/profile/profile.component.html new file mode 100644 index 0000000000..ef59570148 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/profile/profile.component.html @@ -0,0 +1,77 @@ + +
+ + +
+ profile.profile + {{ profile ? profile.get('email').value : '' }} +
+
+ + +
+ +
+
+ + user.email + + + {{ 'user.email-required' | translate }} + + + {{ 'user.invalid-email-format' | translate }} + + + + user.first-name + + + + user.last-name + + + + language.language + + + {{ lang ? ('language.locales.' + lang | translate) : ''}} + + + +
+ +
+
+ + +
+
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/profile/profile.component.scss b/ui-ngx/src/app/modules/home/pages/profile/profile.component.scss new file mode 100644 index 0000000000..4060d42f3b --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/profile/profile.component.scss @@ -0,0 +1,32 @@ +/** + * Copyright © 2016-2019 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 { + mat-card.profile-card { + margin: 8px; + @media #{$mat-gt-sm} { + width: 60%; + } + .mat-headline { + margin: 0; + } + .profile-email { + font-size: 16px; + font-weight: 400; + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/profile/profile.component.ts b/ui-ngx/src/app/modules/home/pages/profile/profile.component.ts new file mode 100644 index 0000000000..1f2dbfd697 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/profile/profile.component.ts @@ -0,0 +1,123 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, OnInit } from '@angular/core'; +import { UserService } from '@core/http/user.service'; +import { User } from '@shared/models/user.model'; +import { Authority } from '@shared/models/authority.enum'; +import { PageComponent } from '@shared/components/page.component'; +import { select, Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { getCurrentAuthUser, selectAuthUser } from '@core/auth/auth.selectors'; +import { mergeMap, take } from 'rxjs/operators'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard'; +import { ActionAuthUpdateUserDetails } from '@core/auth/auth.actions'; +import { environment as env } from '@env/environment'; +import { TranslateService } from '@ngx-translate/core'; +import { ActionSettingsChangeLanguage } from '@core/settings/settings.actions'; +import { ChangePasswordDialogComponent } from '@modules/home/pages/profile/change-password-dialog.component'; +import { MatDialog } from '@angular/material'; +import { DialogService } from '@core/services/dialog.service'; +import { AuthService } from '@core/auth/auth.service'; +import { ActivatedRoute } from '@angular/router'; + +@Component({ + selector: 'tb-profile', + templateUrl: './profile.component.html', + styleUrls: ['./profile.component.scss'] +}) +export class ProfileComponent extends PageComponent implements OnInit, HasConfirmForm { + + authorities = Authority; + profile: FormGroup; + user: User; + languageList = env.supportedLangs; + + constructor(protected store: Store, + private route: ActivatedRoute, + private userService: UserService, + private authService: AuthService, + private translate: TranslateService, + public dialog: MatDialog, + public dialogService: DialogService, + public fb: FormBuilder) { + super(store); + } + + ngOnInit() { + this.buildProfileForm(); + this.userLoaded(this.route.snapshot.data.user); + } + + buildProfileForm() { + this.profile = this.fb.group({ + email: ['', [Validators.required, Validators.email]], + firstName: [''], + lastName: [''], + language: [''] + }); + } + + save(): void { + this.user = {...this.user, ...this.profile.value}; + if (!this.user.additionalInfo) { + this.user.additionalInfo = {}; + } + this.user.additionalInfo.lang = this.profile.get('language').value; + this.userService.saveUser(this.user).subscribe( + (user) => { + this.userLoaded(user); + this.store.dispatch(new ActionAuthUpdateUserDetails({ userDetails: { + additionalInfo: {...user.additionalInfo}, + authority: user.authority, + createdTime: user.createdTime, + tenantId: user.tenantId, + customerId: user.customerId, + email: user.email, + firstName: user.firstName, + id: user.id, + lastName: user.lastName, + } })); + this.store.dispatch(new ActionSettingsChangeLanguage({ userLang: user.additionalInfo.lang })); + } + ); + } + + changePassword(): void { + this.dialog.open(ChangePasswordDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'] + }); + } + + userLoaded(user: User) { + this.user = user; + this.profile.reset(user); + let lang; + if (user.additionalInfo && user.additionalInfo.lang) { + lang = user.additionalInfo.lang; + } else { + lang = this.translate.currentLang; + } + this.profile.get('language').setValue(lang); + } + + confirmForm(): FormGroup { + return this.profile; + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/profile/profile.module.ts b/ui-ngx/src/app/modules/home/pages/profile/profile.module.ts new file mode 100644 index 0000000000..fad3304d5e --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/profile/profile.module.ts @@ -0,0 +1,38 @@ +/// +/// Copyright © 2016-2019 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 { CommonModule } from '@angular/common'; +import { ProfileComponent } from './profile.component'; +import { SharedModule } from '@shared/shared.module'; +import { ProfileRoutingModule } from './profile-routing.module'; +import { ChangePasswordDialogComponent } from '@modules/home/pages/profile/change-password-dialog.component'; + +@NgModule({ + entryComponents: [ + ChangePasswordDialogComponent + ], + declarations: [ + ProfileComponent, + ChangePasswordDialogComponent + ], + imports: [ + CommonModule, + SharedModule, + ProfileRoutingModule + ] +}) +export class ProfileModule { } diff --git a/ui-ngx/src/app/modules/home/pages/tenant/tenant-routing.module.ts b/ui-ngx/src/app/modules/home/pages/tenant/tenant-routing.module.ts new file mode 100644 index 0000000000..e38bbbf92f --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/tenant/tenant-routing.module.ts @@ -0,0 +1,80 @@ +/// +/// Copyright © 2016-2019 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 { Injectable, NgModule } from '@angular/core'; +import { Resolve, RouterModule, Routes } from '@angular/router'; + +import { EntitiesTableComponent } from '@shared/components/entity/entities-table.component'; +import { Authority } from '@shared/models/authority.enum'; +import { TenantsTableConfigResolver } from '@modules/home/pages/tenant/tenants-table-config.resolver'; +import { ProfileComponent } from '@modules/home/pages/profile/profile.component'; +import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard'; +import { Customer } from '@shared/models/customer.model'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { forkJoin, Observable, throwError } from 'rxjs'; +import { getCurrentAuthUser } from '@core/auth/auth.selectors'; +import { catchError, finalize, map, tap } from 'rxjs/operators'; +import {UsersTableConfigResolver} from '../user/users-table-config.resolver'; + +const routes: Routes = [ + { + path: 'tenants', + data: { + breadcrumb: { + label: 'tenant.tenants', + icon: 'supervisor_account' + } + }, + children: [ + { + path: '', + component: EntitiesTableComponent, + data: { + auth: [Authority.SYS_ADMIN], + title: 'tenant.tenants' + }, + resolve: { + entitiesTableConfig: TenantsTableConfigResolver + } + }, + { + path: ':tenantId/users', + component: EntitiesTableComponent, + data: { + auth: [Authority.SYS_ADMIN], + title: 'user.tenant-admins', + breadcrumb: { + label: 'user.tenant-admins', + icon: 'account_circle' + } + }, + resolve: { + entitiesTableConfig: UsersTableConfigResolver + } + } + ] + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], + providers: [ + TenantsTableConfigResolver + ] +}) +export class TenantRoutingModule { } diff --git a/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.html b/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.html new file mode 100644 index 0000000000..e4f3f54a21 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.html @@ -0,0 +1,61 @@ + +
+ + +
+ +
+
+
+
+
+ + tenant.title + + + {{ 'tenant.title-required' | translate }} + + +
+ + tenant.description + + +
+ +
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts b/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts new file mode 100644 index 0000000000..a01408bd08 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts @@ -0,0 +1,76 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { Customer } from '@shared/models/customer.model'; +import { ContactBasedComponent } from '@shared/components/entity/contact-based.component'; +import {Tenant} from '@app/shared/models/tenant.model'; +import {ActionNotificationShow} from '@app/core/notification/notification.actions'; +import {TranslateService} from '@ngx-translate/core'; + +@Component({ + selector: 'tb-tenant', + templateUrl: './tenant.component.html' +}) +export class TenantComponent extends ContactBasedComponent { + + constructor(protected store: Store, + protected translate: TranslateService, + protected fb: FormBuilder) { + super(store, fb); + } + + hideDelete() { + if (this.entitiesTableConfig) { + return !this.entitiesTableConfig.deleteEnabled(this.entity); + } else { + return false; + } + } + + buildEntityForm(entity: Tenant): FormGroup { + return this.fb.group( + { + title: [entity ? entity.title : '', [Validators.required]], + additionalInfo: this.fb.group( + { + description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''] + } + ) + } + ); + } + + updateEntityForm(entity: Tenant) { + this.entityForm.patchValue({title: entity.title}); + this.entityForm.patchValue({additionalInfo: {description: entity.additionalInfo ? entity.additionalInfo.description : ''}}); + } + + onTenantIdCopied(event) { + this.store.dispatch(new ActionNotificationShow( + { + message: this.translate.instant('tenant.idCopiedMessage'), + type: 'success', + duration: 750, + verticalPosition: 'bottom', + horizontalPosition: 'right' + })); + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/tenant/tenant.module.ts b/ui-ngx/src/app/modules/home/pages/tenant/tenant.module.ts new file mode 100644 index 0000000000..83dfaa35bd --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/tenant/tenant.module.ts @@ -0,0 +1,36 @@ +/// +/// Copyright © 2016-2019 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 { CommonModule } from '@angular/common'; +import { SharedModule } from '@shared/shared.module'; +import {TenantComponent} from '@modules/home/pages/tenant/tenant.component'; +import {TenantRoutingModule} from '@modules/home/pages/tenant/tenant-routing.module'; + +@NgModule({ + entryComponents: [ + TenantComponent + ], + declarations: [ + TenantComponent + ], + imports: [ + CommonModule, + SharedModule, + TenantRoutingModule + ] +}) +export class TenantModule { } diff --git a/ui-ngx/src/app/modules/home/pages/tenant/tenants-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/tenant/tenants-table-config.resolver.ts new file mode 100644 index 0000000000..0ad5fe2318 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/tenant/tenants-table-config.resolver.ts @@ -0,0 +1,105 @@ +/// +/// Copyright © 2016-2019 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 { Injectable } from '@angular/core'; + +import { Resolve, Router } from '@angular/router'; + +import { Tenant } from '@shared/models/tenant.model'; +import { + DateEntityTableColumn, + EntityTableColumn, + EntityTableConfig +} from '@shared/components/entity/entities-table-config.models'; +import { TenantService } from '@core/http/tenant.service'; +import { TranslateService } from '@ngx-translate/core'; +import { DatePipe } from '@angular/common'; +import { + EntityType, + entityTypeResources, + entityTypeTranslations +} from '@shared/models/entity-type.models'; +import { TenantComponent } from '@modules/home/pages/tenant/tenant.component'; +import { EntityAction } from '@shared/components/entity/entity-component.models'; +import { User } from '@shared/models/user.model'; + +@Injectable() +export class TenantsTableConfigResolver implements Resolve> { + + private readonly config: EntityTableConfig = new EntityTableConfig(); + + constructor(private tenantService: TenantService, + private translate: TranslateService, + private datePipe: DatePipe, + private router: Router) { + + this.config.entityType = EntityType.CUSTOMER; + this.config.entityComponent = TenantComponent; + this.config.entityTranslations = entityTypeTranslations.get(EntityType.TENANT); + this.config.entityResources = entityTypeResources.get(EntityType.TENANT); + + this.config.columns.push( + new DateEntityTableColumn('createdTime', 'tenant.created-time', this.datePipe, '150px'), + new EntityTableColumn('title', 'tenant.title'), + new EntityTableColumn('email', 'contact.email'), + new EntityTableColumn('country', 'contact.country'), + new EntityTableColumn('city', 'contact.city') + ); + + this.config.cellActionDescriptors.push( + { + name: this.translate.instant('tenant.manage-tenant-admins'), + icon: 'account_circle', + isEnabled: () => true, + onAction: ($event, entity) => this.manageTenantAdmins($event, entity) + } + ); + + this.config.deleteEntityTitle = tenant => this.translate.instant('tenant.delete-tenant-title', { tenantTitle: tenant.title }); + this.config.deleteEntityContent = () => this.translate.instant('tenant.delete-tenant-text'); + this.config.deleteEntitiesTitle = count => this.translate.instant('tenant.delete-tenants-title', {count}); + this.config.deleteEntitiesContent = () => this.translate.instant('tenant.delete-tenants-text'); + + this.config.entitiesFetchFunction = pageLink => this.tenantService.getTenants(pageLink); + this.config.loadEntity = id => this.tenantService.getTenant(id.id); + this.config.saveEntity = tenant => this.tenantService.saveTenant(tenant); + this.config.deleteEntity = id => this.tenantService.deleteTenant(id.id); + this.config.onEntityAction = action => this.onTenantAction(action); + } + + resolve(): EntityTableConfig { + this.config.tableTitle = this.translate.instant('tenant.tenants'); + + return this.config; + } + + manageTenantAdmins($event: Event, tenant: Tenant) { + if ($event) { + $event.stopPropagation(); + } + this.router.navigateByUrl(`tenants/${tenant.id.id}/users`); + } + + onTenantAction(action: EntityAction): boolean { + switch (action.action) { + case 'manageTenantAdmins': + this.manageTenantAdmins(action.event, action.entity); + return true; + } + return false; + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/user/activation-link-dialog.component.html b/ui-ngx/src/app/modules/home/pages/user/activation-link-dialog.component.html new file mode 100644 index 0000000000..86439161d3 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/user/activation-link-dialog.component.html @@ -0,0 +1,59 @@ + +
+ +

user.activation-link

+ + +
+ + +
+
+
+ +
+
{{ activationLink }}
+ +
+
+
+
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/pages/user/activation-link-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/user/activation-link-dialog.component.ts new file mode 100644 index 0000000000..92490c17d5 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/user/activation-link-dialog.component.ts @@ -0,0 +1,64 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Inject, OnInit } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { TranslateService } from '@ngx-translate/core'; +import { ActionNotificationShow } from '@core/notification/notification.actions'; + +export interface ActivationLinkDialogData { + activationLink: string; +} + +@Component({ + selector: 'tb-activation-link-dialog', + templateUrl: './activation-link-dialog.component.html' +}) +export class ActivationLinkDialogComponent extends PageComponent implements OnInit { + + activationLink: string; + + constructor(protected store: Store, + @Inject(MAT_DIALOG_DATA) public data: ActivationLinkDialogData, + public dialogRef: MatDialogRef, + private translate: TranslateService) { + super(store); + this.activationLink = this.data.activationLink; + } + + ngOnInit(): void { + } + + close(): void { + this.dialogRef.close(); + } + + onActivationLinkCopied() { + this.store.dispatch(new ActionNotificationShow( + { + message: this.translate.instant('user.activation-link-copied-message'), + type: 'success', + target: 'activationLinkDialogContent', + duration: 1200, + verticalPosition: 'bottom', + horizontalPosition: 'left' + })); + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.html b/ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.html new file mode 100644 index 0000000000..092aa03842 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.html @@ -0,0 +1,59 @@ + +
+ +

user.add

+ +
+ +
+ + +
+
+ + + user.activation-method + + + {{ activationMethodTranslations.get(activationMethods[activationMethod]) | translate }} + + + +
+
+ + + +
+
diff --git a/ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.ts new file mode 100644 index 0000000000..ae8667dd37 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.ts @@ -0,0 +1,114 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Inject, OnInit, ViewChild } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { NgForm } from '@angular/forms'; +import { UserComponent } from '@modules/home/pages/user/user.component'; +import { Authority } from '@shared/models/authority.enum'; +import { ActivationMethod, activationMethodTranslations, User } from '@shared/models/user.model'; +import { CustomerId } from '@shared/models/id/customer-id'; +import { UserService } from '@core/http/user.service'; +import { Observable } from 'rxjs'; +import { + ActivationLinkDialogComponent, + ActivationLinkDialogData +} from '@modules/home/pages/user/activation-link-dialog.component'; +import {TenantId} from '@app/shared/models/id/tenant-id'; + +export interface AddUserDialogData { + tenantId: string; + customerId: string; + authority: Authority; +} + +@Component({ + selector: 'tb-add-user-dialog', + templateUrl: './add-user-dialog.component.html' +}) +export class AddUserDialogComponent extends PageComponent implements OnInit { + + detailsForm: NgForm; + user: User; + + activationMethods = ActivationMethod; + + activationMethodTranslations = activationMethodTranslations; + + activationMethod = ActivationMethod.DISPLAY_ACTIVATION_LINK; + + @ViewChild(UserComponent, {static: true}) userComponent: UserComponent; + + constructor(protected store: Store, + @Inject(MAT_DIALOG_DATA) public data: AddUserDialogData, + public dialogRef: MatDialogRef, + private userService: UserService, + private dialog: MatDialog) { + super(store); + } + + ngOnInit(): void { + this.user = {} as User; + this.userComponent.isEdit = true; + this.userComponent.entity = this.user; + this.detailsForm = this.userComponent.entityNgForm; + } + + cancel(): void { + this.dialogRef.close(null); + } + + add(): void { + if (this.detailsForm.valid) { + this.user = {...this.user, ...this.userComponent.entityForm.value}; + this.user.authority = this.data.authority; + this.user.tenantId = new TenantId(this.data.tenantId); + this.user.customerId = new CustomerId(this.data.customerId); + const sendActivationEmail = this.activationMethod === ActivationMethod.SEND_ACTIVATION_MAIL; + this.userService.saveUser(this.user, sendActivationEmail).subscribe( + (user) => { + if (this.activationMethod === ActivationMethod.DISPLAY_ACTIVATION_LINK) { + this.userService.getActivationLink(user.id.id).subscribe( + (activationLink) => { + this.displayActivationLink(activationLink).subscribe( + () => { + this.dialogRef.close(user); + } + ); + } + ); + } else { + this.dialogRef.close(user); + } + } + ); + } + } + + displayActivationLink(activationLink: string): Observable { + return this.dialog.open(ActivationLinkDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + activationLink + } + }).afterClosed(); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/user/user-routing.module.ts b/ui-ngx/src/app/modules/home/pages/user/user-routing.module.ts new file mode 100644 index 0000000000..691800cc36 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/user/user-routing.module.ts @@ -0,0 +1,28 @@ +/// +/// Copyright © 2016-2019 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 { UsersTableConfigResolver } from '@modules/home/pages/user/users-table-config.resolver'; + +@NgModule({ + imports: [], + exports: [RouterModule], + providers: [ + UsersTableConfigResolver + ] +}) +export class UserRoutingModule { } diff --git a/ui-ngx/src/app/modules/home/pages/user/user.component.html b/ui-ngx/src/app/modules/home/pages/user/user.component.html new file mode 100644 index 0000000000..b514783dbf --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/user/user.component.html @@ -0,0 +1,90 @@ + +
+ + + + +
+
+
+
+ + user.email + + + {{ 'user.invalid-email-format' | translate }} + + + {{ 'user.email-required' | translate }} + + + + user.first-name + + + + user.last-name + + +
+ + user.description + + +
+
+ + + {{ 'user.always-fullscreen' | translate }} + +
+
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/user/user.component.scss b/ui-ngx/src/app/modules/home/pages/user/user.component.scss new file mode 100644 index 0000000000..b8be9f8df8 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/user/user.component.scss @@ -0,0 +1,35 @@ +/** + * Copyright © 2016-2019 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 { + .tb-default-dashboard { + tb-dashboard-autocomplete { + @media #{$mat-gt-sm} { + padding-right: 12px; + } + + @media #{$mat-lt-md} { + padding-bottom: 12px; + } + } + mat-checkbox { + @media #{$mat-gt-sm} { + margin-top: 16px; + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/user/user.component.ts b/ui-ngx/src/app/modules/home/pages/user/user.component.ts new file mode 100644 index 0000000000..b861af909a --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/user/user.component.ts @@ -0,0 +1,82 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, OnInit } from '@angular/core'; +import { select, Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { EntityComponent } from '@shared/components/entity/entity.component'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { User } from '@shared/models/user.model'; +import { selectAuth, selectUserDetails } from '@core/auth/auth.selectors'; +import { map } from 'rxjs/operators'; +import { Authority } from '@shared/models/authority.enum'; + +@Component({ + selector: 'tb-user', + templateUrl: './user.component.html', + styleUrls: ['./user.component.scss'] +}) +export class UserComponent extends EntityComponent { + + authority = Authority; + + loginAsUserEnabled$ = this.store.pipe( + select(selectAuth), + map((auth) => auth.userTokenAccessEnabled) + ); + + constructor(protected store: Store, + public fb: FormBuilder) { + super(store); + } + + hideDelete() { + if (this.entitiesTableConfig) { + return !this.entitiesTableConfig.deleteEnabled(this.entity); + } else { + return false; + } + } + + buildForm(entity: User): FormGroup { + return this.fb.group( + { + email: [entity ? entity.email : '', [Validators.required, Validators.email]], + firstName: [entity ? entity.firstName : ''], + lastName: [entity ? entity.lastName : ''], + additionalInfo: this.fb.group( + { + description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''], + defaultDashboardId: [entity && entity.additionalInfo ? entity.additionalInfo.defaultDashboardId : null], + defaultDashboardFullscreen: [entity && entity.additionalInfo ? entity.additionalInfo.defaultDashboardFullscreen : false], + } + ) + } + ); + } + + updateForm(entity: User) { + this.entityForm.patchValue({email: entity.email}); + this.entityForm.patchValue({firstName: entity.firstName}); + this.entityForm.patchValue({lastName: entity.lastName}); + this.entityForm.patchValue({additionalInfo: {description: entity.additionalInfo ? entity.additionalInfo.description : ''}}); + this.entityForm.patchValue({additionalInfo: + {defaultDashboardId: entity.additionalInfo ? entity.additionalInfo.defaultDashboardId : null}}); + this.entityForm.patchValue({additionalInfo: + {defaultDashboardFullscreen: entity.additionalInfo ? entity.additionalInfo.defaultDashboardFullscreen : false}}); + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/user/user.module.ts b/ui-ngx/src/app/modules/home/pages/user/user.module.ts new file mode 100644 index 0000000000..a5a4143cec --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/user/user.module.ts @@ -0,0 +1,42 @@ +/// +/// Copyright © 2016-2019 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 { CommonModule } from '@angular/common'; +import { SharedModule } from '@shared/shared.module'; +import { UserComponent } from '@modules/home/pages/user/user.component'; +import { UserRoutingModule } from '@modules/home/pages/user/user-routing.module'; +import { AddUserDialogComponent } from '@modules/home/pages/user/add-user-dialog.component'; +import { ActivationLinkDialogComponent } from '@modules/home/pages/user/activation-link-dialog.component'; + +@NgModule({ + entryComponents: [ + UserComponent, + AddUserDialogComponent, + ActivationLinkDialogComponent + ], + declarations: [ + UserComponent, + AddUserDialogComponent, + ActivationLinkDialogComponent + ], + imports: [ + CommonModule, + SharedModule, + UserRoutingModule + ] +}) +export class UserModule { } diff --git a/ui-ngx/src/app/modules/home/pages/user/users-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/user/users-table-config.resolver.ts new file mode 100644 index 0000000000..b1774cd184 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/user/users-table-config.resolver.ts @@ -0,0 +1,230 @@ +/// +/// Copyright © 2016-2019 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 { Injectable } from '@angular/core'; + +import { ActivatedRouteSnapshot, Resolve } from '@angular/router'; +import { + DateEntityTableColumn, + EntityTableColumn, + EntityTableConfig +} from '@shared/components/entity/entities-table-config.models'; +import { TranslateService } from '@ngx-translate/core'; +import { DatePipe } from '@angular/common'; +import { + EntityType, + entityTypeResources, + entityTypeTranslations +} from '@shared/models/entity-type.models'; +import { User } from '@shared/models/user.model'; +import { UserService } from '@core/http/user.service'; +import { UserComponent } from '@modules/home/pages/user/user.component'; +import { CustomerService } from '@core/http/customer.service'; +import { map, mergeMap, take, tap } from 'rxjs/operators'; +import { forkJoin, noop, Observable, of } from 'rxjs'; +import { Authority } from '@shared/models/authority.enum'; +import { CustomerId } from '@shared/models/id/customer-id'; +import { MatDialog } from '@angular/material'; +import { EntityAction } from '@shared/components/entity/entity-component.models'; +import { + AddUserDialogComponent, + AddUserDialogData +} from '@modules/home/pages/user/add-user-dialog.component'; +import { AuthState } from '@core/auth/auth.models'; +import { select, Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { selectAuth } from '@core/auth/auth.selectors'; +import { AuthService } from '@core/auth/auth.service'; +import { + ActivationLinkDialogComponent, + ActivationLinkDialogData +} from '@modules/home/pages/user/activation-link-dialog.component'; +import { ActionNotificationShow } from '@core/notification/notification.actions'; +import { NULL_UUID } from '@shared/models/id/has-uuid'; +import { Customer } from '@shared/models/customer.model'; +import {TenantService} from '@app/core/http/tenant.service'; +import {TenantId} from '@app/shared/models/id/tenant-id'; + +export interface UsersTableRouteData { + authority: Authority; +} + +@Injectable() +export class UsersTableConfigResolver implements Resolve> { + + private readonly config: EntityTableConfig = new EntityTableConfig(); + + private tenantId: string; + private customerId: string; + private authority: Authority; + private authUser: User; + + constructor(private store: Store, + private userService: UserService, + private authService: AuthService, + private tenantService: TenantService, + private customerService: CustomerService, + private translate: TranslateService, + private datePipe: DatePipe, + private dialog: MatDialog) { + + this.config.entityType = EntityType.USER; + this.config.entityComponent = UserComponent; + this.config.entityTranslations = entityTypeTranslations.get(EntityType.USER); + this.config.entityResources = entityTypeResources.get(EntityType.USER); + + this.config.columns.push( + new DateEntityTableColumn('createdTime', 'user.created-time', this.datePipe, '150px'), + new EntityTableColumn('firstName', 'user.first-name'), + new EntityTableColumn('lastName', 'user.last-name'), + new EntityTableColumn('email', 'user.email') + ); + + this.config.deleteEnabled = user => user && user.id && user.id.id !== this.authUser.id.id; + this.config.deleteEntityTitle = user => this.translate.instant('user.delete-user-title', { userEmail: user.email }); + this.config.deleteEntityContent = () => this.translate.instant('user.delete-user-text'); + this.config.deleteEntitiesTitle = count => this.translate.instant('user.delete-users-title', {count}); + this.config.deleteEntitiesContent = () => this.translate.instant('user.delete-users-text'); + + this.config.loadEntity = id => this.userService.getUser(id.id); + this.config.saveEntity = user => this.saveUser(user); + this.config.deleteEntity = id => this.userService.deleteUser(id.id); + this.config.onEntityAction = action => this.onUserAction(action); + this.config.addEntity = () => this.addUser(); + } + + resolve(route: ActivatedRouteSnapshot): Observable> { + const routeParams = route.params; + return this.store.pipe(select(selectAuth), take(1)).pipe( + tap((auth) => { + this.authUser = auth.userDetails; + this.authority = routeParams.tenantId ? Authority.TENANT_ADMIN : Authority.CUSTOMER_USER; + if (this.authority === Authority.TENANT_ADMIN) { + this.tenantId = routeParams.tenantId; + this.customerId = NULL_UUID; + this.config.entitiesFetchFunction = pageLink => this.userService.getTenantAdmins(this.tenantId, pageLink); + } else { + this.tenantId = this.authUser.tenantId.id; + this.customerId = routeParams.customerId; + this.config.entitiesFetchFunction = pageLink => this.userService.getCustomerUsers(this.customerId, pageLink); + } + this.updateActionCellDescriptors(auth); + }), + mergeMap(() => this.authority === Authority.TENANT_ADMIN ? + this.tenantService.getTenant(this.tenantId) : + this.customerService.getCustomer(this.customerId)), + map((parentEntity) => { + if (this.authority === Authority.TENANT_ADMIN) { + this.config.tableTitle = parentEntity.title + ': ' + this.translate.instant('user.tenant-admins'); + } else { + this.config.tableTitle = parentEntity.title + ': ' + this.translate.instant('user.customer-users'); + } + return this.config; + }) + ); + } + + updateActionCellDescriptors(auth: AuthState) { + this.config.cellActionDescriptors.splice(0); + if (auth.userTokenAccessEnabled) { + this.config.cellActionDescriptors.push( + { + name: this.authority === Authority.TENANT_ADMIN ? + this.translate.instant('user.login-as-tenant-admin') : + this.translate.instant('user.login-as-customer-user'), + icon: 'mdi:login', + isMdiIcon: true, + isEnabled: () => true, + onAction: ($event, entity) => this.loginAsUser($event, entity) + } + ); + } + } + + saveUser(user: User): Observable { + user.tenantId = new TenantId(this.tenantId); + user.customerId = new CustomerId(this.customerId); + user.authority = this.authority; + return this.userService.saveUser(user); + } + + addUser(): Observable { + return this.dialog.open(AddUserDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + tenantId: this.tenantId, + customerId: this.customerId, + authority: this.authority + } + }).afterClosed(); + } + + loginAsUser($event: Event, user: User) { + if ($event) { + $event.stopPropagation(); + } + this.authService.loginAsUser(user.id.id).subscribe(); + } + + displayActivationLink($event: Event, user: User) { + if ($event) { + $event.stopPropagation(); + } + this.userService.getActivationLink(user.id.id).subscribe( + (activationLink) => { + this.dialog.open(ActivationLinkDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + activationLink + } + }); + } + ); + } + + resendActivation($event: Event, user: User) { + if ($event) { + $event.stopPropagation(); + } + this.userService.sendActivationEmail(user.email).subscribe(() => { + this.store.dispatch(new ActionNotificationShow( + { + message: this.translate.instant('user.activation-email-sent-message'), + type: 'success' + })); + }); + } + + onUserAction(action: EntityAction): boolean { + switch (action.action) { + case 'loginAsUser': + this.loginAsUser(action.event, action.entity); + return true; + case 'displayActivationLink': + this.displayActivationLink(action.event, action.entity); + return true; + case 'resendActivation': + this.resendActivation(action.event, action.entity); + return true; + } + return false; + } + +} diff --git a/ui-ngx/src/app/shared/components/contact.component.html b/ui-ngx/src/app/shared/components/contact.component.html new file mode 100644 index 0000000000..f4854853ba --- /dev/null +++ b/ui-ngx/src/app/shared/components/contact.component.html @@ -0,0 +1,63 @@ + +
+ + contact.country + + + {{ country }} + + + +
+ + contact.city + + + + contact.state + + + + contact.postal-code + + + {{ 'contact.postal-code-invalid' | translate }} + + +
+ + contact.address + + + + contact.address2 + + + + contact.phone + + + + contact.email + + + {{ 'user.invalid-email-format' | translate }} + + +
diff --git a/ui-ngx/src/app/shared/components/contact.component.ts b/ui-ngx/src/app/shared/components/contact.component.ts new file mode 100644 index 0000000000..f16993bfbc --- /dev/null +++ b/ui-ngx/src/app/shared/components/contact.component.ts @@ -0,0 +1,34 @@ +/// +/// Copyright © 2016-2019 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, Input } from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import { COUNTRIES } from '@shared/components/contact.models'; + +@Component({ + selector: 'tb-contact', + templateUrl: './contact.component.html' +}) +export class ContactComponent { + + @Input() + parentForm: FormGroup; + + @Input() isEdit: boolean; + + countries = COUNTRIES; + +} diff --git a/ui-ngx/src/app/shared/components/contact.models.ts b/ui-ngx/src/app/shared/components/contact.models.ts new file mode 100644 index 0000000000..e8fc277049 --- /dev/null +++ b/ui-ngx/src/app/shared/components/contact.models.ts @@ -0,0 +1,291 @@ +/// +/// Copyright © 2016-2019 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 const COUNTRIES = [ + 'Afghanistan', + 'Åland Islands', + 'Albania', + 'Algeria', + 'American Samoa', + 'Andorra', + 'Angola', + 'Anguilla', + 'Antarctica', + 'Antigua and Barbuda', + 'Argentina', + 'Armenia', + 'Aruba', + 'Australia', + 'Austria', + 'Azerbaijan', + 'Bahamas', + 'Bahrain', + 'Bangladesh', + 'Barbados', + 'Belarus', + 'Belgium', + 'Belize', + 'Benin', + 'Bermuda', + 'Bhutan', + 'Bolivia', + 'Bonaire, Sint Eustatius and Saba', + 'Bosnia and Herzegovina', + 'Botswana', + 'Bouvet Island', + 'Brazil', + 'British Indian Ocean Territory', + 'Brunei Darussalam', + 'Bulgaria', + 'Burkina Faso', + 'Burundi', + 'Cambodia', + 'Cameroon', + 'Canada', + 'Cape Verde', + 'Cayman Islands', + 'Central African Republic', + 'Chad', + 'Chile', + 'China', + 'Christmas Island', + 'Cocos (Keeling) Islands', + 'Colombia', + 'Comoros', + 'Congo', + 'Congo, The Democratic Republic of the', + 'Cook Islands', + 'Costa Rica', + 'Côte d\'Ivoire', + 'Croatia', + 'Cuba', + 'Curaçao', + 'Cyprus', + 'Czech Republic', + 'Denmark', + 'Djibouti', + 'Dominica', + 'Dominican Republic', + 'Ecuador', + 'Egypt', + 'El Salvador', + 'Equatorial Guinea', + 'Eritrea', + 'Estonia', + 'Ethiopia', + 'Falkland Islands (Malvinas)', + 'Faroe Islands', + 'Fiji', + 'Finland', + 'France', + 'French Guiana', + 'French Polynesia', + 'French Southern Territories', + 'Gabon', + 'Gambia', + 'Georgia', + 'Germany', + 'Ghana', + 'Gibraltar', + 'Greece', + 'Greenland', + 'Grenada', + 'Guadeloupe', + 'Guam', + 'Guatemala', + 'Guernsey', + 'Guinea', + 'Guinea-Bissau', + 'Guyana', + 'Haiti', + 'Heard Island and McDonald Islands', + 'Holy See (Vatican City State)', + 'Honduras', + 'Hong Kong', + 'Hungary', + 'Iceland', + 'India', + 'Indonesia', + 'Iran, Islamic Republic of', + 'Iraq', + 'Ireland', + 'Isle of Man', + 'Israel', + 'Italy', + 'Jamaica', + 'Japan', + 'Jersey', + 'Jordan', + 'Kazakhstan', + 'Kenya', + 'Kiribati', + 'Korea, Democratic People\'s Republic of', + 'Korea, Republic of', + 'Kuwait', + 'Kyrgyzstan', + 'Lao People\'s Democratic Republic', + 'Latvia', + 'Lebanon', + 'Lesotho', + 'Liberia', + 'Libya', + 'Liechtenstein', + 'Lithuania', + 'Luxembourg', + 'Macao', + 'Macedonia, Republic Of', + 'Madagascar', + 'Malawi', + 'Malaysia', + 'Maldives', + 'Mali', + 'Malta', + 'Marshall Islands', + 'Martinique', + 'Mauritania', + 'Mauritius', + 'Mayotte', + 'Mexico', + 'Micronesia, Federated States of', + 'Moldova, Republic of', + 'Monaco', + 'Mongolia', + 'Montenegro', + 'Montserrat', + 'Morocco', + 'Mozambique', + 'Myanmar', + 'Namibia', + 'Nauru', + 'Nepal', + 'Netherlands', + 'New Caledonia', + 'New Zealand', + 'Nicaragua', + 'Niger', + 'Nigeria', + 'Niue', + 'Norfolk Island', + 'Northern Mariana Islands', + 'Norway', + 'Oman', + 'Pakistan', + 'Palau', + 'Palestinian Territory, Occupied', + 'Panama', + 'Papua New Guinea', + 'Paraguay', + 'Peru', + 'Philippines', + 'Pitcairn', + 'Poland', + 'Portugal', + 'Puerto Rico', + 'Qatar', + 'Reunion', + 'Romania', + 'Russian Federation', + 'Rwanda', + 'Saint Barthélemy', + 'Saint Helena, Ascension and Tristan da Cunha', + 'Saint Kitts and Nevis', + 'Saint Lucia', + 'Saint Martin (French Part)', + 'Saint Pierre and Miquelon', + 'Saint Vincent and the Grenadines', + 'Samoa', + 'San Marino', + 'Sao Tome and Principe', + 'Saudi Arabia', + 'Senegal', + 'Serbia', + 'Seychelles', + 'Sierra Leone', + 'Singapore', + 'Sint Maarten (Dutch Part)', + 'Slovakia', + 'Slovenia', + 'Solomon Islands', + 'Somalia', + 'South Africa', + 'South Georgia and the South Sandwich Islands', + 'South Sudan', + 'Spain', + 'Sri Lanka', + 'Sudan', + 'Suriname', + 'Svalbard and Jan Mayen', + 'Swaziland', + 'Sweden', + 'Switzerland', + 'Syrian Arab Republic', + 'Taiwan', + 'Tajikistan', + 'Tanzania, United Republic of', + 'Thailand', + 'Timor-Leste', + 'Togo', + 'Tokelau', + 'Tonga', + 'Trinidad and Tobago', + 'Tunisia', + 'Turkey', + 'Turkmenistan', + 'Turks and Caicos Islands', + 'Tuvalu', + 'Uganda', + 'Ukraine', + 'United Arab Emirates', + 'United Kingdom', + 'United States', + 'United States Minor Outlying Islands', + 'Uruguay', + 'Uzbekistan', + 'Vanuatu', + 'Venezuela', + 'Viet Nam', + 'Virgin Islands, British', + 'Virgin Islands, U.S.', + 'Wallis and Futuna', + 'Western Sahara', + 'Yemen', + 'Zambia', + 'Zimbabwe' +]; + +/* tslint:disable */ +export const POSTAL_CODE_PATTERNS = { + 'United States': '(\\d{5}([\\-]\\d{4})?)', + 'Australia': '[0-9]{4}', + 'Austria': '[0-9]{4}', + 'Belgium': '[0-9]{4}', + 'Brazil': '[0-9]{5}[\\-]?[0-9]{3}', + 'Canada': '^(?!.*[DFIOQU])[A-VXY][0-9][A-Z][ -]?[0-9][A-Z][0-9]$', + 'Denmark': '[0-9]{3,4}', + 'Faroe Islands': '[0-9]{3,4}', + 'Netherlands': '[1-9][0-9]{3}\\s?[a-zA-Z]{2}', + 'Germany': '[0-9]{5}', + 'Hungary': '[0-9]{4}', + 'Italy': '[0-9]{5}', + 'Japan': '\\d{3}-\\d{4}', + 'Luxembourg': '(L\\s*(-|—|–))\\s*?[\\d]{4}', + 'Poland': '[0-9]{2}\\-[0-9]{3}', + 'Spain': '((0[1-9]|5[0-2])|[1-4][0-9])[0-9]{3}', + 'Sweden': '\\d{3}\\s?\\d{2}', + 'United Kingdom': '[A-Za-z]{1,2}[0-9Rr][0-9A-Za-z]? [0-9][ABD-HJLNP-UW-Zabd-hjlnp-uw-z]{2}' +}; +/* tslint:enable */ + diff --git a/ui-ngx/src/app/shared/components/dashboard-autocomplete.component.html b/ui-ngx/src/app/shared/components/dashboard-autocomplete.component.html new file mode 100644 index 0000000000..ebffafd58f --- /dev/null +++ b/ui-ngx/src/app/shared/components/dashboard-autocomplete.component.html @@ -0,0 +1,46 @@ + + + + + + + + + + + {{ translate.get('dashboard.no-dashboards-matching', {entity: searchText}) | async }} + + + + + + + + + + diff --git a/ui-ngx/src/app/shared/components/dashboard-autocomplete.component.ts b/ui-ngx/src/app/shared/components/dashboard-autocomplete.component.ts new file mode 100644 index 0000000000..ad2ba9b4a6 --- /dev/null +++ b/ui-ngx/src/app/shared/components/dashboard-autocomplete.component.ts @@ -0,0 +1,226 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import {AfterViewInit, Component, ElementRef, forwardRef, Input, OnInit, ViewChild} from '@angular/core'; +import {ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR} from '@angular/forms'; +import {Observable, of} from 'rxjs'; +import {PageLink} from '@shared/models/page/page-link'; +import {Direction} from '@shared/models/page/sort-order'; +import {map, mergeMap, startWith, tap} from 'rxjs/operators'; +import {PageData, emptyPageData} from '@shared/models/page/page-data'; +import {DashboardInfo} from '@app/shared/models/dashboard.models'; +import {DashboardId} from '@app/shared/models/id/dashboard-id'; +import {DashboardService} from '@core/http/dashboard.service'; +import {Store} from '@ngrx/store'; +import {AppState} from '@app/core/core.state'; +import {getCurrentAuthUser} from '@app/core/auth/auth.selectors'; +import {Authority} from '@shared/models/authority.enum'; +import {TranslateService} from '@ngx-translate/core'; + +@Component({ + selector: 'tb-dashboard-autocomplete', + templateUrl: './dashboard-autocomplete.component.html', + styleUrls: [], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => DashboardAutocompleteComponent), + multi: true + }] +}) +export class DashboardAutocompleteComponent implements ControlValueAccessor, OnInit, AfterViewInit { + + selectDashboardFormGroup: FormGroup; + + modelValue: DashboardInfo | string | null; + + @Input() + useIdValue = true; + + @Input() + selectFirstDashboard = false; + + @Input() + placeholder: string; + + @Input() + dashboardsScope: 'customer' | 'tenant'; + + @Input() + tenantId: string; + + @Input() + customerId: string; + + @Input() + required: boolean; + + @Input() + disabled: boolean; + + @ViewChild('dashboardInput', {static: true}) dashboardInput: ElementRef; + + filteredDashboards: Observable>; + + private valueLoaded = false; + + private searchText = ''; + + private propagateChange = (v: any) => { }; + + constructor(private store: Store, + public translate: TranslateService, + private dashboardService: DashboardService, + private fb: FormBuilder) { + this.selectDashboardFormGroup = this.fb.group({ + dashboard: [null] + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + ngOnInit() { + + } + + ngAfterViewInit(): void { + this.selectFirstDashboardIfNeeded(); + } + + selectFirstDashboardIfNeeded(): void { + if (this.selectFirstDashboard && !this.modelValue) { + this.getDashboards(new PageLink(1)).subscribe( + (data) => { + if (data.data.length) { + const dashboard = data.data[0]; + this.modelValue = this.useIdValue ? dashboard.id.id : dashboard; + this.selectDashboardFormGroup.get('dashboard').patchValue(dashboard, {emitEvent: false}); + this.propagateChange(this.modelValue); + } + } + ); + } + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + initFilteredResults(): void { + this.filteredDashboards = this.selectDashboardFormGroup.get('dashboard').valueChanges + .pipe( + startWith(''), + tap(value => { + if (this.valueLoaded) { + let modelValue; + if (typeof value === 'string' || !value) { + modelValue = null; + } else { + modelValue = this.useIdValue ? value.id.id : value; + } + this.updateView(modelValue); + } + }), + map(value => value ? (typeof value === 'string' ? value : value.name) : ''), + mergeMap(name => this.fetchDashboards(name) ) + ); + } + + writeValue(value: DashboardInfo | string | null): void { + this.valueLoaded = false; + this.searchText = ''; + this.initFilteredResults(); + if (value != null) { + if (typeof value === 'string') { + this.dashboardService.getDashboardInfo(value).subscribe( + (dashboard) => { + this.modelValue = this.useIdValue ? dashboard.id.id : dashboard; + this.selectDashboardFormGroup.get('dashboard').patchValue(dashboard, {emitEvent: true}); + this.valueLoaded = true; + } + ); + } else { + this.modelValue = this.useIdValue ? value.id.id : value; + this.selectDashboardFormGroup.get('dashboard').patchValue(value, {emitEvent: false}); + this.valueLoaded = true; + } + } else { + this.modelValue = null; + this.selectDashboardFormGroup.get('dashboard').patchValue(null, {emitEvent: false}); + this.valueLoaded = true; + } + } + + updateView(value: DashboardInfo | string | null) { + if (this.modelValue !== value) { + this.modelValue = value; + this.propagateChange(this.modelValue); + } + } + + displayDashboardFn(dashboard?: DashboardInfo): string | undefined { + return dashboard ? dashboard.title : undefined; + } + + fetchDashboards(searchText?: string): Observable> { + this.searchText = searchText; + const pageLink = new PageLink(10, 0, searchText, { + property: 'title', + direction: Direction.ASC + }); + return this.getDashboards(pageLink).pipe( + map(pageData => { + return pageData.data; + }) + ); + } + + getDashboards(pageLink: PageLink): Observable> { + let dashboardsObservable: Observable>; + const authUser = getCurrentAuthUser(this.store); + if (this.dashboardsScope === 'customer' || authUser.authority === Authority.CUSTOMER_USER) { + if (this.customerId) { + dashboardsObservable = this.dashboardService.getCustomerDashboards(this.customerId, pageLink, false, true); + } else { + dashboardsObservable = of(emptyPageData()); + } + } else { + if (authUser.authority === Authority.SYS_ADMIN) { + if (this.tenantId) { + dashboardsObservable = this.dashboardService.getTenantDashboardsByTenantId(this.tenantId, pageLink, false, true); + } else { + dashboardsObservable = of(emptyPageData()); + } + } else { + dashboardsObservable = this.dashboardService.getTenantDashboards(pageLink, false, true); + } + } + return dashboardsObservable; + } + + clear() { + this.selectDashboardFormGroup.get('dashboard').patchValue(null, {emitEvent: true}); + setTimeout(() => { + this.dashboardInput.nativeElement.blur(); + this.dashboardInput.nativeElement.focus(); + }, 0); + } + +} diff --git a/ui-ngx/src/app/shared/components/details-panel.component.html b/ui-ngx/src/app/shared/components/details-panel.component.html new file mode 100644 index 0000000000..05f855fc43 --- /dev/null +++ b/ui-ngx/src/app/shared/components/details-panel.component.html @@ -0,0 +1,56 @@ + +
+ +
+
+ {{ headerTitle }} + {{ headerSubtitle }} + + + +
+ + +
+
+ + +
+
+
+
+ +
diff --git a/ui-ngx/src/app/shared/components/details-panel.component.scss b/ui-ngx/src/app/shared/components/details-panel.component.scss new file mode 100644 index 0000000000..bcbc36cbbd --- /dev/null +++ b/ui-ngx/src/app/shared/components/details-panel.component.scss @@ -0,0 +1,53 @@ +/** + * Copyright © 2016-2019 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 { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + .mat-toolbar-tools { + height: 100%; + min-height: 100px; + max-height: 120px; + } + .tb-details-title { + width: inherit; + margin: 20px 8px 0 0; + overflow: hidden; + font-size: 1rem; + font-weight: 400; + text-overflow: ellipsis; + text-transform: uppercase; + white-space: nowrap; + + @media #{$mat-gt-sm} { + font-size: 1.6rem; + } + } + + .tb-details-subtitle { + width: inherit; + margin: 10px 0; + overflow: hidden; + font-size: 1rem; + text-overflow: ellipsis; + white-space: nowrap; + opacity: .8; + } + +} diff --git a/ui-ngx/src/app/shared/components/details-panel.component.ts b/ui-ngx/src/app/shared/components/details-panel.component.ts new file mode 100644 index 0000000000..43a9f43990 --- /dev/null +++ b/ui-ngx/src/app/shared/components/details-panel.component.ts @@ -0,0 +1,80 @@ +/// +/// Copyright © 2016-2019 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, Output } from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { NgForm } from '@angular/forms'; + +@Component({ + selector: 'tb-details-panel', + templateUrl: './details-panel.component.html', + styleUrls: ['./details-panel.component.scss'] +}) +export class DetailsPanelComponent extends PageComponent { + + @Input() headerHeightPx = 100; + @Input() headerTitle = ''; + @Input() headerSubtitle = ''; + @Input() isReadOnly = false; + @Input() isAlwaysEdit = false; + @Input() theForm: NgForm; + @Output() + closeDetails = new EventEmitter(); + @Output() + toggleDetailsEditMode = new EventEmitter(); + @Output() + applyDetails = new EventEmitter(); + + isEditValue = false; + + @Output() + isEditChange = new EventEmitter(); + + @Input() + get isEdit() { + return this.isEditValue; + } + + set isEdit(val: boolean) { + this.isEditValue = val; + this.isEditChange.emit(this.isEditValue); + } + + + constructor(protected store: Store) { + super(store); + } + + onCloseDetails() { + this.closeDetails.emit(); + } + + onToggleDetailsEditMode() { + if (!this.isAlwaysEdit) { + this.isEdit = !this.isEdit; + } + this.toggleDetailsEditMode.emit(this.isEditValue); + } + + onApplyDetails() { + if (this.theForm.valid) { + this.applyDetails.emit(); + } + } + +} diff --git a/ui-ngx/src/app/shared/components/entity/add-entity-dialog.component.html b/ui-ngx/src/app/shared/components/entity/add-entity-dialog.component.html new file mode 100644 index 0000000000..f3f9c3ec9a --- /dev/null +++ b/ui-ngx/src/app/shared/components/entity/add-entity-dialog.component.html @@ -0,0 +1,51 @@ + +
+ +

{{ translations.add }}

+ +
+ +
+ + +
+
+ +
+
+ + + +
+
diff --git a/ui-ngx/src/app/shared/components/entity/add-entity-dialog.component.scss b/ui-ngx/src/app/shared/components/entity/add-entity-dialog.component.scss new file mode 100644 index 0000000000..bb18c2c7b6 --- /dev/null +++ b/ui-ngx/src/app/shared/components/entity/add-entity-dialog.component.scss @@ -0,0 +1,17 @@ +/** + * Copyright © 2016-2019 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 { +} diff --git a/ui-ngx/src/app/shared/components/entity/add-entity-dialog.component.ts b/ui-ngx/src/app/shared/components/entity/add-entity-dialog.component.ts new file mode 100644 index 0000000000..a4c0de7ec3 --- /dev/null +++ b/ui-ngx/src/app/shared/components/entity/add-entity-dialog.component.ts @@ -0,0 +1,103 @@ +/// +/// Copyright © 2016-2019 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, + ComponentFactoryResolver, + Inject, + OnInit, + SkipSelf, + ViewChild +} from '@angular/core'; +import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { FormControl, FormGroupDirective, NgForm } from '@angular/forms'; +import { EntityTypeResource, EntityTypeTranslation } from '@shared/models/entity-type.models'; +import { EntityTableConfig } from '@shared/components/entity/entities-table-config.models'; +import { BaseData, HasId } from '@shared/models/base-data'; +import { EntityId } from '@shared/models/id/entity-id'; +import { AddEntityDialogData } from '@shared/components/entity/entity-component.models'; +import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; +import { EntityComponent } from '@shared/components/entity/entity.component'; + +@Component({ + selector: 'tb-add-entity-dialog', + templateUrl: './add-entity-dialog.component.html', + providers: [{provide: ErrorStateMatcher, useExisting: AddEntityDialogComponent}], + styleUrls: ['./add-entity-dialog.component.scss'] +}) +export class AddEntityDialogComponent extends PageComponent implements OnInit, ErrorStateMatcher { + + entityComponent: EntityComponent>; + detailsForm: NgForm; + + entitiesTableConfig: EntityTableConfig>; + translations: EntityTypeTranslation; + resources: EntityTypeResource; + entity: BaseData; + + submitted = false; + + @ViewChild('entityDetailsForm', {static: true}) entityDetailsFormAnchor: TbAnchorComponent; + + constructor(protected store: Store, + @Inject(MAT_DIALOG_DATA) public data: AddEntityDialogData>, + public dialogRef: MatDialogRef>, + private componentFactoryResolver: ComponentFactoryResolver, + @SkipSelf() private errorStateMatcher: ErrorStateMatcher) { + super(store); + } + + ngOnInit(): void { + this.entitiesTableConfig = this.data.entitiesTableConfig; + this.translations = this.entitiesTableConfig.entityTranslations; + this.resources = this.entitiesTableConfig.entityResources; + this.entity = {}; + const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.entitiesTableConfig.entityComponent); + const viewContainerRef = this.entityDetailsFormAnchor.viewContainerRef; + viewContainerRef.clear(); + const componentRef = viewContainerRef.createComponent(componentFactory); + this.entityComponent = componentRef.instance; + this.entityComponent.isEdit = true; + this.entityComponent.entitiesTableConfig = this.entitiesTableConfig; + this.entityComponent.entity = this.entity; + this.detailsForm = this.entityComponent.entityNgForm; + } + + isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { + const originalErrorState = this.errorStateMatcher.isErrorState(control, form); + const customErrorState = !!(control && control.invalid && this.submitted); + return originalErrorState || customErrorState; + } + + cancel(): void { + this.dialogRef.close(null); + } + + add(): void { + this.submitted = true; + if (this.detailsForm.valid) { + this.entity = {...this.entity, ...this.entityComponent.entityFormValue()}; + this.entitiesTableConfig.saveEntity(this.entity).subscribe( + (entity) => { + this.dialogRef.close(entity); + } + ); + } + } +} diff --git a/ui-ngx/src/app/shared/components/entity/contact-based.component.ts b/ui-ngx/src/app/shared/components/entity/contact-based.component.ts new file mode 100644 index 0000000000..9f4a50a73f --- /dev/null +++ b/ui-ngx/src/app/shared/components/entity/contact-based.component.ts @@ -0,0 +1,84 @@ +/// +/// Copyright © 2016-2019 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 { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { EntityComponent } from '@shared/components/entity/entity.component'; +import { FormBuilder, FormGroup, ValidatorFn, Validators } from '@angular/forms'; +import { ContactBased } from '@shared/models/contact-based.model'; +import { AfterViewInit } from '@angular/core'; +import { POSTAL_CODE_PATTERNS } from '@shared/components/contact.models'; +import { HasId } from '@shared/models/base-data'; + +export abstract class ContactBasedComponent> extends EntityComponent implements AfterViewInit { + + constructor(protected store: Store, + protected fb: FormBuilder) { + super(store); + } + + buildForm(entity: T): FormGroup { + const entityForm = this.buildEntityForm(entity); + entityForm.addControl('country', this.fb.control(entity ? entity.country : '', [])); + entityForm.addControl('city', this.fb.control(entity ? entity.city : '', [])); + entityForm.addControl('state', this.fb.control(entity ? entity.state : '', [])); + entityForm.addControl('zip', this.fb.control(entity ? entity.zip : '', + this.zipValidators(entity ? entity.country : '') + )); + entityForm.addControl('address', this.fb.control(entity ? entity.address : '', [])); + entityForm.addControl('address2', this.fb.control(entity ? entity.address2 : '', [])); + entityForm.addControl('phone', this.fb.control(entity ? entity.phone : '', [])); + entityForm.addControl('email', this.fb.control(entity ? entity.email : '', [Validators.email])); + return entityForm; + } + + updateForm(entity: T) { + this.updateEntityForm(entity); + this.entityForm.patchValue({country: entity.country}); + this.entityForm.patchValue({city: entity.city}); + this.entityForm.patchValue({state: entity.state}); + this.entityForm.get('zip').setValidators(this.zipValidators(entity.country)); + this.entityForm.patchValue({zip: entity.zip}); + this.entityForm.patchValue({address: entity.address}); + this.entityForm.patchValue({address2: entity.address2}); + this.entityForm.patchValue({phone: entity.phone}); + this.entityForm.patchValue({email: entity.email}); + } + + ngAfterViewInit() { + this.entityForm.get('country').valueChanges.subscribe( + (country) => { + this.entityForm.get('zip').setValidators(this.zipValidators(country)); + this.entityForm.get('zip').updateValueAndValidity({onlySelf: true}); + this.entityForm.get('zip').markAsTouched({onlySelf: true}); + } + ); + } + + zipValidators(country: string): ValidatorFn[] { + const zipValidators = []; + if (country && POSTAL_CODE_PATTERNS[country]) { + const postalCodePattern = POSTAL_CODE_PATTERNS[country]; + zipValidators.push(Validators.pattern(postalCodePattern)); + } + return zipValidators; + } + + abstract buildEntityForm(entity: T): FormGroup; + + abstract updateEntityForm(entity: T); + +} diff --git a/ui-ngx/src/app/shared/components/entity/entities-table-config.models.ts b/ui-ngx/src/app/shared/components/entity/entities-table-config.models.ts new file mode 100644 index 0000000000..5a6c9d26a6 --- /dev/null +++ b/ui-ngx/src/app/shared/components/entity/entities-table-config.models.ts @@ -0,0 +1,137 @@ +/// +/// Copyright © 2016-2019 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 { BaseData, HasId } from '@shared/models/base-data'; +import { EntityId } from '@shared/models/id/entity-id'; +import { EntitiesFetchFunction } from '@shared/models/datasource/entity-datasource'; +import { Observable, of } from 'rxjs'; +import { emptyPageData } from '@shared/models/page/page-data'; +import { DatePipe } from '@angular/common'; +import { Direction, SortOrder } from '@shared/models/page/sort-order'; +import { + EntityType, + EntityTypeResource, + EntityTypeTranslation +} from '@shared/models/entity-type.models'; +import { EntityComponent } from '@shared/components/entity/entity.component'; +import { Type } from '@angular/core'; +import { EntityAction } from '@shared/components/entity/entity-component.models'; +import { HasUUID } from '@shared/models/id/has-uuid'; +import { PageLink } from '@shared/models/page/page-link'; +import { EntitiesTableComponent } from '@shared/components/entity/entities-table.component'; +import { EntityTableHeaderComponent } from '@shared/components/entity/entity-table-header.component'; +import { ActivatedRoute } from '@angular/router'; + +export type EntityBooleanFunction> = (entity: T) => boolean; +export type EntityStringFunction> = (entity: T) => string; +export type EntityCountStringFunction = (count: number) => string; +export type EntityTwoWayOperation> = (entity: T) => Observable; +export type EntityByIdOperation> = (id: HasUUID) => Observable; +export type EntityIdOneWayOperation = (id: HasUUID) => Observable; +export type EntityActionFunction> = (action: EntityAction) => boolean; +export type CreateEntityOperation> = () => Observable; + +export type CellContentFunction> = (entity: T, key: string) => string; +export type CellStyleFunction> = (entity: T, key: string) => object; + +export interface CellActionDescriptor> { + name: string; + nameFunction?: (entity: T) => string; + icon?: string; + isMdiIcon?: boolean; + color?: string; + isEnabled: (entity: T) => boolean; + onAction: ($event: MouseEvent, entity: T) => void; +} + +export interface GroupActionDescriptor> { + name: string; + icon: string; + isEnabled: boolean; + onAction: ($event: MouseEvent, entities: T[]) => void; +} + +export interface HeaderActionDescriptor { + name: string; + icon: string; + isEnabled: () => boolean; + onAction: ($event: MouseEvent) => void; +} + +export class EntityTableColumn> { + constructor(public key: string, + public title: string, + public maxWidth: string = '100%', + public cellContentFunction: CellContentFunction = (entity, property) => entity[property], + public cellStyleFunction: CellStyleFunction = () => ({})) { + } +} + +export class DateEntityTableColumn> extends EntityTableColumn { + constructor(key: string, + title: string, + datePipe: DatePipe, + maxWidth: string = '100%', + dateFormat: string = 'yyyy-MM-dd HH:mm:ss', + cellStyleFunction: CellStyleFunction = () => ({})) { + super(key, + title, + maxWidth, + (entity, property) => datePipe.transform(entity[property], dateFormat), + cellStyleFunction); + } +} + +export class EntityTableConfig, P extends PageLink = PageLink> { + + constructor() {} + + componentsData: any = null; + + loadDataOnInit = true; + onLoadAction: (route: ActivatedRoute) => void = null; + table: EntitiesTableComponent = null; + useTimePageLink = false; + entityType: EntityType = null; + tableTitle = ''; + selectionEnabled = true; + searchEnabled = true; + addEnabled = true; + entitiesDeleteEnabled = true; + detailsPanelEnabled = true; + actionsColumnTitle = null; + entityTranslations: EntityTypeTranslation; + entityResources: EntityTypeResource; + entityComponent: Type>; + defaultSortOrder: SortOrder = {property: 'createdTime', direction: Direction.ASC}; + columns: Array> = []; + cellActionDescriptors: Array> = []; + groupActionDescriptors: Array> = []; + headerActionDescriptors: Array = []; + headerComponent: Type>; + addEntity: CreateEntityOperation = null; + detailsReadonly: EntityBooleanFunction = () => false; + deleteEnabled: EntityBooleanFunction = () => true; + deleteEntityTitle: EntityStringFunction = () => ''; + deleteEntityContent: EntityStringFunction = () => ''; + deleteEntitiesTitle: EntityCountStringFunction = () => ''; + deleteEntitiesContent: EntityCountStringFunction = () => ''; + loadEntity: EntityByIdOperation = () => of(); + saveEntity: EntityTwoWayOperation = (entity) => of(entity); + deleteEntity: EntityIdOneWayOperation = () => of(); + entitiesFetchFunction: EntitiesFetchFunction = () => of(emptyPageData()); + onEntityAction: EntityActionFunction = () => false; +} diff --git a/ui-ngx/src/app/shared/components/entity/entities-table.component.html b/ui-ngx/src/app/shared/components/entity/entities-table.component.html new file mode 100644 index 0000000000..d5d0d95d51 --- /dev/null +++ b/ui-ngx/src/app/shared/components/entity/entities-table.component.html @@ -0,0 +1,182 @@ + + + + + + + +
+
+ +
+ {{ entitiesTableConfig.tableTitle }} + + + + + + + +
+
+ +
+ + +   + + + +
+
+ +
+ + {{ translate.get(translations.selectedEntities, {count: dataSource.selection.selected.length}) | async }} + + + +
+
+
+ + + + + + + + + + + + + {{ column.title | translate }} + + + + + {{ entitiesTableConfig.actionsColumnTitle ? (entitiesTableConfig.actionsColumnTitle | translate) : '' }} + + +
+ +
+
+ + + + +
+
+
+ + +
+ {{ translations.noEntities }} +
+ + +
+
+
+
diff --git a/ui-ngx/src/app/shared/components/entity/entities-table.component.scss b/ui-ngx/src/app/shared/components/entity/entities-table.component.scss new file mode 100644 index 0000000000..b4dd472432 --- /dev/null +++ b/ui-ngx/src/app/shared/components/entity/entities-table.component.scss @@ -0,0 +1,41 @@ +/** + * Copyright © 2016-2019 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 { + width: 100%; + height: 100%; + .tb-entity-table { + .tb-entity-table-content { + width: 100%; + height: 100%; + background: #fff; + + .tb-entity-table-title { + padding-right: 20px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .table-container { + overflow: auto; + } + } + } +} + +:host ::ng-deep .mat-sort-header-sorted .mat-sort-header-arrow { + opacity: 1 !important; +} diff --git a/ui-ngx/src/app/shared/components/entity/entities-table.component.ts b/ui-ngx/src/app/shared/components/entity/entities-table.component.ts new file mode 100644 index 0000000000..0fa6248581 --- /dev/null +++ b/ui-ngx/src/app/shared/components/entity/entities-table.component.ts @@ -0,0 +1,358 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + AfterViewInit, + Component, ComponentFactoryResolver, + ElementRef, + Input, + OnInit, + Type, + ViewChild +} from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { PageLink, TimePageLink } from '@shared/models/page/page-link'; +import { MatDialog, MatPaginator, MatSort } from '@angular/material'; +import { EntitiesDataSource } from '@shared/models/datasource/entity-datasource'; +import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators'; +import { Direction, SortOrder } from '@shared/models/page/sort-order'; +import { forkJoin, fromEvent, merge, Observable } from 'rxjs'; +import { TranslateService } from '@ngx-translate/core'; +import { BaseData, HasId } from '@shared/models/base-data'; +import { EntityId } from '@shared/models/id/entity-id'; +import { ActivatedRoute } from '@angular/router'; +import { + CellActionDescriptor, + EntityTableColumn, + EntityTableConfig, + GroupActionDescriptor, + HeaderActionDescriptor +} from '@shared/components/entity/entities-table-config.models'; +import { EntityTypeTranslation } from '@shared/models/entity-type.models'; +import { DialogService } from '@core/services/dialog.service'; +import { AddEntityDialogComponent } from '@shared/components/entity/add-entity-dialog.component'; +import { + AddEntityDialogData, + EntityAction +} from '@shared/components/entity/entity-component.models'; +import { Timewindow } from '@shared/models/time/time.models'; +import { DomSanitizer } from '@angular/platform-browser'; +import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; + +@Component({ + selector: 'tb-entities-table', + templateUrl: './entities-table.component.html', + styleUrls: ['./entities-table.component.scss'] +}) +export class EntitiesTableComponent extends PageComponent implements AfterViewInit, OnInit { + + @Input() + entitiesTableConfig: EntityTableConfig>; + + translations: EntityTypeTranslation; + + headerActionDescriptors: Array; + groupActionDescriptors: Array>>; + cellActionDescriptors: Array>>; + + columns: Array>>; + displayedColumns: string[] = []; + + selectionEnabled; + + pageLink: PageLink; + textSearchMode = false; + timewindow: Timewindow; + dataSource: EntitiesDataSource>; + + isDetailsOpen = false; + + @ViewChild('entityTableHeader', {static: false}) entityTableHeaderAnchor: TbAnchorComponent; + + @ViewChild('searchInput', {static: false}) searchInputField: ElementRef; + + @ViewChild(MatPaginator, {static: false}) paginator: MatPaginator; + @ViewChild(MatSort, {static: false}) sort: MatSort; + + constructor(protected store: Store, + private route: ActivatedRoute, + public translate: TranslateService, + public dialog: MatDialog, + private dialogService: DialogService, + private domSanitizer: DomSanitizer, + private componentFactoryResolver: ComponentFactoryResolver) { + super(store); + } + + ngOnInit() { + this.entitiesTableConfig = this.entitiesTableConfig || this.route.snapshot.data.entitiesTableConfig; + if (this.entitiesTableConfig.headerComponent) { + const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.entitiesTableConfig.headerComponent); + const viewContainerRef = this.entityTableHeaderAnchor.viewContainerRef; + viewContainerRef.clear(); + const componentRef = viewContainerRef.createComponent(componentFactory); + const headerComponent = componentRef.instance; + headerComponent.entitiesTableConfig = this.entitiesTableConfig; + } + + this.entitiesTableConfig.table = this; + this.translations = this.entitiesTableConfig.entityTranslations; + + this.headerActionDescriptors = [...this.entitiesTableConfig.headerActionDescriptors]; + this.groupActionDescriptors = [...this.entitiesTableConfig.groupActionDescriptors]; + this.cellActionDescriptors = [...this.entitiesTableConfig.cellActionDescriptors]; + + if (this.entitiesTableConfig.entitiesDeleteEnabled) { + this.cellActionDescriptors.push( + { + name: this.translate.instant('action.delete'), + icon: 'delete', + isEnabled: entity => this.entitiesTableConfig.deleteEnabled(entity), + onAction: ($event, entity) => this.deleteEntity($event, entity) + } + ); + } + + this.groupActionDescriptors.push( + { + name: this.translate.instant('action.delete'), + icon: 'delete', + isEnabled: this.entitiesTableConfig.entitiesDeleteEnabled, + onAction: ($event, entities) => this.deleteEntities($event, entities) + } + ); + + this.columns = [...this.entitiesTableConfig.columns]; + + this.selectionEnabled = this.entitiesTableConfig.selectionEnabled; + + if (this.selectionEnabled) { + this.displayedColumns.push('select'); + } + this.columns.forEach( + (column) => { + this.displayedColumns.push(column.key); + } + ); + this.displayedColumns.push('actions'); + + const sortOrder: SortOrder = { property: this.entitiesTableConfig.defaultSortOrder.property, + direction: this.entitiesTableConfig.defaultSortOrder.direction }; + + if (this.entitiesTableConfig.useTimePageLink) { + this.timewindow = Timewindow.historyInterval(24 * 60 * 60 * 1000); + const currentTime = new Date().getTime(); + this.pageLink = new TimePageLink(10, 0, null, sortOrder, + currentTime - this.timewindow.history.timewindowMs, currentTime); + } else { + this.pageLink = new PageLink(10, 0, null, sortOrder); + } + this.dataSource = new EntitiesDataSource>( + this.entitiesTableConfig.entitiesFetchFunction + ); + if (this.entitiesTableConfig.onLoadAction) { + this.entitiesTableConfig.onLoadAction(this.route); + } + if (this.entitiesTableConfig.loadDataOnInit) { + this.dataSource.loadEntities(this.pageLink); + } + } + + ngAfterViewInit() { + + fromEvent(this.searchInputField.nativeElement, 'keyup') + .pipe( + debounceTime(150), + distinctUntilChanged(), + tap(() => { + this.paginator.pageIndex = 0; + this.updateData(); + }) + ) + .subscribe(); + + this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0); + + merge(this.sort.sortChange, this.paginator.page) + .pipe( + tap(() => this.updateData()) + ) + .subscribe(); + } + + addEnabled() { + return this.entitiesTableConfig.addEnabled; + } + + updateData(closeDetails: boolean = true) { + if (closeDetails) { + this.isDetailsOpen = false; + } + this.pageLink.page = this.paginator.pageIndex; + this.pageLink.pageSize = this.paginator.pageSize; + this.pageLink.sortOrder.property = this.sort.active; + this.pageLink.sortOrder.direction = Direction[this.sort.direction.toUpperCase()]; + if (this.entitiesTableConfig.useTimePageLink) { + const timePageLink = this.pageLink as TimePageLink; + if (this.timewindow.history.timewindowMs) { + const currentTime = new Date().getTime(); + timePageLink.startTime = currentTime - this.timewindow.history.timewindowMs; + timePageLink.endTime = currentTime; + } else { + timePageLink.startTime = this.timewindow.history.fixedTimewindow.startTimeMs; + timePageLink.endTime = this.timewindow.history.fixedTimewindow.endTimeMs; + } + } + this.dataSource.loadEntities(this.pageLink); + } + + onRowClick($event: Event, entity) { + if ($event) { + $event.stopPropagation(); + } + if (this.dataSource.toggleCurrentEntity(entity)) { + this.isDetailsOpen = true; + } else { + this.isDetailsOpen = !this.isDetailsOpen; + } + } + + addEntity($event: Event) { + let entity$: Observable>; + if (this.entitiesTableConfig.addEntity) { + entity$ = this.entitiesTableConfig.addEntity(); + } else { + entity$ = this.dialog.open>, + BaseData>(AddEntityDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + entitiesTableConfig: this.entitiesTableConfig + } + }).afterClosed(); + } + entity$.subscribe( + (entity) => { + if (entity) { + this.updateData(); + } + } + ); + } + + onEntityUpdated(entity: BaseData) { + this.updateData(false); + } + + onEntityAction(action: EntityAction>) { + if (action.action === 'delete') { + this.deleteEntity(action.event, action.entity); + } + } + + deleteEntity($event: Event, entity: BaseData) { + if ($event) { + $event.stopPropagation(); + } + this.dialogService.confirm( + this.entitiesTableConfig.deleteEntityTitle(entity), + this.entitiesTableConfig.deleteEntityContent(entity), + this.translate.instant('action.no'), + this.translate.instant('action.yes'), + true + ).subscribe((result) => { + if (result) { + this.entitiesTableConfig.deleteEntity(entity.id).subscribe( + () => { + this.updateData(); + } + ); + } + }); + } + + deleteEntities($event: Event, entities: BaseData[]) { + if ($event) { + $event.stopPropagation(); + } + this.dialogService.confirm( + this.entitiesTableConfig.deleteEntitiesTitle(entities.length), + this.entitiesTableConfig.deleteEntitiesContent(entities.length), + this.translate.instant('action.no'), + this.translate.instant('action.yes'), + true + ).subscribe((result) => { + if (result) { + const tasks: Observable[] = []; + entities.forEach((entity) => { + if (this.entitiesTableConfig.deleteEnabled(entity)) { + tasks.push(this.entitiesTableConfig.deleteEntity(entity.id)); + } + }); + forkJoin(tasks).subscribe( + () => { + this.updateData(); + } + ); + } + }); + } + + onTimewindowChange() { + this.updateData(); + } + + enterFilterMode() { + this.textSearchMode = true; + this.pageLink.textSearch = ''; + setTimeout(() => { + this.searchInputField.nativeElement.focus(); + this.searchInputField.nativeElement.setSelectionRange(0, 0); + }, 10); + } + + exitFilterMode() { + this.textSearchMode = false; + this.pageLink.textSearch = null; + this.paginator.pageIndex = 0; + this.updateData(); + } + + resetSortAndFilter(update: boolean = true) { + this.pageLink.textSearch = null; + if (this.entitiesTableConfig.useTimePageLink) { + this.timewindow = Timewindow.historyInterval(24 * 60 * 60 * 1000); + } + this.paginator.pageIndex = 0; + const sortable = this.sort.sortables.get(this.entitiesTableConfig.defaultSortOrder.property); + this.sort.active = sortable.id; + this.sort.direction = this.entitiesTableConfig.defaultSortOrder.direction === Direction.ASC ? 'asc' : 'desc'; + if (update) { + this.updateData(); + } + } + + cellContent(entity: BaseData, column: EntityTableColumn>) { + return this.domSanitizer.bypassSecurityTrustHtml(column.cellContentFunction(entity, column.key)); + } + + cellStyle(entity: BaseData, column: EntityTableColumn>) { + return {...column.cellStyleFunction(entity, column.key), ...{maxWidth: column.maxWidth}}; + } + +} diff --git a/ui-ngx/src/app/shared/components/entity/entity-component.models.ts b/ui-ngx/src/app/shared/components/entity/entity-component.models.ts new file mode 100644 index 0000000000..a65d44a96c --- /dev/null +++ b/ui-ngx/src/app/shared/components/entity/entity-component.models.ts @@ -0,0 +1,28 @@ +/// +/// Copyright © 2016-2019 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 { BaseData, HasId } from '@shared/models/base-data'; +import { EntityTableConfig } from '@shared/components/entity/entities-table-config.models'; + +export interface AddEntityDialogData> { + entitiesTableConfig: EntityTableConfig; +} + +export interface EntityAction> { + event: Event; + action: string; + entity: T; +} diff --git a/ui-ngx/src/app/shared/components/entity/entity-details-panel.component.html b/ui-ngx/src/app/shared/components/entity/entity-details-panel.component.html new file mode 100644 index 0000000000..23e4633fcf --- /dev/null +++ b/ui-ngx/src/app/shared/components/entity/entity-details-panel.component.html @@ -0,0 +1,43 @@ + + +
+
+
+ + + + + + +
diff --git a/ui-ngx/src/app/shared/components/entity/entity-details-panel.component.scss b/ui-ngx/src/app/shared/components/entity/entity-details-panel.component.scss new file mode 100644 index 0000000000..d30159bdfa --- /dev/null +++ b/ui-ngx/src/app/shared/components/entity/entity-details-panel.component.scss @@ -0,0 +1,31 @@ +/** + * Copyright © 2016-2019 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 { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; +} + +:host ::ng-deep { + .mat-tab-body-wrapper { + position: absolute; + top: 49px; + left: 0; + right: 0; + bottom: 0; + } +} diff --git a/ui-ngx/src/app/shared/components/entity/entity-details-panel.component.ts b/ui-ngx/src/app/shared/components/entity/entity-details-panel.component.ts new file mode 100644 index 0000000000..582b8b389f --- /dev/null +++ b/ui-ngx/src/app/shared/components/entity/entity-details-panel.component.ts @@ -0,0 +1,165 @@ +/// +/// Copyright © 2016-2019 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, + ComponentFactoryResolver, + EventEmitter, + Input, + OnDestroy, + OnInit, + Output, + ViewChild +} from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { EntityTableConfig } from '@shared/components/entity/entities-table-config.models'; +import { BaseData, HasId } from '@shared/models/base-data'; +import { + EntityType, + EntityTypeResource, + EntityTypeTranslation +} from '@shared/models/entity-type.models'; +import { NgForm } from '@angular/forms'; +import { EntityComponent } from '@shared/components/entity/entity.component'; +import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; +import { EntityAction } from '@shared/components/entity/entity-component.models'; +import { Subscription } from 'rxjs'; +// import { AuditLogMode } from '@shared/models/audit-log.models'; + +@Component({ + selector: 'tb-entity-details-panel', + templateUrl: './entity-details-panel.component.html', + styleUrls: ['./entity-details-panel.component.scss'] +}) +export class EntityDetailsPanelComponent extends PageComponent implements OnInit, OnDestroy { + + @Input() entitiesTableConfig: EntityTableConfig>; + + @Output() + closeEntityDetails = new EventEmitter(); + + @Output() + entityUpdated = new EventEmitter>(); + + @Output() + entityAction = new EventEmitter>>(); + + entityComponent: EntityComponent>; + detailsForm: NgForm; + + isEditValue = false; + selectedTab = 0; + + entityTypes = EntityType; + + @ViewChild('entityDetailsForm', {static: true}) entityDetailsFormAnchor: TbAnchorComponent; + + translations: EntityTypeTranslation; + resources: EntityTypeResource; + entity: BaseData; + + private currentEntityId: HasId; + private entityActionSubscription: Subscription; + + constructor(protected store: Store, + private componentFactoryResolver: ComponentFactoryResolver) { + super(store); + } + + @Input() + set entityId(entityId: HasId) { + if (entityId && entityId !== this.currentEntityId) { + this.currentEntityId = entityId; + this.reload(); + } + } + + set isEdit(val: boolean) { + this.isEditValue = val; + this.entityComponent.isEdit = val; + } + + get isEdit() { + return this.isEditValue; + } + + ngOnInit(): void { + this.translations = this.entitiesTableConfig.entityTranslations; + this.resources = this.entitiesTableConfig.entityResources; + this.buildEntityComponent(); + } + + ngOnDestroy(): void { + super.ngOnDestroy(); + if (this.entityActionSubscription) { + this.entityActionSubscription.unsubscribe(); + } + } + + buildEntityComponent() { + const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.entitiesTableConfig.entityComponent); + const viewContainerRef = this.entityDetailsFormAnchor.viewContainerRef; + viewContainerRef.clear(); + const componentRef = viewContainerRef.createComponent(componentFactory); + this.entityComponent = componentRef.instance; + this.entityComponent.isEdit = this.isEdit; + this.entityComponent.entitiesTableConfig = this.entitiesTableConfig; + this.detailsForm = this.entityComponent.entityNgForm; + this.entityActionSubscription = this.entityComponent.entityAction.subscribe((action) => { + this.entityAction.emit(action); + }); + } + + reload(): void { + this.isEdit = false; + this.entitiesTableConfig.loadEntity(this.currentEntityId).subscribe( + (entity) => { + this.entity = entity; + this.entityComponent.entity = entity; + } + ); + } + + onCloseEntityDetails() { + this.closeEntityDetails.emit(); + } + + onToggleEditMode(isEdit: boolean) { + this.isEdit = isEdit; + if (!this.isEdit) { + this.entityComponent.entity = this.entity; + } else { + this.selectedTab = 0; + } + } + + saveEntity() { + if (this.detailsForm.valid) { + const editingEntity = {...this.entity, ...this.entityComponent.entityFormValue()}; + this.entitiesTableConfig.saveEntity(editingEntity).subscribe( + (entity) => { + this.entity = entity; + this.entityComponent.entity = entity; + this.isEdit = false; + this.entityUpdated.emit(this.entity); + } + ); + } + } + +} diff --git a/ui-ngx/src/app/shared/components/entity/entity-table-header.component.ts b/ui-ngx/src/app/shared/components/entity/entity-table-header.component.ts new file mode 100644 index 0000000000..a398567e7f --- /dev/null +++ b/ui-ngx/src/app/shared/components/entity/entity-table-header.component.ts @@ -0,0 +1,36 @@ +/// +/// Copyright © 2016-2019 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 { BaseData, HasId } from '@shared/models/base-data'; +import { PageComponent } from '@shared/components/page.component'; +import { Input, OnInit } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { EntityTableConfig } from '@shared/components/entity/entities-table-config.models'; + +export abstract class EntityTableHeaderComponent> extends PageComponent implements OnInit { + + @Input() + entitiesTableConfig: EntityTableConfig; + + protected constructor(protected store: Store) { + super(store); + } + + ngOnInit() { + } + +} diff --git a/ui-ngx/src/app/shared/components/entity/entity.component.ts b/ui-ngx/src/app/shared/components/entity/entity.component.ts new file mode 100644 index 0000000000..63624a1dcd --- /dev/null +++ b/ui-ngx/src/app/shared/components/entity/entity.component.ts @@ -0,0 +1,110 @@ +/// +/// Copyright © 2016-2019 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 { BaseData, HasId } from '@shared/models/base-data'; +import { FormGroup, NgForm } from '@angular/forms'; +import { PageComponent } from '@shared/components/page.component'; +import { EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { EntityAction } from '@shared/components/entity/entity-component.models'; +import { EntityTableConfig } from '@shared/components/entity/entities-table-config.models'; + +export abstract class EntityComponent> extends PageComponent implements OnInit { + + entityValue: T; + entityForm: FormGroup; + + @ViewChild('entityNgForm', {static: true}) entityNgForm: NgForm; + + isEditValue: boolean; + + @Input() + set isEdit(isEdit: boolean) { + this.isEditValue = isEdit; + this.updateFormState(); + } + + get isEdit() { + return this.isEditValue; + } + + get isAdd(): boolean { + return this.entityValue && !this.entityValue.id; + } + + @Input() + set entity(entity: T) { + this.entityValue = entity; + if (this.entityForm) { + this.entityForm.reset(); + this.updateForm(entity); + } + } + + get entity(): T { + return this.entityValue; + } + + @Input() + entitiesTableConfig: EntityTableConfig; + + @Output() + entityAction = new EventEmitter>(); + + protected constructor(protected store: Store) { + super(store); + } + + ngOnInit() { + this.entityForm = this.buildForm(this.entityValue); + } + + onEntityAction($event: Event, action: string) { + const entityAction = {event: $event, action, entity: this.entity} as EntityAction; + let handled = false; + if (this.entitiesTableConfig) { + handled = this.entitiesTableConfig.onEntityAction(entityAction); + } + if (!handled) { + this.entityAction.emit(entityAction); + } + } + + updateFormState() { + if (this.entityForm) { + if (this.isEditValue) { + this.entityForm.enable({emitEvent: false}); + } else { + this.entityForm.disable({emitEvent: false}); + } + } + } + + entityFormValue() { + const formValue = this.entityForm ? {...this.entityForm.value} : {}; + return this.prepareFormValue(formValue); + } + + prepareFormValue(formValue: any): any { + return formValue; + } + + abstract buildForm(entity: T): FormGroup; + + abstract updateForm(entity: T); + +} diff --git a/ui-ngx/src/app/shared/components/time/datetime-period.component.html b/ui-ngx/src/app/shared/components/time/datetime-period.component.html new file mode 100644 index 0000000000..fa699ff5b4 --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/datetime-period.component.html @@ -0,0 +1,47 @@ + +
+
+ + datetime.date-from + + + + + + datetime.time-from + + + + +
+
+ + datetime.date-to + + + + + + datetime.time-to + + + + +
+
diff --git a/ui-ngx/src/app/shared/components/time/datetime-period.component.scss b/ui-ngx/src/app/shared/components/time/datetime-period.component.scss new file mode 100644 index 0000000000..fbc1b776d6 --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/datetime-period.component.scss @@ -0,0 +1,26 @@ +/** + * Copyright © 2016-2019 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 ::ng-deep { + .mat-form-field-wrapper { + padding-bottom: 8px; + } + .mat-form-field-underline { + bottom: 8px; + } + .mat-form-field-infix { + width: 150px; + } +} diff --git a/ui-ngx/src/app/shared/components/time/datetime-period.component.ts b/ui-ngx/src/app/shared/components/time/datetime-period.component.ts new file mode 100644 index 0000000000..8afd0fb8e8 --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/datetime-period.component.ts @@ -0,0 +1,137 @@ +/// +/// Copyright © 2016-2019 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 } from '@angular/forms'; +import { FixedWindow } from '@shared/models/time/time.models'; + +@Component({ + selector: 'tb-datetime-period', + templateUrl: './datetime-period.component.html', + styleUrls: ['./datetime-period.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => DatetimePeriodComponent), + multi: true + } + ] +}) +export class DatetimePeriodComponent implements OnInit, ControlValueAccessor { + + @Input() disabled: boolean; + + modelValue: FixedWindow; + + startDate: Date; + endDate: Date; + + endTime: any; + + maxStartDate: Date; + minEndDate: Date; + maxEndDate: Date; + + changePending = false; + + private propagateChange = null; + + constructor() { + } + + ngOnInit(): void { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + if (this.changePending && this.propagateChange) { + this.changePending = false; + this.propagateChange(this.modelValue); + } + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + writeValue(datePeriod: FixedWindow): void { + this.modelValue = datePeriod; + if (this.modelValue) { + this.startDate = new Date(this.modelValue.startTimeMs); + this.endDate = new Date(this.modelValue.endTimeMs); + } else { + const date = new Date(); + this.startDate = new Date( + date.getFullYear(), + date.getMonth(), + date.getDate() - 1, + date.getHours(), + date.getMinutes(), + date.getSeconds(), + date.getMilliseconds()); + this.endDate = date; + this.updateView(); + } + this.updateMinMaxDates(); + } + + updateView() { + let value: FixedWindow = null; + if (this.startDate && this.endDate) { + value = new FixedWindow(); + value.startTimeMs = this.startDate.getTime(); + value.endTimeMs = this.endDate.getTime(); + } + this.modelValue = value; + if (!this.propagateChange) { + this.changePending = true; + } else { + this.propagateChange(this.modelValue); + } + } + + updateMinMaxDates() { + this.maxStartDate = new Date(this.endDate.getTime() - 1000); + this.minEndDate = new Date(this.startDate.getTime() + 1000); + this.maxEndDate = new Date(); + } + + onStartDateChange() { + if (this.startDate) { + if (this.startDate.getTime() > this.maxStartDate.getTime()) { + this.startDate = new Date(this.maxStartDate.getTime()); + } + this.updateMinMaxDates(); + } + this.updateView(); + } + + onEndDateChange() { + if (this.endDate) { + if (this.endDate.getTime() < this.minEndDate.getTime()) { + this.endDate = new Date(this.minEndDate.getTime()); + } else if (this.endDate.getTime() > this.maxEndDate.getTime()) { + this.endDate = new Date(this.maxEndDate.getTime()); + } + this.updateMinMaxDates(); + } + this.updateView(); + } + +} diff --git a/ui-ngx/src/app/shared/components/time/timeinterval.component.html b/ui-ngx/src/app/shared/components/time/timeinterval.component.html new file mode 100644 index 0000000000..07ef698147 --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/timeinterval.component.html @@ -0,0 +1,54 @@ + +
+
+ +
+ + timeinterval.days + + + + timeinterval.hours + + + + timeinterval.minutes + + + + timeinterval.seconds + + +
+
+
+ + {{ predefinedName }} + + + {{ interval.name | translate:interval.translateParams }} + + + +
+
+ + +
+
diff --git a/ui-ngx/src/app/shared/components/time/timeinterval.component.scss b/ui-ngx/src/app/shared/components/time/timeinterval.component.scss new file mode 100644 index 0000000000..00c76c042f --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/timeinterval.component.scss @@ -0,0 +1,43 @@ +/** + * Copyright © 2016-2019 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 { + min-width: 355px; + + .advanced-switch { + margin-bottom: 16px; + } + + .advanced-label { + margin: 5px 0; + } + + .interval-section { + min-height: 66px; + .interval-label { + margin-bottom: 7px; + margin-top: -1px; + } + } + +} + +:host ::ng-deep { + .number-input { + .mat-form-field-infix { + width: 70px; + } + } +} diff --git a/ui-ngx/src/app/shared/components/time/timeinterval.component.ts b/ui-ngx/src/app/shared/components/time/timeinterval.component.ts new file mode 100644 index 0000000000..16d72cea85 --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/timeinterval.component.ts @@ -0,0 +1,277 @@ +/// +/// Copyright © 2016-2019 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 { ChangeDetectorRef, Component, forwardRef, Input, OnInit } from '@angular/core'; +import { + ControlValueAccessor, + FormControl, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + Validator +} from '@angular/forms'; +import { Timewindow } from '@shared/models/time/time.models'; +import { TimeInterval, TimeService } from '@core/services/time.service'; + +@Component({ + selector: 'tb-timeinterval', + templateUrl: './timeinterval.component.html', + styleUrls: ['./timeinterval.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => TimeintervalComponent), + multi: true + } + ] +}) +export class TimeintervalComponent implements OnInit, ControlValueAccessor { + + minValue: number; + maxValue: number; + + @Input() + set min(min: number) { + if (typeof min !== 'undefined' && min !== this.minValue) { + this.minValue = min; + this.updateView(); + } + } + + @Input() + set max(max: number) { + if (typeof max !== 'undefined' && max !== this.maxValue) { + this.maxValue = max; + this.updateView(); + } + } + + @Input() predefinedName: string; + @Input() disabled: boolean; + + days = 0; + hours = 0; + mins = 1; + secs = 0; + + intervalMs = 0; + modelValue: number; + + advanced = false; + rendered = false; + + intervals: Array; + + private propagateChange = (_: any) => {}; + + constructor(private timeService: TimeService) { + } + + ngOnInit(): void { + this.boundInterval(); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + writeValue(intervalMs: number): void { + this.modelValue = intervalMs; + this.rendered = true; + if (typeof this.modelValue !== 'undefined') { + const min = this.timeService.boundMinInterval(this.minValue); + const max = this.timeService.boundMaxInterval(this.maxValue); + if (this.modelValue >= min && this.modelValue <= max) { + this.advanced = !this.timeService.matchesExistingInterval(this.minValue, this.maxValue, this.modelValue); + this.setIntervalMs(this.modelValue); + } else { + this.boundInterval(); + } + } + } + + setIntervalMs(intervalMs: number) { + if (!this.advanced) { + this.intervalMs = intervalMs; + } + const intervalSeconds = Math.floor(intervalMs / 1000); + this.days = Math.floor(intervalSeconds / 86400); + this.hours = Math.floor((intervalSeconds % 86400) / 3600); + this.mins = Math.floor(((intervalSeconds % 86400) % 3600) / 60); + this.secs = intervalSeconds % 60; + } + + boundInterval() { + const min = this.timeService.boundMinInterval(this.minValue); + const max = this.timeService.boundMaxInterval(this.maxValue); + this.intervals = this.timeService.getIntervals(this.minValue, this.maxValue); + if (this.rendered) { + let newIntervalMs = this.modelValue; + if (newIntervalMs < min) { + newIntervalMs = min; + } else if (newIntervalMs > max) { + newIntervalMs = max; + } + if (!this.advanced) { + newIntervalMs = this.timeService.boundToPredefinedInterval(min, max, newIntervalMs); + } + if (newIntervalMs !== this.modelValue) { + this.setIntervalMs(newIntervalMs); + this.updateView(); + } + } + } + + updateView() { + if (!this.rendered) { + return; + } + let value = null; + let intervalMs; + if (!this.advanced) { + intervalMs = this.intervalMs; + if (!intervalMs || isNaN(intervalMs)) { + intervalMs = this.calculateIntervalMs(); + } + } else { + intervalMs = this.calculateIntervalMs(); + } + if (!isNaN(intervalMs) && intervalMs > 0) { + value = intervalMs; + } + this.modelValue = value; + this.propagateChange(this.modelValue); + this.boundInterval(); + } + + calculateIntervalMs(): number { + return (this.days * 86400 + + this.hours * 3600 + + this.mins * 60 + + this.secs) * 1000; + } + + onIntervalMsChange() { + this.updateView(); + } + + onAdvancedChange() { + if (!this.advanced) { + this.intervalMs = this.calculateIntervalMs(); + } else { + let intervalMs = this.intervalMs; + if (!intervalMs || isNaN(intervalMs)) { + intervalMs = this.calculateIntervalMs(); + } + this.setIntervalMs(intervalMs); + } + this.updateView(); + } + + onTimeInputChange(type: string) { + switch (type) { + case 'secs': + setTimeout(() => this.onSecsChange(), 0); + break; + case 'mins': + setTimeout(() => this.onMinsChange(), 0); + break; + case 'hours': + setTimeout(() => this.onHoursChange(), 0); + break; + case 'days': + setTimeout(() => this.onDaysChange(), 0); + break; + } + } + + onSecsChange() { + if (typeof this.secs === 'undefined') { + return; + } + if (this.secs < 0) { + if ((this.days + this.hours + this.mins) > 0) { + this.secs = this.secs + 60; + this.mins--; + this.onMinsChange(); + } else { + this.secs = 0; + } + } else if (this.secs >= 60) { + this.secs = this.secs - 60; + this.mins++; + this.onMinsChange(); + } + this.updateView(); + } + + onMinsChange() { + if (typeof this.mins === 'undefined') { + return; + } + if (this.mins < 0) { + if ((this.days + this.hours) > 0) { + this.mins = this.mins + 60; + this.hours--; + this.onHoursChange(); + } else { + this.mins = 0; + } + } else if (this.mins >= 60) { + this.mins = this.mins - 60; + this.hours++; + this.onHoursChange(); + } + this.updateView(); + } + + onHoursChange() { + if (typeof this.hours === 'undefined') { + return; + } + if (this.hours < 0) { + if (this.days > 0) { + this.hours = this.hours + 24; + this.days--; + this.onDaysChange(); + } else { + this.hours = 0; + } + } else if (this.hours >= 24) { + this.hours = this.hours - 24; + this.days++; + this.onDaysChange(); + } + this.updateView(); + } + + onDaysChange() { + if (typeof this.days === 'undefined') { + return; + } + if (this.days < 0) { + this.days = 0; + } + this.updateView(); + } + +} diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html new file mode 100644 index 0000000000..5f85596833 --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html @@ -0,0 +1,127 @@ + +
+
+
+
+ + +
+ +
+
+ +
+ + +
+ +
+
+ +
+ timewindow.time-period + +
+
+
+
+
+
+
+ + aggregation.function + + + {{ aggregationTypesTranslations.get(aggregation) | translate }} + + + +
+ aggregation.limit + + + + + +
+
+
+ + +
+
+ + +
+
+ +
+ + + +
+
+
+
diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss new file mode 100644 index 0000000000..c2613a9482 --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss @@ -0,0 +1,63 @@ +/** + * Copyright © 2016-2019 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 { + width: 100%; + height: 100%; + form, + fieldset { + height: 100%; + } + + .mat-content { + overflow: hidden; + background-color: #fff; + } + + .mat-padding { + padding: 0 16px; + } + + .limit-slider-container { + >:first-child { + margin-right: 16px; + } + >:last-child { + margin-left: 16px; + } + >:first-child, >:last-child { + min-width: 25px; + max-width: 42px; + } + mat-form-field input[type=number] { + text-align: center; + } + } + +} + +:host ::ng-deep { + mat-radio-button { + display: block; + margin-bottom: 16px; + .mat-radio-label { + width: 100%; + align-items: start; + .mat-radio-label-content { + width: 100%; + } + } + } +} diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts new file mode 100644 index 0000000000..badceb71ea --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts @@ -0,0 +1,203 @@ +/// +/// Copyright © 2016-2019 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, + InjectionToken, + OnInit, + ViewChild, + ViewContainerRef +} from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { MillisecondsToTimeStringPipe } from '@shared/pipe/milliseconds-to-time-string.pipe'; +import { + Aggregation, + aggregationTranslations, + AggregationType, + HistoryWindow, + HistoryWindowType, + IntervalWindow, + Timewindow, + TimewindowType +} from '@shared/models/time/time.models'; +import { DatePipe } from '@angular/common'; +import { Overlay, OverlayRef } from '@angular/cdk/overlay'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { TimeService } from '@core/services/time.service'; + +export const TIMEWINDOW_PANEL_DATA = new InjectionToken('TimewindowPanelData'); + +export interface TimewindowPanelData { + historyOnly: boolean; + timewindow: Timewindow; + aggregation: boolean; +} + +@Component({ + selector: 'tb-timewindow-panel', + templateUrl: './timewindow-panel.component.html', + styleUrls: ['./timewindow-panel.component.scss'] +}) +export class TimewindowPanelComponent extends PageComponent implements OnInit { + + historyOnly = false; + + aggregation = false; + + timewindow: Timewindow; + + result: Timewindow; + + timewindowForm: FormGroup; + + historyTypes = HistoryWindowType; + + timewindowTypes = TimewindowType; + + aggregationTypes = AggregationType; + + aggregations = Object.keys(AggregationType); + + aggregationTypesTranslations = aggregationTranslations; + + constructor(@Inject(TIMEWINDOW_PANEL_DATA) public data: TimewindowPanelData, + public overlayRef: OverlayRef, + protected store: Store, + public fb: FormBuilder, + private timeService: TimeService, + private translate: TranslateService, + private millisecondsToTimeStringPipe: MillisecondsToTimeStringPipe, + private datePipe: DatePipe, + private overlay: Overlay, + public viewContainerRef: ViewContainerRef) { + super(store); + this.historyOnly = data.historyOnly; + this.timewindow = data.timewindow; + this.aggregation = data.aggregation; + } + + ngOnInit(): void { + this.timewindowForm = this.fb.group({ + realtime: this.fb.group( + { + timewindowMs: [ + this.timewindow.realtime && typeof this.timewindow.realtime.timewindowMs !== 'undefined' + ? this.timewindow.realtime.timewindowMs : null + ], + interval: [ + this.timewindow.realtime && typeof this.timewindow.realtime.interval !== 'undefined' + ? this.timewindow.realtime.interval : null + ] + } + ), + history: this.fb.group( + { + historyType: [ + this.timewindow.history && typeof this.timewindow.history.historyType !== 'undefined' + ? this.timewindow.history.historyType : HistoryWindowType.LAST_INTERVAL + ], + timewindowMs: [ + this.timewindow.history && typeof this.timewindow.history.timewindowMs !== 'undefined' + ? this.timewindow.history.timewindowMs : null + ], + interval: [ + this.timewindow.history && typeof this.timewindow.history.interval !== 'undefined' + ? this.timewindow.history.interval : null + ], + fixedTimewindow: [ + this.timewindow.history && typeof this.timewindow.history.fixedTimewindow !== 'undefined' + ? this.timewindow.history.fixedTimewindow : null + ] + } + ), + aggregation: this.fb.group( + { + type: [ + this.timewindow.aggregation && typeof this.timewindow.aggregation.type !== 'undefined' + ? this.timewindow.aggregation.type : null + ], + limit: [ + this.timewindow.aggregation && typeof this.timewindow.aggregation.limit !== 'undefined' + ? this.timewindow.aggregation.limit : null, + [Validators.min(this.minDatapointsLimit()), Validators.max(this.maxDatapointsLimit())] + ] + } + ) + }); + } + + update() { + const timewindowFormValue = this.timewindowForm.value; + this.timewindow.realtime = new IntervalWindow(); + this.timewindow.realtime.timewindowMs = timewindowFormValue.realtime.timewindowMs; + this.timewindow.realtime.interval = timewindowFormValue.realtime.interval; + this.timewindow.history = new HistoryWindow(); + this.timewindow.history.historyType = timewindowFormValue.history.historyType; + this.timewindow.history.timewindowMs = timewindowFormValue.history.timewindowMs; + this.timewindow.history.interval = timewindowFormValue.history.interval; + this.timewindow.history.fixedTimewindow = timewindowFormValue.history.fixedTimewindow; + if (this.aggregation) { + this.timewindow.aggregation = new Aggregation(); + this.timewindow.aggregation.type = timewindowFormValue.aggregation.type; + this.timewindow.aggregation.limit = timewindowFormValue.aggregation.limit; + } + this.result = this.timewindow; + this.overlayRef.dispose(); + } + + cancel() { + this.overlayRef.dispose(); + } + + minDatapointsLimit() { + return this.timeService.getMinDatapointsLimit(); + } + + maxDatapointsLimit() { + return this.timeService.getMaxDatapointsLimit(); + } + + minRealtimeAggInterval() { + return this.timeService.minIntervalLimit(this.timewindowForm.get('realtime').get('timewindowMs').value); + } + + maxRealtimeAggInterval() { + return this.timeService.maxIntervalLimit(this.timewindowForm.get('realtime').get('timewindowMs').value); + } + + minHistoryAggInterval() { + return this.timeService.minIntervalLimit(this.currentHistoryTimewindow()); + } + + maxHistoryAggInterval() { + return this.timeService.maxIntervalLimit(this.currentHistoryTimewindow()); + } + + currentHistoryTimewindow() { + const timewindowFormValue = this.timewindowForm.value; + if (timewindowFormValue.history.historyType === HistoryWindowType.LAST_INTERVAL) { + return timewindowFormValue.history.timewindowMs; + } else { + return timewindowFormValue.history.fixedTimewindow.endTimeMs - + timewindowFormValue.history.fixedTimewindow.startTimeMs; + } + } + +} diff --git a/ui-ngx/src/app/shared/components/time/timewindow.component.html b/ui-ngx/src/app/shared/components/time/timewindow.component.html new file mode 100644 index 0000000000..38b65e46a6 --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/timewindow.component.html @@ -0,0 +1,43 @@ + + +
+ + + {{innerValue.displayValue}} + + +
diff --git a/ui-ngx/src/app/shared/components/time/timewindow.component.scss b/ui-ngx/src/app/shared/components/time/timewindow.component.scss new file mode 100644 index 0000000000..0e267d0657 --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/timewindow.component.scss @@ -0,0 +1,26 @@ +/** + * Copyright © 2016-2019 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 { + section.tb-timewindow { + span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + pointer-events: all; + cursor: pointer; + } + } +} diff --git a/ui-ngx/src/app/shared/components/time/timewindow.component.ts b/ui-ngx/src/app/shared/components/time/timewindow.component.ts new file mode 100644 index 0000000000..d28629fcdb --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/timewindow.component.ts @@ -0,0 +1,275 @@ +/// +/// Copyright © 2016-2019 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, Inject, + Input, + OnDestroy, + OnInit, + ViewChild, + ViewContainerRef +} from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { TranslateService } from '@ngx-translate/core'; +import { MillisecondsToTimeStringPipe } from '@shared/pipe/milliseconds-to-time-string.pipe'; +import { + HistoryWindowType, + Timewindow, + TimewindowType +} from '@shared/models/time/time.models'; +import { DatePipe } from '@angular/common'; +import { + Overlay, + CdkOverlayOrigin, + OverlayConfig, + OverlayPositionBuilder, ConnectedPosition, PositionStrategy, OverlayRef +} from '@angular/cdk/overlay'; +import { + TIMEWINDOW_PANEL_DATA, + TimewindowPanelComponent, + TimewindowPanelData +} from '@shared/components/time/timewindow-panel.component'; +import { ComponentPortal, PortalInjector } from '@angular/cdk/portal'; +import { MediaBreakpoints } from '@shared/models/constants'; +import { BreakpointObserver } from '@angular/cdk/layout'; +import { DOCUMENT } from '@angular/common'; +import { WINDOW } from '@core/services/window.service'; +import { TimeService } from '@core/services/time.service'; +import { TooltipPosition } from '@angular/material/typings/tooltip'; + +@Component({ + selector: 'tb-timewindow', + templateUrl: './timewindow.component.html', + styleUrls: ['./timewindow.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => TimewindowComponent), + multi: true + } + ] +}) +export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAccessor { + + historyOnlyValue = false; + + @Input() + set historyOnly(val) { + this.historyOnlyValue = true; + } + + get historyOnly() { + return this.historyOnlyValue; + } + + aggregationValue = false; + + @Input() + set aggregation(val) { + this.aggregationValue = true; + } + + get aggregation() { + return this.aggregationValue; + } + + isToolbarValue = false; + + @Input() + set isToolbar(val) { + this.isToolbarValue = true; + } + + get isToolbar() { + return this.isToolbarValue; + } + + asButtonValue = false; + + @Input() + set asButton(val) { + this.asButtonValue = true; + } + + get asButton() { + return this.asButtonValue; + } + + @Input() + direction: 'left' | 'right' = 'left'; + + @Input() + tooltipPosition: TooltipPosition = 'above'; + + @Input() disabled: boolean; + + @ViewChild('timewindowPanelOrigin', {static: false}) timewindowPanelOrigin: CdkOverlayOrigin; + + innerValue: Timewindow; + + private propagateChange = (_: any) => {}; + + constructor(private translate: TranslateService, + private timeService: TimeService, + private millisecondsToTimeStringPipe: MillisecondsToTimeStringPipe, + private datePipe: DatePipe, + private overlay: Overlay, + public viewContainerRef: ViewContainerRef, + public breakpointObserver: BreakpointObserver, + @Inject(DOCUMENT) private document: Document, + @Inject(WINDOW) private window: Window) { + } + + ngOnInit(): void { + } + + ngOnDestroy(): void { + } + + openEditMode() { + if (this.disabled) { + return; + } + const isGtSm = this.breakpointObserver.isMatched(MediaBreakpoints['gt-sm']); + const position = this.overlay.position(); + const config = new OverlayConfig({ + panelClass: 'tb-timewindow-panel', + backdropClass: 'cdk-overlay-transparent-backdrop', + hasBackdrop: isGtSm, + }); + if (isGtSm) { + config.minWidth = '417px'; + config.maxHeight = '440px'; + const panelHeight = 375; + const panelWidth = 417; + const el = this.timewindowPanelOrigin.elementRef.nativeElement; + const offset = el.getBoundingClientRect(); + const scrollTop = this.window.pageYOffset || this.document.documentElement.scrollTop || this.document.body.scrollTop || 0; + const scrollLeft = this.window.pageXOffset || this.document.documentElement.scrollLeft || this.document.body.scrollLeft || 0; + const bottomY = offset.bottom - scrollTop; + const leftX = offset.left - scrollLeft; + let originX; + let originY; + let overlayX; + let overlayY; + const wHeight = this.document.documentElement.clientHeight; + const wWidth = this.document.documentElement.clientWidth; + if (bottomY + panelHeight > wHeight) { + originY = 'top'; + overlayY = 'bottom'; + } else { + originY = 'bottom'; + overlayY = 'top'; + } + if (leftX + panelWidth > wWidth) { + originX = 'end'; + overlayX = 'end'; + } else { + originX = 'start'; + overlayX = 'start'; + } + const connectedPosition: ConnectedPosition = { + originX, + originY, + overlayX, + overlayY + }; + config.positionStrategy = position.flexibleConnectedTo(this.timewindowPanelOrigin.elementRef) + .withPositions([connectedPosition]); + } else { + config.minWidth = '100%'; + config.minHeight = '100%'; + config.positionStrategy = position.global().top('0%').left('0%') + .right('0%').bottom('0%'); + } + + const overlayRef = this.overlay.create(config); + + overlayRef.backdropClick().subscribe(() => { + overlayRef.dispose(); + }); + + const injector = this._createTimewindowPanelInjector( + overlayRef, + { + timewindow: this.innerValue.clone(), + historyOnly: this.historyOnly, + aggregation: this.aggregation + } + ); + + const componentRef = overlayRef.attach(new ComponentPortal(TimewindowPanelComponent, this.viewContainerRef, injector)); + componentRef.onDestroy(() => { + if (componentRef.instance.result) { + this.innerValue = componentRef.instance.result; + this.updateDisplayValue(); + this.notifyChanged(); + } + }); + } + + private _createTimewindowPanelInjector(overlayRef: OverlayRef, data: TimewindowPanelData): PortalInjector { + const injectionTokens = new WeakMap([ + [TIMEWINDOW_PANEL_DATA, data], + [OverlayRef, overlayRef] + ]); + return new PortalInjector(this.viewContainerRef.injector, injectionTokens); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + writeValue(obj: Timewindow): void { + this.innerValue = Timewindow.initModelFromDefaultTimewindow(obj, this.timeService); + this.updateDisplayValue(); + } + + notifyChanged() { + this.propagateChange(this.innerValue.cloneSelectedTimewindow()); + } + + updateDisplayValue() { + if (this.innerValue.selectedTab === TimewindowType.REALTIME && !this.historyOnly) { + this.innerValue.displayValue = this.translate.instant('timewindow.realtime') + ' - ' + + this.translate.instant('timewindow.last-prefix') + ' ' + + this.millisecondsToTimeStringPipe.transform(this.innerValue.realtime.timewindowMs); + } else { + this.innerValue.displayValue = !this.historyOnly ? (this.translate.instant('timewindow.history') + ' - ') : ''; + if (this.innerValue.history.historyType === HistoryWindowType.LAST_INTERVAL) { + this.innerValue.displayValue += this.translate.instant('timewindow.last-prefix') + ' ' + + this.millisecondsToTimeStringPipe.transform(this.innerValue.history.timewindowMs); + } else { + const startString = this.datePipe.transform(this.innerValue.history.fixedTimewindow.startTimeMs, 'yyyy-MM-dd HH:mm:ss'); + const endString = this.datePipe.transform(this.innerValue.history.fixedTimewindow.endTimeMs, 'yyyy-MM-dd HH:mm:ss'); + this.innerValue.displayValue += this.translate.instant('timewindow.period', {startTime: startString, endTime: endString}); + } + } + } + + hideLabel() { + return this.isToolbar && !this.breakpointObserver.isMatched(MediaBreakpoints['gt-md']); + } + +} diff --git a/ui-ngx/src/app/shared/models/customer.model.ts b/ui-ngx/src/app/shared/models/customer.model.ts index d7da52711d..adfa684fe0 100644 --- a/ui-ngx/src/app/shared/models/customer.model.ts +++ b/ui-ngx/src/app/shared/models/customer.model.ts @@ -23,3 +23,9 @@ export interface Customer extends ContactBased { title: string; additionalInfo?: any; } + +export interface ShortCustomerInfo { + customerId: CustomerId; + title: string; + isPublic: boolean; +} diff --git a/ui-ngx/src/app/shared/models/dashboard.models.ts b/ui-ngx/src/app/shared/models/dashboard.models.ts new file mode 100644 index 0000000000..ce99407541 --- /dev/null +++ b/ui-ngx/src/app/shared/models/dashboard.models.ts @@ -0,0 +1,35 @@ +/// +/// Copyright © 2016-2019 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 {BaseData} from '@shared/models/base-data'; +import {DashboardId} from '@shared/models/id/dashboard-id'; +import {TenantId} from '@shared/models/id/tenant-id'; +import {ShortCustomerInfo} from '@shared/models/customer.model'; + +export interface DashboardInfo extends BaseData { + tenantId: TenantId; + title: string; + assignedCustomers: Array; +} + +export interface DashboardConfiguration { + widgets: Array; + // TODO: +} + +export interface Dashboard extends DashboardInfo { + configuration: DashboardConfiguration; +} diff --git a/ui-ngx/src/app/shared/models/datasource/entity-datasource.ts b/ui-ngx/src/app/shared/models/datasource/entity-datasource.ts new file mode 100644 index 0000000000..f8bb0e2426 --- /dev/null +++ b/ui-ngx/src/app/shared/models/datasource/entity-datasource.ts @@ -0,0 +1,113 @@ +/// +/// Copyright © 2016-2019 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 { EntityId } from '@shared/models/id/entity-id'; +import { PageLink } from '@shared/models/page/page-link'; +import { BehaviorSubject, Observable, of, ReplaySubject } from 'rxjs'; +import { emptyPageData, PageData } from '@shared/models/page/page-data'; +import { BaseData, HasId } from '@shared/models/base-data'; +import { CollectionViewer, DataSource } from '@angular/cdk/typings/collections'; +import { catchError, map, take, tap } from 'rxjs/operators'; +import { SelectionModel } from '@angular/cdk/collections'; + +export type EntitiesFetchFunction, P extends PageLink> = (pageLink: P) => Observable>; + +export class EntitiesDataSource, P extends PageLink = PageLink> implements DataSource { + + private entitiesSubject = new BehaviorSubject([]); + private pageDataSubject = new BehaviorSubject>(emptyPageData()); + + public pageData$ = this.pageDataSubject.asObservable(); + + public selection = new SelectionModel(true, []); + + public currentEntity: T = null; + + constructor(private fetchFunction: EntitiesFetchFunction) {} + + connect(collectionViewer: CollectionViewer): Observable> { + return this.entitiesSubject.asObservable(); + } + + disconnect(collectionViewer: CollectionViewer): void { + this.entitiesSubject.complete(); + this.pageDataSubject.complete(); + } + + loadEntities(pageLink: P): Observable> { + const result = new ReplaySubject>(); + this.fetchFunction(pageLink).pipe( + tap(() => { + this.selection.clear(); + }), + catchError(() => of(emptyPageData())), + ).subscribe( + (pageData) => { + this.entitiesSubject.next(pageData.data); + this.pageDataSubject.next(pageData); + result.next(pageData); + } + ); + return result; + } + + isAllSelected(): Observable { + const numSelected = this.selection.selected.length; + return this.entitiesSubject.pipe( + map((entities) => numSelected === entities.length) + ); + } + + isEmpty(): Observable { + return this.entitiesSubject.pipe( + map((entities) => !entities.length) + ); + } + + total(): Observable { + return this.pageDataSubject.pipe( + map((pageData) => pageData.totalElements) + ); + } + + toggleCurrentEntity(entity: T): boolean { + if (this.currentEntity !== entity) { + this.currentEntity = entity; + return true; + } else { + return false; + } + } + + isCurrentEntity(entity: T): boolean { + return (this.currentEntity && entity && this.currentEntity.id && entity.id) && + (this.currentEntity.id.id === entity.id.id); + } + + masterToggle() { + this.entitiesSubject.pipe( + tap((entities) => { + const numSelected = this.selection.selected.length; + if (numSelected === entities.length) { + this.selection.clear(); + } else { + entities.forEach(row => this.selection.select(row)); + } + }), + take(1) + ).subscribe(); + } +} diff --git a/ui-ngx/src/app/shared/models/id/dashboard-id.ts b/ui-ngx/src/app/shared/models/id/dashboard-id.ts new file mode 100644 index 0000000000..d32877664e --- /dev/null +++ b/ui-ngx/src/app/shared/models/id/dashboard-id.ts @@ -0,0 +1,26 @@ +/// +/// Copyright © 2016-2019 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 { EntityId } from './entity-id'; +import { EntityType } from '@shared/models/entity-type.models'; + +export class DashboardId implements EntityId { + entityType = EntityType.DASHBOARD; + id: string; + constructor(id: string) { + this.id = id; + } +} diff --git a/ui-ngx/src/app/shared/models/settings.models.ts b/ui-ngx/src/app/shared/models/settings.models.ts index 6af535d257..147967b3e7 100644 --- a/ui-ngx/src/app/shared/models/settings.models.ts +++ b/ui-ngx/src/app/shared/models/settings.models.ts @@ -33,3 +33,20 @@ export interface MailServerSettings { username: string; password: string; } + +export interface GeneralSettings { + baseUrl: string; +} + +export interface UserPasswordPolicy { + minimumLength: number; + minimumUppercaseLetters: number; + minimumLowercaseLetters: number; + minimumDigits: number; + minimumSpecialCharacters: number; + passwordExpirationPeriodDays: number; +} + +export interface SecuritySettings { + passwordPolicy: UserPasswordPolicy; +} diff --git a/ui-ngx/src/app/shared/pipe/enum-to-array.pipe.ts b/ui-ngx/src/app/shared/pipe/enum-to-array.pipe.ts new file mode 100644 index 0000000000..02bed81a49 --- /dev/null +++ b/ui-ngx/src/app/shared/pipe/enum-to-array.pipe.ts @@ -0,0 +1,27 @@ +/// +/// Copyright © 2016-2019 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 { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'enumToArray' +}) +export class EnumToArrayPipe implements PipeTransform { + transform(data: object) { + const keys = Object.keys(data); + return keys.slice(keys.length / 2); + } +} diff --git a/ui-ngx/src/app/shared/pipe/highlight.pipe.ts b/ui-ngx/src/app/shared/pipe/highlight.pipe.ts new file mode 100644 index 0000000000..1fca8e7151 --- /dev/null +++ b/ui-ngx/src/app/shared/pipe/highlight.pipe.ts @@ -0,0 +1,28 @@ +/// +/// Copyright © 2016-2019 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 {Pipe, PipeTransform} from '@angular/core'; + +@Pipe({ name: 'highlight' }) +export class HighlightPipe implements PipeTransform { + transform(text: string, search): string { + const pattern = search + .replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); + const regex = new RegExp('^' + pattern, 'i'); + + return search ? text.replace(regex, match => `${match}`) : text; + } +} diff --git a/ui-ngx/src/app/shared/pipe/milliseconds-to-time-string.pipe.ts b/ui-ngx/src/app/shared/pipe/milliseconds-to-time-string.pipe.ts new file mode 100644 index 0000000000..473dcd6d8e --- /dev/null +++ b/ui-ngx/src/app/shared/pipe/milliseconds-to-time-string.pipe.ts @@ -0,0 +1,58 @@ +/// +/// Copyright © 2016-2019 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 { Pipe, PipeTransform } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; + +@Pipe({ + name: 'milliSecondsToTimeString' +}) +export class MillisecondsToTimeStringPipe implements PipeTransform { + + constructor(private translate: TranslateService) { + } + + transform(millseconds: number, args?: any): string { + let seconds = Math.floor(millseconds / 1000); + const days = Math.floor(seconds / 86400); + let hours = Math.floor((seconds % 86400) / 3600); + let minutes = Math.floor(((seconds % 86400) % 3600) / 60); + seconds = seconds % 60; + let timeString = ''; + if (days > 0) { + timeString += this.translate.instant('timewindow.days', {days}); + } + if (hours > 0) { + if (timeString.length === 0 && hours === 1) { + hours = 0; + } + timeString += this.translate.instant('timewindow.hours', {hours}); + } + if (minutes > 0) { + if (timeString.length === 0 && minutes === 1) { + minutes = 0; + } + timeString += this.translate.instant('timewindow.minutes', {minutes}); + } + if (seconds > 0) { + if (timeString.length === 0 && seconds === 1) { + seconds = 0; + } + timeString += this.translate.instant('timewindow.seconds', {seconds}); + } + return timeString; + } +} diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index 0106a0422c..ffd5591240 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -58,39 +58,41 @@ import { NospacePipe } from './pipe/nospace.pipe'; import { TranslateModule } from '@ngx-translate/core'; import { TbCheckboxComponent } from '@shared/components/tb-checkbox.component'; import { HelpComponent } from '@shared/components/help.component'; -// import { EntitiesTableComponent } from '@shared/components/entity/entities-table.component'; -// import { AddEntityDialogComponent } from '@shared/components/entity/add-entity-dialog.component'; -// import { DetailsPanelComponent } from '@shared/components/details-panel.component'; -// import { EntityDetailsPanelComponent } from '@shared/components/entity/entity-details-panel.component'; +import { EntitiesTableComponent } from '@shared/components/entity/entities-table.component'; +import { AddEntityDialogComponent } from '@shared/components/entity/add-entity-dialog.component'; +import { DetailsPanelComponent } from '@shared/components/details-panel.component'; +import { EntityDetailsPanelComponent } from '@shared/components/entity/entity-details-panel.component'; import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; -// import { ContactComponent } from '@shared/components/contact.component'; +import { ContactComponent } from '@shared/components/contact.component'; // import { AuditLogDetailsDialogComponent } from '@shared/components/audit-log/audit-log-details-dialog.component'; // import { AuditLogTableComponent } from '@shared/components/audit-log/audit-log-table.component'; -// import { MillisecondsToTimeStringPipe } from '@shared/pipe/milliseconds-to-time-string.pipe'; -// import { TimewindowComponent } from '@shared/components/time/timewindow.component'; +import { MillisecondsToTimeStringPipe } from '@shared/pipe/milliseconds-to-time-string.pipe'; +import { TimewindowComponent } from '@shared/components/time/timewindow.component'; import { OverlayModule } from '@angular/cdk/overlay'; -// import { TimewindowPanelComponent } from '@shared/components/time/timewindow-panel.component'; -// import { TimeintervalComponent } from '@shared/components/time/timeinterval.component'; -// import { DatetimePeriodComponent } from '@shared/components/time/datetime-period.component'; -// import { EnumToArrayPipe } from '@shared/pipe/enum-to-array.pipe'; +import { TimewindowPanelComponent } from '@shared/components/time/timewindow-panel.component'; +import { TimeintervalComponent } from '@shared/components/time/timeinterval.component'; +import { DatetimePeriodComponent } from '@shared/components/time/datetime-period.component'; +import { EnumToArrayPipe } from '@shared/pipe/enum-to-array.pipe'; import { ClipboardModule } from 'ngx-clipboard'; // import { ValueInputComponent } from '@shared/components/value-input.component'; -// import { IntervalCountPipe } from '@shared/pipe/interval-count.pipe'; import { FullscreenDirective } from '@shared/components/fullscreen.directive'; +import { HighlightPipe } from '@shared/pipe/highlight.pipe'; +import {DashboardAutocompleteComponent} from '@shared/components/dashboard-autocomplete.component'; @NgModule({ providers: [ DatePipe, -// MillisecondsToTimeStringPipe, -// EnumToArrayPipe, + MillisecondsToTimeStringPipe, + EnumToArrayPipe, + HighlightPipe // IntervalCountPipe, ], entryComponents: [ TbSnackBarComponent, TbAnchorComponent, -// AddEntityDialogComponent, + AddEntityDialogComponent, // AuditLogDetailsDialogComponent, -// TimewindowPanelComponent, + TimewindowPanelComponent, ], declarations: [ FooterComponent, @@ -103,22 +105,23 @@ import { FullscreenDirective } from '@shared/components/fullscreen.directive'; TbSnackBarComponent, BreadcrumbComponent, UserMenuComponent, -// EntitiesTableComponent, -// AddEntityDialogComponent, -// DetailsPanelComponent, -// EntityDetailsPanelComponent, -// ContactComponent, + EntitiesTableComponent, + AddEntityDialogComponent, + DetailsPanelComponent, + EntityDetailsPanelComponent, + ContactComponent, // AuditLogTableComponent, // AuditLogDetailsDialogComponent, -// TimewindowComponent, -// TimewindowPanelComponent, -// TimeintervalComponent, -// DatetimePeriodComponent, + TimewindowComponent, + TimewindowPanelComponent, + TimeintervalComponent, + DatetimePeriodComponent, // ValueInputComponent, + DashboardAutocompleteComponent, NospacePipe, -// MillisecondsToTimeStringPipe, -// EnumToArrayPipe, -// IntervalCountPipe + MillisecondsToTimeStringPipe, + EnumToArrayPipe, + HighlightPipe ], imports: [ CommonModule, @@ -169,16 +172,17 @@ import { FullscreenDirective } from '@shared/components/fullscreen.directive'; TbCheckboxComponent, BreadcrumbComponent, UserMenuComponent, -// EntitiesTableComponent, -// AddEntityDialogComponent, -// DetailsPanelComponent, -// EntityDetailsPanelComponent, -// ContactComponent, + EntitiesTableComponent, + AddEntityDialogComponent, + DetailsPanelComponent, + EntityDetailsPanelComponent, + ContactComponent, // AuditLogTableComponent, -// TimewindowComponent, -// TimewindowPanelComponent, -// TimeintervalComponent, -// DatetimePeriodComponent, + TimewindowComponent, + TimewindowPanelComponent, + TimeintervalComponent, + DatetimePeriodComponent, + DashboardAutocompleteComponent, // ValueInputComponent, MatButtonModule, MatCheckboxModule, @@ -215,9 +219,9 @@ import { FullscreenDirective } from '@shared/components/fullscreen.directive'; ReactiveFormsModule, OverlayModule, NospacePipe, -// MillisecondsToTimeStringPipe, -// EnumToArrayPipe, -// IntervalCountPipe, + MillisecondsToTimeStringPipe, + EnumToArrayPipe, + HighlightPipe, TranslateModule ] }) 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 88884094c8..f5e115aafb 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -590,6 +590,7 @@ "add-datasource-prompt": "Please add datasource" }, "details": { + "details": "Details", "edit-mode": "Edit mode", "toggle-edit-mode": "Toggle edit mode" }, @@ -1378,6 +1379,7 @@ "delete-tenants-title": "Are you sure you want to delete { count, plural, 1 {1 tenant} other {# tenants} }?", "delete-tenants-action-title": "Delete { count, plural, 1 {1 tenant} other {# tenants} }", "delete-tenants-text": "Be careful, after the confirmation all selected tenants will be removed and all related data will become unrecoverable.", + "created-time": "Created time", "title": "Title", "title-required": "Title is required.", "description": "Description", @@ -1437,6 +1439,7 @@ "delete-users-text": "Be careful, after the confirmation all selected users will be removed and all related data will become unrecoverable.", "activation-email-sent-message": "Activation email was successfully sent!", "resend-activation": "Resend activation", + "created-time": "Created time", "email": "Email", "email-required": "Email is required.", "invalid-email-format": "Invalid email format.", 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 4fd2be2efb..2465365563 100644 --- a/ui-ngx/src/assets/locale/locale.constant-es_ES.json +++ b/ui-ngx/src/assets/locale/locale.constant-es_ES.json @@ -152,7 +152,7 @@ "aknowledge-alarm-title": "Reconocer alarma", "aknowledge-alarm-text": "¿Está seguro que quiere reconocer la alarma?", "clear-alarms-title": "Quitar { count, plural, 1 {1 alarma} other {# alarmas} }", - "clear-alarms-text": "¿Está seguro de que desea quitar { count, plural, 1 {1 alarma} other {# alarmas}?", + "clear-alarms-text": "¿Está seguro de que desea quitar { count, plural, 1 {1 alarma} other {# alarmas} }?", "clear-alarm-title": "Quitar alarma", "clear-alarm-text": "¿Está seguro que quiere quitar la alarma?", "alarm-status-filter": "Filtro de estado de alarma" 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 1fdec0d6d5..9603442080 100644 --- a/ui-ngx/src/assets/locale/locale.constant-fr_FR.json +++ b/ui-ngx/src/assets/locale/locale.constant-fr_FR.json @@ -88,16 +88,16 @@ "alarm": { "ack-time": "Heure d'acquittement", "acknowledge": "Acquitter", - "aknowledge-alarms-text": "Etes-vous sûr de vouloir acquitter {count, plural, 1 {1 alarme} other {# alarmes}}?", - "aknowledge-alarms-title": "Acquitter {count, plural, 1 {1 alarme} other {# alarmes}}", + "aknowledge-alarms-text": "Etes-vous sûr de vouloir acquitter { count, plural, 1 {1 alarme} other {# alarmes} }?", + "aknowledge-alarms-title": "Acquitter { count, plural, 1 {1 alarme} other {# alarmes} }", "alarm": "Alarme", "alarm-details": "Détails de l'alarme", "alarm-required": "Une alarme est requise", "alarm-status": "Etat d'alarme", "alarms": "Alarmes", "clear": "Effacer", - "clear-alarms-text": "Êtes-vous sûr de vouloir effacer {count, plural, 1 {1 alarme} other {# alarmes}}?", - "clear-alarms-title": "Effacer {count, plural, 1 {1 alarme} other {# alarmes}}", + "clear-alarms-text": "Êtes-vous sûr de vouloir effacer { count, plural, 1 {1 alarme} other {# alarmes} }?", + "clear-alarms-title": "Effacer { count, plural, 1 {1 alarme} other {# alarmes} }", "clear-time": "Heure d'éffacement", "created-time": "Heure de création", "details": "Détails", @@ -125,7 +125,7 @@ "UNACK": "non acquittée" }, "select-alarm": "Sélectionnez une alarme", - "selected-alarms": "{count, plural, 1 {1 alarme} other {# alarmes}} sélectionnées", + "selected-alarms": "{ count, plural, 1 {1 alarme} other {# alarmes} } sélectionnées", "severity": "Gravitée", "severity-critical": "Critique", "severity-indeterminate": "indéterminée", @@ -192,7 +192,7 @@ "assign-asset-to-customer": "Attribuer des Assets au client", "assign-asset-to-customer-text": "Veuillez sélectionner les Assets à attribuer au client", "assign-assets": "Attribuer des Assets", - "assign-assets-text": "Attribuer {count, plural, 1 {1 asset} other {# assets}} au client", + "assign-assets-text": "Attribuer { count, plural, 1 {1 asset} other {# assets} } au client", "assign-new-asset": "Attribuer un nouvel Asset", "assign-to-customer": "Attribuer au client", "assign-to-customer-text": "Veuillez sélectionner le client pour attribuer le ou les Assets", @@ -202,9 +202,9 @@ "delete-asset-text": "Faites attention, après la confirmation, l'Asset et toutes les données associées deviendront irrécupérables.", "delete-asset-title": "Êtes-vous sûr de vouloir supprimer l'Asset '{{assetName}}'?", "delete-assets": "Supprimer des Assets", - "delete-assets-action-title": "Supprimer {count, plural, 1 {1 asset} other {# assets}}", + "delete-assets-action-title": "Supprimer { count, plural, 1 {1 asset} other {# assets} }", "delete-assets-text": "Attention, après la confirmation, tous les Assets sélectionnés seront supprimés et toutes les données associées deviendront irrécupérables.", - "delete-assets-title": "Etes-vous sûr de vouloir supprimer {count, plural, 1 {1 asset} other {# assets}}?", + "delete-assets-title": "Etes-vous sûr de vouloir supprimer { count, plural, 1 {1 asset} other {# assets} }?", "description": "Description", "details": "Détails", "enter-asset-type": "Entrez le type d'Asset", @@ -232,9 +232,9 @@ "unassign-asset-text": "Après la confirmation, l'Asset sera non attribué et ne sera pas accessible au client.", "unassign-asset-title": "Êtes-vous sûr de vouloir retirer l'attribution de l'Asset '{{assetName}}'?", "unassign-assets": "Retirer les Assets", - "unassign-assets-action-title": "Retirer {count, plural, 1 {1 asset} other {# assets}} du client", + "unassign-assets-action-title": "Retirer { count, plural, 1 {1 asset} other {# assets} } du client", "unassign-assets-text": "Après la confirmation, tous les Assets sélectionnés ne seront pas attribués et ne seront pas accessibles au client.", - "unassign-assets-title": "Êtes-vous sûr de vouloir retirer l'attribution de {count, plural, 1 {1 asset} other {# assets}}?", + "unassign-assets-title": "Êtes-vous sûr de vouloir retirer l'attribution de { count, plural, 1 {1 asset} other {# assets} }?", "unassign-from-customer": "Retirer du client", "view-assets": "Afficher les Assets" }, @@ -246,7 +246,7 @@ "attributes-scope": "Etendue des attributs d'entité", "delete-attributes": "Supprimer les attributs", "delete-attributes-text": "Attention, après la confirmation, tous les attributs sélectionnés seront supprimés.", - "delete-attributes-title": "Êtes-vous sûr de vouloir supprimer {count, plural, 1 {1 attribut} other {# attributs}}?", + "delete-attributes-title": "Êtes-vous sûr de vouloir supprimer { count, plural, 1 {1 attribut} other {# attributs} }?", "enter-attribute-value": "Entrez la valeur de l'attribut", "key": "Clé", "key-required": "La Clé d'attribut est requise.", @@ -258,8 +258,8 @@ "scope-latest-telemetry": "Dernière télémétrie", "scope-server": "Attributs du serveur", "scope-shared": "Attributs partagés", - "selected-attributes": "{count, plural, 1 {1 attribut} other {# attributs}} sélectionnés", - "selected-telemetry": "{count, plural, 1 {1 unité de télémétrie} other {# unités de télémétrie}} sélectionnées", + "selected-attributes": "{ count, plural, 1 {1 attribut} other {# attributs} } sélectionnés", + "selected-telemetry": "{ count, plural, 1 {1 unité de télémétrie} other {# unités de télémétrie} } sélectionnées", "show-on-widget": "Afficher sur le widget", "value": "Valeur", "value-required": "La valeur d'attribut est obligatoire.", @@ -358,9 +358,9 @@ "delete": "Supprimer le client", "delete-customer-text": "Faites attention, après la confirmation, le client et toutes les données associées deviendront irrécupérables.", "delete-customer-title": "Êtes-vous sûr de vouloir supprimer le client '{{customerTitle}}'?", - "delete-customers-action-title": "Supprimer {count, plural, 1 {1 client} other {# clients}}", + "delete-customers-action-title": "Supprimer { count, plural, 1 {1 client} other {# clients} }", "delete-customers-text": "Faites attention, après la confirmation, tous les clients sélectionnés seront supprimés et toutes les données associées deviendront irrécupérables.", - "delete-customers-title": "Êtes-vous sûr de vouloir supprimer {count, plural, 1 {1 client} other {# clients}}?", + "delete-customers-title": "Êtes-vous sûr de vouloir supprimer { count, plural, 1 {1 client} other {# clients} }?", "description": "Description", "details": "Détails", "devices": "Dispositifs du client", @@ -397,7 +397,7 @@ "assign-dashboard-to-customer": "Attribuer des tableaux de bord au client", "assign-dashboard-to-customer-text": "Veuillez sélectionner les tableaux de bord à affecter au client", "assign-dashboards": "Attribuer des tableaux de bord", - "assign-dashboards-text": "Attribuer {count, plural, 1 {1 tableau de bord} other {# tableaux de bord}} aux clients", + "assign-dashboards-text": "Attribuer { count, plural, 1 {1 tableau de bord} other {# tableaux de bord} } aux clients", "assign-new-dashboard": "Attribuer un nouveau tableau de bord", "assign-to-customer": "Attribuer au client", "assign-to-customer-text": "Veuillez sélectionner le client pour attribuer le ou les tableaux de bord", @@ -428,9 +428,9 @@ "delete-dashboard-text": "Faites attention, après la confirmation, le tableau de bord et toutes les données associées deviendront irrécupérables.", "delete-dashboard-title": "Êtes-vous sûr de vouloir supprimer le tableau de bord '{{dashboardTitle}}'?", "delete-dashboards": "Supprimer les tableaux de bord", - "delete-dashboards-action-title": "Supprimer {count, plural, 1 {1 tableau de bord} other {# tableaux de bord}}", + "delete-dashboards-action-title": "Supprimer {count, plural, 1 {1 tableau de bord} other {# tableaux de bord} }", "delete-dashboards-text": "Attention, après la confirmation, tous les tableaux de bord sélectionnés seront supprimés et toutes les données associées deviendront irrécupérables.", - "delete-dashboards-title": "Voulez-vous vraiment supprimer {count, plural, 1 {1 tableau de bord} other {# tableaux de bord}}?", + "delete-dashboards-title": "Voulez-vous vraiment supprimer {count, plural, 1 {1 tableau de bord} other {# tableaux de bord} }?", "delete-state": "Supprimer l'état du tableau de bord", "delete-state-text": "Etes-vous sûr de vouloir supprimer l'état du tableau de bord avec le nom '{{stateName}}'?", "delete-state-title": "Supprimer l'état du tableau de bord", @@ -493,7 +493,7 @@ "select-state": "Sélectionnez l'état cible", "select-widget-subtitle": "Liste des types de widgets disponibles", "select-widget-title": "Sélectionner un widget", - "selected-states": "{count, plural, 1 {1 état du tableau de bord} other {# états du tableau de bord}} sélectionnés", + "selected-states": "{count, plural, 1 {1 état du tableau de bord} other {# états du tableau de bord} } sélectionnés", "set-background": "Définir l'arrière-plan", "settings": "Paramètres", "show-details": "Afficher les détails", @@ -515,10 +515,10 @@ "unassign-dashboard-text": "Après la confirmation, le tableau de bord ne sera pas attribué et ne sera pas accessible au client.", "unassign-dashboard-title": "Êtes-vous sûr de vouloir annuler l'affectation du tableau de bord '{{dashboardTitle}}'?", "unassign-dashboards": "Retirer les tableaux de bord", - "unassign-dashboards-action-text": "Annuler l'affectation {count, plural, 1 {1 tableau de bord} other {# tableaux de bord}} des clients", - "unassign-dashboards-action-title": "Annuler l'affectation {count, plural, 1 {1 tableau de bord} other {# tableaux de bord}} du client", + "unassign-dashboards-action-text": "Annuler l'affectation {count, plural, 1 {1 tableau de bord} other {# tableaux de bord} } des clients", + "unassign-dashboards-action-title": "Annuler l'affectation {count, plural, 1 {1 tableau de bord} other {# tableaux de bord} } du client", "unassign-dashboards-text": "Après la confirmation, tous les tableaux de bord sélectionnés ne seront pas attribués et ne seront pas accessibles au client.", - "unassign-dashboards-title": "Etes-vous sûr de vouloir annuler l'affectation {count, plural, 1 {1 tableau de bord} other {# tableaux de bord}}?", + "unassign-dashboards-title": "Etes-vous sûr de vouloir annuler l'affectation {count, plural, 1 {1 tableau de bord} other {# tableaux de bord} }?", "unassign-from-customer": "Retirer du client", "unassign-from-customers": "Retirer les tableaux de bord des clients", "unassign-from-customers-text": "Veuillez sélectionner les clients à annuler l'affectation du ou des tableaux de bord", @@ -541,8 +541,8 @@ "function-types": "Types de fonctions", "function-types-required": "Les types de fonctions sont obligatoires", "label": "Label", - "maximum-function-types": "Maximum {count, plural, 1 {1 type de fonction est autorisé.} other {# types de fonctions sont autorisés}}", - "maximum-timeseries-or-attributes": "Maximum {count, plural, 1 {1 timeseries / attribut est autorisé.} other {# timeseries / attributs sont autorisés}}", + "maximum-function-types": "Maximum {count, plural, 1 {1 type de fonction est autorisé.} other {# types de fonctions sont autorisés} }", + "maximum-timeseries-or-attributes": "Maximum {count, plural, 1 {1 timeseries / attribut est autorisé.} other {# timeseries / attributs sont autorisés} }", "settings": "Paramètres", "timeseries": "Timeseries", "timeseries-or-attributes-required": "Les timeseries / attributs d'entité sont obligatoires.", @@ -580,7 +580,7 @@ "assign-device-to-customer": "Affecter des dispositifs au client", "assign-device-to-customer-text": "Veuillez sélectionner les dispositif à affecter au client", "assign-devices": "Attribuer des dispositifs", - "assign-devices-text": "Attribuer {count, plural, 1 {1 dispositif} other {# dispositifs}} au client", + "assign-devices-text": "Attribuer {count, plural, 1 {1 dispositif} other {# dispositifs} } au client", "assign-new-device": "Attribuer un nouveau dispositif", "assign-to-customer": "Attribuer au client", "assign-to-customer-text": "Veuillez sélectionner le client pour attribuer le ou les dispositifs", @@ -596,9 +596,9 @@ "delete-device-text": "Faites attention, après la confirmation, le dispositif et toutes les données associées deviendront irrécupérables.", "delete-device-title": "Êtes-vous sûr de vouloir supprimer le dispositif '{{deviceName}}'?", "delete-devices": "Supprimer les dispositifs", - "delete-devices-action-title": "Supprimer {count, plural, 1 {1 dispositif} other {# dispositifs}}", + "delete-devices-action-title": "Supprimer {count, plural, 1 {1 dispositif} other {# dispositifs} }", "delete-devices-text": "Faites attention, après la confirmation, tous les dispositifs sélectionnés seront supprimés et toutes les données associées deviendront irrécupérables.", - "delete-devices-title": "Êtes-vous sûr de vouloir supprimer {count, plural, 1 {1 dispositif} other {# dispositifs}}?", + "delete-devices-title": "Êtes-vous sûr de vouloir supprimer {count, plural, 1 {1 dispositif} other {# dispositifs} }?", "description": "Description", "details": "Détails", "device": "Dispositif", @@ -653,9 +653,9 @@ "unassign-device-text": "Après la confirmation, le dispositif ne sera pas attribué et ne sera pas accessible au client.", "unassign-device-title": "Êtes-vous sûr de vouloir annuler l'affection du dispositif {{deviceName}} '?", "unassign-devices": "Annuler l'affectation des dispositifs", - "unassign-devices-action-title": "Annuler l'affectation de {count, plural, 1 {1 dispositif} other {#dispositifs}} du client", + "unassign-devices-action-title": "Annuler l'affectation de {count, plural, 1 {1 dispositif} other {#dispositifs} } du client", "unassign-devices-text": "Après la confirmation, tous les dispositifs sélectionnés ne seront pas attribues et ne seront pas accessibles par le client.", - "unassign-devices-title": "Voulez-vous vraiment annuler l'affectation de {count, plural, 1 {1 dispositif} other {# dispositifs}}?", + "unassign-devices-title": "Voulez-vous vraiment annuler l'affectation de {count, plural, 1 {1 dispositif} other {# dispositifs} }?", "unassign-from-customer": "Retirer du client", "use-device-name-filter": "Utiliser le filtre", "view-credentials": "Afficher les informations d'identification", @@ -696,17 +696,17 @@ "entity-types": "Types d'entité", "key": "Clé", "key-name": "Nom de la clé", - "list-of-alarms": "{count, plural, 1 {Une alarme} other {Liste de # alarmes}}", - "list-of-assets": "{count, plural, 1 {Un Asset} other {Liste de # Assets}}", - "list-of-customers": "{count, plural, 1 {Un client} other {Liste de # clients}}", - "list-of-dashboards": "{count, plural, 1 {Un tableau de bord} other {Liste de # tableaux de bord}}", - "list-of-devices": "{count, plural, 1 {Un dispositif} other {Liste de # dispositifs}}", - "list-of-plugins": "{count, plural, 1 {Un plugin} other {Liste de # plugins}}", - "list-of-rulechains": "{count, plural, 1 {Une chaîne de règles} other {Liste de # chaînes de règles}}", - "list-of-rulenodes": "{count, plural, 1 {Un noeud de règles} other {Liste de # noeuds de règles}}", - "list-of-rules": "{count, plural, 1 {Une règle} other {Liste de # règles}}", - "list-of-tenants": "{count, plural, 1 {Un tenant} other {Liste de # tenants}}", - "list-of-users": "{count, plural, 1 {Un utilisateur} other {Liste de # utilisateurs}}", + "list-of-alarms": "{count, plural, 1 {Une alarme} other {Liste de # alarmes} }", + "list-of-assets": "{count, plural, 1 {Un Asset} other {Liste de # Assets} }", + "list-of-customers": "{count, plural, 1 {Un client} other {Liste de # clients} }", + "list-of-dashboards": "{count, plural, 1 {Un tableau de bord} other {Liste de # tableaux de bord} }", + "list-of-devices": "{count, plural, 1 {Un dispositif} other {Liste de # dispositifs} }", + "list-of-plugins": "{count, plural, 1 {Un plugin} other {Liste de # plugins} }", + "list-of-rulechains": "{count, plural, 1 {Une chaîne de règles} other {Liste de # chaînes de règles} }", + "list-of-rulenodes": "{count, plural, 1 {Un noeud de règles} other {Liste de # noeuds de règles} }", + "list-of-rules": "{count, plural, 1 {Une règle} other {Liste de # règles} }", + "list-of-tenants": "{count, plural, 1 {Un tenant} other {Liste de # tenants} }", + "list-of-users": "{count, plural, 1 {Un utilisateur} other {Liste de # utilisateurs} }", "missing-entity-filter-error": "Le filtre est manquant pour l'alias '{{alias}}'.", "name-starts-with": "Nom commence par", "no-alias-matching": "'{{alias}}' introuvable.", @@ -724,7 +724,7 @@ "rulenode-name-starts-with": "Les noeuds de règles dont le nom commence par '{{prefix}}'", "search": "Recherche d'entités", "select-entities": "Sélectionner des entités", - "selected-entities": "{count, plural, 1 {1 entité} other {# entités}} sélectionnées", + "selected-entities": "{count, plural, 1 {1 entité} other {# entités} } sélectionnées", "tenant-name-starts-with": "Les Tenant dont le nom commence par '{{prefix}}'", "type": "Type", "type-alarm": "Alarme", @@ -832,7 +832,7 @@ "delete-extension-text": "Attention, après la confirmation, l'extension et toutes les données associées deviendront irrécupérables.", "delete-extension-title": "Êtes-vous sûr de vouloir supprimer l'extension '{{extensionId}}'?", "delete-extensions-text": "Attention, après la confirmation, toutes les extensions sélectionnées seront supprimées.", - "delete-extensions-title": "Êtes-vous sûr de vouloir supprimer {count, plural, 1 {1 extension} other {# extensions}}?", + "delete-extensions-title": "Êtes-vous sûr de vouloir supprimer {count, plural, 1 {1 extension} other {# extensions} }?", "device-name-expression": "expression du nom du dispositif", "device-name-filter": "Filtre de nom de dispositif", "device-type-expression": "expression de type de dispositif", @@ -918,7 +918,7 @@ "response-timeout": "Délai de réponse en millisecondes", "response-topic-expression": "Expression du topic de la réponse", "retry-interval": "Intervalle de nouvelle tentative en millisecondes", - "selected-extensions": "{count, plural, 1 {1 extension} other {# extensions}} sélectionné", + "selected-extensions": "{count, plural, 1 {1 extension} other {# extensions} } sélectionné", "server-side-rpc": "RPC côté serveur", "ssl": "Ssl", "sync": { @@ -960,9 +960,9 @@ "delete-item-text": "Faites attention, après la confirmation, cet élément et toutes les données associées deviendront irrécupérables.", "delete-item-title": "Êtes-vous sûr de vouloir supprimer cet élément?", "delete-items": "Supprimer les éléments", - "delete-items-action-title": "Supprimer {count, plural, 1 {1 élément} other {# éléments}}", + "delete-items-action-title": "Supprimer {count, plural, 1 {1 élément} other {# éléments} }", "delete-items-text": "Attention, après la confirmation, tous les éléments sélectionnés seront supprimés et toutes les données associées deviendront irrécupérables.", - "delete-items-title": "Etes-vous sûr de vouloir supprimer {count, plural, 1 {1 élément} other {# éléments}}?", + "delete-items-title": "Etes-vous sûr de vouloir supprimer {count, plural, 1 {1 élément} other {# éléments} }?", "item-details": "Détails de l'élément", "no-items-text": "Aucun élément trouvé", "scroll-to-top": "Défiler vers le haut" @@ -1080,11 +1080,11 @@ "delete-from-relation-text": "Attention, après la confirmation, l'entité actuelle ne sera pas liée à l'entité '{{entityName}}'.", "delete-from-relation-title": "Etes-vous sûr de vouloir supprimer la relation de l'entité '{{entityName}}'?", "delete-from-relations-text": "Attention, après la confirmation, toutes les relations sélectionnées seront supprimées et l'entité actuelle ne sera pas liée aux entités correspondantes.", - "delete-from-relations-title": "Êtes-vous sûr de vouloir supprimer {count, plural, 1 {1 relation} other {# relations}}?", + "delete-from-relations-title": "Êtes-vous sûr de vouloir supprimer {count, plural, 1 {1 relation} other {# relations} }?", "delete-to-relation-text": "Attention, après la confirmation, l'entité '{{entityName}} ne sera plus liée à l'entité actuelle.", "delete-to-relation-title": "Êtes-vous sûr de vouloir supprimer la relation avec l'entité '{{entityName}}'?", "delete-to-relations-text": "Attention, après la confirmation, toutes les relations sélectionnées seront supprimées et les entités correspondantes ne seront pas liées à l'entité en cours.", - "delete-to-relations-title": "Etes-vous sûr de vouloir supprimer {count, plural, 1 {1 relation} other {# relations}}?", + "delete-to-relations-title": "Etes-vous sûr de vouloir supprimer {count, plural, 1 {1 relation} other {# relations} }?", "direction": "Sens", "direction-type": { "FROM": "de", @@ -1105,7 +1105,7 @@ "FROM": "De", "TO": "À" }, - "selected-relations": "{count, plural, 1 {1 relation} other {# relations}} sélectionné", + "selected-relations": "{count, plural, 1 {1 relation} other {# relations} } sélectionné", "to-entity": "À l'entité", "to-entity-name": "vers le nom de l'entité", "to-entity-type": "Vers le type d'entité", @@ -1121,9 +1121,9 @@ "delete": "Supprimer la chaîne de règles", "delete-rulechain-text": "Attention, après la confirmation, la chaîne de règles et toutes les données associées deviendront irrécupérables.", "delete-rulechain-title": "Voulez-vous vraiment supprimer la chaîne de règles '{{ruleChainName}}'?", - "delete-rulechains-action-title": "Supprimer {count, plural, 1 {1 chaîne de règles} other {# chaînes de règles}}", + "delete-rulechains-action-title": "Supprimer {count, plural, 1 {1 chaîne de règles} other {# chaînes de règles} }", "delete-rulechains-text": "Attention, après la confirmation, toutes les chaînes de règles sélectionnées seront supprimées et toutes les données associées deviendront irrécupérables.", - "delete-rulechains-title": "Êtes-vous sûr de vouloir supprimer {count, plural, 1 {1 chaîne de règles} other {# chaînes de règles}}?", + "delete-rulechains-title": "Êtes-vous sûr de vouloir supprimer {count, plural, 1 {1 chaîne de règles} other {# chaînes de règles} }?", "description": "Description", "details": "Détails", "events": "Evénements", @@ -1220,9 +1220,9 @@ "delete": "Supprimer le Tenant", "delete-tenant-text": "Attention, après la confirmation, le Tenant et toutes les données associées deviendront irrécupérables.", "delete-tenant-title": "Etes-vous sûr de vouloir supprimer le tenant '{{tenantTitle}}'?", - "delete-tenants-action-title": "Supprimer {count, plural, 1 {1 tenant} other {# tenants}}", + "delete-tenants-action-title": "Supprimer {count, plural, 1 {1 tenant} other {# tenants} }", "delete-tenants-text": "Attention, après la confirmation, tous les Tenants sélectionnés seront supprimés et toutes les données associées deviendront irrécupérables.", - "delete-tenants-title": "Etes-vous sûr de vouloir supprimer {count, plural, 1 {1 tenant} other {# tenants}}?", + "delete-tenants-title": "Etes-vous sûr de vouloir supprimer {count, plural, 1 {1 tenant} other {# tenants} }?", "description": "Description", "details": "Détails", "events": "Événements", @@ -1242,26 +1242,26 @@ "timeinterval": { "advanced": "Avancé", "days": "Jours", - "days-interval": "{days, plural, 1 {1 jour} other {# jours}}", + "days-interval": "{days, plural, 1 {1 jour} other {# jours} }", "hours": "Heures", - "hours-interval": "{hours, plural, 1 {1 heure} other {# heures}}", + "hours-interval": "{hours, plural, 1 {1 heure} other {# heures} }", "minutes": "Minutes", - "minutes-interval": "{minutes, plural, 1 {1 minute} other {# minutes}}", + "minutes-interval": "{minutes, plural, 1 {1 minute} other {# minutes} }", "seconds": "Secondes", - "seconds-interval": "{seconds, plural, 1 {1 seconde} other {# secondes}}" + "seconds-interval": "{seconds, plural, 1 {1 seconde} other {# secondes} }" }, "timewindow": { "date-range": "Plage de dates", - "days": "{days, plural, 1 {jour} other {# jours}}", + "days": "{days, plural, 1 {jour} other {# jours} }", "edit": "Modifier timewindow", "history": "Historique", - "hours": "{hours, plural, 0 {heure} 1 {1 heure} other {# heures}}", + "hours": "{hours, plural, 0 {heure} 1 {1 heure} other {# heures} }", "last": "Dernier", "last-prefix": "dernier", - "minutes": "{minutes, plural, 0 {minute} 1 {1 minute} other {# minutes}}", + "minutes": "{minutes, plural, 0 {minute} 1 {1 minute} other {# minutes} }", "period": "de {{startTime}} à {{endTime}}", "realtime": "Temps réel", - "seconds": "{seconds, plural, 0 {second} 1 {1 second} other {# seconds}}", + "seconds": "{seconds, plural, 0 {second} 1 {1 second} other {# seconds} }", "time-period": "Période" }, "user": { @@ -1281,9 +1281,9 @@ "delete": "Supprimer l'utilisateur", "delete-user-text": "Attention, après la confirmation, l'utilisateur et toutes les données associées deviendront irrécupérables.", "delete-user-title": "Etes-vous sûr de vouloir supprimer l'utilisateur '{{userEmail}}'?", - "delete-users-action-title": "Supprimer {count, plural, 1 {1 utilisateur} other {# utilisateurs}}", + "delete-users-action-title": "Supprimer {count, plural, 1 {1 utilisateur} other {# utilisateurs} }", "delete-users-text": "Attention, après la confirmation, tous les utilisateurs sélectionnés seront supprimés et toutes les données associées deviendront irrécupérables.", - "delete-users-title": "Etes-vous sûr de vouloir supprimer {count, plural, 1 {1 utilisateur} other {# utilisateurs}}?", + "delete-users-title": "Etes-vous sûr de vouloir supprimer {count, plural, 1 {1 utilisateur} other {# utilisateurs} }?", "description": "Description", "details": "Détails", "display-activation-link": "Afficher le lien d'activation", @@ -1417,7 +1417,7 @@ "general-settings": "Paramètres généraux", "height": "Hauteur", "margin": "Marge", - "maximum-datasources": "Maximum {count, plural, 1 {1 datasource est autorisé.} other {# datasources sont autorisés}}", + "maximum-datasources": "Maximum {count, plural, 1 {1 datasource est autorisé.} other {# datasources sont autorisés} }", "mobile-mode-settings": "Paramètres du mode mobile", "order": "Ordre", "padding": "Padding", @@ -1511,9 +1511,9 @@ "delete": "Supprimer le groupe de widgets", "delete-widgets-bundle-text": "Attention, après la confirmation, le groupe de widgets et toutes les données associées deviendront irrécupérables.", "delete-widgets-bundle-title": "Êtes-vous sûr de vouloir supprimer le groupe de widgets '{{widgetsBundleTitle}}'?", - "delete-widgets-bundles-action-title": "Supprimer {count, plural, 1 {1 groupe de widgets} other {# groupes de widgets}}", + "delete-widgets-bundles-action-title": "Supprimer {count, plural, 1 {1 groupe de widgets} other {# groupes de widgets} }", "delete-widgets-bundles-text": "Attention, après la confirmation, tous les groupes de widgets sélectionnés seront supprimés et toutes les données associées deviendront irrécupérables.", - "delete-widgets-bundles-title": "Voulez-vous vraiment supprimer {count, plural, 1 {1 groupe de widgets} other {# groupes de widgets}}?", + "delete-widgets-bundles-title": "Voulez-vous vraiment supprimer {count, plural, 1 {1 groupe de widgets} other {# groupes de widgets} }?", "details": "Détails", "empty": "Le groupe de widgets est vide", "export": "Exporter le groupe de widgets", diff --git a/ui-ngx/src/assets/locale/locale.constant-ru_RU.json b/ui-ngx/src/assets/locale/locale.constant-ru_RU.json index 504344c749..ae69c79681 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ru_RU.json +++ b/ui-ngx/src/assets/locale/locale.constant-ru_RU.json @@ -626,7 +626,7 @@ "delete-device-title": "Вы точно хотите удалить устройство '{{deviceName}}'?", "delete-device-text": "Внимание, после подтверждения устройство и все связанные с ним данные будут безвозвратно утеряны.", "delete-devices-title": "Вы точно хотите удалить { count, plural, one {1 устройство} few {# устройства} other {# устройств} }?", - "delete-devices-action-title": "Удалить { count, plural, one {1 устройство} few {# устройства} other {# устройств} } }", + "delete-devices-action-title": "Удалить { count, plural, one {1 устройство} few {# устройства} other {# устройств} }", "delete-devices-text": "Внимание, после подтверждения выбранные устройства и все связанные с ними данные будут безвозвратно утеряны..", "unassign-device-title": "Вы точно хотите отозвать устройство '{{deviceName}}'?", "unassign-device-text": "После подтверждения устройство будет недоступно клиенту.", @@ -1064,7 +1064,7 @@ "delete-item-title": "Вы точно хотите удалить этот объект?", "delete-item-text": "Внимание, после подтверждения объект и все связанные с ним данные будут безвозвратно утеряны.", "delete-items-title": "Вы точно хотите удалить { count, plural, one {1 объект} few {# объекта} other {# объектов} }?", - "delete-items-action-title": "Удалить { count, plural, one {1 объект} few {# объекта} other {# объектов}", + "delete-items-action-title": "Удалить { count, plural, one {1 объект} few {# объекта} other {# объектов} }", "delete-items-text": "Внимание, после подтверждения выбранные объекты и все связанные с ними данные будут безвозвратно утеряны.", "add-item-text": "Добавить новый объект", "no-items-text": "Объекты не найдены", @@ -1646,4 +1646,4 @@ "cs_CZ": "Чешский" } } -} \ No newline at end of file +} 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 e3726ec841..0187b348e0 100644 --- a/ui-ngx/src/assets/locale/locale.constant-tr_TR.json +++ b/ui-ngx/src/assets/locale/locale.constant-tr_TR.json @@ -419,7 +419,7 @@ "assign-dashboards": "Kontrol panelleri ata", "assign-new-dashboard": "Yeni kontrol paneli ata", "assign-dashboards-text": "{ count, plural, 1 {1 kontrol panelini} other {# kontrol panelini} } kullanıcı grubuna ata", - "unassign-dashboards-action-text": "Müşterilerden atama { count, plural, 1 {1 gösterge tablosu} other {# panolar}}", + "unassign-dashboards-action-text": "Müşterilerden atama { count, plural, 1 {1 gösterge tablosu} other {# panolar} }", "delete-dashboards": "Kontrol panellerini sil", "unassign-dashboards": "Kontrol panellerinden atamayı kaldır", "unassign-dashboards-action-title": "{ count, plural, 1 {1 kontrol panelinin} other {# kontrol panelinin} } atamaları kullanıcı grubundan kaldır", @@ -710,7 +710,7 @@ "asset-name-starts-with": "İsmi '{{prefix}}' ile başlayan varlıklar", "type-entity-view": "Varlık Görünümü", "type-entity-views": "Varlık Görünümleri", - "list-of-entity-views": "{ count, plural, 1 {Bir varlık görünümü} other {# varlık görüntüleme}} listesi", + "list-of-entity-views": "{ count, plural, 1 {Bir varlık görünümü} other {# varlık görüntüleme} } listesi", "entity-view-name-starts-with": "Adı {{önek}} ile başlayan varlık görünümleri", "type-rule": "Kural", "type-rules": "Kurallar", @@ -742,11 +742,11 @@ "alarm-name-starts-with": "İsmi '{{prefix}}' ile başlayan alarmlar", "type-rulechain": "Kural zinciri", "type-rulechains": "Kural zincirleri", - "list-of-rulechains": "{ count, plural, 1 {Bir kural zinciri} other {# kural zincirinin listesi}}", + "list-of-rulechains": "{ count, plural, 1 {Bir kural zinciri} other {# kural zincirinin listesi} }", "rulechain-name-starts-with": "İsimleri {{prefix}} ile başlayan kural zincirleri", "type-rulenode": "Kural düğümü", "type-rulenodes": "Kural düğümleri", - "list-of-rulenodes": "{ count, plural, 1 {Bir kural node} other {# kural düğümünün listesi}}", + "list-of-rulenodes": "{ count, plural, 1 {Bir kural node} other {# kural düğümünün listesi} }", "rulenode-name-starts-with": "İsimleri '{{prefix}} ile başlayan kural düğümleri", "type-current-customer": "Mevcut Müşteri", "search": "Öğeleri ara", @@ -765,7 +765,7 @@ "aliases": "Varlık Görünümü takma adları", "no-alias-matching": "'{{alias}} bulunamadı. ", "no-aliases-found": "Takma ad bulunamadı", - "no-key-matching": "'{{anahtar bulunamadı.", + "no-key-matching": "'{{key}}' bulunamadı.", "no-keys-found": "Anahtar bulunamadı.", "create-new-alias": "Yeni bir tane oluştur!", "create-new-key": "Yeni bir tane oluştur!", @@ -792,21 +792,21 @@ "add-entity-view-text": "Yeni varlık görünümü ekle", "delete": "Varlık görünümünü sil", "assign-entity-views": "Varlık görünümleri atama", - "assign-entity-views-text": "Müşteriye { count, plural, 1 {1 entityView} other {# entityViews}} atayın ", + "assign-entity-views-text": "Müşteriye { count, plural, 1 {1 entityView} other {# entityViews} } atayın ", "delete-entity-views": "Varlık görünümlerini sil", "unassign-from-customer": "Müşteriden atama", "unassign-entity-views": "Varlık görünümlerini atama", - "unassign-entity-views-action-title": "Müşteriden atama { count, plural, 1 {1 entityView} other {# entityViews}}", + "unassign-entity-views-action-title": "Müşteriden atama { count, plural, 1 {1 entityView} other {# entityViews} }", "assign-new-entity-view": "Yeni varlık görünümü atama", "delete-entity-view-title": "Varlık görünümünü silmek istediğinizden emin misiniz?, {{entityViewName}} '? ", "delete-entity-view-text": "Dikkatli olun, onaylandıktan sonra varlık görünümü ve ilgili tüm veriler kurtarılamayacak.", - "delete-entity-views-title": "{ count, plural, 1 {1 entityView} other {# entityViews}} varlık görünümüne sahip olmak istediğinizden emin misiniz?", - "delete-entity-views-action-title": "Sil { count, plural, 1 {1 entityView} other {# entityViews}}", + "delete-entity-views-title": "{ count, plural, 1 {1 entityView} other {# entityViews} } varlık görünümüne sahip olmak istediğinizden emin misiniz?", + "delete-entity-views-action-title": "Sil { count, plural, 1 {1 entityView} other {# entityViews} }", "delete-entity-views-text": "Dikkatli olun, onaylandıktan sonra tüm seçilen görünümler kaldırılacak ve ilgili tüm veriler kurtarılamayacaktır.", "unassign-entity-view-title": "Varlık görünümünün atamasını kaldırmak istediğinizden emin misiniz? {{entityViewName}} '? ", "unassign-entity-view-text": "Onaydan sonra varlık görünümü atanmamış olacak ve müşteri tarafından erişilemeyecektir.", "unassign-entity-view": "Varlık görünümünün atamasını kaldır", - "unassign-entity-views-title": "{ count, plural, 1 {1 entityView} other {# entityViews}} hesabının atamasını kaldırmak istediğinizden emin misiniz?", + "unassign-entity-views-title": "{ count, plural, 1 {1 entityView} other {# entityViews} } hesabının atamasını kaldırmak istediğinizden emin misiniz?", "unassign-entity-views-text": "Onaylandıktan sonra, seçilen tüm öğe görünümleri atamadan kaldırılacak ve müşteri tarafından erişilemeyecektir.", "entity-view-type": "Varlık Görünümü türü", "entity-view-type-required": "Varlık Görünümü türü gerekli.", @@ -861,7 +861,7 @@ }, "extension": { "extensions": "Uzantılar", - "selected-extensions": "{ count, plural, 1 {1 uzantı} other {# extensions}} seçildi", + "selected-extensions": "{ count, plural, 1 {1 uzantı} other {# extensions} } seçildi", "type": "Tür", "key": "Anahtar", "value": "Değer", @@ -875,7 +875,7 @@ "edit": "Uzantıyı düzenle", "delete-extension-title": "{{ExtensionId}} uzantısını silmek istediğinizden emin misiniz? ", "delete-extension-text": "Dikkatli olun, onaylamadan sonra uzantı ve ilgili tüm veriler kurtarılamaz.", - "delete-extensions-title": "{ count, plural, 1 {1 uzantı} other {# extensions}} silmek istediğinizden emin misiniz?", + "delete-extensions-title": "{ count, plural, 1 {1 uzantı} other {# extensions} } silmek istediğinizden emin misiniz?", "delete-extensions-text": "Dikkatli olun, onaylandıktan sonra tüm seçilen uzantılar kaldırılacak.", "converters": "Dönüştürücü", "converter-id": "Dönüştürücü kimliği", @@ -1606,4 +1606,4 @@ "cs_CZ": "Çekçe" } } -} \ No newline at end of file +} diff --git a/ui-ngx/src/assets/locale/locale.constant-uk_UA.json b/ui-ngx/src/assets/locale/locale.constant-uk_UA.json index 8c2e9c6def..082f8a9b41 100644 --- a/ui-ngx/src/assets/locale/locale.constant-uk_UA.json +++ b/ui-ngx/src/assets/locale/locale.constant-uk_UA.json @@ -457,8 +457,8 @@ "customer-details": "Інформація про клієнта", "delete-customer-title": "Ви впевнені, що хочете видалити клієнта '{{customerTitle}}'?", "delete-customer-text": "Будьте обережні, після підтвердження, клієнт та всі пов'язані з ним дані, стануть недоступними.", - "delete-customers-title": "Ви впевнені, що хочете видалити {count, plural, 1 {1 клієнт}, інші {# клієнти}}?", - "delete-customers-action-title": "Видалити{ count, plural, 1 {1 клієнт} other {# клієнти} }", + "delete-customers-title": "Ви впевнені, що хочете видалити { count, plural, 1 {1 клієнт} other {# клієнти} }?", + "delete-customers-action-title": "Видалити { count, plural, 1 {1 клієнт} other {# клієнти} }", "delete-customers-text": "Будьте обережні, після підтвердження, всі вибрані клієнти будуть видалені і всі пов'язані з ними дані, стануть недоступними.", "manage-users": "Керування користувачами", "manage-assets": "Керування активами", @@ -474,7 +474,7 @@ "select-customer": "Виберіть клієнта", "no-customers-matching": "Клієнтів, які відповідають '{{entity}}' не знайдено.", "customer-required": "Необхідно задати клієнта", - "selected-customers": "{ count, plural, 1 {1 клієнт} інші {# клієнти} } вибрано", + "selected-customers": "{ count, plural, 1 {1 клієнт} other {# клієнти} } вибрано", "search": "Пошук клієнтів", "select-group-to-add": "Виберіть цільову групу, щоб додати вибраних клієнтів", "select-group-to-move": "Виберіть цільову групу для переміщення вибраних клієнтів", 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 a683828a8a..e75317aca1 100644 --- a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json +++ b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json @@ -596,11 +596,11 @@ "manage-credentials": "管理凭据", "delete": "删除设备", "assign-devices": "分配设备", - "assign-devices-text": "将{count,plural,1 {1 设备} other {# 设备}}分配给客户", + "assign-devices-text": "将{count,plural,1 {1 设备} other {# 设备} }分配给客户", "delete-devices": "删除设备", "unassign-from-customer": "取消分配客户", "unassign-devices": "取消分配设备", - "unassign-devices-action-title": "从客户处取消分配{count,plural,1 {1 设备} other {# 设备}}", + "unassign-devices-action-title": "从客户处取消分配{count,plural,1 {1 设备} other {# 设备} }", "assign-new-device": "分配新设备", "make-public-device-title": "您确定要将设备 '{{deviceName}}' 设为公开吗?", "make-public-device-text": "确认后,设备及其所有数据将被设为公开并可被其他人访问。", @@ -609,13 +609,13 @@ "view-credentials": "查看凭据", "delete-device-title": "您确定要删除设备的{{deviceName}}吗?", "delete-device-text": "小心!确认后设备及其所有相关数据将不可恢复。", - "delete-devices-title": "您确定要删除{count,plural,1 {1 设备} other {# 设备}} 吗?", - "delete-devices-action-title": "删除 {count,plural,1 {1 设备} other {# 设备}}", + "delete-devices-title": "您确定要删除{count,plural,1 {1 设备} other {# 设备} } 吗?", + "delete-devices-action-title": "删除 {count,plural,1 {1 设备} other {# 设备} }", "delete-devices-text": "小心!确认后所有选定的设备将被删除,所有相关数据将不可恢复。", "unassign-device-title": "您确定要取消分配设备 '{{deviceName}}'?", "unassign-device-text": "确认后,设备将被取消分配,客户将无法访问。", "unassign-device": "取消分配设备", - "unassign-devices-title": "您确定要取消分配{count,plural,1 {1 设备} other {# 设备}} 吗?", + "unassign-devices-title": "您确定要取消分配{count,plural,1 {1 设备} other {# 设备} } 吗?", "unassign-devices-text": "确认后,所有选定的设备将被取消分配,并且客户将无法访问。", "device-credentials": "设备凭据", "credentials-type": "凭据类型", @@ -792,7 +792,7 @@ "delete-entity-views": "删除实体视图", "unassign-from-customer": "取消分配客户", "unassign-entity-views": "取消分配实体视图", - "unassign-entity-views-action-title": "从客户处取消分配{count,plural,1 {1 实体视图} other {# 实体视图}}", + "unassign-entity-views-action-title": "从客户处取消分配{count,plural,1 {1 实体视图} other {# 实体视图} }", "assign-new-entity-view": "分配新实体视图", "delete-entity-view-title": "确定要删除实体视图 '{{entityViewName}}'?", "delete-entity-view-text": "小心!确认后实体视图及其所有相关数据将不可恢复。", @@ -1192,7 +1192,7 @@ "set-root-rulechain-text": "确认之后,规则链将变为根规格链,并将处理所有传入的传输消息。", "delete-rulechain-title": " 确实要删除规则链'{{ruleChainName}}'吗?", "delete-rulechain-text": "小心,在确认规则链和所有相关数据将变得不可恢复。", - "delete-rulechains-title": "确实要删除{count, plural, 1 { 1 规则链}其他{# 规则链库}}吗?", + "delete-rulechains-title": "确实要删除{count, plural, 1 { 1 规则链} other {# 规则链库} }吗?", "delete-rulechains-action-title": "删除 { count, plural, 1 {1 规则链} other {# 规则链库} }", "delete-rulechains-text": "小心,确认后,所有选定的规则链将被删除,所有相关的数据将变得不可恢复。", "add-rulechain-text": "添加新的规则链", @@ -1283,7 +1283,7 @@ "tenant-details": "租客详情", "delete-tenant-title": "您确定要删除租户'{{tenantTitle}}'吗?", "delete-tenant-text": "小心!确认后,租户和所有相关数据将不可恢复。", - "delete-tenants-title": "您确定要删除 {count,plural,1 {1 租户} other {# 租户}} 吗?", + "delete-tenants-title": "您确定要删除 {count,plural,1 {1 租户} other {# 租户} } 吗?", "delete-tenants-action-title": "删除 { count, plural, 1 {1 租户} other {# 租户} }", "delete-tenants-text": "小心!确认后,所有选定的租户将被删除,所有相关数据将不可恢复。", "title": "标题", From 65939722f31a8a06083bb976d0b93a227c30f672 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 13 Aug 2019 19:58:35 +0300 Subject: [PATCH 007/133] Devices page implementation --- .../server/controller/BaseController.java | 23 +- .../server/controller/DeviceController.java | 69 ++++- .../server/dao/device/DeviceService.java | 13 +- .../server/common/data/DeviceInfo.java | 40 +++ .../server/dao/device/DeviceDao.java | 50 ++++ .../server/dao/device/DeviceServiceImpl.java | 50 +++- .../dao/model/sql/AbstractDeviceEntity.java | 122 +++++++++ .../server/dao/model/sql/DeviceEntity.java | 64 +---- .../dao/model/sql/DeviceInfoEntity.java | 58 +++++ .../dao/sql/device/DeviceRepository.java | 50 ++++ .../server/dao/sql/device/JpaDeviceDao.java | 51 +++- .../server/dao/SqlDaoServiceTestSuite.java | 2 +- .../dao/service/BaseDeviceServiceTest.java | 39 ++- ui-ngx/src/app/core/http/device.service.ts | 74 ++++++ .../src/app/core/services/broadcast.models.ts | 27 ++ .../app/core/services/broadcast.service.ts | 51 ++++ .../pages/device/device-routing.module.ts | 50 ++++ .../device/device-table-header.component.html | 23 ++ .../device/device-table-header.component.scss | 36 +++ .../device/device-table-header.component.ts | 42 ++++ .../home/pages/device/device.component.html | 107 ++++++++ .../home/pages/device/device.component.scss | 19 ++ .../home/pages/device/device.component.ts | 88 +++++++ .../home/pages/device/device.module.ts | 39 +++ .../device/devices-table-config.resolver.ts | 196 +++++++++++++++ .../modules/home/pages/home-pages.module.ts | 2 + .../dashboard-autocomplete.component.ts | 47 ++-- .../entity/entities-table-config.models.ts | 7 +- .../entity/entities-table.component.html | 2 +- .../entity/entities-table.component.ts | 2 +- ...entity-subtype-autocomplete.component.html | 39 +++ .../entity-subtype-autocomplete.component.ts | 228 +++++++++++++++++ .../entity-subtype-select.component.html | 28 +++ .../entity-subtype-select.component.scss | 20 ++ .../entity/entity-subtype-select.component.ts | 238 ++++++++++++++++++ ui-ngx/src/app/shared/models/constants.ts | 3 +- ui-ngx/src/app/shared/models/device.models.ts | 34 +++ .../app/shared/models/entity-type.models.ts | 28 +++ ui-ngx/src/app/shared/models/id/device-id.ts | 26 ++ ui-ngx/src/app/shared/shared.module.ts | 6 + .../assets/locale/locale.constant-en_US.json | 5 +- ui-ngx/src/styles.scss | 7 + ui-ngx/src/theme.scss | 6 + 43 files changed, 1968 insertions(+), 143 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/DeviceInfo.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceInfoEntity.java create mode 100644 ui-ngx/src/app/core/http/device.service.ts create mode 100644 ui-ngx/src/app/core/services/broadcast.models.ts create mode 100644 ui-ngx/src/app/core/services/broadcast.service.ts create mode 100644 ui-ngx/src/app/modules/home/pages/device/device-routing.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/device/device-table-header.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/device/device-table-header.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/device/device-table-header.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/device/device.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/device/device.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/device/device.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/device/device.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts create mode 100644 ui-ngx/src/app/shared/components/entity/entity-subtype-autocomplete.component.html create mode 100644 ui-ngx/src/app/shared/components/entity/entity-subtype-autocomplete.component.ts create mode 100644 ui-ngx/src/app/shared/components/entity/entity-subtype-select.component.html create mode 100644 ui-ngx/src/app/shared/components/entity/entity-subtype-select.component.scss create mode 100644 ui-ngx/src/app/shared/components/entity/entity-subtype-select.component.ts create mode 100644 ui-ngx/src/app/shared/models/device.models.ts create mode 100644 ui-ngx/src/app/shared/models/id/device-id.ts 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 ae447c1a6f..b0c505b575 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -28,16 +28,7 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.ExceptionHandler; import org.thingsboard.server.actors.service.ActorService; -import org.thingsboard.server.common.data.Customer; -import org.thingsboard.server.common.data.Dashboard; -import org.thingsboard.server.common.data.DashboardInfo; -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.EntityView; -import org.thingsboard.server.common.data.HasName; -import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.*; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmId; import org.thingsboard.server.common.data.alarm.AlarmInfo; @@ -381,6 +372,18 @@ public abstract class BaseController { } } + DeviceInfo checkDeviceInfoId(DeviceId deviceId, Operation operation) throws ThingsboardException { + try { + validateId(deviceId, "Incorrect deviceId " + deviceId); + DeviceInfo device = deviceService.findDeviceInfoById(getCurrentUser().getTenantId(), deviceId); + checkNotNull(device); + accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE, operation, deviceId, device); + return device; + } catch (Exception e) { + throw handleException(e, false); + } + } + protected EntityView checkEntityViewId(EntityViewId entityViewId, Operation operation) throws ThingsboardException { try { validateId(entityViewId, "Incorrect entityViewId " + entityViewId); 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 c4396dbc7e..fa0cf7ce39 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -30,11 +30,7 @@ import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.async.DeferredResult; -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.EntitySubtype; -import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.*; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.device.DeviceSearchQuery; import org.thingsboard.server.common.data.exception.ThingsboardException; @@ -79,6 +75,19 @@ public class DeviceController extends BaseController { } } + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/device/info/{deviceId}", method = RequestMethod.GET) + @ResponseBody + public DeviceInfo getDeviceInfoById(@PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { + checkParameter(DEVICE_ID, strDeviceId); + try { + DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); + return checkDeviceInfoId(deviceId, Operation.READ); + } catch (Exception e) { + throw handleException(e); + } + } + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/device", method = RequestMethod.POST) @ResponseBody @@ -287,6 +296,29 @@ public class DeviceController extends BaseController { } } + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/tenant/deviceInfos", params = {"pageSize", "page"}, method = RequestMethod.GET) + @ResponseBody + public PageData getTenantDeviceInfos( + @RequestParam int pageSize, + @RequestParam int page, + @RequestParam(required = false) String type, + @RequestParam(required = false) String textSearch, + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder) throws ThingsboardException { + try { + TenantId tenantId = getCurrentUser().getTenantId(); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + if (type != null && type.trim().length() > 0) { + return checkNotNull(deviceService.findDeviceInfosByTenantIdAndType(tenantId, type, pageLink)); + } else { + return checkNotNull(deviceService.findDeviceInfosByTenantId(tenantId, pageLink)); + } + } catch (Exception e) { + throw handleException(e); + } + } + @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/tenant/devices", params = {"deviceName"}, method = RequestMethod.GET) @ResponseBody @@ -327,6 +359,33 @@ public class DeviceController extends BaseController { } } + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/customer/{customerId}/deviceInfos", params = {"pageSize", "page"}, method = RequestMethod.GET) + @ResponseBody + public PageData getCustomerDeviceInfos( + @PathVariable("customerId") String strCustomerId, + @RequestParam int pageSize, + @RequestParam int page, + @RequestParam(required = false) String type, + @RequestParam(required = false) String textSearch, + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder) throws ThingsboardException { + checkParameter("customerId", strCustomerId); + try { + TenantId tenantId = getCurrentUser().getTenantId(); + CustomerId customerId = new CustomerId(toUUID(strCustomerId)); + checkCustomerId(customerId, Operation.READ); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + if (type != null && type.trim().length() > 0) { + return checkNotNull(deviceService.findDeviceInfosByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink)); + } else { + return checkNotNull(deviceService.findDeviceInfosByTenantIdAndCustomerId(tenantId, customerId, pageLink)); + } + } catch (Exception e) { + throw handleException(e); + } + } + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/devices", params = {"deviceIds"}, method = RequestMethod.GET) @ResponseBody 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 f7e34fbc5b..1bea1d8727 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 @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.device; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceInfo; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.device.DeviceSearchQuery; import org.thingsboard.server.common.data.id.CustomerId; @@ -28,7 +29,9 @@ import org.thingsboard.server.common.data.page.PageLink; import java.util.List; public interface DeviceService { - + + DeviceInfo findDeviceInfoById(TenantId tenantId, DeviceId deviceId); + Device findDeviceById(TenantId tenantId, DeviceId deviceId); ListenableFuture findDeviceByIdAsync(TenantId tenantId, DeviceId deviceId); @@ -45,16 +48,24 @@ public interface DeviceService { PageData findDevicesByTenantId(TenantId tenantId, PageLink pageLink); + PageData findDeviceInfosByTenantId(TenantId tenantId, PageLink pageLink); + PageData findDevicesByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink); + PageData findDeviceInfosByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink); + ListenableFuture> findDevicesByTenantIdAndIdsAsync(TenantId tenantId, List deviceIds); void deleteDevicesByTenantId(TenantId tenantId); PageData findDevicesByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink); + PageData findDeviceInfosByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink); + PageData findDevicesByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, PageLink pageLink); + PageData findDeviceInfosByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, PageLink pageLink); + ListenableFuture> findDevicesByTenantIdCustomerIdAndIdsAsync(TenantId tenantId, CustomerId customerId, List deviceIds); void unassignCustomerDevices(TenantId tenantId, CustomerId customerId); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceInfo.java new file mode 100644 index 0000000000..759e7db332 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceInfo.java @@ -0,0 +1,40 @@ +/** + * Copyright © 2016-2019 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 lombok.Data; +import org.thingsboard.server.common.data.id.DeviceId; + +@Data +public class DeviceInfo extends Device { + + private String customerTitle; + private boolean customerIsPublic; + + public DeviceInfo() { + super(); + } + + public DeviceInfo(DeviceId deviceId) { + super(deviceId); + } + + public DeviceInfo(Device device, String customerTitle, boolean customerIsPublic) { + super(device); + this.customerTitle = customerTitle; + this.customerIsPublic = customerIsPublic; + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java index 64ac69ac59..ff567b7233 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.device; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceInfo; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; @@ -33,6 +34,15 @@ import java.util.UUID; */ public interface DeviceDao extends Dao { + /** + * Find device info by id. + * + * @param tenantId the tenant id + * @param deviceId the device id + * @return the device info object + */ + DeviceInfo findDeviceInfoById(TenantId tenantId, UUID deviceId); + /** * Save or update device object * @@ -50,6 +60,15 @@ public interface DeviceDao extends Dao { */ PageData findDevicesByTenantId(UUID tenantId, PageLink pageLink); + /** + * Find device infos by tenantId and page link. + * + * @param tenantId the tenantId + * @param pageLink the page link + * @return the list of device info objects + */ + PageData findDeviceInfosByTenantId(UUID tenantId, PageLink pageLink); + /** * Find devices by tenantId, type and page link. * @@ -60,6 +79,16 @@ public interface DeviceDao extends Dao { */ PageData findDevicesByTenantIdAndType(UUID tenantId, String type, PageLink pageLink); + /** + * Find device infos by tenantId, type and page link. + * + * @param tenantId the tenantId + * @param type the type + * @param pageLink the page link + * @return the list of device onfo objects + */ + PageData findDeviceInfosByTenantIdAndType(UUID tenantId, String type, PageLink pageLink); + /** * Find devices by tenantId and devices Ids. * @@ -79,6 +108,16 @@ public interface DeviceDao extends Dao { */ PageData findDevicesByTenantIdAndCustomerId(UUID tenantId, UUID customerId, PageLink pageLink); + /** + * Find device infos by tenantId, customerId and page link. + * + * @param tenantId the tenantId + * @param customerId the customerId + * @param pageLink the page link + * @return the list of device info objects + */ + PageData findDeviceInfosByTenantIdAndCustomerId(UUID tenantId, UUID customerId, PageLink pageLink); + /** * Find devices by tenantId, customerId, type and page link. * @@ -90,6 +129,17 @@ public interface DeviceDao extends Dao { */ PageData findDevicesByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, PageLink pageLink); + /** + * Find device infos by tenantId, customerId, type and page link. + * + * @param tenantId the tenantId + * @param customerId the customerId + * @param type the type + * @param pageLink the page link + * @return the list of device info objects + */ + PageData findDeviceInfosByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, PageLink pageLink); + /** * Find devices by tenantId, customerId and devices Ids. 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 ec6d85e0d2..6dca2a4b04 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 @@ -27,12 +27,7 @@ import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; -import org.thingsboard.server.common.data.Customer; -import org.thingsboard.server.common.data.Device; -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.Tenant; +import org.thingsboard.server.common.data.*; import org.thingsboard.server.common.data.device.DeviceSearchQuery; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; @@ -95,6 +90,13 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe @Autowired private CacheManager cacheManager; + @Override + public DeviceInfo findDeviceInfoById(TenantId tenantId, DeviceId deviceId) { + log.trace("Executing findDeviceInfoById [{}]", deviceId); + validateId(deviceId, INCORRECT_DEVICE_ID + deviceId); + return deviceDao.findDeviceInfoById(tenantId, deviceId.getId()); + } + @Override public Device findDeviceById(TenantId tenantId, DeviceId deviceId) { log.trace("Executing findDeviceById [{}]", deviceId); @@ -187,6 +189,14 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe return deviceDao.findDevicesByTenantId(tenantId.getId(), pageLink); } + @Override + public PageData findDeviceInfosByTenantId(TenantId tenantId, PageLink pageLink) { + log.trace("Executing findDeviceInfosByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + validatePageLink(pageLink); + return deviceDao.findDeviceInfosByTenantId(tenantId.getId(), pageLink); + } + @Override public PageData findDevicesByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink) { log.trace("Executing findDevicesByTenantIdAndType, tenantId [{}], type [{}], pageLink [{}]", tenantId, type, pageLink); @@ -196,6 +206,15 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe return deviceDao.findDevicesByTenantIdAndType(tenantId.getId(), type, pageLink); } + @Override + public PageData findDeviceInfosByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink) { + log.trace("Executing findDeviceInfosByTenantIdAndType, tenantId [{}], type [{}], pageLink [{}]", tenantId, type, pageLink); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + validateString(type, "Incorrect type " + type); + validatePageLink(pageLink); + return deviceDao.findDeviceInfosByTenantIdAndType(tenantId.getId(), type, pageLink); + } + @Override public ListenableFuture> findDevicesByTenantIdAndIdsAsync(TenantId tenantId, List deviceIds) { log.trace("Executing findDevicesByTenantIdAndIdsAsync, tenantId [{}], deviceIds [{}]", tenantId, deviceIds); @@ -221,6 +240,15 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe return deviceDao.findDevicesByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), pageLink); } + @Override + public PageData findDeviceInfosByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink) { + log.trace("Executing findDeviceInfosByTenantIdAndCustomerId, tenantId [{}], customerId [{}], pageLink [{}]", tenantId, customerId, pageLink); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + validateId(customerId, INCORRECT_CUSTOMER_ID + customerId); + validatePageLink(pageLink); + return deviceDao.findDeviceInfosByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), pageLink); + } + @Override public PageData findDevicesByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, PageLink pageLink) { log.trace("Executing findDevicesByTenantIdAndCustomerIdAndType, tenantId [{}], customerId [{}], type [{}], pageLink [{}]", tenantId, customerId, type, pageLink); @@ -231,6 +259,16 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe return deviceDao.findDevicesByTenantIdAndCustomerIdAndType(tenantId.getId(), customerId.getId(), type, pageLink); } + @Override + public PageData findDeviceInfosByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, PageLink pageLink) { + log.trace("Executing findDeviceInfosByTenantIdAndCustomerIdAndType, tenantId [{}], customerId [{}], type [{}], pageLink [{}]", tenantId, customerId, type, pageLink); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + validateId(customerId, INCORRECT_CUSTOMER_ID + customerId); + validateString(type, "Incorrect type " + type); + validatePageLink(pageLink); + return deviceDao.findDeviceInfosByTenantIdAndCustomerIdAndType(tenantId.getId(), customerId.getId(), type, pageLink); + } + @Override public ListenableFuture> findDevicesByTenantIdCustomerIdAndIdsAsync(TenantId tenantId, CustomerId customerId, List deviceIds) { log.trace("Executing findDevicesByTenantIdCustomerIdAndIdsAsync, tenantId [{}], customerId [{}], deviceIds [{}]", tenantId, customerId, deviceIds); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java new file mode 100644 index 0000000000..b023f127de --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java @@ -0,0 +1,122 @@ +/** + * Copyright © 2016-2019 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.model.sql; + +import com.datastax.driver.core.utils.UUIDs; +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.hibernate.annotations.Type; +import org.hibernate.annotations.TypeDef; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.model.SearchTextEntity; +import org.thingsboard.server.dao.util.mapping.JsonStringType; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.MappedSuperclass; + +@Data +@EqualsAndHashCode(callSuper = true) +@TypeDef(name = "json", typeClass = JsonStringType.class) +@MappedSuperclass +public abstract class AbstractDeviceEntity extends BaseSqlEntity implements SearchTextEntity { + + @Column(name = ModelConstants.DEVICE_TENANT_ID_PROPERTY) + private String tenantId; + + @Column(name = ModelConstants.DEVICE_CUSTOMER_ID_PROPERTY) + private String customerId; + + @Column(name = ModelConstants.DEVICE_TYPE_PROPERTY) + private String type; + + @Column(name = ModelConstants.DEVICE_NAME_PROPERTY) + private String name; + + @Column(name = ModelConstants.DEVICE_LABEL_PROPERTY) + private String label; + + @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY) + private String searchText; + + @Type(type = "json") + @Column(name = ModelConstants.DEVICE_ADDITIONAL_INFO_PROPERTY) + private JsonNode additionalInfo; + + public AbstractDeviceEntity() { + super(); + } + + public AbstractDeviceEntity(Device device) { + if (device.getId() != null) { + this.setId(device.getId().getId()); + } + if (device.getTenantId() != null) { + this.tenantId = toString(device.getTenantId().getId()); + } + if (device.getCustomerId() != null) { + this.customerId = toString(device.getCustomerId().getId()); + } + this.name = device.getName(); + this.type = device.getType(); + this.label = device.getLabel(); + this.additionalInfo = device.getAdditionalInfo(); + } + + public AbstractDeviceEntity(DeviceEntity deviceEntity) { + this.setId(deviceEntity.getId());; + this.tenantId = deviceEntity.getTenantId(); + this.customerId = deviceEntity.getCustomerId(); + this.type = deviceEntity.getType(); + this.name = deviceEntity.getName(); + this.label = deviceEntity.getLabel(); + this.searchText = deviceEntity.getSearchText(); + this.additionalInfo = deviceEntity.getAdditionalInfo(); + } + + @Override + public String getSearchTextSource() { + return name; + } + + @Override + public void setSearchText(String searchText) { + this.searchText = searchText; + } + + protected Device toDevice() { + Device device = new Device(new DeviceId(getId())); + device.setCreatedTime(UUIDs.unixTimestamp(getId())); + if (tenantId != null) { + device.setTenantId(new TenantId(toUUID(tenantId))); + } + if (customerId != null) { + device.setCustomerId(new CustomerId(toUUID(customerId))); + } + device.setName(name); + device.setType(type); + device.setLabel(label); + device.setAdditionalInfo(additionalInfo); + return device; + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceEntity.java index 66feed0077..f2b06e091c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceEntity.java @@ -39,74 +39,18 @@ import javax.persistence.Table; @Entity @TypeDef(name = "json", typeClass = JsonStringType.class) @Table(name = ModelConstants.DEVICE_COLUMN_FAMILY_NAME) -public final class DeviceEntity extends BaseSqlEntity implements SearchTextEntity { - - @Column(name = ModelConstants.DEVICE_TENANT_ID_PROPERTY) - private String tenantId; - - @Column(name = ModelConstants.DEVICE_CUSTOMER_ID_PROPERTY) - private String customerId; - - @Column(name = ModelConstants.DEVICE_TYPE_PROPERTY) - private String type; - - @Column(name = ModelConstants.DEVICE_NAME_PROPERTY) - private String name; - - @Column(name = ModelConstants.DEVICE_LABEL_PROPERTY) - private String label; - - @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY) - private String searchText; - - @Type(type = "json") - @Column(name = ModelConstants.DEVICE_ADDITIONAL_INFO_PROPERTY) - private JsonNode additionalInfo; +public final class DeviceEntity extends AbstractDeviceEntity { public DeviceEntity() { super(); } public DeviceEntity(Device device) { - if (device.getId() != null) { - this.setId(device.getId().getId()); - } - if (device.getTenantId() != null) { - this.tenantId = toString(device.getTenantId().getId()); - } - if (device.getCustomerId() != null) { - this.customerId = toString(device.getCustomerId().getId()); - } - this.name = device.getName(); - this.type = device.getType(); - this.label = device.getLabel(); - this.additionalInfo = device.getAdditionalInfo(); - } - - @Override - public String getSearchTextSource() { - return name; - } - - @Override - public void setSearchText(String searchText) { - this.searchText = searchText; + super(device); } @Override public Device toData() { - Device device = new Device(new DeviceId(getId())); - device.setCreatedTime(UUIDs.unixTimestamp(getId())); - if (tenantId != null) { - device.setTenantId(new TenantId(toUUID(tenantId))); - } - if (customerId != null) { - device.setCustomerId(new CustomerId(toUUID(customerId))); - } - device.setName(name); - device.setType(type); - device.setLabel(label); - device.setAdditionalInfo(additionalInfo); - return device; + return super.toDevice(); } -} \ No newline at end of file +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceInfoEntity.java new file mode 100644 index 0000000000..2633f84311 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceInfoEntity.java @@ -0,0 +1,58 @@ +/** + * Copyright © 2016-2019 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.model.sql; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import com.fasterxml.jackson.databind.JsonNode; +import org.thingsboard.server.common.data.DeviceInfo; + +import java.util.HashMap; +import java.util.Map; + +@Data +@EqualsAndHashCode(callSuper = true) +public class DeviceInfoEntity extends AbstractDeviceEntity { + + public static final Map deviceInfoColumnMap = new HashMap<>(); + static { + deviceInfoColumnMap.put("customerTitle", "c.title"); + } + + private String customerTitle; + private boolean customerIsPublic; + + public DeviceInfoEntity() { + super(); + } + + public DeviceInfoEntity(DeviceEntity deviceEntity, + String customerTitle, + Object customerAdditionalInfo) { + super(deviceEntity); + this.customerTitle = customerTitle; + if (customerAdditionalInfo != null && ((JsonNode)customerAdditionalInfo).has("isPublic")) { + this.customerIsPublic = ((JsonNode)customerAdditionalInfo).get("isPublic").asBoolean(); + } else { + this.customerIsPublic = false; + } + } + + @Override + public DeviceInfo toData() { + return new DeviceInfo(super.toDevice(), customerTitle, customerIsPublic); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java index 4021881b88..9747f588cd 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java @@ -22,6 +22,7 @@ import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; import org.thingsboard.server.dao.model.sql.DeviceEntity; +import org.thingsboard.server.dao.model.sql.DeviceInfoEntity; import org.thingsboard.server.dao.util.SqlDao; import java.util.List; @@ -32,6 +33,11 @@ import java.util.List; @SqlDao public interface DeviceRepository extends PagingAndSortingRepository { + @Query("SELECT new org.thingsboard.server.dao.model.sql.DeviceInfoEntity(d, c.title, c.additionalInfo) " + + "FROM DeviceEntity d " + + "LEFT JOIN CustomerEntity c on c.id = d.customerId " + + "WHERE d.id = :deviceId") + DeviceInfoEntity findDeviceInfoById(@Param("deviceId") String deviceId); @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + "AND d.customerId = :customerId " + @@ -41,12 +47,32 @@ public interface DeviceRepository extends PagingAndSortingRepository findDeviceInfosByTenantIdAndCustomerId(@Param("tenantId") String tenantId, + @Param("customerId") String customerId, + @Param("searchText") String searchText, + Pageable pageable); + @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + "AND LOWER(d.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))") Page findByTenantId(@Param("tenantId") String tenantId, @Param("textSearch") String textSearch, Pageable pageable); + @Query("SELECT new org.thingsboard.server.dao.model.sql.DeviceInfoEntity(d, c.title, c.additionalInfo) " + + "FROM DeviceEntity d " + + "LEFT JOIN CustomerEntity c on c.id = d.customerId " + + "WHERE d.tenantId = :tenantId " + + "AND LOWER(d.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))") + Page findDeviceInfosByTenantId(@Param("tenantId") String tenantId, + @Param("textSearch") String textSearch, + Pageable pageable); + @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + "AND d.type = :type " + "AND LOWER(d.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))") @@ -55,6 +81,17 @@ public interface DeviceRepository extends PagingAndSortingRepository findDeviceInfosByTenantIdAndType(@Param("tenantId") String tenantId, + @Param("type") String type, + @Param("textSearch") String textSearch, + Pageable pageable); + @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + "AND d.customerId = :customerId " + "AND d.type = :type " + @@ -65,6 +102,19 @@ public interface DeviceRepository extends PagingAndSortingRepository findDeviceInfosByTenantIdAndCustomerIdAndType(@Param("tenantId") String tenantId, + @Param("customerId") String customerId, + @Param("type") String type, + @Param("textSearch") String textSearch, + Pageable pageable); + @Query("SELECT DISTINCT d.type FROM DeviceEntity d WHERE d.tenantId = :tenantId") List findTenantDeviceTypes(@Param("tenantId") String tenantId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java index cc195eb3d2..2dee5d6185 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java @@ -20,16 +20,14 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.EntitySubtype; -import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.UUIDConverter; +import org.thingsboard.server.common.data.*; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.device.DeviceDao; import org.thingsboard.server.dao.model.sql.DeviceEntity; +import org.thingsboard.server.dao.model.sql.DeviceInfoEntity; import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; import org.thingsboard.server.dao.util.SqlDao; @@ -64,6 +62,11 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao return deviceRepository; } + @Override + public DeviceInfo findDeviceInfoById(TenantId tenantId, UUID deviceId) { + return DaoUtil.getData(deviceRepository.findDeviceInfoById(fromTimeUUID(deviceId))); + } + @Override public PageData findDevicesByTenantId(UUID tenantId, PageLink pageLink) { return DaoUtil.toPageData( @@ -73,6 +76,15 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao DaoUtil.toPageable(pageLink))); } + @Override + public PageData findDeviceInfosByTenantId(UUID tenantId, PageLink pageLink) { + return DaoUtil.toPageData( + deviceRepository.findDeviceInfosByTenantId( + fromTimeUUID(tenantId), + Objects.toString(pageLink.getTextSearch(), ""), + DaoUtil.toPageable(pageLink, DeviceInfoEntity.deviceInfoColumnMap))); + } + @Override public ListenableFuture> findDevicesByTenantIdAndIdsAsync(UUID tenantId, List deviceIds) { return service.submit(() -> DaoUtil.convertDataList(deviceRepository.findDevicesByTenantIdAndIdIn(UUIDConverter.fromTimeUUID(tenantId), fromTimeUUIDs(deviceIds)))); @@ -88,6 +100,16 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao DaoUtil.toPageable(pageLink))); } + @Override + public PageData findDeviceInfosByTenantIdAndCustomerId(UUID tenantId, UUID customerId, PageLink pageLink) { + return DaoUtil.toPageData( + deviceRepository.findDeviceInfosByTenantIdAndCustomerId( + fromTimeUUID(tenantId), + fromTimeUUID(customerId), + Objects.toString(pageLink.getTextSearch(), ""), + DaoUtil.toPageable(pageLink, DeviceInfoEntity.deviceInfoColumnMap))); + } + @Override public ListenableFuture> findDevicesByTenantIdCustomerIdAndIdsAsync(UUID tenantId, UUID customerId, List deviceIds) { return service.submit(() -> DaoUtil.convertDataList( @@ -110,6 +132,16 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao DaoUtil.toPageable(pageLink))); } + @Override + public PageData findDeviceInfosByTenantIdAndType(UUID tenantId, String type, PageLink pageLink) { + return DaoUtil.toPageData( + deviceRepository.findDeviceInfosByTenantIdAndType( + fromTimeUUID(tenantId), + type, + Objects.toString(pageLink.getTextSearch(), ""), + DaoUtil.toPageable(pageLink, DeviceInfoEntity.deviceInfoColumnMap))); + } + @Override public PageData findDevicesByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, PageLink pageLink) { return DaoUtil.toPageData( @@ -121,6 +153,17 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao DaoUtil.toPageable(pageLink))); } + @Override + public PageData findDeviceInfosByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, PageLink pageLink) { + return DaoUtil.toPageData( + deviceRepository.findDeviceInfosByTenantIdAndCustomerIdAndType( + fromTimeUUID(tenantId), + fromTimeUUID(customerId), + type, + Objects.toString(pageLink.getTextSearch(), ""), + DaoUtil.toPageable(pageLink, DeviceInfoEntity.deviceInfoColumnMap))); + } + @Override public ListenableFuture> findTenantDeviceTypesAsync(UUID tenantId) { return service.submit(() -> convertTenantDeviceTypesToDto(tenantId, deviceRepository.findTenantDeviceTypes(fromTimeUUID(tenantId)))); diff --git a/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java b/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java index 6dee664716..bde567bd7e 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java +++ b/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java @@ -24,7 +24,7 @@ import java.util.Arrays; @RunWith(ClasspathSuite.class) @ClassnameFilters({ - "org.thingsboard.server.dao.service.*ServiceSqlTest" + "org.thingsboard.server.dao.service.*DeviceServiceSqlTest" }) public class SqlDaoServiceTestSuite { diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java index f4f910d01e..0f62c4ef68 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java @@ -21,10 +21,7 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import org.thingsboard.server.common.data.Customer; -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.EntitySubtype; -import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.*; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; @@ -264,7 +261,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { @Test public void testFindDevicesByTenantIdAndName() { String title1 = "Device title 1"; - List devicesTitle1 = new ArrayList<>(); + List devicesTitle1 = new ArrayList<>(); for (int i=0;i<143;i++) { Device device = new Device(); device.setTenantId(tenantId); @@ -273,10 +270,10 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); device.setName(name); device.setType("default"); - devicesTitle1.add(deviceService.saveDevice(device)); + devicesTitle1.add(new DeviceInfo(deviceService.saveDevice(device), null, false)); } String title2 = "Device title 2"; - List devicesTitle2 = new ArrayList<>(); + List devicesTitle2 = new ArrayList<>(); for (int i=0;i<175;i++) { Device device = new Device(); device.setTenantId(tenantId); @@ -285,14 +282,14 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); device.setName(name); device.setType("default"); - devicesTitle2.add(deviceService.saveDevice(device)); + devicesTitle2.add(new DeviceInfo(deviceService.saveDevice(device), null, false)); } - List loadedDevicesTitle1 = new ArrayList<>(); + List loadedDevicesTitle1 = new ArrayList<>(); PageLink pageLink = new PageLink(15, 0, title1); - PageData pageData = null; + PageData pageData = null; do { - pageData = deviceService.findDevicesByTenantId(tenantId, pageLink); + pageData = deviceService.findDeviceInfosByTenantId(tenantId, pageLink); loadedDevicesTitle1.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); @@ -304,10 +301,10 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { Assert.assertEquals(devicesTitle1, loadedDevicesTitle1); - List loadedDevicesTitle2 = new ArrayList<>(); + List loadedDevicesTitle2 = new ArrayList<>(); pageLink = new PageLink(4, 0, title2); do { - pageData = deviceService.findDevicesByTenantId(tenantId, pageLink); + pageData = deviceService.findDeviceInfosByTenantId(tenantId, pageLink); loadedDevicesTitle2.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); @@ -324,7 +321,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { } pageLink = new PageLink(4, 0, title1); - pageData = deviceService.findDevicesByTenantId(tenantId, pageLink); + pageData = deviceService.findDeviceInfosByTenantId(tenantId, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -333,7 +330,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { } pageLink = new PageLink(4, 0, title2); - pageData = deviceService.findDevicesByTenantId(tenantId, pageLink); + pageData = deviceService.findDeviceInfosByTenantId(tenantId, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); } @@ -431,21 +428,21 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { customer = customerService.saveCustomer(customer); CustomerId customerId = customer.getId(); - List devices = new ArrayList<>(); + List devices = new ArrayList<>(); for (int i=0;i<278;i++) { Device device = new Device(); device.setTenantId(tenantId); device.setName("Device"+i); device.setType("default"); device = deviceService.saveDevice(device); - devices.add(deviceService.assignDeviceToCustomer(tenantId, device.getId(), customerId)); + devices.add(new DeviceInfo(deviceService.assignDeviceToCustomer(tenantId, device.getId(), customerId), customer.getTitle(), customer.isPublic())); } - List loadedDevices = new ArrayList<>(); + List loadedDevices = new ArrayList<>(); PageLink pageLink = new PageLink(23); - PageData pageData = null; + PageData pageData = null; do { - pageData = deviceService.findDevicesByTenantIdAndCustomerId(tenantId, customerId, pageLink); + pageData = deviceService.findDeviceInfosByTenantIdAndCustomerId(tenantId, customerId, pageLink); loadedDevices.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); @@ -460,7 +457,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { deviceService.unassignCustomerDevices(tenantId, customerId); pageLink = new PageLink(33); - pageData = deviceService.findDevicesByTenantIdAndCustomerId(tenantId, customerId, pageLink); + pageData = deviceService.findDeviceInfosByTenantIdAndCustomerId(tenantId, customerId, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertTrue(pageData.getData().isEmpty()); diff --git a/ui-ngx/src/app/core/http/device.service.ts b/ui-ngx/src/app/core/http/device.service.ts new file mode 100644 index 0000000000..19c34e5881 --- /dev/null +++ b/ui-ngx/src/app/core/http/device.service.ts @@ -0,0 +1,74 @@ +/// +/// Copyright © 2016-2019 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 { Injectable } from '@angular/core'; +import { defaultHttpOptions } from './http-utils'; +import { Observable } from 'rxjs/index'; +import { HttpClient } from '@angular/common/http'; +import { PageLink } from '@shared/models/page/page-link'; +import { PageData } from '@shared/models/page/page-data'; +import { Tenant } from '@shared/models/tenant.model'; +import {DashboardInfo, Dashboard} from '@shared/models/dashboard.models'; +import {map} from 'rxjs/operators'; +import {DeviceInfo, Device} from '@app/shared/models/device.models'; +import {EntitySubtype} from '@app/shared/models/entity-type.models'; + +@Injectable({ + providedIn: 'root' +}) +export class DeviceService { + + constructor( + private http: HttpClient + ) { } + + public getTenantDeviceInfos(pageLink: PageLink, type: string = '', ignoreErrors: boolean = false, + ignoreLoading: boolean = false): Observable> { + return this.http.get>(`/api/tenant/deviceInfos${pageLink.toQuery()}&type=${type}`, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public getCustomerDeviceInfos(customerId: string, pageLink: PageLink, type: string = '', ignoreErrors: boolean = false, + ignoreLoading: boolean = false): Observable> { + return this.http.get>(`/api/customer/${customerId}/deviceInfos${pageLink.toQuery()}&type=${type}`, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public getDevice(deviceId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.get(`/api/device/${deviceId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public getDeviceInfo(deviceId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.get(`/api/device/info/${deviceId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public saveDevice(device: Device, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.post('/api/device', device, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public deleteDevice(deviceId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { + return this.http.delete(`/api/device/${deviceId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public getDeviceTypes(ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + return this.http.get>('/api/device/types', defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public unassignDeviceFromCustomer(deviceId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { + return this.http.delete(`/api/customer/device/${deviceId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + +} diff --git a/ui-ngx/src/app/core/services/broadcast.models.ts b/ui-ngx/src/app/core/services/broadcast.models.ts new file mode 100644 index 0000000000..7b83dd397b --- /dev/null +++ b/ui-ngx/src/app/core/services/broadcast.models.ts @@ -0,0 +1,27 @@ +/// +/// Copyright © 2016-2019 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 interface BroadcastMessage { + name: string; + args?: Array; +} + +export interface BroadcastEvent { + name: string; +} + +export type BroadcastListener = (event: BroadcastEvent, ...args: Array) => void; diff --git a/ui-ngx/src/app/core/services/broadcast.service.ts b/ui-ngx/src/app/core/services/broadcast.service.ts new file mode 100644 index 0000000000..7461ce3d3d --- /dev/null +++ b/ui-ngx/src/app/core/services/broadcast.service.ts @@ -0,0 +1,51 @@ +/// +/// Copyright © 2016-2019 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 {Injectable} from '@angular/core'; +import {Subject, Subscription} from 'rxjs'; +import {NotificationMessage} from '@core/notification/notification.models'; +import {BroadcastEvent, BroadcastListener, BroadcastMessage} from '@core/services/broadcast.models'; +import {filter} from 'rxjs/operators'; + +@Injectable({ + providedIn: 'root' +}) +export class BroadcastService { + + private broadcastSubject: Subject = new Subject(); + + broadcast(name: string, ...args: Array) { + const message = { + name, + args + } as BroadcastMessage; + this.broadcastSubject.next(message); + } + + on(name: string, listener: BroadcastListener): Subscription { + return this.broadcastSubject.asObservable().pipe( + filter((message) => message.name === name) + ).subscribe( + (message) => { + const event = { + name: message.name + } as BroadcastEvent; + listener(event, message.args); + } + ); + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/device/device-routing.module.ts b/ui-ngx/src/app/modules/home/pages/device/device-routing.module.ts new file mode 100644 index 0000000000..78ccb6370e --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/device/device-routing.module.ts @@ -0,0 +1,50 @@ +/// +/// Copyright © 2016-2019 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 {EntitiesTableComponent} from '@shared/components/entity/entities-table.component'; +import {Authority} from '@shared/models/authority.enum'; +import {DevicesTableConfigResolver} from '@modules/home/pages/device/devices-table-config.resolver'; + +const routes: Routes = [ + { + path: 'devices', + component: EntitiesTableComponent, + data: { + auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], + title: 'device.devices', + devicesType: 'tenant', + breadcrumb: { + label: 'device.devices', + icon: 'devices_other' + } + }, + resolve: { + entitiesTableConfig: DevicesTableConfigResolver + } + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], + providers: [ + DevicesTableConfigResolver + ] +}) +export class DeviceRoutingModule { } diff --git a/ui-ngx/src/app/modules/home/pages/device/device-table-header.component.html b/ui-ngx/src/app/modules/home/pages/device/device-table-header.component.html new file mode 100644 index 0000000000..f4088933bc --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/device/device-table-header.component.html @@ -0,0 +1,23 @@ + + + diff --git a/ui-ngx/src/app/modules/home/pages/device/device-table-header.component.scss b/ui-ngx/src/app/modules/home/pages/device/device-table-header.component.scss new file mode 100644 index 0000000000..cb7fe8d04b --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/device/device-table-header.component.scss @@ -0,0 +1,36 @@ +/** + * Copyright © 2016-2019 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 { + flex: 1; + display: flex; + justify-content: flex-start; +} + +:host ::ng-deep { + tb-entity-subtype-select { + mat-form-field { + font-size: 16px; + + .mat-form-field-wrapper { + padding-bottom: 0; + } + + .mat-form-field-underline { + bottom: 0; + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/device/device-table-header.component.ts b/ui-ngx/src/app/modules/home/pages/device/device-table-header.component.ts new file mode 100644 index 0000000000..4a89ebc60c --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/device/device-table-header.component.ts @@ -0,0 +1,42 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { EntityTableHeaderComponent } from '@shared/components/entity/entity-table-header.component'; +import {DeviceInfo} from '@app/shared/models/device.models'; +import {EntityType} from '@shared/models/entity-type.models'; + +@Component({ + selector: 'tb-device-table-header', + templateUrl: './device-table-header.component.html', + styleUrls: ['./device-table-header.component.scss'] +}) +export class DeviceTableHeaderComponent extends EntityTableHeaderComponent { + + entityType = EntityType; + + constructor(protected store: Store) { + super(store); + } + + deviceTypeChanged(deviceType: string) { + this.entitiesTableConfig.componentsData.deviceType = deviceType; + this.entitiesTableConfig.table.updateData(); + } + +} 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 new file mode 100644 index 0000000000..879f1d2757 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/device/device.component.html @@ -0,0 +1,107 @@ + +
+ + + + + +
+ + +
+
+
+ + device.assignedToCustomer + + +
+ {{ 'device.device-public' | translate }} +
+
+
+ + device.name + + + {{ 'device.name-required' | translate }} + + + + + + device.label + + +
+ + {{ 'device.is-gateway' | translate }} + + + device.description + + +
+
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/device/device.component.scss b/ui-ngx/src/app/modules/home/pages/device/device.component.scss new file mode 100644 index 0000000000..d18a4874d0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/device/device.component.scss @@ -0,0 +1,19 @@ +/** + * Copyright © 2016-2019 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 { + +} diff --git a/ui-ngx/src/app/modules/home/pages/device/device.component.ts b/ui-ngx/src/app/modules/home/pages/device/device.component.ts new file mode 100644 index 0000000000..3d287c5c62 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/device/device.component.ts @@ -0,0 +1,88 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, OnInit } from '@angular/core'; +import { select, Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { EntityComponent } from '@shared/components/entity/entity.component'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { User } from '@shared/models/user.model'; +import { selectAuth, selectUserDetails } from '@core/auth/auth.selectors'; +import { map } from 'rxjs/operators'; +import { Authority } from '@shared/models/authority.enum'; +import {DeviceInfo} from '@shared/models/device.models'; +import {EntityType} from '@shared/models/entity-type.models'; +import {NULL_UUID} from '@shared/models/id/has-uuid'; + +@Component({ + selector: 'tb-device', + templateUrl: './device.component.html', + styleUrls: ['./device.component.scss'] +}) +export class DeviceComponent extends EntityComponent { + + entityType = EntityType; + + deviceScope: 'tenant' | 'customer' | 'customer_user'; + + constructor(protected store: Store, + public fb: FormBuilder) { + super(store); + } + + ngOnInit() { + this.deviceScope = this.entitiesTableConfig.componentsData.deviceScope; + super.ngOnInit(); + } + + hideDelete() { + if (this.entitiesTableConfig) { + return !this.entitiesTableConfig.deleteEnabled(this.entity); + } else { + return false; + } + } + + isAssignedToCustomer(entity: DeviceInfo): boolean { + return entity && entity.customerId && entity.customerId.id !== NULL_UUID; + } + + buildForm(entity: DeviceInfo): FormGroup { + return this.fb.group( + { + name: [entity ? entity.name : '', [Validators.required]], + type: [entity ? entity.type : null, [Validators.required]], + label: [entity ? entity.label : ''], + additionalInfo: this.fb.group( + { + gateway: [entity && entity.additionalInfo ? entity.additionalInfo.gateway : false], + description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''], + } + ) + } + ); + } + + updateForm(entity: DeviceInfo) { + this.entityForm.patchValue({name: entity.name}); + this.entityForm.patchValue({type: entity.type}); + this.entityForm.patchValue({label: entity.label}); + this.entityForm.patchValue({additionalInfo: + {gateway: entity.additionalInfo ? entity.additionalInfo.gateway : false}}); + this.entityForm.patchValue({additionalInfo: {description: entity.additionalInfo ? entity.additionalInfo.description : ''}}); + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/device/device.module.ts b/ui-ngx/src/app/modules/home/pages/device/device.module.ts new file mode 100644 index 0000000000..de4f34eeef --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/device/device.module.ts @@ -0,0 +1,39 @@ +/// +/// Copyright © 2016-2019 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 { CommonModule } from '@angular/common'; +import { SharedModule } from '@shared/shared.module'; +import {DeviceComponent} from '@modules/home/pages/device/device.component'; +import {DeviceRoutingModule} from './device-routing.module'; +import {DeviceTableHeaderComponent} from '@modules/home/pages/device/device-table-header.component'; + +@NgModule({ + entryComponents: [ + DeviceComponent, + DeviceTableHeaderComponent + ], + declarations: [ + DeviceComponent, + DeviceTableHeaderComponent + ], + imports: [ + CommonModule, + SharedModule, + DeviceRoutingModule + ] +}) +export class DeviceModule { } 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 new file mode 100644 index 0000000000..27e5c86a84 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts @@ -0,0 +1,196 @@ +/// +/// Copyright © 2016-2019 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 { Injectable } from '@angular/core'; + +import {ActivatedRouteSnapshot, Resolve, Router} from '@angular/router'; + +import { Tenant } from '@shared/models/tenant.model'; +import { + CellActionDescriptor, + checkBoxCell, + DateEntityTableColumn, + EntityTableColumn, + EntityTableConfig +} from '@shared/components/entity/entities-table-config.models'; +import { TenantService } from '@core/http/tenant.service'; +import { TranslateService } from '@ngx-translate/core'; +import { DatePipe } from '@angular/common'; +import { + EntityType, + entityTypeResources, + entityTypeTranslations +} from '@shared/models/entity-type.models'; +import { TenantComponent } from '@modules/home/pages/tenant/tenant.component'; +import { EntityAction } from '@shared/components/entity/entity-component.models'; +import { User } from '@shared/models/user.model'; +import {Device, DeviceInfo} from '@app/shared/models/device.models'; +import {DeviceComponent} from '@modules/home/pages/device/device.component'; +import {Observable, of} from 'rxjs'; +import {select, Store} from '@ngrx/store'; +import {selectAuth, selectAuthUser} from '@core/auth/auth.selectors'; +import {map, mergeMap, take, tap} from 'rxjs/operators'; +import {AppState} from '@core/core.state'; +import {DeviceService} from '@app/core/http/device.service'; +import {Authority} from '@app/shared/models/authority.enum'; +import {CustomerService} from '@core/http/customer.service'; +import {Customer} from '@app/shared/models/customer.model'; +import {NULL_UUID} from '@shared/models/id/has-uuid'; +import {BroadcastService} from '@core/services/broadcast.service'; +import {DeviceTableHeaderComponent} from '@modules/home/pages/device/device-table-header.component'; + +@Injectable() +export class DevicesTableConfigResolver implements Resolve> { + + private readonly config: EntityTableConfig = new EntityTableConfig(); + + private customerId: string; + + constructor(private store: Store, + private broadcast: BroadcastService, + private deviceService: DeviceService, + private customerService: CustomerService, + private translate: TranslateService, + private datePipe: DatePipe, + private router: Router) { + + this.config.entityType = EntityType.CUSTOMER; + this.config.entityComponent = DeviceComponent; + this.config.entityTranslations = entityTypeTranslations.get(EntityType.DEVICE); + this.config.entityResources = entityTypeResources.get(EntityType.DEVICE); + + this.config.deleteEntityTitle = device => this.translate.instant('device.delete-device-title', { deviceName: device.name }); + this.config.deleteEntityContent = () => this.translate.instant('device.delete-device-text'); + this.config.deleteEntitiesTitle = count => this.translate.instant('device.delete-devices-title', {count}); + this.config.deleteEntitiesContent = () => this.translate.instant('device.delete-devices-text'); + + this.config.loadEntity = id => this.deviceService.getDeviceInfo(id.id); + this.config.saveEntity = device => { + return this.deviceService.saveDevice(device).pipe( + tap(() => { + this.broadcast.broadcast('deviceSaved'); + }), + mergeMap((savedDevice) => this.deviceService.getDeviceInfo(savedDevice.id.id) + )); + }; + this.config.onEntityAction = action => this.onDeviceAction(action); + + this.config.headerComponent = DeviceTableHeaderComponent; + + } + + resolve(route: ActivatedRouteSnapshot): Observable> { + const routeParams = route.params; + this.config.componentsData = { + deviceScope: route.data.devicesType, + deviceType: '' + }; + this.customerId = routeParams.customerId; + return this.store.pipe(select(selectAuthUser), take(1)).pipe( + tap((authUser) => { + if (authUser.authority === Authority.CUSTOMER_USER) { + this.config.componentsData.deviceScope = 'customer_user'; + this.customerId = authUser.customerId; + } + }), + mergeMap(() => + this.customerId ? this.customerService.getCustomer(this.customerId) : of(null as Customer) + ), + map((parentCustomer) => { + if (parentCustomer) { + if (parentCustomer.additionalInfo && parentCustomer.additionalInfo.isPublic) { + this.config.tableTitle = this.translate.instant('customer.public-devices'); + } else { + this.config.tableTitle = parentCustomer.title + ': ' + this.translate.instant('device.devices'); + } + } else { + this.config.tableTitle = this.translate.instant('device.devices'); + } + this.config.columns = this.configureColumns(this.config.componentsData.deviceScope); + this.configureEntityFunctions(this.config.componentsData.deviceScope); + this.config.cellActionDescriptors = this.configureCellActions(this.config.componentsData.deviceScope); + return this.config; + }) + ); + } + + configureColumns(deviceScope: string): Array> { + const columns: Array> = [ + new DateEntityTableColumn('createdTime', 'device.created-time', this.datePipe, '150px'), + new EntityTableColumn('name', 'device.name'), + new EntityTableColumn('type', 'device.device-type'), + new EntityTableColumn('label', 'device.label') + ]; + if (deviceScope === 'tenant') { + columns.push( + new EntityTableColumn('customerTitle', 'customer.customer'), + new EntityTableColumn('customerIsPublic', 'device.public', '60px', + entity => { + return checkBoxCell(entity.customerIsPublic); + }, () => ({}), false), + ); + } + columns.push( + new EntityTableColumn('gateway', 'device.is-gateway', '60px', + entity => { + return checkBoxCell(entity.additionalInfo && entity.additionalInfo.gateway); + }, () => ({}), false) + ); + return columns; + } + + configureEntityFunctions(deviceScope: string): void { + if (deviceScope === 'tenant') { + this.config.entitiesFetchFunction = pageLink => this.deviceService.getTenantDeviceInfos(pageLink, this.config.componentsData.deviceType); + this.config.deleteEntity = id => this.deviceService.deleteDevice(id.id); + } else { + this.config.entitiesFetchFunction = pageLink => this.deviceService.getCustomerDeviceInfos(this.customerId, pageLink, this.config.componentsData.deviceType); + this.config.deleteEntity = id => this.deviceService.unassignDeviceFromCustomer(id.id); + } + } + + configureCellActions(deviceScope: string): Array> { + const actions: Array> = []; + if (deviceScope === 'tenant') { + actions.push( + { + name: this.translate.instant('device.make-public'), + icon: 'share', + isEnabled: (entity) => (!entity.customerId || entity.customerId.id === NULL_UUID), + onAction: ($event, entity) => this.makePublic($event, entity) + } + ); + } + return actions; + } + + makePublic($event: Event, device: Device) { + if ($event) { + $event.stopPropagation(); + } + // TODO: + } + + onDeviceAction(action: EntityAction): boolean { + switch (action.action) { + case 'makePublic': + this.makePublic(action.event, action.entity); + return true; + } + return false; + } + +} 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 08cf06cf2d..b7fe5c611d 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 @@ -23,6 +23,7 @@ import { TenantModule } from '@modules/home/pages/tenant/tenant.module'; // import { CustomerModule } from '@modules/home/pages/customer/customer.module'; // import { AuditLogModule } from '@modules/home/pages/audit-log/audit-log.module'; import { UserModule } from '@modules/home/pages/user/user.module'; +import {DeviceModule} from '@modules/home/pages/device/device.module'; @NgModule({ exports: [ @@ -30,6 +31,7 @@ import { UserModule } from '@modules/home/pages/user/user.module'; HomeLinksModule, ProfileModule, TenantModule, + DeviceModule, // CustomerModule, // AuditLogModule, UserModule diff --git a/ui-ngx/src/app/shared/components/dashboard-autocomplete.component.ts b/ui-ngx/src/app/shared/components/dashboard-autocomplete.component.ts index ad2ba9b4a6..41067c10e4 100644 --- a/ui-ngx/src/app/shared/components/dashboard-autocomplete.component.ts +++ b/ui-ngx/src/app/shared/components/dashboard-autocomplete.component.ts @@ -74,8 +74,6 @@ export class DashboardAutocompleteComponent implements ControlValueAccessor, OnI filteredDashboards: Observable>; - private valueLoaded = false; - private searchText = ''; private propagateChange = (v: any) => { }; @@ -97,7 +95,21 @@ export class DashboardAutocompleteComponent implements ControlValueAccessor, OnI } ngOnInit() { - + this.filteredDashboards = this.selectDashboardFormGroup.get('dashboard').valueChanges + .pipe( + tap(value => { + let modelValue; + if (typeof value === 'string' || !value) { + modelValue = null; + } else { + modelValue = this.useIdValue ? value.id.id : value; + } + this.updateView(modelValue); + }), + startWith(''), + map(value => value ? (typeof value === 'string' ? value : value.name) : ''), + mergeMap(name => this.fetchDashboards(name) ) + ); } ngAfterViewInit(): void { @@ -123,48 +135,23 @@ export class DashboardAutocompleteComponent implements ControlValueAccessor, OnI this.disabled = isDisabled; } - initFilteredResults(): void { - this.filteredDashboards = this.selectDashboardFormGroup.get('dashboard').valueChanges - .pipe( - startWith(''), - tap(value => { - if (this.valueLoaded) { - let modelValue; - if (typeof value === 'string' || !value) { - modelValue = null; - } else { - modelValue = this.useIdValue ? value.id.id : value; - } - this.updateView(modelValue); - } - }), - map(value => value ? (typeof value === 'string' ? value : value.name) : ''), - mergeMap(name => this.fetchDashboards(name) ) - ); - } - writeValue(value: DashboardInfo | string | null): void { - this.valueLoaded = false; this.searchText = ''; - this.initFilteredResults(); if (value != null) { if (typeof value === 'string') { this.dashboardService.getDashboardInfo(value).subscribe( (dashboard) => { this.modelValue = this.useIdValue ? dashboard.id.id : dashboard; this.selectDashboardFormGroup.get('dashboard').patchValue(dashboard, {emitEvent: true}); - this.valueLoaded = true; } ); } else { this.modelValue = this.useIdValue ? value.id.id : value; - this.selectDashboardFormGroup.get('dashboard').patchValue(value, {emitEvent: false}); - this.valueLoaded = true; + this.selectDashboardFormGroup.get('dashboard').patchValue(value, {emitEvent: true}); } } else { this.modelValue = null; - this.selectDashboardFormGroup.get('dashboard').patchValue(null, {emitEvent: false}); - this.valueLoaded = true; + this.selectDashboardFormGroup.get('dashboard').patchValue(null, {emitEvent: true}); } } diff --git a/ui-ngx/src/app/shared/components/entity/entities-table-config.models.ts b/ui-ngx/src/app/shared/components/entity/entities-table-config.models.ts index 5a6c9d26a6..28cfcd6a18 100644 --- a/ui-ngx/src/app/shared/components/entity/entities-table-config.models.ts +++ b/ui-ngx/src/app/shared/components/entity/entities-table-config.models.ts @@ -76,7 +76,8 @@ export class EntityTableColumn> { public title: string, public maxWidth: string = '100%', public cellContentFunction: CellContentFunction = (entity, property) => entity[property], - public cellStyleFunction: CellStyleFunction = () => ({})) { + public cellStyleFunction: CellStyleFunction = () => ({}), + public sortable: boolean = true) { } } @@ -135,3 +136,7 @@ export class EntityTableConfig, P extends PageLink = P entitiesFetchFunction: EntitiesFetchFunction = () => of(emptyPageData()); onEntityAction: EntityActionFunction = () => false; } + +export function checkBoxCell(value: boolean): string { + return `${value ? 'check_box' : 'check_box_outline_blank'}`; +} diff --git a/ui-ngx/src/app/shared/components/entity/entities-table.component.html b/ui-ngx/src/app/shared/components/entity/entities-table.component.html index d5d0d95d51..6cbcd63490 100644 --- a/ui-ngx/src/app/shared/components/entity/entities-table.component.html +++ b/ui-ngx/src/app/shared/components/entity/entities-table.component.html @@ -120,7 +120,7 @@ - {{ column.title | translate }} + {{ column.title | translate }} diff --git a/ui-ngx/src/app/shared/components/entity/entities-table.component.ts b/ui-ngx/src/app/shared/components/entity/entities-table.component.ts index 0fa6248581..a415d54ee8 100644 --- a/ui-ngx/src/app/shared/components/entity/entities-table.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entities-table.component.ts @@ -82,7 +82,7 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn isDetailsOpen = false; - @ViewChild('entityTableHeader', {static: false}) entityTableHeaderAnchor: TbAnchorComponent; + @ViewChild('entityTableHeader', {static: true}) entityTableHeaderAnchor: TbAnchorComponent; @ViewChild('searchInput', {static: false}) searchInputField: ElementRef; diff --git a/ui-ngx/src/app/shared/components/entity/entity-subtype-autocomplete.component.html b/ui-ngx/src/app/shared/components/entity/entity-subtype-autocomplete.component.html new file mode 100644 index 0000000000..692460d940 --- /dev/null +++ b/ui-ngx/src/app/shared/components/entity/entity-subtype-autocomplete.component.html @@ -0,0 +1,39 @@ + + + {{ entitySubtypeText | translate }} + + + + + + + + + {{ entitySubtypeRequiredText | translate }} + + diff --git a/ui-ngx/src/app/shared/components/entity/entity-subtype-autocomplete.component.ts b/ui-ngx/src/app/shared/components/entity/entity-subtype-autocomplete.component.ts new file mode 100644 index 0000000000..34044a45b2 --- /dev/null +++ b/ui-ngx/src/app/shared/components/entity/entity-subtype-autocomplete.component.ts @@ -0,0 +1,228 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import {AfterViewInit, Component, ElementRef, forwardRef, Input, OnInit, ViewChild, OnDestroy} from '@angular/core'; +import {ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR} from '@angular/forms'; +import {Observable, of, throwError, Subscription} from 'rxjs'; +import {PageLink} from '@shared/models/page/page-link'; +import {Direction} from '@shared/models/page/sort-order'; +import {filter, map, mergeMap, publishReplay, refCount, startWith, tap, publish} from 'rxjs/operators'; +import {PageData, emptyPageData} from '@shared/models/page/page-data'; +import {DashboardInfo} from '@app/shared/models/dashboard.models'; +import {DashboardId} from '@app/shared/models/id/dashboard-id'; +import {DashboardService} from '@core/http/dashboard.service'; +import {Store} from '@ngrx/store'; +import {AppState} from '@app/core/core.state'; +import {getCurrentAuthUser} from '@app/core/auth/auth.selectors'; +import {Authority} from '@shared/models/authority.enum'; +import {TranslateService} from '@ngx-translate/core'; +import {DeviceService} from '@core/http/device.service'; +import {EntitySubtype, EntityType} from '@app/shared/models/entity-type.models'; +import {BroadcastService} from '@app/core/services/broadcast.service'; + +@Component({ + selector: 'tb-entity-subtype-autocomplete', + templateUrl: './entity-subtype-autocomplete.component.html', + styleUrls: [], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => EntitySubTypeAutocompleteComponent), + multi: true + }] +}) +export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor, OnInit, AfterViewInit, OnDestroy { + + subTypeFormGroup: FormGroup; + + modelValue: string | null; + + @Input() + entityType: EntityType; + + @Input() + required: boolean; + + @Input() + disabled: boolean; + + @ViewChild('subTypeInput', {static: true}) subTypeInput: ElementRef; + + selectEntitySubtypeText: string; + entitySubtypeText: string; + entitySubtypeRequiredText: string; + + filteredSubTypes: Observable>; + + subTypes: Observable>; + + private broadcastSubscription: Subscription; + + private searchText = ''; + + private propagateChange = (v: any) => { }; + + constructor(private store: Store, + private broadcast: BroadcastService, + public translate: TranslateService, + private deviceService: DeviceService, + private fb: FormBuilder) { + this.subTypeFormGroup = this.fb.group({ + subType: [null] + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + ngOnInit() { + + switch (this.entityType) { + case EntityType.ASSET: + this.selectEntitySubtypeText = 'asset.select-asset-type'; + this.entitySubtypeText = 'asset.asset-type'; + this.entitySubtypeRequiredText = 'asset.asset-type-required'; + this.broadcastSubscription = this.broadcast.on('assetSaved', () => { + this.subTypes = null; + }); + break; + case EntityType.DEVICE: + this.selectEntitySubtypeText = 'device.select-device-type'; + this.entitySubtypeText = 'device.device-type'; + this.entitySubtypeRequiredText = 'device.device-type-required'; + this.broadcastSubscription = this.broadcast.on('deviceSaved', () => { + this.subTypes = null; + }); + break; + case EntityType.ENTITY_VIEW: + this.selectEntitySubtypeText = 'entity-view.select-entity-view-type'; + this.entitySubtypeText = 'entity-view.entity-view-type'; + this.entitySubtypeRequiredText = 'entity-view.entity-view-type-required'; + this.broadcastSubscription = this.broadcast.on('entityViewSaved', () => { + this.subTypes = null; + }); + break; + } + + this.filteredSubTypes = this.subTypeFormGroup.get('subType').valueChanges + .pipe( + tap(value => { + let modelValue; + if (!value) { + modelValue = null; + } else if (typeof value === 'string') { + modelValue = value; + } else { + modelValue = value.type; + } + this.updateView(modelValue); + }), + startWith(''), + map(value => value ? (typeof value === 'string' ? value : value.type) : ''), + mergeMap(type => this.fetchSubTypes(type) ) + ); + } + + ngAfterViewInit(): void { + } + + ngOnDestroy(): void { + if (this.broadcastSubscription) { + this.broadcastSubscription.unsubscribe(); + } + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + writeValue(value: string | null): void { + this.searchText = ''; + if (value != null) { + this.modelValue = value; + this.fetchSubTypes(value, true).subscribe( + (subTypes) => { + const subType = subTypes && subTypes.length === 1 ? subTypes[0] : null; + this.subTypeFormGroup.get('subType').patchValue(subType, {emitEvent: true}); + } + ); + } else { + this.modelValue = null; + this.subTypeFormGroup.get('subType').patchValue(null, {emitEvent: true}); + } + } + + updateView(value: string | null) { + if (this.modelValue !== value) { + this.modelValue = value; + this.propagateChange(this.modelValue); + } + } + + displaySubTypeFn(subType?: EntitySubtype): string | undefined { + return subType ? subType.type : undefined; + } + + fetchSubTypes(searchText?: string, strictMatch: boolean = false): Observable> { + this.searchText = searchText; + return this.getSubTypes().pipe( + map(subTypes => subTypes.filter( subType => { + if (strictMatch) { + return searchText ? subType.type === searchText : false; + } else { + return searchText ? subType.type.toUpperCase().startsWith(searchText.toUpperCase()) : true; + } + })) + ); + } + + getSubTypes(): Observable> { + if (!this.subTypes) { + switch (this.entityType) { + case EntityType.ASSET: + // TODO: + break; + case EntityType.DEVICE: + this.subTypes = this.deviceService.getDeviceTypes(false, true); + break; + case EntityType.ENTITY_VIEW: + // TODO: + break; + } + if (this.subTypes) { + this.subTypes = this.subTypes.pipe( + publishReplay(1), + refCount() + ); + } else { + return throwError(null); + } + } + return this.subTypes; + } + + clear() { + this.subTypeFormGroup.get('subType').patchValue(null, {emitEvent: true}); + setTimeout(() => { + this.subTypeInput.nativeElement.blur(); + this.subTypeInput.nativeElement.focus(); + }, 0); + } + +} diff --git a/ui-ngx/src/app/shared/components/entity/entity-subtype-select.component.html b/ui-ngx/src/app/shared/components/entity/entity-subtype-select.component.html new file mode 100644 index 0000000000..651b274905 --- /dev/null +++ b/ui-ngx/src/app/shared/components/entity/entity-subtype-select.component.html @@ -0,0 +1,28 @@ + + + {{ entitySubtypeTitle | translate }} + + + {{ displaySubTypeFn(subType) }} + + + + {{ entitySubtypeRequiredText | translate }} + + diff --git a/ui-ngx/src/app/shared/components/entity/entity-subtype-select.component.scss b/ui-ngx/src/app/shared/components/entity/entity-subtype-select.component.scss new file mode 100644 index 0000000000..bfd904b713 --- /dev/null +++ b/ui-ngx/src/app/shared/components/entity/entity-subtype-select.component.scss @@ -0,0 +1,20 @@ +/** + * Copyright © 2016-2019 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 { + mat-select.tb-entity-subtype-select { + min-width: 200px; + } +} diff --git a/ui-ngx/src/app/shared/components/entity/entity-subtype-select.component.ts b/ui-ngx/src/app/shared/components/entity/entity-subtype-select.component.ts new file mode 100644 index 0000000000..db31c53f56 --- /dev/null +++ b/ui-ngx/src/app/shared/components/entity/entity-subtype-select.component.ts @@ -0,0 +1,238 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import {AfterViewInit, Component, ElementRef, forwardRef, Input, OnInit, ViewChild, OnDestroy} from '@angular/core'; +import {ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR} from '@angular/forms'; +import {Observable, of, throwError, Subscription, Subject} from 'rxjs'; +import {PageLink} from '@shared/models/page/page-link'; +import {Direction} from '@shared/models/page/sort-order'; +import {filter, map, mergeMap, publishReplay, refCount, startWith, tap, publish} from 'rxjs/operators'; +import {PageData, emptyPageData} from '@shared/models/page/page-data'; +import {DashboardInfo} from '@app/shared/models/dashboard.models'; +import {DashboardId} from '@app/shared/models/id/dashboard-id'; +import {DashboardService} from '@core/http/dashboard.service'; +import {Store} from '@ngrx/store'; +import {AppState} from '@app/core/core.state'; +import {getCurrentAuthUser} from '@app/core/auth/auth.selectors'; +import {Authority} from '@shared/models/authority.enum'; +import {TranslateService} from '@ngx-translate/core'; +import {DeviceService} from '@core/http/device.service'; +import {EntitySubtype, EntityType} from '@app/shared/models/entity-type.models'; +import {BroadcastService} from '@app/core/services/broadcast.service'; + +@Component({ + selector: 'tb-entity-subtype-select', + templateUrl: './entity-subtype-select.component.html', + styleUrls: ['./entity-subtype-select.component.scss'], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => EntitySubTypeSelectComponent), + multi: true + }] +}) +export class EntitySubTypeSelectComponent implements ControlValueAccessor, OnInit, AfterViewInit, OnDestroy { + + subTypeFormGroup: FormGroup; + + modelValue: string | null; + + @Input() + entityType: EntityType; + + @Input() + showLabel: boolean; + + @Input() + required: boolean; + + @Input() + disabled: boolean; + + @Input() + typeTranslatePrefix: string; + + @ViewChild('subTypeInput', {static: true}) subTypeInput: ElementRef; + + entitySubtypeTitle: string; + entitySubtypeRequiredText: string; + + subTypesOptions: Observable>; + + private subTypesOptionsSubject: Subject = new Subject(); + + subTypes: Observable>; + + private broadcastSubscription: Subscription; + + private propagateChange = (v: any) => { }; + + constructor(private store: Store, + private broadcast: BroadcastService, + public translate: TranslateService, + private deviceService: DeviceService, + private fb: FormBuilder) { + this.subTypeFormGroup = this.fb.group({ + subType: [null] + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + ngOnInit() { + + switch (this.entityType) { + case EntityType.ASSET: + this.entitySubtypeTitle = 'asset.asset-type'; + this.entitySubtypeRequiredText = 'asset.asset-type-required'; + this.broadcastSubscription = this.broadcast.on('assetSaved', () => { + this.subTypes = null; + this.subTypesOptionsSubject.next(''); + }); + break; + case EntityType.DEVICE: + this.entitySubtypeTitle = 'device.device-type'; + this.entitySubtypeRequiredText = 'device.device-type-required'; + this.broadcastSubscription = this.broadcast.on('deviceSaved', () => { + this.subTypes = null; + this.subTypesOptionsSubject.next(''); + }); + break; + case EntityType.ENTITY_VIEW: + this.entitySubtypeTitle = 'entity-view.entity-view-type'; + this.entitySubtypeRequiredText = 'entity-view.entity-view-type-required'; + this.broadcastSubscription = this.broadcast.on('entityViewSaved', () => { + this.subTypes = null; + this.subTypesOptionsSubject.next(''); + }); + break; + } + + this.subTypesOptions = this.subTypesOptionsSubject.asObservable().pipe( + startWith(''), + mergeMap(() => this.getSubTypes()) + ); + + this.subTypeFormGroup.get('subType').valueChanges.subscribe( + (value) => { + let modelValue; + if (!value || value === '') { + modelValue = ''; + } else { + modelValue = value.type; + } + this.updateView(modelValue); + } + ); + } + + ngAfterViewInit(): void { + } + + ngOnDestroy(): void { + if (this.broadcastSubscription) { + this.broadcastSubscription.unsubscribe(); + } + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + writeValue(value: string | null): void { + if (value != null && value !== '') { + this.modelValue = value; + this.findSubTypes(value).subscribe( + (subTypes) => { + const subType = subTypes && subTypes.length === 1 ? subTypes[0] : ''; + this.subTypeFormGroup.get('subType').patchValue(subType, {emitEvent: true}); + } + ); + } else { + this.modelValue = ''; + this.subTypeFormGroup.get('subType').patchValue('', {emitEvent: true}); + } + } + + updateView(value: string | null) { + if (this.modelValue !== value) { + this.modelValue = value; + this.propagateChange(this.modelValue); + } + } + + displaySubTypeFn(subType?: EntitySubtype | string): string | undefined { + if (subType && typeof subType !== 'string') { + if (this.typeTranslatePrefix) { + return this.translate.instant(this.typeTranslatePrefix + '.' + subType.type); + } else { + return subType.type; + } + } else { + return this.translate.instant('entity.all-subtypes'); + } + } + + findSubTypes(searchText?: string): Observable> { + return this.getSubTypes().pipe( + map(subTypes => subTypes.filter( subType => { + return searchText ? (typeof subType === 'string' ? false : subType.type === searchText) : false; + })) + ); + } + + getSubTypes(): Observable> { + if (!this.subTypes) { + switch (this.entityType) { + case EntityType.ASSET: + // TODO: + break; + case EntityType.DEVICE: + this.subTypes = this.deviceService.getDeviceTypes(false, true); + break; + case EntityType.ENTITY_VIEW: + // TODO: + break; + } + if (this.subTypes) { + this.subTypes = this.subTypes.pipe( + map((allSubtypes) => { + allSubtypes.unshift(''); + return allSubtypes; + }), + publishReplay(1), + refCount() + ); + } else { + return throwError(null); + } + } + return this.subTypes; + } + + clear() { + this.subTypeFormGroup.get('subType').patchValue(null, {emitEvent: true}); + setTimeout(() => { + this.subTypeInput.nativeElement.blur(); + this.subTypeInput.nativeElement.focus(); + }, 0); + } + +} diff --git a/ui-ngx/src/app/shared/models/constants.ts b/ui-ngx/src/app/shared/models/constants.ts index b57950bef6..f92a20eed2 100644 --- a/ui-ngx/src/app/shared/models/constants.ts +++ b/ui-ngx/src/app/shared/models/constants.ts @@ -59,7 +59,8 @@ export const HelpLinks = { securitySettings: helpBaseUrl + '/docs/user-guide/ui/security-settings', tenants: helpBaseUrl + '/docs/user-guide/ui/tenants', customers: helpBaseUrl + '/docs/user-guide/customers', - users: helpBaseUrl + '/docs/user-guide/ui/users' + users: helpBaseUrl + '/docs/user-guide/ui/users', + devices: helpBaseUrl + '/docs/user-guide/ui/devices' } }; diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts new file mode 100644 index 0000000000..ebd3acb44a --- /dev/null +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -0,0 +1,34 @@ +/// +/// Copyright © 2016-2019 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 {BaseData} from '@shared/models/base-data'; +import {DeviceId} from './id/device-id'; +import {TenantId} from '@shared/models/id/tenant-id'; +import {CustomerId} from '@shared/models/id/customer-id'; + +export interface Device extends BaseData { + tenantId: TenantId; + customerId: CustomerId; + name: string; + type: string; + label: string; + additionalInfo?: any; +} + +export interface DeviceInfo extends Device { + customerTitle: string; + customerIsPublic: boolean; +} diff --git a/ui-ngx/src/app/shared/models/entity-type.models.ts b/ui-ngx/src/app/shared/models/entity-type.models.ts index 36747d3380..4b9687f019 100644 --- a/ui-ngx/src/app/shared/models/entity-type.models.ts +++ b/ui-ngx/src/app/shared/models/entity-type.models.ts @@ -1,3 +1,5 @@ +import {TenantId} from './id/tenant-id'; + /// /// Copyright © 2016-2019 The Thingsboard Authors /// @@ -88,6 +90,20 @@ export const entityTypeTranslations = new Map search: 'user.search', selectedEntities: 'user.selected-users' } + ], + [ + EntityType.DEVICE, + { + type: 'entity.type-device', + typePlural: 'entity.type-devices', + list: 'entity.list-of-devices', + nameStartsWith: 'entity.device-name-starts-with', + details: 'device.device-details', + add: 'device.add', + noEntities: 'device.no-devices-text', + search: 'device.search', + selectedEntities: 'device.selected-devices' + } ] ] ); @@ -111,6 +127,18 @@ export const entityTypeResources = new Map( { helpLinkId: 'users' } + ], + [ + EntityType.DEVICE, + { + helpLinkId: 'devices' + } ] ] ); + +export interface EntitySubtype { + tenantId: TenantId; + entityType: EntityType; + type: string; +} diff --git a/ui-ngx/src/app/shared/models/id/device-id.ts b/ui-ngx/src/app/shared/models/id/device-id.ts new file mode 100644 index 0000000000..9c79b35342 --- /dev/null +++ b/ui-ngx/src/app/shared/models/id/device-id.ts @@ -0,0 +1,26 @@ +/// +/// Copyright © 2016-2019 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 { EntityId } from './entity-id'; +import { EntityType } from '@shared/models/entity-type.models'; + +export class DeviceId implements EntityId { + entityType = EntityType.DEVICE; + id: string; + constructor(id: string) { + this.id = id; + } +} diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index ffd5591240..1390aefefb 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -78,6 +78,8 @@ import { ClipboardModule } from 'ngx-clipboard'; import { FullscreenDirective } from '@shared/components/fullscreen.directive'; import { HighlightPipe } from '@shared/pipe/highlight.pipe'; import {DashboardAutocompleteComponent} from '@shared/components/dashboard-autocomplete.component'; +import {EntitySubTypeAutocompleteComponent} from '@shared/components/entity/entity-subtype-autocomplete.component'; +import {EntitySubTypeSelectComponent} from './components/entity/entity-subtype-select.component'; @NgModule({ providers: [ @@ -118,6 +120,8 @@ import {DashboardAutocompleteComponent} from '@shared/components/dashboard-autoc DatetimePeriodComponent, // ValueInputComponent, DashboardAutocompleteComponent, + EntitySubTypeAutocompleteComponent, + EntitySubTypeSelectComponent, NospacePipe, MillisecondsToTimeStringPipe, EnumToArrayPipe, @@ -183,6 +187,8 @@ import {DashboardAutocompleteComponent} from '@shared/components/dashboard-autoc TimeintervalComponent, DatetimePeriodComponent, DashboardAutocompleteComponent, + EntitySubTypeAutocompleteComponent, + EntitySubTypeSelectComponent, // ValueInputComponent, MatButtonModule, MatCheckboxModule, 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 f5e115aafb..34fe3394dc 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -673,6 +673,7 @@ "no-device-types-matching": "No device types matching '{{entitySubtype}}' were found.", "device-type-list-empty": "No device types selected.", "device-types": "Device types", + "created-time": "Created time", "name": "Name", "name-required": "Name is required.", "description": "Description", @@ -691,7 +692,9 @@ "device-public": "Device is public", "select-device": "Select device", "import": "Import device", - "device-file": "Device file" + "device-file": "Device file", + "search": "Search devices", + "selected-devices": "{ count, plural, 1 {1 device} other {# devices} } selected" }, "dialog": { "close": "Close dialog" diff --git a/ui-ngx/src/styles.scss b/ui-ngx/src/styles.scss index e2ea5e36c9..bd55482c9b 100644 --- a/ui-ngx/src/styles.scss +++ b/ui-ngx/src/styles.scss @@ -209,6 +209,13 @@ label { } } +div { + &.tb-small { + font-size: 14px; + color: rgba(0, 0, 0, .54); + } +} + pre.tb-highlight { display: block; padding: 15px; diff --git a/ui-ngx/src/theme.scss b/ui-ngx/src/theme.scss index 33b7b11673..662ae1632a 100644 --- a/ui-ngx/src/theme.scss +++ b/ui-ngx/src/theme.scss @@ -253,6 +253,12 @@ $tb-dark-theme: get-tb-dark-theme( } } + .mat-cell { + mat-icon { + color: rgba(0, 0, 0, .54); + } + } + mat-toolbar.mat-primary { button.mat-icon-button { mat-icon { From 6c6d4fca83956a61cb2e07a1c5233ccd7b11cf56 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 13 Aug 2019 20:04:28 +0300 Subject: [PATCH 008/133] Minor improvements --- ui-ngx/src/app/modules/home/pages/device/device.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 879f1d2757..1719186e09 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 @@ -94,7 +94,7 @@
- + {{ 'device.is-gateway' | translate }} From 3700bcaa5d7cbecd9b573b82d14c51a9060e0403 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 14 Aug 2019 19:55:24 +0300 Subject: [PATCH 009/133] Devices page implementation --- .../ThingsboardSecurityConfiguration.java | 4 +- .../thingsboard/server/config/WebConfig.java | 2 +- ui-ngx/src/app/core/http/customer.service.ts | 4 +- ui-ngx/src/app/core/http/device.service.ts | 39 ++- ui-ngx/src/app/core/http/entity.service.ts | 263 +++++++++++++++++ .../device-credentials-dialog.component.html | 79 +++++ .../device-credentials-dialog.component.ts | 138 +++++++++ .../home/pages/device/device.component.ts | 38 +++ .../home/pages/device/device.module.ts | 7 +- .../device/devices-table-config.resolver.ts | 132 ++++++++- .../home/pages/profile/profile.component.ts | 38 ++- .../entity/entities-table-config.models.ts | 1 + .../entity/entities-table.component.html | 31 +- .../entity/entity-autocomplete.component.html | 43 +++ .../entity/entity-autocomplete.component.ts | 274 ++++++++++++++++++ ui-ngx/src/app/shared/models/alarm.models.ts | 51 ++++ ui-ngx/src/app/shared/models/asset.models.ts | 34 +++ ui-ngx/src/app/shared/models/device.models.ts | 20 ++ .../app/shared/models/entity-type.models.ts | 4 + .../app/shared/models/entity-view.models.ts | 50 ++++ ui-ngx/src/app/shared/models/id/alarm-id.ts | 26 ++ ui-ngx/src/app/shared/models/id/asset-id.ts | 26 ++ .../shared/models/id/device-credentials-id.ts | 24 ++ .../app/shared/models/id/entity-view-id.ts | 26 ++ .../src/app/shared/models/id/rule-chain-id.ts | 26 ++ .../src/app/shared/models/id/rule-node-id.ts | 26 ++ .../app/shared/models/id/widget-type-id.ts | 26 ++ .../app/shared/models/id/widgets-bundle-id.ts | 26 ++ .../app/shared/models/rule-chain.models.ts | 37 +++ .../src/app/shared/models/rule-node.models.ts | 36 +++ .../app/shared/models/widget-type.models.ts | 33 +++ .../app/shared/models/widgets-bundle.model.ts | 26 ++ ui-ngx/src/app/shared/shared.module.ts | 3 + 33 files changed, 1551 insertions(+), 42 deletions(-) create mode 100644 ui-ngx/src/app/core/http/entity.service.ts create mode 100644 ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.ts create mode 100644 ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.html create mode 100644 ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts create mode 100644 ui-ngx/src/app/shared/models/alarm.models.ts create mode 100644 ui-ngx/src/app/shared/models/asset.models.ts create mode 100644 ui-ngx/src/app/shared/models/entity-view.models.ts create mode 100644 ui-ngx/src/app/shared/models/id/alarm-id.ts create mode 100644 ui-ngx/src/app/shared/models/id/asset-id.ts create mode 100644 ui-ngx/src/app/shared/models/id/device-credentials-id.ts create mode 100644 ui-ngx/src/app/shared/models/id/entity-view-id.ts create mode 100644 ui-ngx/src/app/shared/models/id/rule-chain-id.ts create mode 100644 ui-ngx/src/app/shared/models/id/rule-node-id.ts create mode 100644 ui-ngx/src/app/shared/models/id/widget-type-id.ts create mode 100644 ui-ngx/src/app/shared/models/id/widgets-bundle-id.ts create mode 100644 ui-ngx/src/app/shared/models/rule-chain.models.ts create mode 100644 ui-ngx/src/app/shared/models/rule-node.models.ts create mode 100644 ui-ngx/src/app/shared/models/widget-type.models.ts create mode 100644 ui-ngx/src/app/shared/models/widgets-bundle.model.ts 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 653ad68232..f826264c72 100644 --- a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java @@ -68,7 +68,7 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt public static final String FORM_BASED_LOGIN_ENTRY_POINT = "/api/auth/login"; public static final String PUBLIC_LOGIN_ENTRY_POINT = "/api/auth/login/public"; public static final String TOKEN_REFRESH_ENTRY_POINT = "/api/auth/token"; - protected static final String[] NON_TOKEN_BASED_AUTH_ENTRY_POINTS = new String[] {"/index.html", "/static/**", "/api/noauth/**", "/webjars/**"}; + protected static final String[] NON_TOKEN_BASED_AUTH_ENTRY_POINTS = new String[] {"/index.html", "/assets/**", "/static/**", "/api/noauth/**", "/webjars/**"}; public static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/api/**"; public static final String WS_TOKEN_BASED_AUTH_ENTRY_POINT = "/api/ws/**"; @@ -155,7 +155,7 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt @Override public void configure(WebSecurity web) throws Exception { - web.ignoring().antMatchers("/static/**"); + web.ignoring().antMatchers("/*.js","/*.css","/*.ico","/assets/**","/static/**"); } @Override diff --git a/application/src/main/java/org/thingsboard/server/config/WebConfig.java b/application/src/main/java/org/thingsboard/server/config/WebConfig.java index a7b5019d74..d764238fbc 100644 --- a/application/src/main/java/org/thingsboard/server/config/WebConfig.java +++ b/application/src/main/java/org/thingsboard/server/config/WebConfig.java @@ -21,7 +21,7 @@ import org.springframework.web.bind.annotation.RequestMapping; @Controller public class WebConfig { - @RequestMapping(value = "/{path:^(?!api$)(?!static$)(?!webjars$)[^\\.]*}/**") + @RequestMapping(value = "/{path:^(?!api$)(?!assets$)(?!static$)(?!webjars$)[^\\.]*}/**") public String redirect() { return "forward:/index.html"; } diff --git a/ui-ngx/src/app/core/http/customer.service.ts b/ui-ngx/src/app/core/http/customer.service.ts index c84502cc38..f36d2dcf41 100644 --- a/ui-ngx/src/app/core/http/customer.service.ts +++ b/ui-ngx/src/app/core/http/customer.service.ts @@ -31,9 +31,9 @@ export class CustomerService { private http: HttpClient ) { } - public getCustomers(tenantId: string, pageLink: PageLink, ignoreErrors: boolean = false, + public getCustomers(pageLink: PageLink, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { - return this.http.get>(`/api/tenant/${tenantId}/customers${pageLink.toQuery()}`, + return this.http.get>(`/api/customers${pageLink.toQuery()}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); } diff --git a/ui-ngx/src/app/core/http/device.service.ts b/ui-ngx/src/app/core/http/device.service.ts index 19c34e5881..f970a90475 100644 --- a/ui-ngx/src/app/core/http/device.service.ts +++ b/ui-ngx/src/app/core/http/device.service.ts @@ -16,15 +16,16 @@ import { Injectable } from '@angular/core'; import { defaultHttpOptions } from './http-utils'; -import { Observable } from 'rxjs/index'; +import { Observable, Subject, ReplaySubject } from 'rxjs/index'; import { HttpClient } from '@angular/common/http'; import { PageLink } from '@shared/models/page/page-link'; import { PageData } from '@shared/models/page/page-data'; import { Tenant } from '@shared/models/tenant.model'; import {DashboardInfo, Dashboard} from '@shared/models/dashboard.models'; import {map} from 'rxjs/operators'; -import {DeviceInfo, Device} from '@app/shared/models/device.models'; +import {DeviceInfo, Device, DeviceCredentials} from '@app/shared/models/device.models'; import {EntitySubtype} from '@app/shared/models/entity-type.models'; +import {AuthService} from '../auth/auth.service'; @Injectable({ providedIn: 'root' @@ -67,6 +68,40 @@ export class DeviceService { return this.http.get>('/api/device/types', defaultHttpOptions(ignoreLoading, ignoreErrors)); } + public getDeviceCredentials(deviceId: string, sync: boolean = false, ignoreErrors: boolean = false, + ignoreLoading: boolean = false): Observable { + const url = `/api/device/${deviceId}/credentials`; + if (sync) { + const responseSubject = new ReplaySubject(); + const request = new XMLHttpRequest(); + request.open('GET', url, false); + request.setRequestHeader('Accept', 'application/json, text/plain, */*'); + const jwtToken = AuthService.getJwtToken(); + if (jwtToken) { + request.setRequestHeader('X-Authorization', 'Bearer ' + jwtToken); + } + request.send(null); + if (request.status === 200) { + const credentials = JSON.parse(request.responseText) as DeviceCredentials; + responseSubject.next(credentials); + } else { + responseSubject.error(null); + } + return responseSubject.asObservable(); + } else { + return this.http.get(url, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + } + + public saveDeviceCredentials(deviceCredentials: DeviceCredentials, ignoreErrors: boolean = false, + ignoreLoading: boolean = false): Observable { + return this.http.post('/api/device/credentials', deviceCredentials, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public makeDevicePublic(deviceId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.post(`/api/customer/public/device/${deviceId}`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + public unassignDeviceFromCustomer(deviceId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { return this.http.delete(`/api/customer/device/${deviceId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); } diff --git a/ui-ngx/src/app/core/http/entity.service.ts b/ui-ngx/src/app/core/http/entity.service.ts new file mode 100644 index 0000000000..c7bfdcc2ae --- /dev/null +++ b/ui-ngx/src/app/core/http/entity.service.ts @@ -0,0 +1,263 @@ +/// +/// Copyright © 2016-2019 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 {Injectable} from '@angular/core'; +import {Observable, throwError, of, empty, EMPTY} from 'rxjs/index'; +import {HttpClient} from '@angular/common/http'; +import {PageLink} from '@shared/models/page/page-link'; +import {EntityType} from '@shared/models/entity-type.models'; +import {BaseData} from '@shared/models/base-data'; +import {EntityId} from '@shared/models/id/entity-id'; +import {DeviceService} from '@core/http/device.service'; +import {TenantService} from '@core/http/tenant.service'; +import {CustomerService} from '@core/http/customer.service'; +import {UserService} from './user.service'; +import {DashboardService} from '@core/http/dashboard.service'; +import {Direction} from '@shared/models/page/sort-order'; +import {PageData} from '@shared/models/page/page-data'; +import {getCurrentAuthUser} from '../auth/auth.selectors'; +import {Store} from '@ngrx/store'; +import {AppState} from '@core/core.state'; +import {Authority} from '@shared/models/authority.enum'; +import {Tenant} from '@shared/models/tenant.model'; +import {concatMap, expand, map, toArray} from 'rxjs/operators'; +import {Customer} from '@app/shared/models/customer.model'; + +@Injectable({ + providedIn: 'root' +}) +export class EntityService { + + constructor( + private http: HttpClient, + private store: Store, + private deviceService: DeviceService, + private tenantService: TenantService, + private customerService: CustomerService, + private userService: UserService, + private dashboardService: DashboardService + ) { } + + private getEntityObservable(entityType: EntityType, entityId: string, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + + let observable: Observable>; + switch (entityType) { + case EntityType.DEVICE: + observable = this.deviceService.getDevice(entityId, ignoreErrors, ignoreLoading); + break; + case EntityType.ASSET: + // TODO: + break; + case EntityType.ENTITY_VIEW: + // TODO: + break; + case EntityType.TENANT: + observable = this.tenantService.getTenant(entityId, ignoreErrors, ignoreLoading); + break; + case EntityType.CUSTOMER: + observable = this.customerService.getCustomer(entityId, ignoreErrors, ignoreLoading); + break; + case EntityType.DASHBOARD: + observable = this.dashboardService.getDashboardInfo(entityId, ignoreErrors, ignoreLoading); + break; + case EntityType.USER: + observable = this.userService.getUser(entityId, ignoreErrors, ignoreLoading); + break; + case EntityType.RULE_CHAIN: + // TODO: + break; + case EntityType.ALARM: + console.error('Get Alarm Entity is not implemented!'); + break; + } + return observable; + } + + public getEntity(entityType: EntityType, entityId: string, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + const entityObservable = this.getEntityObservable(entityType, entityId, ignoreErrors, ignoreLoading); + if (entityObservable) { + return entityObservable; + } else { + return throwError(null); + } + } + + private getSingleTenantByPageLinkObservable(pageLink: PageLink, + ignoreErrors: boolean = false, + ignoreLoading: boolean = false): Observable> { + const authUser = getCurrentAuthUser(this.store); + const tenantId = authUser.tenantId; + return this.tenantService.getTenant(tenantId, ignoreErrors, ignoreLoading).pipe( + map((tenant) => { + const result = { + data: [], + totalPages: 0, + totalElements: 0, + hasNext: false + } as PageData; + if (tenant.title.toLowerCase().startsWith(pageLink.textSearch.toLowerCase())) { + result.data.push(tenant); + result.totalPages = 1; + result.totalElements = 1; + } + return result; + }) + ); + } + + private getSingleCustomerByPageLinkObservable(pageLink: PageLink, + ignoreErrors: boolean = false, + ignoreLoading: boolean = false): Observable> { + const authUser = getCurrentAuthUser(this.store); + const customerId = authUser.customerId; + return this.customerService.getCustomer(customerId, ignoreErrors, ignoreLoading).pipe( + map((customer) => { + const result = { + data: [], + totalPages: 0, + totalElements: 0, + hasNext: false + } as PageData; + if (customer.title.toLowerCase().startsWith(pageLink.textSearch.toLowerCase())) { + result.data.push(customer); + result.totalPages = 1; + result.totalElements = 1; + } + return result; + }) + ); + } + + private getEntitiesByPageLinkObservable(entityType: EntityType, pageLink: PageLink, subType: string = '', + ignoreErrors: boolean = false, + ignoreLoading: boolean = false): Observable>> { + let entitiesObservable: Observable>>; + const authUser = getCurrentAuthUser(this.store); + const customerId = authUser.customerId; + switch (entityType) { + case EntityType.DEVICE: + pageLink.sortOrder.property = 'name'; + if (authUser.authority === Authority.CUSTOMER_USER) { + entitiesObservable = this.deviceService.getCustomerDeviceInfos(customerId, pageLink, subType, ignoreErrors, ignoreLoading); + } else { + entitiesObservable = this.deviceService.getTenantDeviceInfos(pageLink, subType, ignoreErrors, ignoreLoading); + } + break; + case EntityType.ASSET: + pageLink.sortOrder.property = 'name'; + if (authUser.authority === Authority.CUSTOMER_USER) { + // TODO: + } else { + // TODO: + } + break; + case EntityType.ENTITY_VIEW: + pageLink.sortOrder.property = 'name'; + if (authUser.authority === Authority.CUSTOMER_USER) { + // TODO: + } else { + // TODO: + } + break; + case EntityType.TENANT: + pageLink.sortOrder.property = 'title'; + if (authUser.authority === Authority.TENANT_ADMIN) { + entitiesObservable = this.getSingleTenantByPageLinkObservable(pageLink, ignoreErrors, ignoreLoading); + } else { + entitiesObservable = this.tenantService.getTenants(pageLink, ignoreErrors, ignoreLoading); + } + break; + case EntityType.CUSTOMER: + pageLink.sortOrder.property = 'title'; + if (authUser.authority === Authority.CUSTOMER_USER) { + entitiesObservable = this.getSingleCustomerByPageLinkObservable(pageLink, ignoreErrors, ignoreLoading); + } else { + entitiesObservable = this.customerService.getCustomers(pageLink, ignoreErrors, ignoreLoading); + } + break; + case EntityType.RULE_CHAIN: + pageLink.sortOrder.property = 'name'; + // TODO: + break; + case EntityType.DASHBOARD: + pageLink.sortOrder.property = 'title'; + if (authUser.authority === Authority.CUSTOMER_USER) { + entitiesObservable = this.dashboardService.getCustomerDashboards(customerId, pageLink, ignoreErrors, ignoreLoading); + } else { + entitiesObservable = this.dashboardService.getTenantDashboards(pageLink, ignoreErrors, ignoreLoading); + } + break; + case EntityType.USER: + console.error('Get User Entities is not implemented!'); + break; + case EntityType.ALARM: + console.error('Get Alarm Entities is not implemented!'); + break; + } + return entitiesObservable; + } + + private getEntitiesByPageLink(entityType: EntityType, pageLink: PageLink, subType: string = '', + ignoreErrors: boolean = false, + ignoreLoading: boolean = false): Observable>> { + const entitiesObservable: Observable>> = + this.getEntitiesByPageLinkObservable(entityType, pageLink, subType, ignoreErrors, ignoreLoading); + if (entitiesObservable) { + return entitiesObservable.pipe( + expand((data) => { + if (data.hasNext) { + pageLink.page += 1; + return this.getEntitiesByPageLinkObservable(entityType, pageLink, subType, ignoreErrors, ignoreLoading); + } else { + return EMPTY; + } + }), + map((data) => data.data), + concatMap((data) => data), + toArray() + ); + } else { + return of(null); + } + } + + public getEntitiesByNameFilter(entityType: EntityType, entityNameFilter: string, + pageSize: number, subType: string = '', + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable>> { + const pageLink = new PageLink(pageSize, 0, entityNameFilter, { + property: 'name', + direction: Direction.ASC + }); + if (pageSize === -1) { // all + pageLink.pageSize = 100; + return this.getEntitiesByPageLink(entityType, pageLink, subType, ignoreErrors, ignoreLoading).pipe( + map((data) => data && data.length ? data : null) + ); + } else { + const entitiesObservable: Observable>> = + this.getEntitiesByPageLinkObservable(entityType, pageLink, subType, ignoreErrors, ignoreLoading); + if (entitiesObservable) { + return entitiesObservable.pipe( + map((data) => data && data.data.length ? data.data : null) + ); + } else { + return of(null); + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.html b/ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.html new file mode 100644 index 0000000000..95975082a9 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.html @@ -0,0 +1,79 @@ + +
+ +

device.device-credentials

+ + +
+ + +
+
+
+ + device.credentials-type + + + {{ credentialTypeNamesMap.get(credentialsType) }} + + + + + device.access-token + + + {{ 'device.access-token-required' | translate }} + + + {{ 'device.access-token-invalid' | translate }} + + + + device.rsa-key + + + {{ 'device.rsa-key-required' | translate }} + + +
+
+
+ + + +
+
diff --git a/ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.ts new file mode 100644 index 0000000000..a8d9d1e7b1 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.ts @@ -0,0 +1,138 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import {Component, OnInit, SkipSelf, Inject} from '@angular/core'; +import {ErrorStateMatcher, MatDialogRef, MAT_DIALOG_DATA} from '@angular/material'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import {FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators} from '@angular/forms'; +import { ActionNotificationShow } from '@core/notification/notification.actions'; +import { TranslateService } from '@ngx-translate/core'; +import { AuthService } from '@core/auth/auth.service'; +import {DeviceService} from '@core/http/device.service'; +import {DeviceCredentials, DeviceCredentialsType, credentialTypeNames} from '@shared/models/device.models'; + +export interface DeviceCredentialsDialogData { + isReadOnly: boolean; + deviceId: string; +} + +@Component({ + selector: 'tb-device-credentials-dialog', + templateUrl: './device-credentials-dialog.component.html', + providers: [{provide: ErrorStateMatcher, useExisting: DeviceCredentialsDialogComponent}], + styleUrls: [] +}) +export class DeviceCredentialsDialogComponent extends PageComponent implements OnInit, ErrorStateMatcher { + + deviceCredentialsFormGroup: FormGroup; + + isReadOnly: boolean; + + deviceCredentials: DeviceCredentials; + + submitted = false; + + deviceCredentialsType = DeviceCredentialsType; + + credentialsTypes = Object.keys(DeviceCredentialsType); + + credentialTypeNamesMap = credentialTypeNames; + + constructor(protected store: Store, + @Inject(MAT_DIALOG_DATA) public data: DeviceCredentialsDialogData, + private deviceService: DeviceService, + @SkipSelf() private errorStateMatcher: ErrorStateMatcher, + public dialogRef: MatDialogRef, + public fb: FormBuilder) { + super(store); + + this.isReadOnly = data.isReadOnly; + } + + ngOnInit(): void { + this.deviceCredentialsFormGroup = this.fb.group({ + credentialsType: [DeviceCredentialsType.ACCESS_TOKEN], + credentialsId: [''], + credentialsValue: [''] + }); + if (this.isReadOnly) { + this.deviceCredentialsFormGroup.disable({emitEvent: false}); + } else { + this.registerDisableOnLoadFormControl(this.deviceCredentialsFormGroup.get('credentialsType')); + } + this.loadDeviceCredentials(); + } + + isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { + const originalErrorState = this.errorStateMatcher.isErrorState(control, form); + const customErrorState = !!(control && control.invalid && this.submitted); + return originalErrorState || customErrorState; + } + + loadDeviceCredentials() { + this.deviceService.getDeviceCredentials(this.data.deviceId).subscribe( + (deviceCredentials) => { + this.deviceCredentials = deviceCredentials; + this.deviceCredentialsFormGroup.patchValue({ + credentialsType: deviceCredentials.credentialsType, + credentialsId: deviceCredentials.credentialsId, + credentialsValue: deviceCredentials.credentialsValue + }); + this.updateValidators(); + } + ); + } + + credentialsTypeChanged(): void { + this.deviceCredentialsFormGroup.patchValue( + {credentialsId: null, credentialsValue: null}, {emitEvent: true}); + this.updateValidators(); + } + + updateValidators(): void { + const crendetialsType = this.deviceCredentialsFormGroup.get('credentialsType').value as DeviceCredentialsType; + switch (crendetialsType) { + case DeviceCredentialsType.ACCESS_TOKEN: + this.deviceCredentialsFormGroup.get('credentialsId').setValidators([Validators.max(20), Validators.pattern(/^.{1,20}$/)]); + this.deviceCredentialsFormGroup.get('credentialsId').updateValueAndValidity(); + this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([]); + this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity(); + break; + case DeviceCredentialsType.X509_CERTIFICATE: + this.deviceCredentialsFormGroup.get('credentialsValue').setValidators([Validators.required]); + this.deviceCredentialsFormGroup.get('credentialsValue').updateValueAndValidity(); + this.deviceCredentialsFormGroup.get('credentialsId').setValidators([]); + this.deviceCredentialsFormGroup.get('credentialsId').updateValueAndValidity(); + break; + } + } + + cancel(): void { + this.dialogRef.close(null); + } + + save(): void { + this.submitted = true; + this.deviceCredentials = {...this.deviceCredentials, ...this.deviceCredentialsFormGroup.value}; + this.deviceService.saveDeviceCredentials(this.deviceCredentials).subscribe( + (deviceCredentials) => { + this.dialogRef.close(deviceCredentials); + } + ); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/device/device.component.ts b/ui-ngx/src/app/modules/home/pages/device/device.component.ts index 3d287c5c62..ca86fa33af 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device.component.ts +++ b/ui-ngx/src/app/modules/home/pages/device/device.component.ts @@ -26,6 +26,10 @@ import { Authority } from '@shared/models/authority.enum'; import {DeviceInfo} from '@shared/models/device.models'; import {EntityType} from '@shared/models/entity-type.models'; import {NULL_UUID} from '@shared/models/id/has-uuid'; +import {ActionNotificationShow} from '@core/notification/notification.actions'; +import {TranslateService} from '@ngx-translate/core'; +import {DeviceService} from '@core/http/device.service'; +import {ClipboardService} from 'ngx-clipboard'; @Component({ selector: 'tb-device', @@ -39,6 +43,9 @@ export class DeviceComponent extends EntityComponent { deviceScope: 'tenant' | 'customer' | 'customer_user'; constructor(protected store: Store, + protected translate: TranslateService, + private deviceService: DeviceService, + private clipboardService: ClipboardService, public fb: FormBuilder) { super(store); } @@ -85,4 +92,35 @@ export class DeviceComponent extends EntityComponent { this.entityForm.patchValue({additionalInfo: {description: entity.additionalInfo ? entity.additionalInfo.description : ''}}); } + + onDeviceIdCopied($event) { + this.store.dispatch(new ActionNotificationShow( + { + message: this.translate.instant('device.idCopiedMessage'), + type: 'success', + duration: 750, + verticalPosition: 'bottom', + horizontalPosition: 'right' + })); + } + + copyAccessToken($event) { + if (this.entity.id) { + this.deviceService.getDeviceCredentials(this.entity.id.id, true).subscribe( + (deviceCredentials) => { + const credentialsId = deviceCredentials.credentialsId; + if (this.clipboardService.copyFromContent(credentialsId)) { + this.store.dispatch(new ActionNotificationShow( + { + message: this.translate.instant('device.accessTokenCopiedMessage'), + type: 'success', + duration: 750, + verticalPosition: 'bottom', + horizontalPosition: 'right' + })); + } + } + ); + } + } } diff --git a/ui-ngx/src/app/modules/home/pages/device/device.module.ts b/ui-ngx/src/app/modules/home/pages/device/device.module.ts index de4f34eeef..50bcbb5b56 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device.module.ts +++ b/ui-ngx/src/app/modules/home/pages/device/device.module.ts @@ -20,15 +20,18 @@ import { SharedModule } from '@shared/shared.module'; import {DeviceComponent} from '@modules/home/pages/device/device.component'; import {DeviceRoutingModule} from './device-routing.module'; import {DeviceTableHeaderComponent} from '@modules/home/pages/device/device-table-header.component'; +import {DeviceCredentialsDialogComponent} from '@modules/home/pages/device/device-credentials-dialog.component'; @NgModule({ entryComponents: [ DeviceComponent, - DeviceTableHeaderComponent + DeviceTableHeaderComponent, + DeviceCredentialsDialogComponent ], declarations: [ DeviceComponent, - DeviceTableHeaderComponent + DeviceTableHeaderComponent, + DeviceCredentialsDialogComponent ], imports: [ CommonModule, 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 27e5c86a84..9cc9ecb078 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 @@ -24,7 +24,8 @@ import { checkBoxCell, DateEntityTableColumn, EntityTableColumn, - EntityTableConfig + EntityTableConfig, + HeaderActionDescriptor } from '@shared/components/entity/entities-table-config.models'; import { TenantService } from '@core/http/tenant.service'; import { TranslateService } from '@ngx-translate/core'; @@ -37,7 +38,7 @@ import { import { TenantComponent } from '@modules/home/pages/tenant/tenant.component'; import { EntityAction } from '@shared/components/entity/entity-component.models'; import { User } from '@shared/models/user.model'; -import {Device, DeviceInfo} from '@app/shared/models/device.models'; +import {Device, DeviceCredentials, DeviceInfo} from '@app/shared/models/device.models'; import {DeviceComponent} from '@modules/home/pages/device/device.component'; import {Observable, of} from 'rxjs'; import {select, Store} from '@ngrx/store'; @@ -51,6 +52,12 @@ import {Customer} from '@app/shared/models/customer.model'; import {NULL_UUID} from '@shared/models/id/has-uuid'; import {BroadcastService} from '@core/services/broadcast.service'; import {DeviceTableHeaderComponent} from '@modules/home/pages/device/device-table-header.component'; +import {MatDialog} from '@angular/material'; +import { + DeviceCredentialsDialogComponent, + DeviceCredentialsDialogData +} from '@modules/home/pages/device/device-credentials-dialog.component'; +import {DialogService} from '@core/services/dialog.service'; @Injectable() export class DevicesTableConfigResolver implements Resolve> { @@ -63,9 +70,11 @@ export class DevicesTableConfigResolver implements Resolve this.deviceService.getTenantDeviceInfos(pageLink, this.config.componentsData.deviceType); + this.config.entitiesFetchFunction = pageLink => + this.deviceService.getTenantDeviceInfos(pageLink, this.config.componentsData.deviceType); this.config.deleteEntity = id => this.deviceService.deleteDevice(id.id); } else { - this.config.entitiesFetchFunction = pageLink => this.deviceService.getCustomerDeviceInfos(this.customerId, pageLink, this.config.componentsData.deviceType); + this.config.entitiesFetchFunction = pageLink => + this.deviceService.getCustomerDeviceInfos(this.customerId, pageLink, this.config.componentsData.deviceType); this.config.deleteEntity = id => this.deviceService.unassignDeviceFromCustomer(id.id); } } - configureCellActions(deviceScope: string): Array> { - const actions: Array> = []; + configureCellActions(deviceScope: string): Array> { + const actions: Array> = []; if (deviceScope === 'tenant') { actions.push( { @@ -177,18 +189,122 @@ export class DevicesTableConfigResolver implements Resolve { + const actions: Array = []; + actions.push( + { + name: this.translate.instant('device.add-device-text'), + icon: 'insert_drive_file', + isEnabled: () => true, + onAction: ($event) => this.config.table.addEntity($event) + }, + { + name: this.translate.instant('device.import'), + icon: 'file_upload', + isEnabled: () => true, + onAction: ($event) => this.importDevices($event) + } + ); + return actions; + } + + importDevices($event: Event) { + if ($event) { + $event.stopPropagation(); + } + // TODO: + } + makePublic($event: Event, device: Device) { + if ($event) { + $event.stopPropagation(); + } + this.dialogService.confirm( + this.translate.instant('device.make-public-device-title', {deviceName: device.name}), + this.translate.instant('device.make-public-device-text'), + this.translate.instant('action.no'), + this.translate.instant('action.yes'), + true + ).subscribe((res) => { + if (res) { + this.deviceService.makeDevicePublic(device.id.id).subscribe( + () => { + this.config.table.updateData(); + } + ); + } + } + ); + } + + assignToCustomer($event: Event, device: Device) { if ($event) { $event.stopPropagation(); } // TODO: } - onDeviceAction(action: EntityAction): boolean { + unassignFromCustomer($event: Event, device: DeviceInfo) { + if ($event) { + $event.stopPropagation(); + } + const isPublic = device.customerIsPublic; + let title; + let content; + if (isPublic) { + title = this.translate.instant('device.make-private-device-title', {deviceName: device.name}); + content = this.translate.instant('device.make-private-device-text'); + } else { + title = this.translate.instant('device.unassign-device-title', {deviceName: device.name}); + content = this.translate.instant('device.unassign-device-text'); + } + this.dialogService.confirm( + this.translate.instant(title), + this.translate.instant(content), + this.translate.instant('action.no'), + this.translate.instant('action.yes'), + true + ).subscribe((res) => { + if (res) { + this.deviceService.unassignDeviceFromCustomer(device.id.id).subscribe( + () => { + this.config.table.updateData(); + } + ); + } + } + ); + } + + manageCredentials($event: Event, device: Device) { + if ($event) { + $event.stopPropagation(); + } + this.dialog.open(DeviceCredentialsDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + deviceId: device.id.id, + isReadOnly: this.config.componentsData.deviceScope === 'customer_user' + } + }); + } + + onDeviceAction(action: EntityAction): boolean { switch (action.action) { case 'makePublic': this.makePublic(action.event, action.entity); return true; + case 'assignToCustomer': + this.assignToCustomer(action.event, action.entity); + return true; + case 'unassignFromCustomer': + this.unassignFromCustomer(action.event, action.entity); + return true; + case 'manageCredentials': + this.manageCredentials(action.event, action.entity); + return true; } return false; } diff --git a/ui-ngx/src/app/modules/home/pages/profile/profile.component.ts b/ui-ngx/src/app/modules/home/pages/profile/profile.component.ts index 1f2dbfd697..7bb273ae0b 100644 --- a/ui-ngx/src/app/modules/home/pages/profile/profile.component.ts +++ b/ui-ngx/src/app/modules/home/pages/profile/profile.component.ts @@ -14,26 +14,24 @@ /// limitations under the License. /// -import { Component, OnInit } from '@angular/core'; -import { UserService } from '@core/http/user.service'; -import { User } from '@shared/models/user.model'; -import { Authority } from '@shared/models/authority.enum'; -import { PageComponent } from '@shared/components/page.component'; -import { select, Store } from '@ngrx/store'; -import { AppState } from '@core/core.state'; -import { getCurrentAuthUser, selectAuthUser } from '@core/auth/auth.selectors'; -import { mergeMap, take } from 'rxjs/operators'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard'; -import { ActionAuthUpdateUserDetails } from '@core/auth/auth.actions'; -import { environment as env } from '@env/environment'; -import { TranslateService } from '@ngx-translate/core'; -import { ActionSettingsChangeLanguage } from '@core/settings/settings.actions'; -import { ChangePasswordDialogComponent } from '@modules/home/pages/profile/change-password-dialog.component'; -import { MatDialog } from '@angular/material'; -import { DialogService } from '@core/services/dialog.service'; -import { AuthService } from '@core/auth/auth.service'; -import { ActivatedRoute } from '@angular/router'; +import {Component, OnInit} from '@angular/core'; +import {UserService} from '@core/http/user.service'; +import {User} from '@shared/models/user.model'; +import {Authority} from '@shared/models/authority.enum'; +import {PageComponent} from '@shared/components/page.component'; +import {Store} from '@ngrx/store'; +import {AppState} from '@core/core.state'; +import {FormBuilder, FormGroup, Validators} from '@angular/forms'; +import {HasConfirmForm} from '@core/guards/confirm-on-exit.guard'; +import {ActionAuthUpdateUserDetails} from '@core/auth/auth.actions'; +import {environment as env} from '@env/environment'; +import {TranslateService} from '@ngx-translate/core'; +import {ActionSettingsChangeLanguage} from '@core/settings/settings.actions'; +import {ChangePasswordDialogComponent} from '@modules/home/pages/profile/change-password-dialog.component'; +import {MatDialog} from '@angular/material'; +import {DialogService} from '@core/services/dialog.service'; +import {AuthService} from '@core/auth/auth.service'; +import {ActivatedRoute} from '@angular/router'; @Component({ selector: 'tb-profile', diff --git a/ui-ngx/src/app/shared/components/entity/entities-table-config.models.ts b/ui-ngx/src/app/shared/components/entity/entities-table-config.models.ts index 28cfcd6a18..c140636c0c 100644 --- a/ui-ngx/src/app/shared/components/entity/entities-table-config.models.ts +++ b/ui-ngx/src/app/shared/components/entity/entities-table-config.models.ts @@ -122,6 +122,7 @@ export class EntityTableConfig, P extends PageLink = P cellActionDescriptors: Array> = []; groupActionDescriptors: Array> = []; headerActionDescriptors: Array = []; + addActionDescriptors: Array = []; headerComponent: Type>; addEntity: CreateEntityOperation = null; detailsReadonly: EntityBooleanFunction = () => false; diff --git a/ui-ngx/src/app/shared/components/entity/entities-table.component.html b/ui-ngx/src/app/shared/components/entity/entities-table.component.html index 6cbcd63490..9ddc5ed91e 100644 --- a/ui-ngx/src/app/shared/components/entity/entities-table.component.html +++ b/ui-ngx/src/app/shared/components/entity/entities-table.component.html @@ -42,11 +42,32 @@ asButton historyOnly> - +
+ + + + + + + +
+ + + + + + + {{ translate.get(noEntitiesMatchingText, {entity: searchText}) | async }} + + + + + {{ entityRequiredText | translate }} + +
diff --git a/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts b/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts new file mode 100644 index 0000000000..982e929071 --- /dev/null +++ b/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts @@ -0,0 +1,274 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import {AfterViewInit, Component, ElementRef, forwardRef, Input, OnInit, ViewChild} from '@angular/core'; +import {ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR} from '@angular/forms'; +import {Observable} from 'rxjs'; +import {map, mergeMap, startWith, tap} from 'rxjs/operators'; +import {Store} from '@ngrx/store'; +import {AppState} from '@app/core/core.state'; +import {TranslateService} from '@ngx-translate/core'; +import {AliasEntityType, EntityType} from '@shared/models/entity-type.models'; +import {BaseData} from '@shared/models/base-data'; +import {EntityId} from '@shared/models/id/entity-id'; +import {EntityService} from '@core/http/entity.service'; + +@Component({ + selector: 'tb-entity-autocomplete', + templateUrl: './entity-autocomplete.component.html', + styleUrls: [], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => EntityAutocompleteComponent), + multi: true + }] +}) +export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit, AfterViewInit { + + selectEntityFormGroup: FormGroup; + + modelValue: string | null; + + entityTypeValue: EntityType | AliasEntityType; + + entitySubtypeValue: string; + + @Input() + set entityType(entityType: EntityType) { + if (this.entityTypeValue !== entityType) { + this.entityTypeValue = entityType; + this.load(); + } + } + + @Input() + set entitySubtype(entitySubtype: string) { + if (this.entitySubtypeValue !== entitySubtype) { + this.entitySubtypeValue = entitySubtype; + const currentEntity = this.getCurrentEntity(); + if (currentEntity) { + if ((currentEntity as any).type !== this.entitySubtypeValue) { + this.reset(); + } + } + } + } + + @Input() + excludeEntityIds: Array; + + @Input() + required: boolean; + + @Input() + disabled: boolean; + + @ViewChild('entityInput', {static: true}) entityInput: ElementRef; + + entityText: string; + noEntitiesMatchingText: string; + entityRequiredText: string; + + filteredEntities: Observable>>; + + private searchText = ''; + + private propagateChange = (v: any) => { }; + + constructor(private store: Store, + public translate: TranslateService, + private entityService: EntityService, + private fb: FormBuilder) { + this.selectEntityFormGroup = this.fb.group({ + entity: [null] + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + ngOnInit() { + this.filteredEntities = this.selectEntityFormGroup.get('dashboard').valueChanges + .pipe( + tap(value => { + let modelValue; + if (typeof value === 'string' || !value) { + modelValue = null; + } else { + modelValue = value.id.id; + } + this.updateView(modelValue); + }), + startWith>(''), + map(value => value ? (typeof value === 'string' ? value : value.name) : ''), + mergeMap(name => this.fetchEntities(name) ) + ); + } + + ngAfterViewInit(): void {} + + load(): void { + if (this.entityTypeValue) { + switch (this.entityTypeValue) { + case EntityType.ASSET: + this.entityText = 'asset.asset'; + this.noEntitiesMatchingText = 'asset.no-assets-matching'; + this.entityRequiredText = 'asset.asset-required'; + break; + case EntityType.DEVICE: + this.entityText = 'device.device'; + this.noEntitiesMatchingText = 'device.no-devices-matching'; + this.entityRequiredText = 'device.device-required'; + break; + case EntityType.ENTITY_VIEW: + this.entityText = 'entity-view.entity-view'; + this.noEntitiesMatchingText = 'entity-view.no-entity-views-matching'; + this.entityRequiredText = 'entity-view.entity-view-required'; + break; + case EntityType.RULE_CHAIN: + this.entityText = 'rulechain.rulechain'; + this.noEntitiesMatchingText = 'rulechain.no-rulechains-matching'; + this.entityRequiredText = 'rulechain.rulechain-required'; + break; + case EntityType.TENANT: + this.entityText = 'tenant.tenant'; + this.noEntitiesMatchingText = 'tenant.no-tenants-matching'; + this.entityRequiredText = 'tenant.tenant-required'; + break; + case EntityType.CUSTOMER: + this.entityText = 'customer.customer'; + this.noEntitiesMatchingText = 'customer.no-customers-matching'; + this.entityRequiredText = 'customer.customer-required'; + break; + case EntityType.USER: + this.entityText = 'user.user'; + this.noEntitiesMatchingText = 'user.no-users-matching'; + this.entityRequiredText = 'user.user-required'; + break; + case EntityType.DASHBOARD: + this.entityText = 'dashboard.dashboard'; + this.noEntitiesMatchingText = 'dashboard.no-dashboards-matching'; + this.entityRequiredText = 'dashboard.dashboard-required'; + break; + case EntityType.ALARM: + this.entityText = 'alarm.alarm'; + this.noEntitiesMatchingText = 'alarm.no-alarms-matching'; + this.entityRequiredText = 'alarm.alarm-required'; + break; + case AliasEntityType.CURRENT_CUSTOMER: + this.entityText = 'customer.default-customer'; + this.noEntitiesMatchingText = 'customer.no-customers-matching'; + this.entityRequiredText = 'customer.default-customer-required'; + break; + } + } + const currentEntity = this.getCurrentEntity(); + if (currentEntity) { + const currentEntityType = currentEntity.id.entityType; + if (this.entityTypeValue && currentEntityType !== this.entityTypeValue) { + this.reset(); + } + } + } + + getCurrentEntity(): BaseData | null { + const currentEntity = this.selectEntityFormGroup.get('entity').value; + if (currentEntity && typeof currentEntity !== 'string') { + return currentEntity as BaseData; + } else { + return null; + } + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + writeValue(value: string | null): void { + this.searchText = ''; + if (value != null) { + let targetEntityType = this.entityTypeValue; + if (targetEntityType === AliasEntityType.CURRENT_CUSTOMER) { + targetEntityType = EntityType.CUSTOMER; + } + this.entityService.getEntity(targetEntityType, value).subscribe( + (entity) => { + this.modelValue = entity.id.id; + this.selectEntityFormGroup.get('entity').patchValue(entity, {emitEvent: true}); + } + ); + } else { + this.modelValue = null; + this.selectEntityFormGroup.get('entity').patchValue(null, {emitEvent: true}); + } + } + + reset() { + this.selectEntityFormGroup.get('entity').patchValue(null, {emitEvent: true}); + } + + updateView(value: string | null) { + if (this.modelValue !== value) { + this.modelValue = value; + this.propagateChange(this.modelValue); + } + } + + displayEntityFn(entity?: BaseData): string | undefined { + return entity ? entity.name : undefined; + } + + fetchEntities(searchText?: string): Observable>> { + this.searchText = searchText; + let targetEntityType = this.entityTypeValue; + if (targetEntityType === AliasEntityType.CURRENT_CUSTOMER) { + targetEntityType = EntityType.CUSTOMER; + } + return this.entityService.getEntitiesByNameFilter(targetEntityType, searchText, + 50, this.entitySubtypeValue, false, true).pipe( + map((data) => { + if (data) { + if (this.excludeEntityIds && this.excludeEntityIds.length) { + const entities: Array> = []; + data.forEach((entity) => { + if (this.excludeEntityIds.indexOf(entity.id.id) === -1) { + entities.push(entity); + } + }); + return entities; + } else { + return data; + } + } else { + return []; + } + } + )); + } + + clear() { + this.selectEntityFormGroup.get('entity').patchValue(null, {emitEvent: true}); + setTimeout(() => { + this.entityInput.nativeElement.blur(); + this.entityInput.nativeElement.focus(); + }, 0); + } + +} diff --git a/ui-ngx/src/app/shared/models/alarm.models.ts b/ui-ngx/src/app/shared/models/alarm.models.ts new file mode 100644 index 0000000000..8fbd24de3b --- /dev/null +++ b/ui-ngx/src/app/shared/models/alarm.models.ts @@ -0,0 +1,51 @@ +/// +/// Copyright © 2016-2019 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 {BaseData} from '@shared/models/base-data'; +import {AssetId} from '@shared/models/id/asset-id'; +import {TenantId} from '@shared/models/id/tenant-id'; +import {CustomerId} from '@shared/models/id/customer-id'; +import {AlarmId} from '@shared/models/id/alarm-id'; +import {EntityId} from '@shared/models/id/entity-id'; + +export enum AlarmSeverity { + CRITICAL = 'CRITICAL', + MAJOR = 'MAJOR', + MINOR = 'MINOR', + WARNING = 'WARNING', + INDETERMINATE = 'INDETERMINATE' +} + +export enum AlarmStatus { + ACTIVE_UNACK = 'ACTIVE_UNACK', + ACTIVE_ACK = 'ACTIVE_ACK', + CLEARED_UNACK = 'CLEARED_UNACK', + CLEARED_ACK = 'CLEARED_ACK' +} + +export interface Alarm extends BaseData { + tenantId: TenantId; + type: string; + originator: EntityId; + severity: AlarmSeverity; + status: AlarmStatus; + startTs: number; + endTs: number; + ackTs: number; + clearTs: number; + propagate: boolean; + details?: any; +} diff --git a/ui-ngx/src/app/shared/models/asset.models.ts b/ui-ngx/src/app/shared/models/asset.models.ts new file mode 100644 index 0000000000..b048317d22 --- /dev/null +++ b/ui-ngx/src/app/shared/models/asset.models.ts @@ -0,0 +1,34 @@ +/// +/// Copyright © 2016-2019 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 {BaseData} from '@shared/models/base-data'; +import {AssetId} from './id/asset-id'; +import {TenantId} from '@shared/models/id/tenant-id'; +import {CustomerId} from '@shared/models/id/customer-id'; +import {DeviceCredentialsId} from '@shared/models/id/device-credentials-id'; + +export interface Asset extends BaseData { + tenantId: TenantId; + customerId: CustomerId; + name: string; + type: string; + additionalInfo?: any; +} + +export interface AssetInfo extends Asset { + customerTitle: string; + customerIsPublic: boolean; +} diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index ebd3acb44a..be0d1637da 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -18,6 +18,7 @@ import {BaseData} from '@shared/models/base-data'; import {DeviceId} from './id/device-id'; import {TenantId} from '@shared/models/id/tenant-id'; import {CustomerId} from '@shared/models/id/customer-id'; +import {DeviceCredentialsId} from '@shared/models/id/device-credentials-id'; export interface Device extends BaseData { tenantId: TenantId; @@ -32,3 +33,22 @@ export interface DeviceInfo extends Device { customerTitle: string; customerIsPublic: boolean; } + +export enum DeviceCredentialsType { + ACCESS_TOKEN = 'ACCESS_TOKEN', + X509_CERTIFICATE = 'X509_CERTIFICATE' +} + +export const credentialTypeNames = new Map( + [ + [DeviceCredentialsType.ACCESS_TOKEN, 'Access token'], + [DeviceCredentialsType.X509_CERTIFICATE, 'X.509 Certificate'], + ] +); + +export interface DeviceCredentials extends BaseData { + deviceId: DeviceId; + credentialsType: DeviceCredentialsType; + credentialsId: string; + credentialsValue: string; +} diff --git a/ui-ngx/src/app/shared/models/entity-type.models.ts b/ui-ngx/src/app/shared/models/entity-type.models.ts index 4b9687f019..475d24d67f 100644 --- a/ui-ngx/src/app/shared/models/entity-type.models.ts +++ b/ui-ngx/src/app/shared/models/entity-type.models.ts @@ -31,6 +31,10 @@ export enum EntityType { WIDGET_TYPE = 'WIDGET_TYPE' } +export enum AliasEntityType { + CURRENT_CUSTOMER = 'CURRENT_CUSTOMER' +} + export interface EntityTypeTranslation { type: string; typePlural: string; diff --git a/ui-ngx/src/app/shared/models/entity-view.models.ts b/ui-ngx/src/app/shared/models/entity-view.models.ts new file mode 100644 index 0000000000..88f4a1aa64 --- /dev/null +++ b/ui-ngx/src/app/shared/models/entity-view.models.ts @@ -0,0 +1,50 @@ +/// +/// Copyright © 2016-2019 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 {BaseData} from '@shared/models/base-data'; +import {AssetId} from './id/asset-id'; +import {TenantId} from '@shared/models/id/tenant-id'; +import {CustomerId} from '@shared/models/id/customer-id'; +import {EntityViewId} from '@shared/models/id/entity-view-id'; +import {EntityId} from '@shared/models/id/entity-id'; + +export interface AttributesEntityView { + cs: Array; + ss: Array; + sh: Array; +} + +export interface TelemetryEntityView { + timeseries: Array; + attributes: AttributesEntityView; +} + +export interface EntityView extends BaseData { + tenantId: TenantId; + customerId: CustomerId; + entityId: EntityId; + name: string; + type: string; + keys: TelemetryEntityView; + startTimeMs: number; + endTimeMs: number; + additionalInfo?: any; +} + +export interface EntityViewInfo extends EntityView { + customerTitle: string; + customerIsPublic: boolean; +} diff --git a/ui-ngx/src/app/shared/models/id/alarm-id.ts b/ui-ngx/src/app/shared/models/id/alarm-id.ts new file mode 100644 index 0000000000..8c5b80cf00 --- /dev/null +++ b/ui-ngx/src/app/shared/models/id/alarm-id.ts @@ -0,0 +1,26 @@ +/// +/// Copyright © 2016-2019 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 { EntityId } from './entity-id'; +import { EntityType } from '@shared/models/entity-type.models'; + +export class AlarmId implements EntityId { + entityType = EntityType.ALARM; + id: string; + constructor(id: string) { + this.id = id; + } +} diff --git a/ui-ngx/src/app/shared/models/id/asset-id.ts b/ui-ngx/src/app/shared/models/id/asset-id.ts new file mode 100644 index 0000000000..cfb9afa0dd --- /dev/null +++ b/ui-ngx/src/app/shared/models/id/asset-id.ts @@ -0,0 +1,26 @@ +/// +/// Copyright © 2016-2019 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 { EntityId } from './entity-id'; +import { EntityType } from '@shared/models/entity-type.models'; + +export class AssetId implements EntityId { + entityType = EntityType.ASSET; + id: string; + constructor(id: string) { + this.id = id; + } +} diff --git a/ui-ngx/src/app/shared/models/id/device-credentials-id.ts b/ui-ngx/src/app/shared/models/id/device-credentials-id.ts new file mode 100644 index 0000000000..a76d5a5251 --- /dev/null +++ b/ui-ngx/src/app/shared/models/id/device-credentials-id.ts @@ -0,0 +1,24 @@ +/// +/// Copyright © 2016-2019 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 { HasUUID } from '@shared/models/id/has-uuid'; + +export class DeviceCredentialsId implements HasUUID { + id: string; + constructor(id: string) { + this.id = id; + } +} diff --git a/ui-ngx/src/app/shared/models/id/entity-view-id.ts b/ui-ngx/src/app/shared/models/id/entity-view-id.ts new file mode 100644 index 0000000000..dbc16acddd --- /dev/null +++ b/ui-ngx/src/app/shared/models/id/entity-view-id.ts @@ -0,0 +1,26 @@ +/// +/// Copyright © 2016-2019 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 { EntityId } from './entity-id'; +import { EntityType } from '@shared/models/entity-type.models'; + +export class EntityViewId implements EntityId { + entityType = EntityType.ENTITY_VIEW; + id: string; + constructor(id: string) { + this.id = id; + } +} diff --git a/ui-ngx/src/app/shared/models/id/rule-chain-id.ts b/ui-ngx/src/app/shared/models/id/rule-chain-id.ts new file mode 100644 index 0000000000..f34bd581d4 --- /dev/null +++ b/ui-ngx/src/app/shared/models/id/rule-chain-id.ts @@ -0,0 +1,26 @@ +/// +/// Copyright © 2016-2019 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 { EntityId } from './entity-id'; +import { EntityType } from '@shared/models/entity-type.models'; + +export class RuleChainId implements EntityId { + entityType = EntityType.RULE_CHAIN; + id: string; + constructor(id: string) { + this.id = id; + } +} diff --git a/ui-ngx/src/app/shared/models/id/rule-node-id.ts b/ui-ngx/src/app/shared/models/id/rule-node-id.ts new file mode 100644 index 0000000000..83b06fa681 --- /dev/null +++ b/ui-ngx/src/app/shared/models/id/rule-node-id.ts @@ -0,0 +1,26 @@ +/// +/// Copyright © 2016-2019 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 { EntityId } from './entity-id'; +import { EntityType } from '@shared/models/entity-type.models'; + +export class RuleNodeId implements EntityId { + entityType = EntityType.RULE_NODE; + id: string; + constructor(id: string) { + this.id = id; + } +} diff --git a/ui-ngx/src/app/shared/models/id/widget-type-id.ts b/ui-ngx/src/app/shared/models/id/widget-type-id.ts new file mode 100644 index 0000000000..850d279de7 --- /dev/null +++ b/ui-ngx/src/app/shared/models/id/widget-type-id.ts @@ -0,0 +1,26 @@ +/// +/// Copyright © 2016-2019 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 { EntityId } from './entity-id'; +import { EntityType } from '@shared/models/entity-type.models'; + +export class WidgetTypeId implements EntityId { + entityType = EntityType.WIDGET_TYPE; + id: string; + constructor(id: string) { + this.id = id; + } +} diff --git a/ui-ngx/src/app/shared/models/id/widgets-bundle-id.ts b/ui-ngx/src/app/shared/models/id/widgets-bundle-id.ts new file mode 100644 index 0000000000..2e560c2bfc --- /dev/null +++ b/ui-ngx/src/app/shared/models/id/widgets-bundle-id.ts @@ -0,0 +1,26 @@ +/// +/// Copyright © 2016-2019 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 { EntityId } from './entity-id'; +import { EntityType } from '@shared/models/entity-type.models'; + +export class WidgetsBundleId implements EntityId { + entityType = EntityType.WIDGETS_BUNDLE; + id: string; + constructor(id: string) { + this.id = id; + } +} diff --git a/ui-ngx/src/app/shared/models/rule-chain.models.ts b/ui-ngx/src/app/shared/models/rule-chain.models.ts new file mode 100644 index 0000000000..52d396d888 --- /dev/null +++ b/ui-ngx/src/app/shared/models/rule-chain.models.ts @@ -0,0 +1,37 @@ +/// +/// Copyright © 2016-2019 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 {BaseData} from '@shared/models/base-data'; +import {AssetId} from '@shared/models/id/asset-id'; +import {TenantId} from '@shared/models/id/tenant-id'; +import {CustomerId} from '@shared/models/id/customer-id'; +import {RuleChainId} from '@shared/models/id/rule-chain-id'; +import {RuleNodeId} from '@shared/models/id/rule-node-id'; + +export interface RuleChainConfiguration { + todo: Array; + // TODO: +} + +export interface RuleChain extends BaseData { + tenantId: TenantId; + name: string; + firstRuleNodeId: RuleNodeId; + root: boolean; + debugMode: boolean; + configuration: RuleChainConfiguration; + additionalInfo?: any; +} diff --git a/ui-ngx/src/app/shared/models/rule-node.models.ts b/ui-ngx/src/app/shared/models/rule-node.models.ts new file mode 100644 index 0000000000..5448bd21af --- /dev/null +++ b/ui-ngx/src/app/shared/models/rule-node.models.ts @@ -0,0 +1,36 @@ +/// +/// Copyright © 2016-2019 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 {BaseData} from '@shared/models/base-data'; +import {AssetId} from '@shared/models/id/asset-id'; +import {TenantId} from '@shared/models/id/tenant-id'; +import {CustomerId} from '@shared/models/id/customer-id'; +import {RuleChainId} from '@shared/models/id/rule-chain-id'; +import {RuleNodeId} from '@shared/models/id/rule-node-id'; + +export interface RuleNodeConfiguration { + todo: Array; + // TODO: +} + +export interface RuleNode extends BaseData { + ruleChainId: RuleChainId; + type: string; + name: string; + debugMode: boolean; + configuration: RuleNodeConfiguration; + additionalInfo?: any; +} diff --git a/ui-ngx/src/app/shared/models/widget-type.models.ts b/ui-ngx/src/app/shared/models/widget-type.models.ts new file mode 100644 index 0000000000..f081e0942f --- /dev/null +++ b/ui-ngx/src/app/shared/models/widget-type.models.ts @@ -0,0 +1,33 @@ +/// +/// Copyright © 2016-2019 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 {BaseData} from '@shared/models/base-data'; +import {TenantId} from '@shared/models/id/tenant-id'; +import {WidgetsBundleId} from '@shared/models/id/widgets-bundle-id'; +import {WidgetTypeId} from '@shared/models/id/widget-type-id'; + +export interface WidgetTypeDescriptor { + todo: Array; + // TODO: +} + +export interface WidgetType extends BaseData { + tenantId: TenantId; + bundleAlias: string; + alias: string; + name: string; + descriptor: WidgetTypeDescriptor; +} diff --git a/ui-ngx/src/app/shared/models/widgets-bundle.model.ts b/ui-ngx/src/app/shared/models/widgets-bundle.model.ts new file mode 100644 index 0000000000..5d0454bff4 --- /dev/null +++ b/ui-ngx/src/app/shared/models/widgets-bundle.model.ts @@ -0,0 +1,26 @@ +/// +/// Copyright © 2016-2019 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 {BaseData} from '@shared/models/base-data'; +import {TenantId} from '@shared/models/id/tenant-id'; +import {WidgetsBundleId} from '@shared/models/id/widgets-bundle-id'; + +export interface WidgetsBundle extends BaseData { + tenantId: TenantId; + alias: string; + title: string; + image: string; +} diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index 1390aefefb..d309519b1b 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -80,6 +80,7 @@ import { HighlightPipe } from '@shared/pipe/highlight.pipe'; import {DashboardAutocompleteComponent} from '@shared/components/dashboard-autocomplete.component'; import {EntitySubTypeAutocompleteComponent} from '@shared/components/entity/entity-subtype-autocomplete.component'; import {EntitySubTypeSelectComponent} from './components/entity/entity-subtype-select.component'; +import {EntityAutocompleteComponent} from './components/entity/entity-autocomplete.component'; @NgModule({ providers: [ @@ -122,6 +123,7 @@ import {EntitySubTypeSelectComponent} from './components/entity/entity-subtype-s DashboardAutocompleteComponent, EntitySubTypeAutocompleteComponent, EntitySubTypeSelectComponent, + EntityAutocompleteComponent, NospacePipe, MillisecondsToTimeStringPipe, EnumToArrayPipe, @@ -189,6 +191,7 @@ import {EntitySubTypeSelectComponent} from './components/entity/entity-subtype-s DashboardAutocompleteComponent, EntitySubTypeAutocompleteComponent, EntitySubTypeSelectComponent, + EntityAutocompleteComponent, // ValueInputComponent, MatButtonModule, MatCheckboxModule, From 984e260be38ca2c16f37bbfa9ff68aa496d89084 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 15 Aug 2019 14:48:14 +0300 Subject: [PATCH 010/133] Devices and Customers pages implementation --- ui-ngx/src/app/core/http/device.service.ts | 5 + .../assign-to-customer-dialog.component.html | 57 +++++ .../assign-to-customer-dialog.component.ts | 117 ++++++++++ .../home/dialogs/home-dialogs.module.ts | 38 +++ .../pages/customer/customer-routing.module.ts | 72 ++++++ .../pages/customer/customer.component.html | 79 +++++++ .../home/pages/customer/customer.component.ts | 79 +++++++ .../home/pages/customer/customer.module.ts | 36 +++ .../customers-table-config.resolver.ts | 105 +++++++++ .../home/pages/device/device.module.ts | 2 + .../device/devices-table-config.resolver.ts | 217 +++++++++++++++--- .../modules/home/pages/home-pages.module.ts | 4 +- .../tenant/tenants-table-config.resolver.ts | 2 +- .../pages/user/users-table-config.resolver.ts | 3 +- .../entity/entities-table-config.models.ts | 4 +- .../entity/entities-table.component.html | 48 ++-- .../entity/entities-table.component.ts | 51 +++- .../entity/entity-autocomplete.component.ts | 2 +- .../entity/entity-details-panel.component.ts | 4 +- .../models/datasource/entity-datasource.ts | 4 +- .../assets/locale/locale.constant-en_US.json | 1 + 21 files changed, 853 insertions(+), 77 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.ts create mode 100644 ui-ngx/src/app/modules/home/dialogs/home-dialogs.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/customer/customer.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/customer/customer.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/customer/customer.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/customer/customers-table-config.resolver.ts diff --git a/ui-ngx/src/app/core/http/device.service.ts b/ui-ngx/src/app/core/http/device.service.ts index f970a90475..a45f89077b 100644 --- a/ui-ngx/src/app/core/http/device.service.ts +++ b/ui-ngx/src/app/core/http/device.service.ts @@ -102,6 +102,11 @@ export class DeviceService { return this.http.post(`/api/customer/public/device/${deviceId}`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); } + public assignDeviceToCustomer(customerId: string, deviceId: string, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.post(`/api/customer/${customerId}/device/${deviceId}`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + public unassignDeviceFromCustomer(deviceId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { return this.http.delete(`/api/customer/device/${deviceId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); } diff --git a/ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.html b/ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.html new file mode 100644 index 0000000000..3d9f36c62d --- /dev/null +++ b/ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.html @@ -0,0 +1,57 @@ + +
+ +

{{ assignToCustomerTitle | translate }}

+ + +
+ + +
+
+
+ {{ assignToCustomerText | translate }} + + +
+
+
+ + + +
+
diff --git a/ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.ts b/ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.ts new file mode 100644 index 0000000000..ea40fea272 --- /dev/null +++ b/ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.ts @@ -0,0 +1,117 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import {Component, Inject, OnInit, SkipSelf} from '@angular/core'; +import {ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef} from '@angular/material'; +import {PageComponent} from '@shared/components/page.component'; +import {Store} from '@ngrx/store'; +import {AppState} from '@core/core.state'; +import {FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators} from '@angular/forms'; +import {DeviceService} from '@core/http/device.service'; +import {EntityId} from '@shared/models/id/entity-id'; +import {EntityType} from '@shared/models/entity-type.models'; +import {forkJoin, Observable} from 'rxjs'; + +export interface AssignToCustomerDialogData { + entityIds: Array; + entityType: EntityType; +} + +@Component({ + selector: 'tb-assign-to-customer-dialog', + templateUrl: './assign-to-customer-dialog.component.html', + providers: [{provide: ErrorStateMatcher, useExisting: AssignToCustomerDialogComponent}], + styleUrls: [] +}) +export class AssignToCustomerDialogComponent extends PageComponent implements OnInit, ErrorStateMatcher { + + assignToCustomerFormGroup: FormGroup; + + submitted = false; + + entityType = EntityType; + + assignToCustomerTitle: string; + assignToCustomerText: string; + + constructor(protected store: Store, + @Inject(MAT_DIALOG_DATA) public data: AssignToCustomerDialogData, + private deviceService: DeviceService, + @SkipSelf() private errorStateMatcher: ErrorStateMatcher, + public dialogRef: MatDialogRef, + public fb: FormBuilder) { + super(store); + } + + ngOnInit(): void { + this.assignToCustomerFormGroup = this.fb.group({ + customerId: [null, [Validators.required]] + }); + switch (this.data.entityType) { + case EntityType.DEVICE: + this.assignToCustomerTitle = 'device.assign-device-to-customer'; + this.assignToCustomerText = 'device.assign-to-customer-text'; + break; + case EntityType.ASSET: + // TODO: + break; + case EntityType.ENTITY_VIEW: + // TODO: + break; + } + } + + isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { + const originalErrorState = this.errorStateMatcher.isErrorState(control, form); + const customErrorState = !!(control && control.invalid && this.submitted); + return originalErrorState || customErrorState; + } + + cancel(): void { + this.dialogRef.close(false); + } + + assign(): void { + this.submitted = true; + const customerId: string = this.assignToCustomerFormGroup.get('customerId').value; + const tasks: Observable[] = []; + this.data.entityIds.forEach( + (entityId) => { + tasks.push(this.getAssignToCustomerTask(customerId, entityId.id)); + } + ); + forkJoin(tasks).subscribe( + () => { + this.dialogRef.close(true); + } + ); + } + + private getAssignToCustomerTask(customerId: string, entityId: string): Observable { + switch (this.data.entityType) { + case EntityType.DEVICE: + return this.deviceService.assignDeviceToCustomer(customerId, entityId); + break; + case EntityType.ASSET: + // TODO: + break; + case EntityType.ENTITY_VIEW: + // TODO: + break; + } + } + +} diff --git a/ui-ngx/src/app/modules/home/dialogs/home-dialogs.module.ts b/ui-ngx/src/app/modules/home/dialogs/home-dialogs.module.ts new file mode 100644 index 0000000000..de8d55263d --- /dev/null +++ b/ui-ngx/src/app/modules/home/dialogs/home-dialogs.module.ts @@ -0,0 +1,38 @@ +/// +/// Copyright © 2016-2019 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 { CommonModule } from '@angular/common'; +import { SharedModule } from '@app/shared/shared.module'; +import {AssignToCustomerDialogComponent} from '@modules/home/dialogs/assign-to-customer-dialog.component'; + +@NgModule({ + entryComponents: [ + AssignToCustomerDialogComponent + ], + declarations: + [ + AssignToCustomerDialogComponent + ], + imports: [ + CommonModule, + SharedModule + ], + exports: [ + AssignToCustomerDialogComponent + ] +}) +export class HomeDialogsModule { } diff --git a/ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts b/ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts new file mode 100644 index 0000000000..e2a8c44359 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts @@ -0,0 +1,72 @@ +/// +/// Copyright © 2016-2019 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 {EntitiesTableComponent} from '@shared/components/entity/entities-table.component'; +import {Authority} from '@shared/models/authority.enum'; +import {UsersTableConfigResolver} from '../user/users-table-config.resolver'; +import {CustomersTableConfigResolver} from './customers-table-config.resolver'; + +const routes: Routes = [ + { + path: 'customers', + data: { + breadcrumb: { + label: 'customer.customers', + icon: 'supervisor_account' + } + }, + children: [ + { + path: '', + component: EntitiesTableComponent, + data: { + auth: [Authority.TENANT_ADMIN], + title: 'customer.customers' + }, + resolve: { + entitiesTableConfig: CustomersTableConfigResolver + } + }, + { + path: ':customerId/users', + component: EntitiesTableComponent, + data: { + auth: [Authority.TENANT_ADMIN], + title: 'user.customer-users', + breadcrumb: { + label: 'user.customer-users', + icon: 'account_circle' + } + }, + resolve: { + entitiesTableConfig: UsersTableConfigResolver + } + } + ] + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], + providers: [ + CustomersTableConfigResolver + ] +}) +export class CustomerRoutingModule { } diff --git a/ui-ngx/src/app/modules/home/pages/customer/customer.component.html b/ui-ngx/src/app/modules/home/pages/customer/customer.component.html new file mode 100644 index 0000000000..8637388038 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/customer/customer.component.html @@ -0,0 +1,79 @@ + +
+ + + + + +
+ +
+
+
+
+
+ + customer.title + + + {{ 'customer.title-required' | translate }} + + +
+ + customer.description + + +
+ +
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/customer/customer.component.ts b/ui-ngx/src/app/modules/home/pages/customer/customer.component.ts new file mode 100644 index 0000000000..3a7d4b55cb --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/customer/customer.component.ts @@ -0,0 +1,79 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { Customer } from '@shared/models/customer.model'; +import { ContactBasedComponent } from '@shared/components/entity/contact-based.component'; +import {Tenant} from '@app/shared/models/tenant.model'; +import {ActionNotificationShow} from '@app/core/notification/notification.actions'; +import {TranslateService} from '@ngx-translate/core'; + +@Component({ + selector: 'tb-customer', + templateUrl: './customer.component.html' +}) +export class CustomerComponent extends ContactBasedComponent { + + isPublic = false; + + constructor(protected store: Store, + protected translate: TranslateService, + protected fb: FormBuilder) { + super(store, fb); + } + + hideDelete() { + if (this.entitiesTableConfig) { + return !this.entitiesTableConfig.deleteEnabled(this.entity); + } else { + return false; + } + } + + buildEntityForm(entity: Customer): FormGroup { + return this.fb.group( + { + title: [entity ? entity.title : '', [Validators.required]], + additionalInfo: this.fb.group( + { + description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''] + } + ) + } + ); + } + + updateEntityForm(entity: Customer) { + this.isPublic = entity.additionalInfo && entity.additionalInfo.isPublic; + this.entityForm.patchValue({title: entity.title}); + this.entityForm.patchValue({additionalInfo: {description: entity.additionalInfo ? entity.additionalInfo.description : ''}}); + } + + onCustomerIdCopied(event) { + this.store.dispatch(new ActionNotificationShow( + { + message: this.translate.instant('customer.idCopiedMessage'), + type: 'success', + duration: 750, + verticalPosition: 'bottom', + horizontalPosition: 'right' + })); + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/customer/customer.module.ts b/ui-ngx/src/app/modules/home/pages/customer/customer.module.ts new file mode 100644 index 0000000000..c0b5ff743f --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/customer/customer.module.ts @@ -0,0 +1,36 @@ +/// +/// Copyright © 2016-2019 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 { CommonModule } from '@angular/common'; +import { SharedModule } from '@shared/shared.module'; +import {CustomerComponent} from '@modules/home/pages/customer/customer.component'; +import {CustomerRoutingModule} from './customer-routing.module'; + +@NgModule({ + entryComponents: [ + CustomerComponent + ], + declarations: [ + CustomerComponent + ], + imports: [ + CommonModule, + SharedModule, + CustomerRoutingModule + ] +}) +export class CustomerModule { } diff --git a/ui-ngx/src/app/modules/home/pages/customer/customers-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/customer/customers-table-config.resolver.ts new file mode 100644 index 0000000000..3eb876d0a4 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/customer/customers-table-config.resolver.ts @@ -0,0 +1,105 @@ +/// +/// Copyright © 2016-2019 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 { Injectable } from '@angular/core'; + +import { Resolve, Router } from '@angular/router'; + +import { Tenant } from '@shared/models/tenant.model'; +import { + DateEntityTableColumn, + EntityTableColumn, + EntityTableConfig +} from '@shared/components/entity/entities-table-config.models'; +import { TranslateService } from '@ngx-translate/core'; +import { DatePipe } from '@angular/common'; +import { + EntityType, + entityTypeResources, + entityTypeTranslations +} from '@shared/models/entity-type.models'; +import { EntityAction } from '@shared/components/entity/entity-component.models'; +import {Customer} from '@app/shared/models/customer.model'; +import {CustomerService} from '@app/core/http/customer.service'; +import {CustomerComponent} from '@modules/home/pages/customer/customer.component'; + +@Injectable() +export class CustomersTableConfigResolver implements Resolve> { + + private readonly config: EntityTableConfig = new EntityTableConfig(); + + constructor(private customerService: CustomerService, + private translate: TranslateService, + private datePipe: DatePipe, + private router: Router) { + + this.config.entityType = EntityType.CUSTOMER; + this.config.entityComponent = CustomerComponent; + this.config.entityTranslations = entityTypeTranslations.get(EntityType.CUSTOMER); + this.config.entityResources = entityTypeResources.get(EntityType.CUSTOMER); + + this.config.columns.push( + new DateEntityTableColumn('createdTime', 'customer.created-time', this.datePipe, '150px'), + new EntityTableColumn('title', 'customer.title'), + new EntityTableColumn('email', 'contact.email'), + new EntityTableColumn('country', 'contact.country'), + new EntityTableColumn('city', 'contact.city') + ); + + this.config.cellActionDescriptors.push( + { + name: this.translate.instant('customer.manage-customer-users'), + icon: 'account_circle', + isEnabled: (customer) => !customer.additionalInfo || !customer.additionalInfo.isPublic, + onAction: ($event, entity) => this.manageCustomerUsers($event, entity) + } + ); + + this.config.deleteEntityTitle = customer => this.translate.instant('customer.delete-customer-title', { customerTitle: customer.title }); + this.config.deleteEntityContent = () => this.translate.instant('customer.delete-customer-text'); + this.config.deleteEntitiesTitle = count => this.translate.instant('customer.delete-customers-title', {count}); + this.config.deleteEntitiesContent = () => this.translate.instant('customer.delete-customers-text'); + + this.config.entitiesFetchFunction = pageLink => this.customerService.getCustomers(pageLink); + this.config.loadEntity = id => this.customerService.getCustomer(id.id); + this.config.saveEntity = customer => this.customerService.saveCustomer(customer); + this.config.deleteEntity = id => this.customerService.deleteCustomer(id.id); + this.config.onEntityAction = action => this.onCustomerAction(action); + } + + resolve(): EntityTableConfig { + this.config.tableTitle = this.translate.instant('customer.customers'); + + return this.config; + } + + manageCustomerUsers($event: Event, customer: Customer) { + if ($event) { + $event.stopPropagation(); + } + this.router.navigateByUrl(`customers/${customer.id.id}/users`); + } + + onCustomerAction(action: EntityAction): boolean { + switch (action.action) { + case 'manageUsers': + this.manageCustomerUsers(action.event, action.entity); + return true; + } + return false; + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/device/device.module.ts b/ui-ngx/src/app/modules/home/pages/device/device.module.ts index 50bcbb5b56..2644e2b26a 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device.module.ts +++ b/ui-ngx/src/app/modules/home/pages/device/device.module.ts @@ -21,6 +21,7 @@ import {DeviceComponent} from '@modules/home/pages/device/device.component'; import {DeviceRoutingModule} from './device-routing.module'; import {DeviceTableHeaderComponent} from '@modules/home/pages/device/device-table-header.component'; import {DeviceCredentialsDialogComponent} from '@modules/home/pages/device/device-credentials-dialog.component'; +import {HomeDialogsModule} from '../../dialogs/home-dialogs.module'; @NgModule({ entryComponents: [ @@ -36,6 +37,7 @@ import {DeviceCredentialsDialogComponent} from '@modules/home/pages/device/devic imports: [ CommonModule, SharedModule, + HomeDialogsModule, DeviceRoutingModule ] }) 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 9cc9ecb078..a57777f318 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 @@ -14,35 +14,26 @@ /// limitations under the License. /// -import { Injectable } from '@angular/core'; +import {Injectable} from '@angular/core'; import {ActivatedRouteSnapshot, Resolve, Router} from '@angular/router'; - -import { Tenant } from '@shared/models/tenant.model'; import { CellActionDescriptor, checkBoxCell, DateEntityTableColumn, EntityTableColumn, - EntityTableConfig, + EntityTableConfig, GroupActionDescriptor, HeaderActionDescriptor } from '@shared/components/entity/entities-table-config.models'; -import { TenantService } from '@core/http/tenant.service'; -import { TranslateService } from '@ngx-translate/core'; -import { DatePipe } from '@angular/common'; -import { - EntityType, - entityTypeResources, - entityTypeTranslations -} from '@shared/models/entity-type.models'; -import { TenantComponent } from '@modules/home/pages/tenant/tenant.component'; -import { EntityAction } from '@shared/components/entity/entity-component.models'; -import { User } from '@shared/models/user.model'; +import {TranslateService} from '@ngx-translate/core'; +import {DatePipe} from '@angular/common'; +import {EntityType, entityTypeResources, entityTypeTranslations} from '@shared/models/entity-type.models'; +import {EntityAction} from '@shared/components/entity/entity-component.models'; import {Device, DeviceCredentials, DeviceInfo} from '@app/shared/models/device.models'; import {DeviceComponent} from '@modules/home/pages/device/device.component'; -import {Observable, of} from 'rxjs'; +import {forkJoin, Observable, of} from 'rxjs'; import {select, Store} from '@ngrx/store'; -import {selectAuth, selectAuthUser} from '@core/auth/auth.selectors'; +import {selectAuthUser} from '@core/auth/auth.selectors'; import {map, mergeMap, take, tap} from 'rxjs/operators'; import {AppState} from '@core/core.state'; import {DeviceService} from '@app/core/http/device.service'; @@ -58,6 +49,11 @@ import { DeviceCredentialsDialogData } from '@modules/home/pages/device/device-credentials-dialog.component'; import {DialogService} from '@core/services/dialog.service'; +import { + AssignToCustomerDialogComponent, + AssignToCustomerDialogData +} from '@modules/home/dialogs/assign-to-customer-dialog.component'; +import {DeviceId} from '@app/shared/models/id/device-id'; @Injectable() export class DevicesTableConfigResolver implements Resolve> { @@ -76,7 +72,7 @@ export class DevicesTableConfigResolver implements Resolve this.config.componentsData.deviceScope === 'tenant'; return this.config; }) ); @@ -175,7 +175,7 @@ export class DevicesTableConfigResolver implements Resolve> { - const actions: Array> = []; + const actions: Array> = []; if (deviceScope === 'tenant') { actions.push( { @@ -183,6 +183,87 @@ export class DevicesTableConfigResolver implements Resolve (!entity.customerId || entity.customerId.id === NULL_UUID), onAction: ($event, entity) => this.makePublic($event, entity) + }, + { + name: this.translate.instant('device.assign-to-customer'), + icon: 'assignment_ind', + isEnabled: (entity) => (!entity.customerId || entity.customerId.id === NULL_UUID), + onAction: ($event, entity) => this.assignToCustomer($event, [entity.id]) + }, + { + name: this.translate.instant('device.unassign-from-customer'), + icon: 'assignment_return', + isEnabled: (entity) => (entity.customerId && entity.customerId.id !== NULL_UUID && !entity.customerIsPublic), + onAction: ($event, entity) => this.unassignFromCustomer($event, entity) + }, + { + name: this.translate.instant('device.make-private'), + icon: 'reply', + isEnabled: (entity) => (entity.customerId && entity.customerId.id !== NULL_UUID && entity.customerIsPublic), + onAction: ($event, entity) => this.unassignFromCustomer($event, entity) + }, + { + name: this.translate.instant('device.manage-credentials'), + icon: 'security', + isEnabled: (entity) => true, + onAction: ($event, entity) => this.manageCredentials($event, entity) + } + ); + } + if (deviceScope === 'customer') { + actions.push( + { + name: this.translate.instant('device.unassign-from-customer'), + icon: 'assignment_return', + isEnabled: (entity) => (entity.customerId && entity.customerId.id !== NULL_UUID && !entity.customerIsPublic), + onAction: ($event, entity) => this.unassignFromCustomer($event, entity) + }, + { + name: this.translate.instant('device.make-private'), + icon: 'reply', + isEnabled: (entity) => (entity.customerId && entity.customerId.id !== NULL_UUID && entity.customerIsPublic), + onAction: ($event, entity) => this.unassignFromCustomer($event, entity) + }, + { + name: this.translate.instant('device.manage-credentials'), + icon: 'security', + isEnabled: (entity) => true, + onAction: ($event, entity) => this.manageCredentials($event, entity) + } + ); + } + if (deviceScope === 'customer_user') { + actions.push( + { + name: this.translate.instant('device.view-credentials'), + icon: 'security', + isEnabled: (entity) => true, + onAction: ($event, entity) => this.manageCredentials($event, entity) + } + ); + } + return actions; + } + + configureGroupActions(deviceScope: string): Array> { + const actions: Array> = []; + if (deviceScope === 'tenant') { + actions.push( + { + name: this.translate.instant('device.assign-devices'), + icon: 'assignment_ind', + isEnabled: true, + onAction: ($event, entities) => this.assignToCustomer($event, entities.map((entity) => entity.id)) + } + ); + } + if (deviceScope === 'customer') { + actions.push( + { + name: this.translate.instant('device.unassign-devices'), + icon: 'assignment_return', + isEnabled: true, + onAction: ($event, entities) => this.unassignDevicesFromCustomer($event, entities) } ); } @@ -191,20 +272,32 @@ export class DevicesTableConfigResolver implements Resolve { const actions: Array = []; - actions.push( - { - name: this.translate.instant('device.add-device-text'), - icon: 'insert_drive_file', - isEnabled: () => true, - onAction: ($event) => this.config.table.addEntity($event) - }, - { - name: this.translate.instant('device.import'), - icon: 'file_upload', - isEnabled: () => true, - onAction: ($event) => this.importDevices($event) - } - ); + if (deviceScope === 'tenant') { + actions.push( + { + name: this.translate.instant('device.add-device-text'), + icon: 'insert_drive_file', + isEnabled: () => true, + onAction: ($event) => this.config.table.addEntity($event) + }, + { + name: this.translate.instant('device.import'), + icon: 'file_upload', + isEnabled: () => true, + onAction: ($event) => this.importDevices($event) + } + ); + } + if (deviceScope === 'customer') { + actions.push( + { + name: this.translate.instant('device.assign-new-device'), + icon: 'add', + isEnabled: () => true, + onAction: ($event) => this.addDevicesToCustomer($event) + } + ); + } return actions; } @@ -215,6 +308,13 @@ export class DevicesTableConfigResolver implements Resolve) { if ($event) { $event.stopPropagation(); } - // TODO: + this.dialog.open(AssignToCustomerDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + entityIds: deviceIds, + entityType: EntityType.DEVICE + } + }).afterClosed() + .subscribe((res) => { + if (res) { + this.config.table.updateData(); + } + }); } unassignFromCustomer($event: Event, device: DeviceInfo) { @@ -259,8 +372,8 @@ export class DevicesTableConfigResolver implements Resolve) { + if ($event) { + $event.stopPropagation(); + } + this.dialogService.confirm( + this.translate.instant('device.unassign-devices-title', {count: devices.length}), + this.translate.instant('device.unassign-devices-text'), + this.translate.instant('action.no'), + this.translate.instant('action.yes'), + true + ).subscribe((res) => { + if (res) { + const tasks: Observable[] = []; + devices.forEach( + (device) => { + tasks.push(this.deviceService.unassignDeviceFromCustomer(device.id.id)); + } + ); + forkJoin(tasks).subscribe( + () => { + this.config.table.updateData(); + } + ); + } + } + ); + } + manageCredentials($event: Event, device: Device) { if ($event) { $event.stopPropagation(); @@ -297,7 +438,7 @@ export class DevicesTableConfigResolver implements Resolve name: this.authority === Authority.TENANT_ADMIN ? this.translate.instant('user.login-as-tenant-admin') : this.translate.instant('user.login-as-customer-user'), - icon: 'mdi:login', - isMdiIcon: true, + mdiIcon: 'mdi:login', isEnabled: () => true, onAction: ($event, entity) => this.loginAsUser($event, entity) } diff --git a/ui-ngx/src/app/shared/components/entity/entities-table-config.models.ts b/ui-ngx/src/app/shared/components/entity/entities-table-config.models.ts index c140636c0c..f61dd04fb6 100644 --- a/ui-ngx/src/app/shared/components/entity/entities-table-config.models.ts +++ b/ui-ngx/src/app/shared/components/entity/entities-table-config.models.ts @@ -51,8 +51,8 @@ export interface CellActionDescriptor> { name: string; nameFunction?: (entity: T) => string; icon?: string; - isMdiIcon?: boolean; - color?: string; + mdiIcon?: string; + style?: any; isEnabled: (entity: T) => boolean; onAction: ($event: MouseEvent, entity: T) => void; } diff --git a/ui-ngx/src/app/shared/components/entity/entities-table.component.html b/ui-ngx/src/app/shared/components/entity/entities-table.component.html index 9ddc5ed91e..89238df962 100644 --- a/ui-ngx/src/app/shared/components/entity/entities-table.component.html +++ b/ui-ngx/src/app/shared/components/entity/entities-table.component.html @@ -52,20 +52,30 @@ - - - + + + +
@@ -172,10 +182,8 @@ [disabled]="isLoading$ | async" [fxShow]="actionDescriptor.isEnabled(entity)" (click)="actionDescriptor.onAction($event, entity)"> - + {{actionDescriptor.icon}} - {{ actionDescriptor.nameFunction ? actionDescriptor.nameFunction(entity) : actionDescriptor.name }} diff --git a/ui-ngx/src/app/shared/components/entity/entities-table.component.ts b/ui-ngx/src/app/shared/components/entity/entities-table.component.ts index a415d54ee8..da9dc3331e 100644 --- a/ui-ngx/src/app/shared/components/entity/entities-table.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entities-table.component.ts @@ -21,7 +21,8 @@ import { Input, OnInit, Type, - ViewChild + ViewChild, + ChangeDetectionStrategy } from '@angular/core'; import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; @@ -51,13 +52,14 @@ import { EntityAction } from '@shared/components/entity/entity-component.models'; import { Timewindow } from '@shared/models/time/time.models'; -import { DomSanitizer } from '@angular/platform-browser'; +import {DomSanitizer, SafeHtml} from '@angular/platform-browser'; import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; @Component({ selector: 'tb-entities-table', templateUrl: './entities-table.component.html', - styleUrls: ['./entities-table.component.scss'] + styleUrls: ['./entities-table.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush }) export class EntitiesTableComponent extends PageComponent implements AfterViewInit, OnInit { @@ -73,6 +75,10 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn columns: Array>>; displayedColumns: string[] = []; + cellContentCache: Array = []; + + cellStyleCache: Array = []; + selectionEnabled; pageLink: PageLink; @@ -139,7 +145,10 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn this.columns = [...this.entitiesTableConfig.columns]; - this.selectionEnabled = this.entitiesTableConfig.selectionEnabled; + const enabledGroupActionDescriptors = + this.groupActionDescriptors.filter((descriptor) => descriptor.isEnabled); + + this.selectionEnabled = this.entitiesTableConfig.selectionEnabled && enabledGroupActionDescriptors.length; if (this.selectionEnabled) { this.displayedColumns.push('select'); @@ -163,7 +172,10 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn this.pageLink = new PageLink(10, 0, null, sortOrder); } this.dataSource = new EntitiesDataSource>( - this.entitiesTableConfig.entitiesFetchFunction + this.entitiesTableConfig.entitiesFetchFunction, + () => { + this.dataLoaded(); + } ); if (this.entitiesTableConfig.onLoadAction) { this.entitiesTableConfig.onLoadAction(this.route); @@ -221,6 +233,11 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn this.dataSource.loadEntities(this.pageLink); } + private dataLoaded() { + this.cellContentCache.length = 0; + this.cellStyleCache.length = 0; + } + onRowClick($event: Event, entity) { if ($event) { $event.stopPropagation(); @@ -347,12 +364,28 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn } } - cellContent(entity: BaseData, column: EntityTableColumn>) { - return this.domSanitizer.bypassSecurityTrustHtml(column.cellContentFunction(entity, column.key)); + cellContent(entity: BaseData, column: EntityTableColumn>, row: number, col: number) { + const index = row * this.columns.length + col; + let res = this.cellContentCache[index]; + if (!res) { + res = this.domSanitizer.bypassSecurityTrustHtml(column.cellContentFunction(entity, column.key)); + this.cellContentCache[index] = res; + } + return res; + } + + cellStyle(entity: BaseData, column: EntityTableColumn>, row: number, col: number) { + const index = row * this.columns.length + col; + let res = this.cellStyleCache[index]; + if (!res) { + res = {...column.cellStyleFunction(entity, column.key), ...{maxWidth: column.maxWidth}}; + this.cellStyleCache[index] = res; + } + return res; } - cellStyle(entity: BaseData, column: EntityTableColumn>) { - return {...column.cellStyleFunction(entity, column.key), ...{maxWidth: column.maxWidth}}; + trackByColumnKey(index, column: EntityTableColumn>) { + return column.key; } } diff --git a/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts b/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts index 982e929071..be51e544aa 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts @@ -105,7 +105,7 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit } ngOnInit() { - this.filteredEntities = this.selectEntityFormGroup.get('dashboard').valueChanges + this.filteredEntities = this.selectEntityFormGroup.get('entity').valueChanges .pipe( tap(value => { let modelValue; diff --git a/ui-ngx/src/app/shared/components/entity/entity-details-panel.component.ts b/ui-ngx/src/app/shared/components/entity/entity-details-panel.component.ts index 582b8b389f..e61daaac2e 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-details-panel.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-details-panel.component.ts @@ -15,6 +15,7 @@ /// import { + ChangeDetectionStrategy, Component, ComponentFactoryResolver, EventEmitter, @@ -44,7 +45,8 @@ import { Subscription } from 'rxjs'; @Component({ selector: 'tb-entity-details-panel', templateUrl: './entity-details-panel.component.html', - styleUrls: ['./entity-details-panel.component.scss'] + styleUrls: ['./entity-details-panel.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush }) export class EntityDetailsPanelComponent extends PageComponent implements OnInit, OnDestroy { diff --git a/ui-ngx/src/app/shared/models/datasource/entity-datasource.ts b/ui-ngx/src/app/shared/models/datasource/entity-datasource.ts index f8bb0e2426..ecfdc755f6 100644 --- a/ui-ngx/src/app/shared/models/datasource/entity-datasource.ts +++ b/ui-ngx/src/app/shared/models/datasource/entity-datasource.ts @@ -36,7 +36,8 @@ export class EntitiesDataSource, P extends PageLink = public currentEntity: T = null; - constructor(private fetchFunction: EntitiesFetchFunction) {} + constructor(private fetchFunction: EntitiesFetchFunction, + private dataLoadedFunction: () => void) {} connect(collectionViewer: CollectionViewer): Observable> { return this.entitiesSubject.asObservable(); @@ -59,6 +60,7 @@ export class EntitiesDataSource, P extends PageLink = this.entitiesSubject.next(pageData.data); this.pageDataSubject.next(pageData); result.next(pageData); + this.dataLoadedFunction(); } ); return result; 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 34fe3394dc..63ce8dbe30 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -395,6 +395,7 @@ "manage-assets": "Manage assets", "manage-devices": "Manage devices", "manage-dashboards": "Manage dashboards", + "created-time": "Created time", "title": "Title", "title-required": "Title is required.", "description": "Description", From 680cd1d2c1b91ce0971c87fbbe040c8e6b40739e Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 15 Aug 2019 20:39:56 +0300 Subject: [PATCH 011/133] Customers page --- ui-ngx/src/app/core/http/device.service.ts | 4 + ui-ngx/src/app/core/http/entity.service.ts | 76 ++++++- ...entities-to-customer-dialog.component.html | 58 +++++ ...d-entities-to-customer-dialog.component.ts | 118 ++++++++++ .../home/dialogs/home-dialogs.module.ts | 10 +- .../pages/customer/customer-routing.module.ts | 17 ++ .../customers-table-config.resolver.ts | 57 +++++ .../device/devices-table-config.resolver.ts | 19 +- .../dashboard-autocomplete.component.ts | 9 +- .../entity/entities-table-config.models.ts | 1 + .../entity/entities-table.component.html | 1 + .../entity/entities-table.component.ts | 1 + .../entity/entity-autocomplete.component.ts | 23 +- .../entity/entity-list.component.html | 48 +++++ .../entity/entity-list.component.ts | 202 ++++++++++++++++++ .../entity-subtype-autocomplete.component.ts | 9 +- .../models/datasource/entity-datasource.ts | 16 +- ui-ngx/src/app/shared/shared.module.ts | 9 +- 18 files changed, 660 insertions(+), 18 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.ts create mode 100644 ui-ngx/src/app/shared/components/entity/entity-list.component.html create mode 100644 ui-ngx/src/app/shared/components/entity/entity-list.component.ts diff --git a/ui-ngx/src/app/core/http/device.service.ts b/ui-ngx/src/app/core/http/device.service.ts index a45f89077b..d233e6650f 100644 --- a/ui-ngx/src/app/core/http/device.service.ts +++ b/ui-ngx/src/app/core/http/device.service.ts @@ -52,6 +52,10 @@ export class DeviceService { return this.http.get(`/api/device/${deviceId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); } + public getDevices(deviceIds: Array, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + return this.http.get>(`/api/devices?deviceIds=${deviceIds.join(',')}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + public getDeviceInfo(deviceId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { return this.http.get(`/api/device/info/${deviceId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); } diff --git a/ui-ngx/src/app/core/http/entity.service.ts b/ui-ngx/src/app/core/http/entity.service.ts index c7bfdcc2ae..f0f22c50da 100644 --- a/ui-ngx/src/app/core/http/entity.service.ts +++ b/ui-ngx/src/app/core/http/entity.service.ts @@ -15,7 +15,7 @@ /// import {Injectable} from '@angular/core'; -import {Observable, throwError, of, empty, EMPTY} from 'rxjs/index'; +import {Observable, throwError, of, empty, EMPTY, forkJoin} from 'rxjs/index'; import {HttpClient} from '@angular/common/http'; import {PageLink} from '@shared/models/page/page-link'; import {EntityType} from '@shared/models/entity-type.models'; @@ -86,7 +86,6 @@ export class EntityService { } return observable; } - public getEntity(entityType: EntityType, entityId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { const entityObservable = this.getEntityObservable(entityType, entityId, ignoreErrors, ignoreLoading); @@ -97,6 +96,79 @@ export class EntityService { } } + private getEntitiesByIdsObservable(fetchEntityFunction: (entityId: string) => Observable>, + entityIds: Array): Observable>> { + const tasks: Observable>[] = []; + entityIds.forEach((entityId) => { + tasks.push(fetchEntityFunction(entityId)); + }); + return forkJoin(tasks).pipe( + map((entities) => { + if (entities) { + entities.sort((entity1, entity2) => { + const index1 = entityIds.indexOf(entity1.id.id); + const index2 = entityIds.indexOf(entity2.id.id); + return index1 - index2; + }); + return entities; + } else { + return []; + } + }) + ); + } + + + private getEntitiesObservable(entityType: EntityType, entityIds: Array, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable>> { + let observable: Observable>>; + switch (entityType) { + case EntityType.DEVICE: + observable = this.deviceService.getDevices(entityIds, ignoreErrors, ignoreLoading); + break; + case EntityType.ASSET: + // TODO: + break; + case EntityType.ENTITY_VIEW: + // TODO: + break; + case EntityType.TENANT: + observable = this.getEntitiesByIdsObservable( + (id) => this.tenantService.getTenant(id, ignoreErrors, ignoreLoading), + entityIds); + break; + case EntityType.CUSTOMER: + observable = this.getEntitiesByIdsObservable( + (id) => this.customerService.getCustomer(id, ignoreErrors, ignoreLoading), + entityIds); + break; + case EntityType.DASHBOARD: + observable = this.getEntitiesByIdsObservable( + (id) => this.dashboardService.getDashboardInfo(id, ignoreErrors, ignoreLoading), + entityIds); + break; + case EntityType.USER: + observable = this.getEntitiesByIdsObservable( + (id) => this.userService.getUser(id, ignoreErrors, ignoreLoading), + entityIds); + break; + case EntityType.ALARM: + console.error('Get Alarm Entity is not implemented!'); + break; + } + return observable; + } + + public getEntities(entityType: EntityType, entityIds: Array, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable>> { + const entitiesObservable = this.getEntitiesObservable(entityType, entityIds, ignoreErrors, ignoreLoading); + if (entitiesObservable) { + return entitiesObservable; + } else { + return throwError(null); + } + } + private getSingleTenantByPageLinkObservable(pageLink: PageLink, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { diff --git a/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.html b/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.html new file mode 100644 index 0000000000..44efa5634b --- /dev/null +++ b/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.html @@ -0,0 +1,58 @@ + +
+ +

{{ assignToCustomerTitle | translate }}

+ + +
+ + +
+
+
+ {{ assignToCustomerText | translate }} + + +
+
+
+ + + +
+
diff --git a/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.ts b/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.ts new file mode 100644 index 0000000000..19a4ac3be7 --- /dev/null +++ b/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.ts @@ -0,0 +1,118 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import {Component, Inject, OnInit, SkipSelf} from '@angular/core'; +import {ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef} from '@angular/material'; +import {PageComponent} from '@shared/components/page.component'; +import {Store} from '@ngrx/store'; +import {AppState} from '@core/core.state'; +import {FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators} from '@angular/forms'; +import {DeviceService} from '@core/http/device.service'; +import {EntityId} from '@shared/models/id/entity-id'; +import {EntityType} from '@shared/models/entity-type.models'; +import {forkJoin, Observable} from 'rxjs'; + +export interface AddEntitiesToCustomerDialogData { + customerId: string; + entityType: EntityType; +} + +@Component({ + selector: 'tb-add-entities-to-customer-dialog', + templateUrl: './add-entities-to-customer-dialog.component.html', + providers: [{provide: ErrorStateMatcher, useExisting: AddEntitiesToCustomerDialogComponent}], + styleUrls: [] +}) +export class AddEntitiesToCustomerDialogComponent extends PageComponent implements OnInit, ErrorStateMatcher { + + addEntitiesToCustomerFormGroup: FormGroup; + + submitted = false; + + entityType: EntityType; + + assignToCustomerTitle: string; + assignToCustomerText: string; + + constructor(protected store: Store, + @Inject(MAT_DIALOG_DATA) public data: AddEntitiesToCustomerDialogData, + private deviceService: DeviceService, + @SkipSelf() private errorStateMatcher: ErrorStateMatcher, + public dialogRef: MatDialogRef, + public fb: FormBuilder) { + super(store); + this.entityType = data.entityType; + } + + ngOnInit(): void { + this.addEntitiesToCustomerFormGroup = this.fb.group({ + entityIds: [null, [Validators.required]] + }); + switch (this.data.entityType) { + case EntityType.DEVICE: + this.assignToCustomerTitle = 'device.assign-device-to-customer'; + this.assignToCustomerText = 'device.assign-device-to-customer-text'; + break; + case EntityType.ASSET: + // TODO: + break; + case EntityType.ENTITY_VIEW: + // TODO: + break; + } + } + + isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { + const originalErrorState = this.errorStateMatcher.isErrorState(control, form); + const customErrorState = !!(control && control.invalid && this.submitted); + return originalErrorState || customErrorState; + } + + cancel(): void { + this.dialogRef.close(false); + } + + assign(): void { + this.submitted = true; + const entityIds: Array = this.addEntitiesToCustomerFormGroup.get('entityIds').value; + const tasks: Observable[] = []; + entityIds.forEach( + (entityId) => { + tasks.push(this.getAssignToCustomerTask(this.data.customerId, entityId)); + } + ); + forkJoin(tasks).subscribe( + () => { + this.dialogRef.close(true); + } + ); + } + + private getAssignToCustomerTask(customerId: string, entityId: string): Observable { + switch (this.data.entityType) { + case EntityType.DEVICE: + return this.deviceService.assignDeviceToCustomer(customerId, entityId); + break; + case EntityType.ASSET: + // TODO: + break; + case EntityType.ENTITY_VIEW: + // TODO: + break; + } + } + +} diff --git a/ui-ngx/src/app/modules/home/dialogs/home-dialogs.module.ts b/ui-ngx/src/app/modules/home/dialogs/home-dialogs.module.ts index de8d55263d..92dcf50231 100644 --- a/ui-ngx/src/app/modules/home/dialogs/home-dialogs.module.ts +++ b/ui-ngx/src/app/modules/home/dialogs/home-dialogs.module.ts @@ -18,21 +18,25 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { SharedModule } from '@app/shared/shared.module'; import {AssignToCustomerDialogComponent} from '@modules/home/dialogs/assign-to-customer-dialog.component'; +import {AddEntitiesToCustomerDialogComponent} from '@modules/home/dialogs/add-entities-to-customer-dialog.component'; @NgModule({ entryComponents: [ - AssignToCustomerDialogComponent + AssignToCustomerDialogComponent, + AddEntitiesToCustomerDialogComponent ], declarations: [ - AssignToCustomerDialogComponent + AssignToCustomerDialogComponent, + AddEntitiesToCustomerDialogComponent ], imports: [ CommonModule, SharedModule ], exports: [ - AssignToCustomerDialogComponent + AssignToCustomerDialogComponent, + AddEntitiesToCustomerDialogComponent ] }) export class HomeDialogsModule { } diff --git a/ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts b/ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts index e2a8c44359..317b894402 100644 --- a/ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts @@ -21,6 +21,7 @@ import {EntitiesTableComponent} from '@shared/components/entity/entities-table.c import {Authority} from '@shared/models/authority.enum'; import {UsersTableConfigResolver} from '../user/users-table-config.resolver'; import {CustomersTableConfigResolver} from './customers-table-config.resolver'; +import {DevicesTableConfigResolver} from '@modules/home/pages/device/devices-table-config.resolver'; const routes: Routes = [ { @@ -57,6 +58,22 @@ const routes: Routes = [ resolve: { entitiesTableConfig: UsersTableConfigResolver } + }, + { + path: ':customerId/devices', + component: EntitiesTableComponent, + data: { + auth: [Authority.TENANT_ADMIN], + title: 'customer.devices', + devicesType: 'customer', + breadcrumb: { + label: 'customer.devices', + icon: 'devices_other' + } + }, + resolve: { + entitiesTableConfig: DevicesTableConfigResolver + } } ] } diff --git a/ui-ngx/src/app/modules/home/pages/customer/customers-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/customer/customers-table-config.resolver.ts index 3eb876d0a4..fa558cf0c3 100644 --- a/ui-ngx/src/app/modules/home/pages/customer/customers-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/customer/customers-table-config.resolver.ts @@ -65,6 +65,39 @@ export class CustomersTableConfigResolver implements Resolve !customer.additionalInfo || !customer.additionalInfo.isPublic, onAction: ($event, entity) => this.manageCustomerUsers($event, entity) + }, + { + name: this.translate.instant('customer.manage-customer-assets'), + nameFunction: (customer) => { + return customer.additionalInfo && customer.additionalInfo.isPublic + ? this.translate.instant('customer.manage-public-assets') + : this.translate.instant('customer.manage-customer-assets'); + }, + icon: 'domain', + isEnabled: (customer) => true, + onAction: ($event, entity) => this.manageCustomerAssets($event, entity) + }, + { + name: this.translate.instant('customer.manage-customer-devices'), + nameFunction: (customer) => { + return customer.additionalInfo && customer.additionalInfo.isPublic + ? this.translate.instant('customer.manage-public-devices') + : this.translate.instant('customer.manage-customer-devices'); + }, + icon: 'devices_other', + isEnabled: (customer) => true, + onAction: ($event, entity) => this.manageCustomerDevices($event, entity) + }, + { + name: this.translate.instant('customer.manage-customer-dashboards'), + nameFunction: (customer) => { + return customer.additionalInfo && customer.additionalInfo.isPublic + ? this.translate.instant('customer.manage-public-dashboards') + : this.translate.instant('customer.manage-customer-dashboards'); + }, + icon: 'dashboard', + isEnabled: (customer) => true, + onAction: ($event, entity) => this.manageCustomerDashboards($event, entity) } ); @@ -78,6 +111,9 @@ export class CustomersTableConfigResolver implements Resolve this.customerService.saveCustomer(customer); this.config.deleteEntity = id => this.customerService.deleteCustomer(id.id); this.config.onEntityAction = action => this.onCustomerAction(action); + this.config.deleteEnabled = (customer) => customer && (!customer.additionalInfo || !customer.additionalInfo.isPublic); + this.config.entitySelectionEnabled = (customer) => customer && (!customer.additionalInfo || !customer.additionalInfo.isPublic); + this.config.detailsReadonly = (customer) => customer && customer.additionalInfo && customer.additionalInfo.isPublic; } resolve(): EntityTableConfig { @@ -93,6 +129,27 @@ export class CustomersTableConfigResolver implements Resolve): boolean { switch (action.action) { case 'manageUsers': 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 a57777f318..eb7b1a808e 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 @@ -54,6 +54,10 @@ import { AssignToCustomerDialogData } from '@modules/home/dialogs/assign-to-customer-dialog.component'; import {DeviceId} from '@app/shared/models/id/device-id'; +import { + AddEntitiesToCustomerDialogComponent, + AddEntitiesToCustomerDialogData +} from '../../dialogs/add-entities-to-customer-dialog.component'; @Injectable() export class DevicesTableConfigResolver implements Resolve> { @@ -312,7 +316,20 @@ export class DevicesTableConfigResolver implements Resolve(AddEntitiesToCustomerDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + customerId: this.customerId, + entityType: EntityType.DEVICE + } + }).afterClosed() + .subscribe((res) => { + if (res) { + this.config.table.updateData(); + } + }); } makePublic($event: Event, device: Device) { diff --git a/ui-ngx/src/app/shared/components/dashboard-autocomplete.component.ts b/ui-ngx/src/app/shared/components/dashboard-autocomplete.component.ts index 41067c10e4..6b280bc40e 100644 --- a/ui-ngx/src/app/shared/components/dashboard-autocomplete.component.ts +++ b/ui-ngx/src/app/shared/components/dashboard-autocomplete.component.ts @@ -29,6 +29,7 @@ import {AppState} from '@app/core/core.state'; import {getCurrentAuthUser} from '@app/core/auth/auth.selectors'; import {Authority} from '@shared/models/authority.enum'; import {TranslateService} from '@ngx-translate/core'; +import {coerceBooleanProperty} from '@angular/cdk/coercion'; @Component({ selector: 'tb-dashboard-autocomplete', @@ -64,8 +65,14 @@ export class DashboardAutocompleteComponent implements ControlValueAccessor, OnI @Input() customerId: string; + private requiredValue: boolean; + get required(): boolean { + return this.requiredValue; + } @Input() - required: boolean; + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } @Input() disabled: boolean; diff --git a/ui-ngx/src/app/shared/components/entity/entities-table-config.models.ts b/ui-ngx/src/app/shared/components/entity/entities-table-config.models.ts index f61dd04fb6..c3dbaf77ec 100644 --- a/ui-ngx/src/app/shared/components/entity/entities-table-config.models.ts +++ b/ui-ngx/src/app/shared/components/entity/entities-table-config.models.ts @@ -126,6 +126,7 @@ export class EntityTableConfig, P extends PageLink = P headerComponent: Type>; addEntity: CreateEntityOperation = null; detailsReadonly: EntityBooleanFunction = () => false; + entitySelectionEnabled: EntityBooleanFunction = () => true; deleteEnabled: EntityBooleanFunction = () => true; deleteEntityTitle: EntityStringFunction = () => ''; deleteEntityContent: EntityStringFunction = () => ''; diff --git a/ui-ngx/src/app/shared/components/entity/entities-table.component.html b/ui-ngx/src/app/shared/components/entity/entities-table.component.html index 89238df962..268ce79e41 100644 --- a/ui-ngx/src/app/shared/components/entity/entities-table.component.html +++ b/ui-ngx/src/app/shared/components/entity/entities-table.component.html @@ -145,6 +145,7 @@ diff --git a/ui-ngx/src/app/shared/components/entity/entities-table.component.ts b/ui-ngx/src/app/shared/components/entity/entities-table.component.ts index da9dc3331e..e21b048541 100644 --- a/ui-ngx/src/app/shared/components/entity/entities-table.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entities-table.component.ts @@ -173,6 +173,7 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn } this.dataSource = new EntitiesDataSource>( this.entitiesTableConfig.entitiesFetchFunction, + this.entitiesTableConfig.entitySelectionEnabled, () => { this.dataLoaded(); } diff --git a/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts b/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts index be51e544aa..d6830ee882 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts @@ -17,7 +17,7 @@ import {AfterViewInit, Component, ElementRef, forwardRef, Input, OnInit, ViewChild} from '@angular/core'; import {ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR} from '@angular/forms'; import {Observable} from 'rxjs'; -import {map, mergeMap, startWith, tap} from 'rxjs/operators'; +import {map, mergeMap, startWith, tap, share} from 'rxjs/operators'; import {Store} from '@ngrx/store'; import {AppState} from '@app/core/core.state'; import {TranslateService} from '@ngx-translate/core'; @@ -25,6 +25,7 @@ import {AliasEntityType, EntityType} from '@shared/models/entity-type.models'; import {BaseData} from '@shared/models/base-data'; import {EntityId} from '@shared/models/id/entity-id'; import {EntityService} from '@core/http/entity.service'; +import {coerceBooleanProperty} from '@angular/cdk/coercion'; @Component({ selector: 'tb-entity-autocomplete', @@ -70,8 +71,14 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit @Input() excludeEntityIds: Array; + private requiredValue: boolean; + get required(): boolean { + return this.requiredValue; + } @Input() - required: boolean; + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } @Input() disabled: boolean; @@ -115,10 +122,14 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit modelValue = value.id.id; } this.updateView(modelValue); + if (value === null) { + this.clear(); + } }), startWith>(''), map(value => value ? (typeof value === 'string' ? value : value.name) : ''), - mergeMap(name => this.fetchEntities(name) ) + mergeMap(name => this.fetchEntities(name) ), + share() ); } @@ -216,12 +227,12 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit ); } else { this.modelValue = null; - this.selectEntityFormGroup.get('entity').patchValue(null, {emitEvent: true}); + this.selectEntityFormGroup.get('entity').patchValue('', {emitEvent: true}); } } reset() { - this.selectEntityFormGroup.get('entity').patchValue(null, {emitEvent: true}); + this.selectEntityFormGroup.get('entity').patchValue('', {emitEvent: true}); } updateView(value: string | null) { @@ -264,7 +275,7 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit } clear() { - this.selectEntityFormGroup.get('entity').patchValue(null, {emitEvent: true}); + this.selectEntityFormGroup.get('entity').patchValue('', {emitEvent: true}); setTimeout(() => { this.entityInput.nativeElement.blur(); this.entityInput.nativeElement.focus(); diff --git a/ui-ngx/src/app/shared/components/entity/entity-list.component.html b/ui-ngx/src/app/shared/components/entity/entity-list.component.html new file mode 100644 index 0000000000..d35ed12b64 --- /dev/null +++ b/ui-ngx/src/app/shared/components/entity/entity-list.component.html @@ -0,0 +1,48 @@ + + + + + {{entity.name}} + close + + + + + + + + + + {{ translate.get('entity.no-entities-matching', {entity: searchText}) | async }} + + + + + {{ 'entity.entity-list-empty' | translate }} + + diff --git a/ui-ngx/src/app/shared/components/entity/entity-list.component.ts b/ui-ngx/src/app/shared/components/entity/entity-list.component.ts new file mode 100644 index 0000000000..e8bb92fb83 --- /dev/null +++ b/ui-ngx/src/app/shared/components/entity/entity-list.component.ts @@ -0,0 +1,202 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import {AfterViewInit, Component, ElementRef, forwardRef, Input, OnInit, SkipSelf, ViewChild} from '@angular/core'; +import { + ControlValueAccessor, + FormBuilder, + FormControl, + FormGroup, + FormGroupDirective, + NG_VALUE_ACCESSOR, NgForm +} from '@angular/forms'; +import {Observable} from 'rxjs'; +import {map, mergeMap, startWith, tap, share, pairwise, filter} from 'rxjs/operators'; +import {Store} from '@ngrx/store'; +import {AppState} from '@app/core/core.state'; +import {TranslateService} from '@ngx-translate/core'; +import {AliasEntityType, EntityType} from '@shared/models/entity-type.models'; +import {BaseData} from '@shared/models/base-data'; +import {EntityId} from '@shared/models/id/entity-id'; +import {EntityService} from '@core/http/entity.service'; +import {ErrorStateMatcher, MatAutocomplete, MatAutocompleteSelectedEvent, MatChipList} from '@angular/material'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; + +@Component({ + selector: 'tb-entity-list', + templateUrl: './entity-list.component.html', + styleUrls: [], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => EntityListComponent), + multi: true + } + ] +}) +export class EntityListComponent implements ControlValueAccessor, OnInit, AfterViewInit { + + entityListFormGroup: FormGroup; + + modelValue: Array | null; + + entityTypeValue: EntityType; + + @Input() + set entityType(entityType: EntityType) { + if (this.entityTypeValue !== entityType) { + this.entityTypeValue = entityType; + this.reset(); + } + } + + private requiredValue: boolean; + get required(): boolean { + return this.requiredValue; + } + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + @Input() + disabled: boolean; + + @ViewChild('entityInput', {static: false}) entityInput: ElementRef; + @ViewChild('entityAutocomplete', {static: false}) matAutocomplete: MatAutocomplete; + @ViewChild('chipList', {static: false}) chipList: MatChipList; + + entities: Array> = []; + filteredEntities: Observable>>; + + private searchText = ''; + + private propagateChange = (v: any) => { }; + + constructor(private store: Store, + public translate: TranslateService, + private entityService: EntityService, + private fb: FormBuilder) { + this.entityListFormGroup = this.fb.group({ + entity: [null] + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + ngOnInit() { + this.filteredEntities = this.entityListFormGroup.get('entity').valueChanges + .pipe( + startWith>(''), + tap((value) => { + if (value && typeof value !== 'string') { + this.add(value); + } else if (value === null) { + this.clear(this.entityInput.nativeElement.value); + } + }), + filter((value) => typeof value === 'string'), + map((value) => value ? (typeof value === 'string' ? value : value.name) : ''), + mergeMap(name => this.fetchEntities(name) ), + share() + ); + } + + ngAfterViewInit(): void {} + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + writeValue(value: Array | null): void { + this.searchText = ''; + if (value != null) { + this.modelValue = value; + this.entityService.getEntities(this.entityTypeValue, value).subscribe( + (entities) => { + this.entities = entities; + } + ); + } else { + this.entities = []; + this.modelValue = null; + } + } + + reset() { + this.entities = []; + this.modelValue = null; + this.entityListFormGroup.get('entity').patchValue('', {emitEvent: true}); + this.propagateChange(this.modelValue); + } + + add(entity: BaseData): void { + if (!this.modelValue || this.modelValue.indexOf(entity.id.id) === -1) { + if (!this.modelValue) { + this.modelValue = []; + } + this.modelValue.push(entity.id.id); + this.entities.push(entity); + if (this.required) { + this.chipList.errorState = false; + } + } + this.propagateChange(this.modelValue); + this.clear(); + } + + remove(entity: BaseData) { + const index = this.entities.indexOf(entity); + if (index >= 0) { + this.entities.splice(index, 1); + this.modelValue.splice(index, 1); + if (!this.modelValue.length) { + this.modelValue = null; + if (this.required) { + this.chipList.errorState = true; + } + } + this.propagateChange(this.modelValue); + this.clear(); + } + } + + displayEntityFn(entity?: BaseData): string | undefined { + return entity ? entity.name : undefined; + } + + fetchEntities(searchText?: string): Observable>> { + this.searchText = searchText; + return this.entityService.getEntitiesByNameFilter(this.entityTypeValue, searchText, + 50, '', false, true).pipe( + map((data) => data ? data : [])); + } + + clear(value: string = '') { + this.entityInput.nativeElement.value = value; + this.entityListFormGroup.get('entity').patchValue(value, {emitEvent: true}); + setTimeout(() => { + this.entityInput.nativeElement.blur(); + this.entityInput.nativeElement.focus(); + }, 0); + } + +} diff --git a/ui-ngx/src/app/shared/components/entity/entity-subtype-autocomplete.component.ts b/ui-ngx/src/app/shared/components/entity/entity-subtype-autocomplete.component.ts index 34044a45b2..b2d2e3b185 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-subtype-autocomplete.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-subtype-autocomplete.component.ts @@ -32,6 +32,7 @@ import {TranslateService} from '@ngx-translate/core'; import {DeviceService} from '@core/http/device.service'; import {EntitySubtype, EntityType} from '@app/shared/models/entity-type.models'; import {BroadcastService} from '@app/core/services/broadcast.service'; +import {coerceBooleanProperty} from '@angular/cdk/coercion'; @Component({ selector: 'tb-entity-subtype-autocomplete', @@ -52,8 +53,14 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor, @Input() entityType: EntityType; + private requiredValue: boolean; + get required(): boolean { + return this.requiredValue; + } @Input() - required: boolean; + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } @Input() disabled: boolean; diff --git a/ui-ngx/src/app/shared/models/datasource/entity-datasource.ts b/ui-ngx/src/app/shared/models/datasource/entity-datasource.ts index ecfdc755f6..a61677f668 100644 --- a/ui-ngx/src/app/shared/models/datasource/entity-datasource.ts +++ b/ui-ngx/src/app/shared/models/datasource/entity-datasource.ts @@ -22,6 +22,7 @@ import { BaseData, HasId } from '@shared/models/base-data'; import { CollectionViewer, DataSource } from '@angular/cdk/typings/collections'; import { catchError, map, take, tap } from 'rxjs/operators'; import { SelectionModel } from '@angular/cdk/collections'; +import {EntityBooleanFunction} from '@shared/components/entity/entities-table-config.models'; export type EntitiesFetchFunction, P extends PageLink> = (pageLink: P) => Observable>; @@ -37,6 +38,7 @@ export class EntitiesDataSource, P extends PageLink = public currentEntity: T = null; constructor(private fetchFunction: EntitiesFetchFunction, + private selectionEnabledFunction: EntityBooleanFunction, private dataLoadedFunction: () => void) {} connect(collectionViewer: CollectionViewer): Observable> { @@ -69,7 +71,7 @@ export class EntitiesDataSource, P extends PageLink = isAllSelected(): Observable { const numSelected = this.selection.selected.length; return this.entitiesSubject.pipe( - map((entities) => numSelected === entities.length) + map((entities) => numSelected === this.selectableEntitiesCount(entities)) ); } @@ -103,13 +105,21 @@ export class EntitiesDataSource, P extends PageLink = this.entitiesSubject.pipe( tap((entities) => { const numSelected = this.selection.selected.length; - if (numSelected === entities.length) { + if (numSelected === this.selectableEntitiesCount(entities)) { this.selection.clear(); } else { - entities.forEach(row => this.selection.select(row)); + entities.forEach(row => { + if (this.selectionEnabledFunction(row)) { + this.selection.select(row); + } + }); } }), take(1) ).subscribe(); } + + private selectableEntitiesCount(entities: Array): number { + return entities.filter((entity) => this.selectionEnabledFunction(entity)).length; + } } diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index d309519b1b..23d19c39a5 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -47,7 +47,9 @@ import { MatDatepickerModule, MatSliderModule, MatExpansionModule, - MatStepperModule, MatAutocompleteModule + MatStepperModule, + MatAutocompleteModule, + MatChipsModule } from '@angular/material'; import { MatDatetimepickerModule, MatNativeDatetimeModule } from '@mat-datetimepicker/core'; import { FlexLayoutModule } from '@angular/flex-layout'; @@ -81,6 +83,7 @@ import {DashboardAutocompleteComponent} from '@shared/components/dashboard-autoc import {EntitySubTypeAutocompleteComponent} from '@shared/components/entity/entity-subtype-autocomplete.component'; import {EntitySubTypeSelectComponent} from './components/entity/entity-subtype-select.component'; import {EntityAutocompleteComponent} from './components/entity/entity-autocomplete.component'; +import {EntityListComponent} from '@shared/components/entity/entity-list.component'; @NgModule({ providers: [ @@ -124,6 +127,7 @@ import {EntityAutocompleteComponent} from './components/entity/entity-autocomple EntitySubTypeAutocompleteComponent, EntitySubTypeSelectComponent, EntityAutocompleteComponent, + EntityListComponent, NospacePipe, MillisecondsToTimeStringPipe, EnumToArrayPipe, @@ -162,6 +166,7 @@ import {EntityAutocompleteComponent} from './components/entity/entity-autocomple MatExpansionModule, MatStepperModule, MatAutocompleteModule, + MatChipsModule, ClipboardModule, FlexLayoutModule.withConfig({addFlexToParent: false}), FormsModule, @@ -192,6 +197,7 @@ import {EntityAutocompleteComponent} from './components/entity/entity-autocomple EntitySubTypeAutocompleteComponent, EntitySubTypeSelectComponent, EntityAutocompleteComponent, + EntityListComponent, // ValueInputComponent, MatButtonModule, MatCheckboxModule, @@ -222,6 +228,7 @@ import {EntityAutocompleteComponent} from './components/entity/entity-autocomple MatExpansionModule, MatStepperModule, MatAutocompleteModule, + MatChipsModule, ClipboardModule, FlexLayoutModule, FormsModule, From 7ff599f7c40b15202137dcc5def2e286f0bad9cd Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 16 Aug 2019 19:40:20 +0300 Subject: [PATCH 012/133] Introduct AssetInfo and EntityViewInfo dtos --- .../server/controller/AssetController.java | 64 +++++++ .../server/controller/BaseController.java | 25 +++ .../controller/EntityViewController.java | 69 ++++++- .../src/main/resources/thingsboard.yml | 1 + .../BaseEntityViewControllerTest.java | 43 +++-- .../server/dao/asset/AssetService.java | 11 ++ .../dao/entityview/EntityViewService.java | 11 ++ .../server/common/data/EntityViewInfo.java | 40 ++++ .../server/common/data/asset/AssetInfo.java | 40 ++++ .../server/dao/asset/AssetDao.java | 50 +++++ .../server/dao/asset/BaseAssetService.java | 44 +++++ .../server/dao/device/DeviceDao.java | 2 +- .../server/dao/entityview/EntityViewDao.java | 51 +++++- .../dao/entityview/EntityViewServiceImpl.java | 53 +++++- .../dao/model/sql/AbstractAssetEntity.java | 129 +++++++++++++ .../model/sql/AbstractEntityViewEntity.java | 172 ++++++++++++++++++ .../server/dao/model/sql/AssetEntity.java | 77 +------- .../server/dao/model/sql/AssetInfoEntity.java | 58 ++++++ .../server/dao/model/sql/DeviceEntity.java | 9 - .../dao/model/sql/EntityViewEntity.java | 118 +----------- .../dao/model/sql/EntityViewInfoEntity.java | 58 ++++++ .../server/dao/sql/asset/AssetRepository.java | 52 ++++++ .../server/dao/sql/asset/JpaAssetDao.java | 47 +++++ .../sql/entityview/EntityViewRepository.java | 51 ++++++ .../dao/sql/entityview/JpaEntityViewDao.java | 51 +++++- .../server/dao/SqlDaoServiceTestSuite.java | 2 +- .../dao/service/BaseAssetServiceTest.java | 35 ++-- dao/src/test/resources/nosql-test.properties | 1 + dao/src/test/resources/sql-test.properties | 1 + 29 files changed, 1119 insertions(+), 246 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/EntityViewInfo.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetInfo.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAssetEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractEntityViewEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/AssetInfoEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityViewInfoEntity.java diff --git a/application/src/main/java/org/thingsboard/server/controller/AssetController.java b/application/src/main/java/org/thingsboard/server/controller/AssetController.java index 060ce10cb7..639f8da948 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AssetController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AssetController.java @@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.asset.AssetInfo; import org.thingsboard.server.common.data.asset.AssetSearchQuery; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; @@ -69,6 +70,19 @@ public class AssetController extends BaseController { } } + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/asset/info/{assetId}", method = RequestMethod.GET) + @ResponseBody + public AssetInfo getAssetInfoById(@PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { + checkParameter(ASSET_ID, strAssetId); + try { + AssetId assetId = new AssetId(toUUID(strAssetId)); + return checkAssetInfoId(assetId, Operation.READ); + } catch (Exception e) { + throw handleException(e); + } + } + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/asset", method = RequestMethod.POST) @ResponseBody @@ -229,6 +243,29 @@ public class AssetController extends BaseController { } } + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/tenant/assetInfos", params = {"pageSize", "page"}, method = RequestMethod.GET) + @ResponseBody + public PageData getTenantAssetInfos( + @RequestParam int pageSize, + @RequestParam int page, + @RequestParam(required = false) String type, + @RequestParam(required = false) String textSearch, + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder) throws ThingsboardException { + try { + TenantId tenantId = getCurrentUser().getTenantId(); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + if (type != null && type.trim().length() > 0) { + return checkNotNull(assetService.findAssetInfosByTenantIdAndType(tenantId, type, pageLink)); + } else { + return checkNotNull(assetService.findAssetInfosByTenantId(tenantId, pageLink)); + } + } catch (Exception e) { + throw handleException(e); + } + } + @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/tenant/assets", params = {"assetName"}, method = RequestMethod.GET) @ResponseBody @@ -269,6 +306,33 @@ public class AssetController extends BaseController { } } + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/customer/{customerId}/assetInfos", params = {"pageSize", "page"}, method = RequestMethod.GET) + @ResponseBody + public PageData getCustomerAssetInfos( + @PathVariable("customerId") String strCustomerId, + @RequestParam int pageSize, + @RequestParam int page, + @RequestParam(required = false) String type, + @RequestParam(required = false) String textSearch, + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder) throws ThingsboardException { + checkParameter("customerId", strCustomerId); + try { + TenantId tenantId = getCurrentUser().getTenantId(); + CustomerId customerId = new CustomerId(toUUID(strCustomerId)); + checkCustomerId(customerId, Operation.READ); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + if (type != null && type.trim().length() > 0) { + return checkNotNull(assetService.findAssetInfosByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink)); + } else { + return checkNotNull(assetService.findAssetInfosByTenantIdAndCustomerId(tenantId, customerId, pageLink)); + } + } catch (Exception e) { + throw handleException(e); + } + } + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/assets", params = {"assetIds"}, method = RequestMethod.GET) @ResponseBody 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 b0c505b575..c363a11a56 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -33,6 +33,7 @@ import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmId; import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.asset.AssetInfo; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; @@ -396,6 +397,18 @@ public abstract class BaseController { } } + EntityViewInfo checkEntityViewInfoId(EntityViewId entityViewId, Operation operation) throws ThingsboardException { + try { + validateId(entityViewId, "Incorrect entityViewId " + entityViewId); + EntityViewInfo entityView = entityViewService.findEntityViewInfoById(getCurrentUser().getTenantId(), entityViewId); + checkNotNull(entityView); + accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE, operation, entityViewId, entityView); + return entityView; + } catch (Exception e) { + throw handleException(e, false); + } + } + Asset checkAssetId(AssetId assetId, Operation operation) throws ThingsboardException { try { validateId(assetId, "Incorrect assetId " + assetId); @@ -408,6 +421,18 @@ public abstract class BaseController { } } + AssetInfo checkAssetInfoId(AssetId assetId, Operation operation) throws ThingsboardException { + try { + validateId(assetId, "Incorrect assetId " + assetId); + AssetInfo asset = assetService.findAssetInfoById(getCurrentUser().getTenantId(), assetId); + checkNotNull(asset); + accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE, operation, assetId, asset); + return asset; + } catch (Exception e) { + throw handleException(e, false); + } + } + Alarm checkAlarmId(AlarmId alarmId, Operation operation) throws ThingsboardException { try { validateId(alarmId, "Incorrect alarmId " + alarmId); diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java b/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java index e8438e0703..c0476aa93b 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java @@ -29,11 +29,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; -import org.thingsboard.server.common.data.Customer; -import org.thingsboard.server.common.data.DataConstants; -import org.thingsboard.server.common.data.EntitySubtype; -import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.EntityView; +import org.thingsboard.server.common.data.*; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery; import org.thingsboard.server.common.data.exception.ThingsboardException; @@ -82,6 +78,19 @@ public class EntityViewController extends BaseController { } } + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/entityView/info/{entityViewId}", method = RequestMethod.GET) + @ResponseBody + public EntityViewInfo getEntityViewInfoById(@PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException { + checkParameter(ENTITY_VIEW_ID, strEntityViewId); + try { + EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId)); + return checkEntityViewInfoId(entityViewId, Operation.READ); + } catch (Exception e) { + throw handleException(e); + } + } + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/entityView", method = RequestMethod.POST) @ResponseBody @@ -282,6 +291,33 @@ public class EntityViewController extends BaseController { } } + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/customer/{customerId}/entityViewInfos", params = {"pageSize", "page"}, method = RequestMethod.GET) + @ResponseBody + public PageData getCustomerEntityViewInfos( + @PathVariable("customerId") String strCustomerId, + @RequestParam int pageSize, + @RequestParam int page, + @RequestParam(required = false) String type, + @RequestParam(required = false) String textSearch, + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder) throws ThingsboardException { + checkParameter("customerId", strCustomerId); + try { + TenantId tenantId = getCurrentUser().getTenantId(); + CustomerId customerId = new CustomerId(toUUID(strCustomerId)); + checkCustomerId(customerId, Operation.READ); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + if (type != null && type.trim().length() > 0) { + return checkNotNull(entityViewService.findEntityViewInfosByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink)); + } else { + return checkNotNull(entityViewService.findEntityViewInfosByTenantIdAndCustomerId(tenantId, customerId, pageLink)); + } + } catch (Exception e) { + throw handleException(e); + } + } + @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/tenant/entityViews", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody @@ -306,6 +342,29 @@ public class EntityViewController extends BaseController { } } + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/tenant/entityViewInfos", params = {"pageSize", "page"}, method = RequestMethod.GET) + @ResponseBody + public PageData getTenantEntityViewInfos( + @RequestParam int pageSize, + @RequestParam int page, + @RequestParam(required = false) String type, + @RequestParam(required = false) String textSearch, + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder) throws ThingsboardException { + try { + TenantId tenantId = getCurrentUser().getTenantId(); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + if (type != null && type.trim().length() > 0) { + return checkNotNull(entityViewService.findEntityViewInfosByTenantIdAndType(tenantId, type, pageLink)); + } else { + return checkNotNull(entityViewService.findEntityViewInfosByTenantId(tenantId, pageLink)); + } + } catch (Exception e) { + throw handleException(e); + } + } + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/entityViews", method = RequestMethod.POST) @ResponseBody diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 01f6f130ee..7fdabc443f 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -308,6 +308,7 @@ spring.resources.chain: enabled: "true" spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation: "true" +spring.jpa.properties.hibernate.order_by.default_null_ordering: "last" # SQL DAO Configuration spring: diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java index 04f315bfc9..982d925d6b 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java @@ -25,11 +25,7 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import org.thingsboard.server.common.data.Customer; -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.EntityView; -import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.*; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.objects.AttributesEntityView; import org.thingsboard.server.common.data.objects.TelemetryEntityView; @@ -214,16 +210,20 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes @Test public void testGetCustomerEntityViews() throws Exception { - CustomerId customerId = doPost("/api/customer", getNewCustomer("Test customer"), Customer.class).getId(); - String urlTemplate = "/api/customer/" + customerId.getId().toString() + "/entityViews?"; + Customer customer = doPost("/api/customer", getNewCustomer("Test customer"), Customer.class); + CustomerId customerId = customer.getId(); + String urlTemplate = "/api/customer/" + customerId.getId().toString() + "/entityViewInfos?"; - List views = new ArrayList<>(); + List views = new ArrayList<>(); for (int i = 0; i < 128; i++) { - views.add(doPost("/api/customer/" + customerId.getId().toString() + "/entityView/" - + getNewSavedEntityView("Test entity view " + i).getId().getId().toString(), EntityView.class)); + views.add( + new EntityViewInfo(doPost("/api/customer/" + customerId.getId().toString() + "/entityView/" + + getNewSavedEntityView("Test entity view " + i).getId().getId().toString(), EntityView.class), + customer.getTitle(), customer.isPublic()) + ); } - List loadedViews = loadListOf(new PageLink(23), urlTemplate); + List loadedViews = loadListOfInfo(new PageLink(23), urlTemplate); Collections.sort(views, idComparator); Collections.sort(loadedViews, idComparator); @@ -274,11 +274,11 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes @Test public void testGetTenantEntityViews() throws Exception { - List views = new ArrayList<>(); + List views = new ArrayList<>(); for (int i = 0; i < 178; i++) { - views.add(getNewSavedEntityView("Test entity view" + i)); + views.add(new EntityViewInfo(getNewSavedEntityView("Test entity view" + i), null, false)); } - List loadedViews = loadListOf(new PageLink(23), "/api/tenant/entityViews?"); + List loadedViews = loadListOfInfo(new PageLink(23), "/api/tenant/entityViewInfos?"); Collections.sort(views, idComparator); Collections.sort(loadedViews, idComparator); @@ -530,4 +530,19 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes return loadedItems; } + + private List loadListOfInfo(PageLink pageLink, String urlTemplate) throws Exception { + List loadedItems = new ArrayList<>(); + PageData pageData; + do { + pageData = doGetTypedWithPageLink(urlTemplate, new TypeReference>() { + }, pageLink); + loadedItems.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageLink.nextPageLink(); + } + } while (pageData.hasNext()); + + return loadedItems; + } } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetService.java index 64803654fd..2a9cdcc690 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetService.java @@ -18,6 +18,7 @@ package org.thingsboard.server.dao.asset; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.asset.AssetInfo; import org.thingsboard.server.common.data.asset.AssetSearchQuery; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; @@ -30,6 +31,8 @@ import java.util.Optional; public interface AssetService { + AssetInfo findAssetInfoById(TenantId tenantId, AssetId assetId); + Asset findAssetById(TenantId tenantId, AssetId assetId); ListenableFuture findAssetByIdAsync(TenantId tenantId, AssetId assetId); @@ -46,16 +49,24 @@ public interface AssetService { PageData findAssetsByTenantId(TenantId tenantId, PageLink pageLink); + PageData findAssetInfosByTenantId(TenantId tenantId, PageLink pageLink); + PageData findAssetsByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink); + PageData findAssetInfosByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink); + ListenableFuture> findAssetsByTenantIdAndIdsAsync(TenantId tenantId, List assetIds); void deleteAssetsByTenantId(TenantId tenantId); PageData findAssetsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink); + PageData findAssetInfosByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink); + PageData findAssetsByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, PageLink pageLink); + PageData findAssetInfosByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, PageLink pageLink); + ListenableFuture> findAssetsByTenantIdCustomerIdAndIdsAsync(TenantId tenantId, CustomerId customerId, List assetIds); void unassignCustomerAssets(TenantId tenantId, CustomerId customerId); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java index 310f302725..3c9d6db690 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java @@ -18,6 +18,7 @@ package org.thingsboard.server.dao.entityview; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityView; +import org.thingsboard.server.common.data.EntityViewInfo; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery; import org.thingsboard.server.common.data.id.CustomerId; @@ -42,18 +43,28 @@ public interface EntityViewService { void unassignCustomerEntityViews(TenantId tenantId, CustomerId customerId); + EntityViewInfo findEntityViewInfoById(TenantId tenantId, EntityViewId entityViewId); + EntityView findEntityViewById(TenantId tenantId, EntityViewId entityViewId); EntityView findEntityViewByTenantIdAndName(TenantId tenantId, String name); PageData findEntityViewByTenantId(TenantId tenantId, PageLink pageLink); + PageData findEntityViewInfosByTenantId(TenantId tenantId, PageLink pageLink); + PageData findEntityViewByTenantIdAndType(TenantId tenantId, PageLink pageLink, String type); + PageData findEntityViewInfosByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink); + PageData findEntityViewsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink); + PageData findEntityViewInfosByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink); + PageData findEntityViewsByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, PageLink pageLink, String type); + PageData findEntityViewInfosByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, PageLink pageLink); + ListenableFuture> findEntityViewsByQuery(TenantId tenantId, EntityViewSearchQuery query); ListenableFuture findEntityViewByIdAsync(TenantId tenantId, EntityViewId entityViewId); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityViewInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityViewInfo.java new file mode 100644 index 0000000000..7cf66faff4 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EntityViewInfo.java @@ -0,0 +1,40 @@ +/** + * Copyright © 2016-2019 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 lombok.Data; +import org.thingsboard.server.common.data.id.EntityViewId; + +@Data +public class EntityViewInfo extends EntityView { + + private String customerTitle; + private boolean customerIsPublic; + + public EntityViewInfo() { + super(); + } + + public EntityViewInfo(EntityViewId entityViewId) { + super(entityViewId); + } + + public EntityViewInfo(EntityView entityView, String customerTitle, boolean customerIsPublic) { + super(entityView); + this.customerTitle = customerTitle; + this.customerIsPublic = customerIsPublic; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetInfo.java new file mode 100644 index 0000000000..ece667bc54 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetInfo.java @@ -0,0 +1,40 @@ +/** + * Copyright © 2016-2019 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.asset; + +import lombok.Data; +import org.thingsboard.server.common.data.id.AssetId; + +@Data +public class AssetInfo extends Asset { + + private String customerTitle; + private boolean customerIsPublic; + + public AssetInfo() { + super(); + } + + public AssetInfo(AssetId assetId) { + super(assetId); + } + + public AssetInfo(Asset asset, String customerTitle, boolean customerIsPublic) { + super(asset); + this.customerTitle = customerTitle; + this.customerIsPublic = customerIsPublic; + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java index 4caffc43d5..656dd96e3b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java @@ -18,6 +18,7 @@ package org.thingsboard.server.dao.asset; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.asset.AssetInfo; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -33,6 +34,15 @@ import java.util.UUID; */ public interface AssetDao extends Dao { + /** + * Find asset info by id. + * + * @param tenantId the tenant id + * @param assetId the asset id + * @return the asset info object + */ + AssetInfo findAssetInfoById(TenantId tenantId, UUID assetId); + /** * Save or update asset object * @@ -50,6 +60,15 @@ public interface AssetDao extends Dao { */ PageData findAssetsByTenantId(UUID tenantId, PageLink pageLink); + /** + * Find asset infos by tenantId and page link. + * + * @param tenantId the tenantId + * @param pageLink the page link + * @return the list of asset info objects + */ + PageData findAssetInfosByTenantId(UUID tenantId, PageLink pageLink); + /** * Find assets by tenantId, type and page link. * @@ -60,6 +79,16 @@ public interface AssetDao extends Dao { */ PageData findAssetsByTenantIdAndType(UUID tenantId, String type, PageLink pageLink); + /** + * Find asset infos by tenantId, type and page link. + * + * @param tenantId the tenantId + * @param type the type + * @param pageLink the page link + * @return the list of asset info objects + */ + PageData findAssetInfosByTenantIdAndType(UUID tenantId, String type, PageLink pageLink); + /** * Find assets by tenantId and assets Ids. * @@ -79,6 +108,16 @@ public interface AssetDao extends Dao { */ PageData findAssetsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, PageLink pageLink); + /** + * Find asset infos by tenantId, customerId and page link. + * + * @param tenantId the tenantId + * @param customerId the customerId + * @param pageLink the page link + * @return the list of asset info objects + */ + PageData findAssetInfosByTenantIdAndCustomerId(UUID tenantId, UUID customerId, PageLink pageLink); + /** * Find assets by tenantId, customerId, type and page link. * @@ -90,6 +129,17 @@ public interface AssetDao extends Dao { */ PageData findAssetsByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, PageLink pageLink); + /** + * Find asset infos by tenantId, customerId, type and page link. + * + * @param tenantId the tenantId + * @param customerId the customerId + * @param type the type + * @param pageLink the page link + * @return the list of asset info objects + */ + PageData findAssetInfosByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, PageLink pageLink); + /** * Find assets by tenantId, customerId and assets Ids. * diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java b/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java index c57bd8d937..c45892139e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java @@ -33,6 +33,7 @@ import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.asset.AssetInfo; import org.thingsboard.server.common.data.asset.AssetSearchQuery; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; @@ -85,6 +86,13 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ @Autowired private CacheManager cacheManager; + @Override + public AssetInfo findAssetInfoById(TenantId tenantId, AssetId assetId) { + log.trace("Executing findAssetInfoById [{}]", assetId); + validateId(assetId, INCORRECT_ASSET_ID + assetId); + return assetDao.findAssetInfoById(tenantId, assetId.getId()); + } + @Override public Asset findAssetById(TenantId tenantId, AssetId assetId) { log.trace("Executing findAssetById [{}]", assetId); @@ -164,6 +172,14 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ return assetDao.findAssetsByTenantId(tenantId.getId(), pageLink); } + @Override + public PageData findAssetInfosByTenantId(TenantId tenantId, PageLink pageLink) { + log.trace("Executing findAssetInfosByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + validatePageLink(pageLink); + return assetDao.findAssetInfosByTenantId(tenantId.getId(), pageLink); + } + @Override public PageData findAssetsByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink) { log.trace("Executing findAssetsByTenantIdAndType, tenantId [{}], type [{}], pageLink [{}]", tenantId, type, pageLink); @@ -173,6 +189,15 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ return assetDao.findAssetsByTenantIdAndType(tenantId.getId(), type, pageLink); } + @Override + public PageData findAssetInfosByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink) { + log.trace("Executing findAssetInfosByTenantIdAndType, tenantId [{}], type [{}], pageLink [{}]", tenantId, type, pageLink); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + validateString(type, "Incorrect type " + type); + validatePageLink(pageLink); + return assetDao.findAssetInfosByTenantIdAndType(tenantId.getId(), type, pageLink); + } + @Override public ListenableFuture> findAssetsByTenantIdAndIdsAsync(TenantId tenantId, List assetIds) { log.trace("Executing findAssetsByTenantIdAndIdsAsync, tenantId [{}], assetIds [{}]", tenantId, assetIds); @@ -197,6 +222,15 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ return assetDao.findAssetsByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), pageLink); } + @Override + public PageData findAssetInfosByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink) { + log.trace("Executing findAssetInfosByTenantIdAndCustomerId, tenantId [{}], customerId [{}], pageLink [{}]", tenantId, customerId, pageLink); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + validateId(customerId, INCORRECT_CUSTOMER_ID + customerId); + validatePageLink(pageLink); + return assetDao.findAssetInfosByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), pageLink); + } + @Override public PageData findAssetsByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, PageLink pageLink) { log.trace("Executing findAssetsByTenantIdAndCustomerIdAndType, tenantId [{}], customerId [{}], type [{}], pageLink [{}]", tenantId, customerId, type, pageLink); @@ -207,6 +241,16 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ return assetDao.findAssetsByTenantIdAndCustomerIdAndType(tenantId.getId(), customerId.getId(), type, pageLink); } + @Override + public PageData findAssetInfosByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, PageLink pageLink) { + log.trace("Executing findAssetInfosByTenantIdAndCustomerIdAndType, tenantId [{}], customerId [{}], type [{}], pageLink [{}]", tenantId, customerId, type, pageLink); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + validateId(customerId, INCORRECT_CUSTOMER_ID + customerId); + validateString(type, "Incorrect type " + type); + validatePageLink(pageLink); + return assetDao.findAssetInfosByTenantIdAndCustomerIdAndType(tenantId.getId(), customerId.getId(), type, pageLink); + } + @Override public ListenableFuture> findAssetsByTenantIdCustomerIdAndIdsAsync(TenantId tenantId, CustomerId customerId, List assetIds) { log.trace("Executing findAssetsByTenantIdAndCustomerIdAndIdsAsync, tenantId [{}], customerId [{}], assetIds [{}]", tenantId, customerId, assetIds); diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java index ff567b7233..932d5ad845 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java @@ -85,7 +85,7 @@ public interface DeviceDao extends Dao { * @param tenantId the tenantId * @param type the type * @param pageLink the page link - * @return the list of device onfo objects + * @return the list of device info objects */ PageData findDeviceInfosByTenantIdAndType(UUID tenantId, String type, PageLink pageLink); diff --git a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewDao.java b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewDao.java index ebc101861f..5465ac320d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewDao.java @@ -16,9 +16,9 @@ package org.thingsboard.server.dao.entityview; import com.google.common.util.concurrent.ListenableFuture; -import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityView; +import org.thingsboard.server.common.data.EntityViewInfo; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -33,6 +33,15 @@ import java.util.UUID; */ public interface EntityViewDao extends Dao { + /** + * Find entity view info by id. + * + * @param tenantId the tenant id + * @param assetId the asset id + * @return the entity view info object + */ + EntityViewInfo findEntityViewInfoById(TenantId tenantId, UUID entityViewId); + /** * Save or update device object * @@ -50,6 +59,15 @@ public interface EntityViewDao extends Dao { */ PageData findEntityViewsByTenantId(UUID tenantId, PageLink pageLink); + /** + * Find entity view infos by tenantId and page link. + * + * @param tenantId the tenantId + * @param pageLink the page link + * @return the list of entity view info objects + */ + PageData findEntityViewInfosByTenantId(UUID tenantId, PageLink pageLink); + /** * Find entity views by tenantId, type and page link. * @@ -60,6 +78,16 @@ public interface EntityViewDao extends Dao { */ PageData findEntityViewsByTenantIdAndType(UUID tenantId, String type, PageLink pageLink); + /** + * Find entity view infos by tenantId, type and page link. + * + * @param tenantId the tenantId + * @param type the type + * @param pageLink the page link + * @return the list of entity view info objects + */ + PageData findEntityViewInfosByTenantIdAndType(UUID tenantId, String type, PageLink pageLink); + /** * Find entity views by tenantId and entity view name. * @@ -81,6 +109,16 @@ public interface EntityViewDao extends Dao { UUID customerId, PageLink pageLink); + /** + * Find entity view infos by tenantId, customerId and page link. + * + * @param tenantId the tenantId + * @param customerId the customerId + * @param pageLink the page link + * @return the list of entity view info objects + */ + PageData findEntityViewInfosByTenantIdAndCustomerId(UUID tenantId, UUID customerId, PageLink pageLink); + /** * Find entity views by tenantId, customerId, type and page link. * @@ -95,6 +133,17 @@ public interface EntityViewDao extends Dao { String type, PageLink pageLink); + /** + * Find entity view infos by tenantId, customerId, type and page link. + * + * @param tenantId the tenantId + * @param customerId the customerId + * @param type the type + * @param pageLink the page link + * @return the list of entity view info objects + */ + PageData findEntityViewInfosByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, PageLink pageLink); + ListenableFuture> findEntityViewsByTenantIdAndEntityIdAsync(UUID tenantId, UUID entityId); /** diff --git a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java index b0f323b2a6..08ffe5be51 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java @@ -28,11 +28,7 @@ import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Caching; import org.springframework.stereotype.Service; -import org.thingsboard.server.common.data.Customer; -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.Tenant; +import org.thingsboard.server.common.data.*; import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; @@ -124,6 +120,13 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti customerEntityViewsUnAssigner.removeEntities(tenantId, customerId); } + @Override + public EntityViewInfo findEntityViewInfoById(TenantId tenantId, EntityViewId entityViewId) { + log.trace("Executing findEntityViewInfoById [{}]", entityViewId); + validateId(entityViewId, INCORRECT_ENTITY_VIEW_ID + entityViewId); + return entityViewDao.findEntityViewInfoById(tenantId, entityViewId.getId()); + } + @Cacheable(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityViewId}") @Override public EntityView findEntityViewById(TenantId tenantId, EntityViewId entityViewId) { @@ -149,6 +152,14 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti return entityViewDao.findEntityViewsByTenantId(tenantId.getId(), pageLink); } + @Override + public PageData findEntityViewInfosByTenantId(TenantId tenantId, PageLink pageLink) { + log.trace("Executing findEntityViewInfosByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + validatePageLink(pageLink); + return entityViewDao.findEntityViewInfosByTenantId(tenantId.getId(), pageLink); + } + @Override public PageData findEntityViewByTenantIdAndType(TenantId tenantId, PageLink pageLink, String type) { log.trace("Executing findEntityViewByTenantIdAndType, tenantId [{}], pageLink [{}], type [{}]", tenantId, pageLink, type); @@ -158,6 +169,15 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti return entityViewDao.findEntityViewsByTenantIdAndType(tenantId.getId(), type, pageLink); } + @Override + public PageData findEntityViewInfosByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink) { + log.trace("Executing findEntityViewInfosByTenantIdAndType, tenantId [{}], pageLink [{}], type [{}]", tenantId, pageLink, type); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + validatePageLink(pageLink); + validateString(type, "Incorrect type " + type); + return entityViewDao.findEntityViewInfosByTenantIdAndType(tenantId.getId(), type, pageLink); + } + @Override public PageData findEntityViewsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink) { @@ -170,6 +190,17 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti customerId.getId(), pageLink); } + @Override + public PageData findEntityViewInfosByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink) { + log.trace("Executing findEntityViewInfosByTenantIdAndCustomerId, tenantId [{}], customerId [{}]," + + " pageLink [{}]", tenantId, customerId, pageLink); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + validateId(customerId, INCORRECT_CUSTOMER_ID + customerId); + validatePageLink(pageLink); + return entityViewDao.findEntityViewInfosByTenantIdAndCustomerId(tenantId.getId(), + customerId.getId(), pageLink); + } + @Override public PageData findEntityViewsByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, PageLink pageLink, String type) { log.trace("Executing findEntityViewsByTenantIdAndCustomerIdAndType, tenantId [{}], customerId [{}]," + @@ -182,6 +213,18 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti customerId.getId(), type, pageLink); } + @Override + public PageData findEntityViewInfosByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, PageLink pageLink) { + log.trace("Executing findEntityViewInfosByTenantIdAndCustomerIdAndType, tenantId [{}], customerId [{}]," + + " pageLink [{}], type [{}]", tenantId, customerId, pageLink, type); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + validateId(customerId, INCORRECT_CUSTOMER_ID + customerId); + validatePageLink(pageLink); + validateString(type, "Incorrect type " + type); + return entityViewDao.findEntityViewInfosByTenantIdAndCustomerIdAndType(tenantId.getId(), + customerId.getId(), type, pageLink); + } + @Override public ListenableFuture> findEntityViewsByQuery(TenantId tenantId, EntityViewSearchQuery query) { ListenableFuture> relations = relationService.findByQuery(tenantId, query.toEntitySearchQuery()); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAssetEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAssetEntity.java new file mode 100644 index 0000000000..19c38f84d1 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAssetEntity.java @@ -0,0 +1,129 @@ +/** + * Copyright © 2016-2019 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.model.sql; + +import com.datastax.driver.core.utils.UUIDs; +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.hibernate.annotations.Type; +import org.hibernate.annotations.TypeDef; +import org.thingsboard.server.common.data.UUIDConverter; +import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.id.AssetId; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.model.SearchTextEntity; +import org.thingsboard.server.dao.util.mapping.JsonStringType; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.MappedSuperclass; +import javax.persistence.Table; + +import static org.thingsboard.server.dao.model.ModelConstants.ASSET_COLUMN_FAMILY_NAME; +import static org.thingsboard.server.dao.model.ModelConstants.ASSET_CUSTOMER_ID_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.ASSET_NAME_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.ASSET_TENANT_ID_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.ASSET_TYPE_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY; + +@Data +@EqualsAndHashCode(callSuper = true) +@TypeDef(name = "json", typeClass = JsonStringType.class) +@MappedSuperclass +public abstract class AbstractAssetEntity extends BaseSqlEntity implements SearchTextEntity { + + @Column(name = ASSET_TENANT_ID_PROPERTY) + private String tenantId; + + @Column(name = ASSET_CUSTOMER_ID_PROPERTY) + private String customerId; + + @Column(name = ASSET_NAME_PROPERTY) + private String name; + + @Column(name = ASSET_TYPE_PROPERTY) + private String type; + + @Column(name = SEARCH_TEXT_PROPERTY) + private String searchText; + + @Type(type = "json") + @Column(name = ModelConstants.ASSET_ADDITIONAL_INFO_PROPERTY) + private JsonNode additionalInfo; + + public AbstractAssetEntity() { + super(); + } + + public AbstractAssetEntity(Asset asset) { + if (asset.getId() != null) { + this.setId(asset.getId().getId()); + } + if (asset.getTenantId() != null) { + this.tenantId = UUIDConverter.fromTimeUUID(asset.getTenantId().getId()); + } + if (asset.getCustomerId() != null) { + this.customerId = UUIDConverter.fromTimeUUID(asset.getCustomerId().getId()); + } + this.name = asset.getName(); + this.type = asset.getType(); + this.additionalInfo = asset.getAdditionalInfo(); + } + + public AbstractAssetEntity(AssetEntity assetEntity) { + this.setId(assetEntity.getId()); + this.tenantId = assetEntity.getTenantId(); + this.customerId = assetEntity.getCustomerId(); + this.type = assetEntity.getType(); + this.name = assetEntity.getName(); + this.searchText = assetEntity.getSearchText(); + this.additionalInfo = assetEntity.getAdditionalInfo(); + } + + @Override + public String getSearchTextSource() { + return name; + } + + @Override + public void setSearchText(String searchText) { + this.searchText = searchText; + } + + public String getSearchText() { + return searchText; + } + + protected Asset toAsset() { + Asset asset = new Asset(new AssetId(UUIDConverter.fromString(id))); + asset.setCreatedTime(UUIDs.unixTimestamp(UUIDConverter.fromString(id))); + if (tenantId != null) { + asset.setTenantId(new TenantId(UUIDConverter.fromString(tenantId))); + } + if (customerId != null) { + asset.setCustomerId(new CustomerId(UUIDConverter.fromString(customerId))); + } + asset.setName(name); + asset.setType(type); + asset.setAdditionalInfo(additionalInfo); + return asset; + } + +} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractEntityViewEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractEntityViewEntity.java new file mode 100644 index 0000000000..4aec387ae3 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractEntityViewEntity.java @@ -0,0 +1,172 @@ +/** + * Copyright © 2016-2019 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.model.sql; + +import com.datastax.driver.core.utils.UUIDs; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.extern.slf4j.Slf4j; +import org.hibernate.annotations.Type; +import org.hibernate.annotations.TypeDef; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.EntityView; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.EntityIdFactory; +import org.thingsboard.server.common.data.id.EntityViewId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.objects.TelemetryEntityView; +import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.model.SearchTextEntity; +import org.thingsboard.server.dao.util.mapping.JsonStringType; + +import javax.persistence.*; +import java.io.IOException; + +import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_TYPE_PROPERTY; + +/** + * Created by Victor Basanets on 8/30/2017. + */ + +@Data +@EqualsAndHashCode(callSuper = true) +@TypeDef(name = "json", typeClass = JsonStringType.class) +@MappedSuperclass +@Slf4j +public abstract class AbstractEntityViewEntity extends BaseSqlEntity implements SearchTextEntity { + + @Column(name = ModelConstants.ENTITY_VIEW_ENTITY_ID_PROPERTY) + private String entityId; + + @Enumerated(EnumType.STRING) + @Column(name = ENTITY_TYPE_PROPERTY) + private EntityType entityType; + + @Column(name = ModelConstants.ENTITY_VIEW_TENANT_ID_PROPERTY) + private String tenantId; + + @Column(name = ModelConstants.ENTITY_VIEW_CUSTOMER_ID_PROPERTY) + private String customerId; + + @Column(name = ModelConstants.DEVICE_TYPE_PROPERTY) + private String type; + + @Column(name = ModelConstants.ENTITY_VIEW_NAME_PROPERTY) + private String name; + + @Column(name = ModelConstants.ENTITY_VIEW_KEYS_PROPERTY) + private String keys; + + @Column(name = ModelConstants.ENTITY_VIEW_START_TS_PROPERTY) + private long startTs; + + @Column(name = ModelConstants.ENTITY_VIEW_END_TS_PROPERTY) + private long endTs; + + @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY) + private String searchText; + + @Type(type = "json") + @Column(name = ModelConstants.ENTITY_VIEW_ADDITIONAL_INFO_PROPERTY) + private JsonNode additionalInfo; + + private static final ObjectMapper mapper = new ObjectMapper(); + + public AbstractEntityViewEntity() { + super(); + } + + public AbstractEntityViewEntity(EntityView entityView) { + if (entityView.getId() != null) { + this.setId(entityView.getId().getId()); + } + if (entityView.getEntityId() != null) { + this.entityId = toString(entityView.getEntityId().getId()); + this.entityType = entityView.getEntityId().getEntityType(); + } + if (entityView.getTenantId() != null) { + this.tenantId = toString(entityView.getTenantId().getId()); + } + if (entityView.getCustomerId() != null) { + this.customerId = toString(entityView.getCustomerId().getId()); + } + this.type = entityView.getType(); + this.name = entityView.getName(); + try { + this.keys = mapper.writeValueAsString(entityView.getKeys()); + } catch (IOException e) { + log.error("Unable to serialize entity view keys!", e); + } + this.startTs = entityView.getStartTimeMs(); + this.endTs = entityView.getEndTimeMs(); + this.searchText = entityView.getSearchText(); + this.additionalInfo = entityView.getAdditionalInfo(); + } + + public AbstractEntityViewEntity(EntityViewEntity entityViewEntity) { + this.setId(entityViewEntity.getId()); + this.entityId = entityViewEntity.getEntityId(); + this.entityType = entityViewEntity.getEntityType(); + this.tenantId = entityViewEntity.getTenantId(); + this.customerId = entityViewEntity.getCustomerId(); + this.type = entityViewEntity.getType(); + this.name = entityViewEntity.getName(); + this.keys = entityViewEntity.getKeys(); + this.startTs = entityViewEntity.getStartTs(); + this.endTs = entityViewEntity.getEndTs(); + this.searchText = entityViewEntity.getSearchText(); + this.additionalInfo = entityViewEntity.getAdditionalInfo(); + } + + @Override + public String getSearchTextSource() { + return name; + } + + @Override + public void setSearchText(String searchText) { + this.searchText = searchText; + } + + protected EntityView toEntityView() { + EntityView entityView = new EntityView(new EntityViewId(getId())); + entityView.setCreatedTime(UUIDs.unixTimestamp(getId())); + + if (entityId != null) { + entityView.setEntityId(EntityIdFactory.getByTypeAndId(entityType.name(), toUUID(entityId).toString())); + } + if (tenantId != null) { + entityView.setTenantId(new TenantId(toUUID(tenantId))); + } + if (customerId != null) { + entityView.setCustomerId(new CustomerId(toUUID(customerId))); + } + entityView.setType(type); + entityView.setName(name); + try { + entityView.setKeys(mapper.readValue(keys, TelemetryEntityView.class)); + } catch (IOException e) { + log.error("Unable to read entity view keys!", e); + } + entityView.setStartTimeMs(startTs); + entityView.setEndTimeMs(endTs); + entityView.setAdditionalInfo(additionalInfo); + return entityView; + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AssetEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AssetEntity.java index 043a02b813..920d58ebd2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AssetEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AssetEntity.java @@ -15,106 +15,35 @@ */ package org.thingsboard.server.dao.model.sql; -import com.datastax.driver.core.utils.UUIDs; -import com.fasterxml.jackson.databind.JsonNode; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; -import org.thingsboard.server.common.data.UUIDConverter; import org.thingsboard.server.common.data.asset.Asset; -import org.thingsboard.server.common.data.id.AssetId; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.dao.model.BaseSqlEntity; -import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.model.SearchTextEntity; import org.thingsboard.server.dao.util.mapping.JsonStringType; -import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Table; import static org.thingsboard.server.dao.model.ModelConstants.ASSET_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.ASSET_CUSTOMER_ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ASSET_NAME_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ASSET_TENANT_ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ASSET_TYPE_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY; @Data @EqualsAndHashCode(callSuper = true) @Entity @TypeDef(name = "json", typeClass = JsonStringType.class) @Table(name = ASSET_COLUMN_FAMILY_NAME) -public final class AssetEntity extends BaseSqlEntity implements SearchTextEntity { - - @Column(name = ASSET_TENANT_ID_PROPERTY) - private String tenantId; - - @Column(name = ASSET_CUSTOMER_ID_PROPERTY) - private String customerId; - - @Column(name = ASSET_NAME_PROPERTY) - private String name; - - @Column(name = ASSET_TYPE_PROPERTY) - private String type; - - @Column(name = SEARCH_TEXT_PROPERTY) - private String searchText; - - @Type(type = "json") - @Column(name = ModelConstants.ASSET_ADDITIONAL_INFO_PROPERTY) - private JsonNode additionalInfo; +public final class AssetEntity extends AbstractAssetEntity { public AssetEntity() { super(); } public AssetEntity(Asset asset) { - if (asset.getId() != null) { - this.setId(asset.getId().getId()); - } - if (asset.getTenantId() != null) { - this.tenantId = UUIDConverter.fromTimeUUID(asset.getTenantId().getId()); - } - if (asset.getCustomerId() != null) { - this.customerId = UUIDConverter.fromTimeUUID(asset.getCustomerId().getId()); - } - this.name = asset.getName(); - this.type = asset.getType(); - this.additionalInfo = asset.getAdditionalInfo(); - } - - @Override - public String getSearchTextSource() { - return name; - } - - @Override - public void setSearchText(String searchText) { - this.searchText = searchText; - } - - public String getSearchText() { - return searchText; + super(asset); } @Override public Asset toData() { - Asset asset = new Asset(new AssetId(UUIDConverter.fromString(id))); - asset.setCreatedTime(UUIDs.unixTimestamp(UUIDConverter.fromString(id))); - if (tenantId != null) { - asset.setTenantId(new TenantId(UUIDConverter.fromString(tenantId))); - } - if (customerId != null) { - asset.setCustomerId(new CustomerId(UUIDConverter.fromString(customerId))); - } - asset.setName(name); - asset.setType(type); - asset.setAdditionalInfo(additionalInfo); - return asset; + return super.toAsset(); } } \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AssetInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AssetInfoEntity.java new file mode 100644 index 0000000000..bb292c8cec --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AssetInfoEntity.java @@ -0,0 +1,58 @@ +/** + * Copyright © 2016-2019 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.model.sql; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.asset.AssetInfo; + +import java.util.HashMap; +import java.util.Map; + +@Data +@EqualsAndHashCode(callSuper = true) +public class AssetInfoEntity extends AbstractAssetEntity { + + public static final Map assetInfoColumnMap = new HashMap<>(); + static { + assetInfoColumnMap.put("customerTitle", "c.title"); + } + + private String customerTitle; + private boolean customerIsPublic; + + public AssetInfoEntity() { + super(); + } + + public AssetInfoEntity(AssetEntity assetEntity, + String customerTitle, + Object customerAdditionalInfo) { + super(assetEntity); + this.customerTitle = customerTitle; + if (customerAdditionalInfo != null && ((JsonNode)customerAdditionalInfo).has("isPublic")) { + this.customerIsPublic = ((JsonNode)customerAdditionalInfo).get("isPublic").asBoolean(); + } else { + this.customerIsPublic = false; + } + } + + @Override + public AssetInfo toData() { + return new AssetInfo(super.toAsset(), customerTitle, customerIsPublic); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceEntity.java index f2b06e091c..7008312125 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceEntity.java @@ -15,22 +15,13 @@ */ package org.thingsboard.server.dao.model.sql; -import com.datastax.driver.core.utils.UUIDs; -import com.fasterxml.jackson.databind.JsonNode; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.model.SearchTextEntity; import org.thingsboard.server.dao.util.mapping.JsonStringType; -import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Table; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityViewEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityViewEntity.java index 4eddeba6a9..e8138d0ce7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityViewEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityViewEntity.java @@ -15,34 +15,15 @@ */ package org.thingsboard.server.dao.model.sql; -import com.datastax.driver.core.utils.UUIDs; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import lombok.Data; import lombok.EqualsAndHashCode; -import lombok.extern.slf4j.Slf4j; -import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; -import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.EntityIdFactory; -import org.thingsboard.server.common.data.id.EntityViewId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.objects.TelemetryEntityView; -import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.model.SearchTextEntity; import org.thingsboard.server.dao.util.mapping.JsonStringType; -import javax.persistence.Column; import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; import javax.persistence.Table; -import java.io.IOException; - -import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_TYPE_PROPERTY; /** * Created by Victor Basanets on 8/30/2017. @@ -53,111 +34,18 @@ import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_TYPE_PROPER @Entity @TypeDef(name = "json", typeClass = JsonStringType.class) @Table(name = ModelConstants.ENTITY_VIEW_TABLE_FAMILY_NAME) -@Slf4j -public class EntityViewEntity extends BaseSqlEntity implements SearchTextEntity { - - @Column(name = ModelConstants.ENTITY_VIEW_ENTITY_ID_PROPERTY) - private String entityId; - - @Enumerated(EnumType.STRING) - @Column(name = ENTITY_TYPE_PROPERTY) - private EntityType entityType; - - @Column(name = ModelConstants.ENTITY_VIEW_TENANT_ID_PROPERTY) - private String tenantId; - - @Column(name = ModelConstants.ENTITY_VIEW_CUSTOMER_ID_PROPERTY) - private String customerId; - - @Column(name = ModelConstants.DEVICE_TYPE_PROPERTY) - private String type; - - @Column(name = ModelConstants.ENTITY_VIEW_NAME_PROPERTY) - private String name; - - @Column(name = ModelConstants.ENTITY_VIEW_KEYS_PROPERTY) - private String keys; - - @Column(name = ModelConstants.ENTITY_VIEW_START_TS_PROPERTY) - private long startTs; - - @Column(name = ModelConstants.ENTITY_VIEW_END_TS_PROPERTY) - private long endTs; - - @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY) - private String searchText; - - @Type(type = "json") - @Column(name = ModelConstants.ENTITY_VIEW_ADDITIONAL_INFO_PROPERTY) - private JsonNode additionalInfo; - - private static final ObjectMapper mapper = new ObjectMapper(); +public class EntityViewEntity extends AbstractEntityViewEntity { public EntityViewEntity() { super(); } public EntityViewEntity(EntityView entityView) { - if (entityView.getId() != null) { - this.setId(entityView.getId().getId()); - } - if (entityView.getEntityId() != null) { - this.entityId = toString(entityView.getEntityId().getId()); - this.entityType = entityView.getEntityId().getEntityType(); - } - if (entityView.getTenantId() != null) { - this.tenantId = toString(entityView.getTenantId().getId()); - } - if (entityView.getCustomerId() != null) { - this.customerId = toString(entityView.getCustomerId().getId()); - } - this.type = entityView.getType(); - this.name = entityView.getName(); - try { - this.keys = mapper.writeValueAsString(entityView.getKeys()); - } catch (IOException e) { - log.error("Unable to serialize entity view keys!", e); - } - this.startTs = entityView.getStartTimeMs(); - this.endTs = entityView.getEndTimeMs(); - this.searchText = entityView.getSearchText(); - this.additionalInfo = entityView.getAdditionalInfo(); - } - - @Override - public String getSearchTextSource() { - return name; - } - - @Override - public void setSearchText(String searchText) { - this.searchText = searchText; + super(entityView); } @Override public EntityView toData() { - EntityView entityView = new EntityView(new EntityViewId(getId())); - entityView.setCreatedTime(UUIDs.unixTimestamp(getId())); - - if (entityId != null) { - entityView.setEntityId(EntityIdFactory.getByTypeAndId(entityType.name(), toUUID(entityId).toString())); - } - if (tenantId != null) { - entityView.setTenantId(new TenantId(toUUID(tenantId))); - } - if (customerId != null) { - entityView.setCustomerId(new CustomerId(toUUID(customerId))); - } - entityView.setType(type); - entityView.setName(name); - try { - entityView.setKeys(mapper.readValue(keys, TelemetryEntityView.class)); - } catch (IOException e) { - log.error("Unable to read entity view keys!", e); - } - entityView.setStartTimeMs(startTs); - entityView.setEndTimeMs(endTs); - entityView.setAdditionalInfo(additionalInfo); - return entityView; + return super.toEntityView(); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityViewInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityViewInfoEntity.java new file mode 100644 index 0000000000..de07405e23 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/EntityViewInfoEntity.java @@ -0,0 +1,58 @@ +/** + * Copyright © 2016-2019 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.model.sql; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.EntityViewInfo; + +import java.util.HashMap; +import java.util.Map; + +@Data +@EqualsAndHashCode(callSuper = true) +public class EntityViewInfoEntity extends AbstractEntityViewEntity { + + public static final Map entityViewInfoColumnMap = new HashMap<>(); + static { + entityViewInfoColumnMap.put("customerTitle", "c.title"); + } + + private String customerTitle; + private boolean customerIsPublic; + + public EntityViewInfoEntity() { + super(); + } + + public EntityViewInfoEntity(EntityViewEntity entityViewEntity, + String customerTitle, + Object customerAdditionalInfo) { + super(entityViewEntity); + this.customerTitle = customerTitle; + if (customerAdditionalInfo != null && ((JsonNode)customerAdditionalInfo).has("isPublic")) { + this.customerIsPublic = ((JsonNode)customerAdditionalInfo).get("isPublic").asBoolean(); + } else { + this.customerIsPublic = false; + } + } + + @Override + public EntityViewInfo toData() { + return new EntityViewInfo(super.toEntityView(), customerTitle, customerIsPublic); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java index de21137286..d0d7e5deb5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java @@ -22,6 +22,7 @@ import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; import org.thingsboard.server.dao.model.sql.AssetEntity; +import org.thingsboard.server.dao.model.sql.AssetInfoEntity; import org.thingsboard.server.dao.util.SqlDao; import java.util.List; @@ -32,12 +33,27 @@ import java.util.List; @SqlDao public interface AssetRepository extends PagingAndSortingRepository { + @Query("SELECT new org.thingsboard.server.dao.model.sql.AssetInfoEntity(a, c.title, c.additionalInfo) " + + "FROM AssetEntity a " + + "LEFT JOIN CustomerEntity c on c.id = a.customerId " + + "WHERE a.id = :assetId") + AssetInfoEntity findAssetInfoById(@Param("assetId") String assetId); + @Query("SELECT a FROM AssetEntity a WHERE a.tenantId = :tenantId " + "AND LOWER(a.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))") Page findByTenantId(@Param("tenantId") String tenantId, @Param("textSearch") String textSearch, Pageable pageable); + @Query("SELECT new org.thingsboard.server.dao.model.sql.AssetInfoEntity(a, c.title, c.additionalInfo) " + + "FROM AssetEntity a " + + "LEFT JOIN CustomerEntity c on c.id = a.customerId " + + "WHERE a.tenantId = :tenantId " + + "AND LOWER(a.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))") + Page findAssetInfosByTenantId(@Param("tenantId") String tenantId, + @Param("textSearch") String textSearch, + Pageable pageable); + @Query("SELECT a FROM AssetEntity a WHERE a.tenantId = :tenantId " + "AND a.customerId = :customerId " + "AND LOWER(a.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))") @@ -46,6 +62,17 @@ public interface AssetRepository extends PagingAndSortingRepository findAssetInfosByTenantIdAndCustomerId(@Param("tenantId") String tenantId, + @Param("customerId") String customerId, + @Param("searchText") String searchText, + Pageable pageable); + List findByTenantIdAndIdIn(String tenantId, List assetIds); List findByTenantIdAndCustomerIdAndIdIn(String tenantId, String customerId, List assetIds); @@ -60,6 +87,18 @@ public interface AssetRepository extends PagingAndSortingRepository findAssetInfosByTenantIdAndType(@Param("tenantId") String tenantId, + @Param("type") String type, + @Param("textSearch") String textSearch, + Pageable pageable); + + @Query("SELECT a FROM AssetEntity a WHERE a.tenantId = :tenantId " + "AND a.customerId = :customerId AND a.type = :type " + "AND LOWER(a.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))") @@ -69,6 +108,19 @@ public interface AssetRepository extends PagingAndSortingRepository findAssetInfosByTenantIdAndCustomerIdAndType(@Param("tenantId") String tenantId, + @Param("customerId") String customerId, + @Param("type") String type, + @Param("textSearch") String textSearch, + Pageable pageable); + @Query("SELECT DISTINCT a.type FROM AssetEntity a WHERE a.tenantId = :tenantId") List findTenantAssetTypes(@Param("tenantId") String tenantId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java index 576df99566..3db672fb74 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java @@ -23,12 +23,14 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.asset.AssetInfo; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.asset.AssetDao; import org.thingsboard.server.dao.model.sql.AssetEntity; +import org.thingsboard.server.dao.model.sql.AssetInfoEntity; import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; import org.thingsboard.server.dao.util.SqlDao; @@ -63,6 +65,11 @@ public class JpaAssetDao extends JpaAbstractSearchTextDao im return assetRepository; } + @Override + public AssetInfo findAssetInfoById(TenantId tenantId, UUID assetId) { + return DaoUtil.getData(assetRepository.findAssetInfoById(fromTimeUUID(assetId))); + } + @Override public PageData findAssetsByTenantId(UUID tenantId, PageLink pageLink) { return DaoUtil.toPageData(assetRepository @@ -72,6 +79,15 @@ public class JpaAssetDao extends JpaAbstractSearchTextDao im DaoUtil.toPageable(pageLink))); } + @Override + public PageData findAssetInfosByTenantId(UUID tenantId, PageLink pageLink) { + return DaoUtil.toPageData( + assetRepository.findAssetInfosByTenantId( + fromTimeUUID(tenantId), + Objects.toString(pageLink.getTextSearch(), ""), + DaoUtil.toPageable(pageLink, AssetInfoEntity.assetInfoColumnMap))); + } + @Override public ListenableFuture> findAssetsByTenantIdAndIdsAsync(UUID tenantId, List assetIds) { return service.submit(() -> @@ -88,6 +104,16 @@ public class JpaAssetDao extends JpaAbstractSearchTextDao im DaoUtil.toPageable(pageLink))); } + @Override + public PageData findAssetInfosByTenantIdAndCustomerId(UUID tenantId, UUID customerId, PageLink pageLink) { + return DaoUtil.toPageData( + assetRepository.findAssetInfosByTenantIdAndCustomerId( + fromTimeUUID(tenantId), + fromTimeUUID(customerId), + Objects.toString(pageLink.getTextSearch(), ""), + DaoUtil.toPageable(pageLink, AssetInfoEntity.assetInfoColumnMap))); + } + @Override public ListenableFuture> findAssetsByTenantIdAndCustomerIdAndIdsAsync(UUID tenantId, UUID customerId, List assetIds) { return service.submit(() -> @@ -110,6 +136,16 @@ public class JpaAssetDao extends JpaAbstractSearchTextDao im DaoUtil.toPageable(pageLink))); } + @Override + public PageData findAssetInfosByTenantIdAndType(UUID tenantId, String type, PageLink pageLink) { + return DaoUtil.toPageData( + assetRepository.findAssetInfosByTenantIdAndType( + fromTimeUUID(tenantId), + type, + Objects.toString(pageLink.getTextSearch(), ""), + DaoUtil.toPageable(pageLink, AssetInfoEntity.assetInfoColumnMap))); + } + @Override public PageData findAssetsByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, PageLink pageLink) { return DaoUtil.toPageData(assetRepository @@ -121,6 +157,17 @@ public class JpaAssetDao extends JpaAbstractSearchTextDao im DaoUtil.toPageable(pageLink))); } + @Override + public PageData findAssetInfosByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, PageLink pageLink) { + return DaoUtil.toPageData( + assetRepository.findAssetInfosByTenantIdAndCustomerIdAndType( + fromTimeUUID(tenantId), + fromTimeUUID(customerId), + type, + Objects.toString(pageLink.getTextSearch(), ""), + DaoUtil.toPageable(pageLink, AssetInfoEntity.assetInfoColumnMap))); + } + @Override public ListenableFuture> findTenantAssetTypesAsync(UUID tenantId) { return service.submit(() -> convertTenantAssetTypesToDto(tenantId, assetRepository.findTenantAssetTypes(fromTimeUUID(tenantId)))); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/EntityViewRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/EntityViewRepository.java index 7d9d224f2b..e944f9a786 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/EntityViewRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/EntityViewRepository.java @@ -22,6 +22,7 @@ import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; import org.thingsboard.server.dao.model.sql.EntityViewEntity; +import org.thingsboard.server.dao.model.sql.EntityViewInfoEntity; import org.thingsboard.server.dao.util.SqlDao; import java.util.List; @@ -32,12 +33,27 @@ import java.util.List; @SqlDao public interface EntityViewRepository extends PagingAndSortingRepository { + @Query("SELECT new org.thingsboard.server.dao.model.sql.EntityViewInfoEntity(e, c.title, c.additionalInfo) " + + "FROM EntityViewEntity e " + + "LEFT JOIN CustomerEntity c on c.id = e.customerId " + + "WHERE e.id = :entityViewId") + EntityViewInfoEntity findEntityViewInfoById(@Param("entityViewId") String entityViewId); + @Query("SELECT e FROM EntityViewEntity e WHERE e.tenantId = :tenantId " + "AND LOWER(e.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))") Page findByTenantId(@Param("tenantId") String tenantId, @Param("textSearch") String textSearch, Pageable pageable); + @Query("SELECT new org.thingsboard.server.dao.model.sql.EntityViewInfoEntity(e, c.title, c.additionalInfo) " + + "FROM EntityViewEntity e " + + "LEFT JOIN CustomerEntity c on c.id = e.customerId " + + "WHERE e.tenantId = :tenantId " + + "AND LOWER(e.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))") + Page findEntityViewInfosByTenantId(@Param("tenantId") String tenantId, + @Param("textSearch") String textSearch, + Pageable pageable); + @Query("SELECT e FROM EntityViewEntity e WHERE e.tenantId = :tenantId " + "AND e.type = :type " + "AND LOWER(e.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))") @@ -46,6 +62,17 @@ public interface EntityViewRepository extends PagingAndSortingRepository findEntityViewInfosByTenantIdAndType(@Param("tenantId") String tenantId, + @Param("type") String type, + @Param("textSearch") String textSearch, + Pageable pageable); + @Query("SELECT e FROM EntityViewEntity e WHERE e.tenantId = :tenantId " + "AND e.customerId = :customerId " + "AND LOWER(e.searchText) LIKE LOWER(CONCAT(:searchText, '%'))") @@ -54,6 +81,17 @@ public interface EntityViewRepository extends PagingAndSortingRepository findEntityViewInfosByTenantIdAndCustomerId(@Param("tenantId") String tenantId, + @Param("customerId") String customerId, + @Param("searchText") String searchText, + Pageable pageable); + @Query("SELECT e FROM EntityViewEntity e WHERE e.tenantId = :tenantId " + "AND e.customerId = :customerId " + "AND e.type = :type " + @@ -64,6 +102,19 @@ public interface EntityViewRepository extends PagingAndSortingRepository findEntityViewInfosByTenantIdAndCustomerIdAndType(@Param("tenantId") String tenantId, + @Param("customerId") String customerId, + @Param("type") String type, + @Param("textSearch") String textSearch, + Pageable pageable); + EntityViewEntity findByTenantIdAndName(String tenantId, String name); List findAllByTenantIdAndEntityId(String tenantId, String entityId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/JpaEntityViewDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/JpaEntityViewDao.java index a668cbda60..8caccfafac 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/JpaEntityViewDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/JpaEntityViewDao.java @@ -20,16 +20,14 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Component; -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.UUIDConverter; +import org.thingsboard.server.common.data.*; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.entityview.EntityViewDao; import org.thingsboard.server.dao.model.sql.EntityViewEntity; +import org.thingsboard.server.dao.model.sql.EntityViewInfoEntity; import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; import org.thingsboard.server.dao.util.SqlDao; @@ -64,6 +62,11 @@ public class JpaEntityViewDao extends JpaAbstractSearchTextDao findEntityViewsByTenantId(UUID tenantId, PageLink pageLink) { return DaoUtil.toPageData( @@ -73,6 +76,15 @@ public class JpaEntityViewDao extends JpaAbstractSearchTextDao findEntityViewInfosByTenantId(UUID tenantId, PageLink pageLink) { + return DaoUtil.toPageData( + entityViewRepository.findEntityViewInfosByTenantId( + fromTimeUUID(tenantId), + Objects.toString(pageLink.getTextSearch(), ""), + DaoUtil.toPageable(pageLink, EntityViewInfoEntity.entityViewInfoColumnMap))); + } + @Override public PageData findEntityViewsByTenantIdAndType(UUID tenantId, String type, PageLink pageLink) { return DaoUtil.toPageData( @@ -83,6 +95,16 @@ public class JpaEntityViewDao extends JpaAbstractSearchTextDao findEntityViewInfosByTenantIdAndType(UUID tenantId, String type, PageLink pageLink) { + return DaoUtil.toPageData( + entityViewRepository.findEntityViewInfosByTenantIdAndType( + fromTimeUUID(tenantId), + type, + Objects.toString(pageLink.getTextSearch(), ""), + DaoUtil.toPageable(pageLink, EntityViewInfoEntity.entityViewInfoColumnMap))); + } + @Override public Optional findEntityViewByTenantIdAndName(UUID tenantId, String name) { return Optional.ofNullable( @@ -102,6 +124,16 @@ public class JpaEntityViewDao extends JpaAbstractSearchTextDao findEntityViewInfosByTenantIdAndCustomerId(UUID tenantId, UUID customerId, PageLink pageLink) { + return DaoUtil.toPageData( + entityViewRepository.findEntityViewInfosByTenantIdAndCustomerId( + fromTimeUUID(tenantId), + fromTimeUUID(customerId), + Objects.toString(pageLink.getTextSearch(), ""), + DaoUtil.toPageable(pageLink, EntityViewInfoEntity.entityViewInfoColumnMap))); + } + @Override public PageData findEntityViewsByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, PageLink pageLink) { return DaoUtil.toPageData( @@ -114,6 +146,17 @@ public class JpaEntityViewDao extends JpaAbstractSearchTextDao findEntityViewInfosByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, PageLink pageLink) { + return DaoUtil.toPageData( + entityViewRepository.findEntityViewInfosByTenantIdAndCustomerIdAndType( + fromTimeUUID(tenantId), + fromTimeUUID(customerId), + type, + Objects.toString(pageLink.getTextSearch(), ""), + DaoUtil.toPageable(pageLink, EntityViewInfoEntity.entityViewInfoColumnMap))); + } + @Override public ListenableFuture> findEntityViewsByTenantIdAndEntityIdAsync(UUID tenantId, UUID entityId) { return service.submit(() -> DaoUtil.convertDataList( diff --git a/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java b/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java index bde567bd7e..6dee664716 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java +++ b/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java @@ -24,7 +24,7 @@ import java.util.Arrays; @RunWith(ClasspathSuite.class) @ClassnameFilters({ - "org.thingsboard.server.dao.service.*DeviceServiceSqlTest" + "org.thingsboard.server.dao.service.*ServiceSqlTest" }) public class SqlDaoServiceTestSuite { diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseAssetServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseAssetServiceTest.java index 8fdeeb1e11..4082dbeff7 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseAssetServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseAssetServiceTest.java @@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.asset.AssetInfo; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; @@ -252,7 +253,7 @@ public abstract class BaseAssetServiceTest extends AbstractServiceTest { @Test public void testFindAssetsByTenantIdAndName() { String title1 = "Asset title 1"; - List assetsTitle1 = new ArrayList<>(); + List assetsTitle1 = new ArrayList<>(); for (int i=0;i<143;i++) { Asset asset = new Asset(); asset.setTenantId(tenantId); @@ -261,10 +262,10 @@ public abstract class BaseAssetServiceTest extends AbstractServiceTest { name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); asset.setName(name); asset.setType("default"); - assetsTitle1.add(assetService.saveAsset(asset)); + assetsTitle1.add(new AssetInfo(assetService.saveAsset(asset), null, false)); } String title2 = "Asset title 2"; - List assetsTitle2 = new ArrayList<>(); + List assetsTitle2 = new ArrayList<>(); for (int i=0;i<175;i++) { Asset asset = new Asset(); asset.setTenantId(tenantId); @@ -273,14 +274,14 @@ public abstract class BaseAssetServiceTest extends AbstractServiceTest { name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); asset.setName(name); asset.setType("default"); - assetsTitle2.add(assetService.saveAsset(asset)); + assetsTitle2.add(new AssetInfo(assetService.saveAsset(asset), null, false)); } - List loadedAssetsTitle1 = new ArrayList<>(); + List loadedAssetsTitle1 = new ArrayList<>(); PageLink pageLink = new PageLink(15, 0, title1); - PageData pageData = null; + PageData pageData = null; do { - pageData = assetService.findAssetsByTenantId(tenantId, pageLink); + pageData = assetService.findAssetInfosByTenantId(tenantId, pageLink); loadedAssetsTitle1.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); @@ -292,10 +293,10 @@ public abstract class BaseAssetServiceTest extends AbstractServiceTest { Assert.assertEquals(assetsTitle1, loadedAssetsTitle1); - List loadedAssetsTitle2 = new ArrayList<>(); + List loadedAssetsTitle2 = new ArrayList<>(); pageLink = new PageLink(4, 0, title2); do { - pageData = assetService.findAssetsByTenantId(tenantId, pageLink); + pageData = assetService.findAssetInfosByTenantId(tenantId, pageLink); loadedAssetsTitle2.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); @@ -312,7 +313,7 @@ public abstract class BaseAssetServiceTest extends AbstractServiceTest { } pageLink = new PageLink(4, 0, title1); - pageData = assetService.findAssetsByTenantId(tenantId, pageLink); + pageData = assetService.findAssetInfosByTenantId(tenantId, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -321,7 +322,7 @@ public abstract class BaseAssetServiceTest extends AbstractServiceTest { } pageLink = new PageLink(4, 0, title2); - pageData = assetService.findAssetsByTenantId(tenantId, pageLink); + pageData = assetService.findAssetInfosByTenantId(tenantId, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); } @@ -419,21 +420,21 @@ public abstract class BaseAssetServiceTest extends AbstractServiceTest { customer = customerService.saveCustomer(customer); CustomerId customerId = customer.getId(); - List assets = new ArrayList<>(); + List assets = new ArrayList<>(); for (int i=0;i<278;i++) { Asset asset = new Asset(); asset.setTenantId(tenantId); asset.setName("Asset"+i); asset.setType("default"); asset = assetService.saveAsset(asset); - assets.add(assetService.assignAssetToCustomer(tenantId, asset.getId(), customerId)); + assets.add(new AssetInfo(assetService.assignAssetToCustomer(tenantId, asset.getId(), customerId), customer.getTitle(), customer.isPublic())); } - List loadedAssets = new ArrayList<>(); + List loadedAssets = new ArrayList<>(); PageLink pageLink = new PageLink(23); - PageData pageData = null; + PageData pageData = null; do { - pageData = assetService.findAssetsByTenantIdAndCustomerId(tenantId, customerId, pageLink); + pageData = assetService.findAssetInfosByTenantIdAndCustomerId(tenantId, customerId, pageLink); loadedAssets.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); @@ -448,7 +449,7 @@ public abstract class BaseAssetServiceTest extends AbstractServiceTest { assetService.unassignCustomerAssets(tenantId, customerId); pageLink = new PageLink(33); - pageData = assetService.findAssetsByTenantIdAndCustomerId(tenantId, customerId, pageLink); + pageData = assetService.findAssetInfosByTenantIdAndCustomerId(tenantId, customerId, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertTrue(pageData.getData().isEmpty()); diff --git a/dao/src/test/resources/nosql-test.properties b/dao/src/test/resources/nosql-test.properties index 556a024c7d..07d1f3dd3d 100644 --- a/dao/src/test/resources/nosql-test.properties +++ b/dao/src/test/resources/nosql-test.properties @@ -4,6 +4,7 @@ sql.ts_inserts_executor_type=fixed sql.ts_inserts_fixed_thread_pool_size=10 spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true +spring.jpa.properties.hibernate.order_by.default_null_ordering=last spring.jpa.show-sql=false spring.jpa.hibernate.ddl-auto=none spring.jpa.database-platform=org.hibernate.dialect.HSQLDialect diff --git a/dao/src/test/resources/sql-test.properties b/dao/src/test/resources/sql-test.properties index 121f1b9a7a..ae2f217181 100644 --- a/dao/src/test/resources/sql-test.properties +++ b/dao/src/test/resources/sql-test.properties @@ -4,6 +4,7 @@ sql.ts_inserts_executor_type=fixed sql.ts_inserts_fixed_thread_pool_size=10 spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true +spring.jpa.properties.hibernate.order_by.default_null_ordering=last spring.jpa.show-sql=false spring.jpa.hibernate.ddl-auto=validate spring.jpa.database-platform=org.hibernate.dialect.HSQLDialect From bd8a104b9f4900b2d25003a1fe681d2a7617149d Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 16 Aug 2019 20:09:56 +0300 Subject: [PATCH 013/133] UI: Asset and Entity View services --- ui-ngx/src/app/core/http/asset.service.ts | 84 +++++++++++++++++++ .../src/app/core/http/entity-view.service.ts | 83 ++++++++++++++++++ ui-ngx/src/app/core/http/entity.service.ts | 23 +++-- ...d-entities-to-customer-dialog.component.ts | 8 +- .../assign-to-customer-dialog.component.ts | 8 +- .../entity-subtype-autocomplete.component.ts | 8 +- .../entity/entity-subtype-select.component.ts | 8 +- 7 files changed, 206 insertions(+), 16 deletions(-) create mode 100644 ui-ngx/src/app/core/http/asset.service.ts create mode 100644 ui-ngx/src/app/core/http/entity-view.service.ts diff --git a/ui-ngx/src/app/core/http/asset.service.ts b/ui-ngx/src/app/core/http/asset.service.ts new file mode 100644 index 0000000000..e7cd9af761 --- /dev/null +++ b/ui-ngx/src/app/core/http/asset.service.ts @@ -0,0 +1,84 @@ +/// +/// Copyright © 2016-2019 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 {Injectable} from '@angular/core'; +import {defaultHttpOptions} from './http-utils'; +import {Observable} from 'rxjs/index'; +import {HttpClient} from '@angular/common/http'; +import {PageLink} from '@shared/models/page/page-link'; +import {PageData} from '@shared/models/page/page-data'; +import {EntitySubtype} from '@app/shared/models/entity-type.models'; +import {Asset, AssetInfo} from '@app/shared/models/asset.models'; + +@Injectable({ + providedIn: 'root' +}) +export class AssetService { + + constructor( + private http: HttpClient + ) { } + + public getTenantAssetInfos(pageLink: PageLink, type: string = '', ignoreErrors: boolean = false, + ignoreLoading: boolean = false): Observable> { + return this.http.get>(`/api/tenant/assetInfos${pageLink.toQuery()}&type=${type}`, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public getCustomerAssetInfos(customerId: string, pageLink: PageLink, type: string = '', ignoreErrors: boolean = false, + ignoreLoading: boolean = false): Observable> { + return this.http.get>(`/api/customer/${customerId}/assetInfos${pageLink.toQuery()}&type=${type}`, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public getAsset(assetId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.get(`/api/asset/${assetId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public getAssets(assetIds: Array, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + return this.http.get>(`/api/assets?assetIds=${assetIds.join(',')}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public getAssetInfo(assetId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.get(`/api/asset/info/${assetId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public saveAsset(asset: Asset, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.post('/api/asset', asset, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public deleteAsset(assetId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { + return this.http.delete(`/api/asset/${assetId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public getAssetTypes(ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + return this.http.get>('/api/asset/types', defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public makeAssetPublic(assetId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.post(`/api/customer/public/asset/${assetId}`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public assignAssetToCustomer(customerId: string, assetId: string, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.post(`/api/customer/${customerId}/asset/${assetId}`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public unassignAssetFromCustomer(assetId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { + return this.http.delete(`/api/customer/asset/${assetId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + +} diff --git a/ui-ngx/src/app/core/http/entity-view.service.ts b/ui-ngx/src/app/core/http/entity-view.service.ts new file mode 100644 index 0000000000..c22c2e0d74 --- /dev/null +++ b/ui-ngx/src/app/core/http/entity-view.service.ts @@ -0,0 +1,83 @@ +/// +/// Copyright © 2016-2019 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 {Injectable} from '@angular/core'; +import {defaultHttpOptions} from './http-utils'; +import {Observable} from 'rxjs/index'; +import {HttpClient} from '@angular/common/http'; +import {PageLink} from '@shared/models/page/page-link'; +import {PageData} from '@shared/models/page/page-data'; +import {EntitySubtype} from '@app/shared/models/entity-type.models'; +import {EntityView, EntityViewInfo} from '@app/shared/models/entity-view.models'; + +@Injectable({ + providedIn: 'root' +}) +export class EntityViewService { + + constructor( + private http: HttpClient + ) { } + + public getTenantEntityViewInfos(pageLink: PageLink, type: string = '', ignoreErrors: boolean = false, + ignoreLoading: boolean = false): Observable> { + return this.http.get>(`/api/tenant/entityViewInfos${pageLink.toQuery()}&type=${type}`, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public getCustomerEntityViewInfos(customerId: string, pageLink: PageLink, type: string = '', ignoreErrors: boolean = false, + ignoreLoading: boolean = false): Observable> { + return this.http.get>(`/api/customer/${customerId}/entityViewInfos${pageLink.toQuery()}&type=${type}`, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public getEntityView(entityViewId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.get(`/api/entityView/${entityViewId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public getEntityViewInfo(entityViewId: string, ignoreErrors: boolean = false, + ignoreLoading: boolean = false): Observable { + return this.http.get(`/api/entityView/info/${entityViewId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public saveEntityView(entityView: EntityView, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.post('/api/entityView', entityView, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public deleteEntityView(entityViewId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { + return this.http.delete(`/api/entityView/${entityViewId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public getEntityViewTypes(ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + return this.http.get>('/api/entityView/types', defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public makeEntityViewPublic(entityViewId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.post(`/api/customer/public/entityView/${entityViewId}`, null, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public assignEntityViewToCustomer(customerId: string, entityViewId: string, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.post(`/api/customer/${customerId}/entityView/${entityViewId}`, null, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public unassignEntityViewFromCustomer(entityViewId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { + return this.http.delete(`/api/customer/entityView/${entityViewId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + +} diff --git a/ui-ngx/src/app/core/http/entity.service.ts b/ui-ngx/src/app/core/http/entity.service.ts index f0f22c50da..ef4b306710 100644 --- a/ui-ngx/src/app/core/http/entity.service.ts +++ b/ui-ngx/src/app/core/http/entity.service.ts @@ -35,6 +35,8 @@ import {Authority} from '@shared/models/authority.enum'; import {Tenant} from '@shared/models/tenant.model'; import {concatMap, expand, map, toArray} from 'rxjs/operators'; import {Customer} from '@app/shared/models/customer.model'; +import {AssetService} from '@core/http/asset.service'; +import {EntityViewService} from '@core/http/entity-view.service'; @Injectable({ providedIn: 'root' @@ -45,6 +47,8 @@ export class EntityService { private http: HttpClient, private store: Store, private deviceService: DeviceService, + private assetService: AssetService, + private entityViewService: EntityViewService, private tenantService: TenantService, private customerService: CustomerService, private userService: UserService, @@ -60,10 +64,10 @@ export class EntityService { observable = this.deviceService.getDevice(entityId, ignoreErrors, ignoreLoading); break; case EntityType.ASSET: - // TODO: + observable = this.assetService.getAsset(entityId, ignoreErrors, ignoreLoading); break; case EntityType.ENTITY_VIEW: - // TODO: + observable = this.entityViewService.getEntityView(entityId, ignoreErrors, ignoreLoading); break; case EntityType.TENANT: observable = this.tenantService.getTenant(entityId, ignoreErrors, ignoreLoading); @@ -127,10 +131,12 @@ export class EntityService { observable = this.deviceService.getDevices(entityIds, ignoreErrors, ignoreLoading); break; case EntityType.ASSET: - // TODO: + observable = this.assetService.getAssets(entityIds, ignoreErrors, ignoreLoading); break; case EntityType.ENTITY_VIEW: - // TODO: + observable = this.getEntitiesByIdsObservable( + (id) => this.entityViewService.getEntityView(id, ignoreErrors, ignoreLoading), + entityIds); break; case EntityType.TENANT: observable = this.getEntitiesByIdsObservable( @@ -233,17 +239,18 @@ export class EntityService { case EntityType.ASSET: pageLink.sortOrder.property = 'name'; if (authUser.authority === Authority.CUSTOMER_USER) { - // TODO: + entitiesObservable = this.assetService.getCustomerAssetInfos(customerId, pageLink, subType, ignoreErrors, ignoreLoading); } else { - // TODO: + entitiesObservable = this.assetService.getTenantAssetInfos(pageLink, subType, ignoreErrors, ignoreLoading); } break; case EntityType.ENTITY_VIEW: pageLink.sortOrder.property = 'name'; if (authUser.authority === Authority.CUSTOMER_USER) { - // TODO: + entitiesObservable = this.entityViewService.getCustomerEntityViewInfos(customerId, pageLink, + subType, ignoreErrors, ignoreLoading); } else { - // TODO: + entitiesObservable = this.entityViewService.getTenantEntityViewInfos(pageLink, subType, ignoreErrors, ignoreLoading); } break; case EntityType.TENANT: diff --git a/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.ts b/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.ts index 19a4ac3be7..7420ddaf7f 100644 --- a/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.ts @@ -24,6 +24,8 @@ import {DeviceService} from '@core/http/device.service'; import {EntityId} from '@shared/models/id/entity-id'; import {EntityType} from '@shared/models/entity-type.models'; import {forkJoin, Observable} from 'rxjs'; +import {AssetService} from '@core/http/asset.service'; +import {EntityViewService} from '@core/http/entity-view.service'; export interface AddEntitiesToCustomerDialogData { customerId: string; @@ -50,6 +52,8 @@ export class AddEntitiesToCustomerDialogComponent extends PageComponent implemen constructor(protected store: Store, @Inject(MAT_DIALOG_DATA) public data: AddEntitiesToCustomerDialogData, private deviceService: DeviceService, + private assetService: AssetService, + private entityViewService: EntityViewService, @SkipSelf() private errorStateMatcher: ErrorStateMatcher, public dialogRef: MatDialogRef, public fb: FormBuilder) { @@ -107,10 +111,10 @@ export class AddEntitiesToCustomerDialogComponent extends PageComponent implemen return this.deviceService.assignDeviceToCustomer(customerId, entityId); break; case EntityType.ASSET: - // TODO: + return this.assetService.assignAssetToCustomer(customerId, entityId); break; case EntityType.ENTITY_VIEW: - // TODO: + return this.entityViewService.assignEntityViewToCustomer(customerId, entityId); break; } } diff --git a/ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.ts b/ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.ts index ea40fea272..b7bc4dbc09 100644 --- a/ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.ts @@ -24,6 +24,8 @@ import {DeviceService} from '@core/http/device.service'; import {EntityId} from '@shared/models/id/entity-id'; import {EntityType} from '@shared/models/entity-type.models'; import {forkJoin, Observable} from 'rxjs'; +import {AssetService} from '@core/http/asset.service'; +import {EntityViewService} from '@core/http/entity-view.service'; export interface AssignToCustomerDialogData { entityIds: Array; @@ -50,6 +52,8 @@ export class AssignToCustomerDialogComponent extends PageComponent implements On constructor(protected store: Store, @Inject(MAT_DIALOG_DATA) public data: AssignToCustomerDialogData, private deviceService: DeviceService, + private assetService: AssetService, + private entityViewService: EntityViewService, @SkipSelf() private errorStateMatcher: ErrorStateMatcher, public dialogRef: MatDialogRef, public fb: FormBuilder) { @@ -106,10 +110,10 @@ export class AssignToCustomerDialogComponent extends PageComponent implements On return this.deviceService.assignDeviceToCustomer(customerId, entityId); break; case EntityType.ASSET: - // TODO: + return this.assetService.assignAssetToCustomer(customerId, entityId); break; case EntityType.ENTITY_VIEW: - // TODO: + return this.entityViewService.assignEntityViewToCustomer(customerId, entityId); break; } } diff --git a/ui-ngx/src/app/shared/components/entity/entity-subtype-autocomplete.component.ts b/ui-ngx/src/app/shared/components/entity/entity-subtype-autocomplete.component.ts index b2d2e3b185..3cc79ab416 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-subtype-autocomplete.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-subtype-autocomplete.component.ts @@ -33,6 +33,8 @@ import {DeviceService} from '@core/http/device.service'; import {EntitySubtype, EntityType} from '@app/shared/models/entity-type.models'; import {BroadcastService} from '@app/core/services/broadcast.service'; import {coerceBooleanProperty} from '@angular/cdk/coercion'; +import {AssetService} from '@core/http/asset.service'; +import {EntityViewService} from '@core/http/entity-view.service'; @Component({ selector: 'tb-entity-subtype-autocomplete', @@ -85,6 +87,8 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor, private broadcast: BroadcastService, public translate: TranslateService, private deviceService: DeviceService, + private assetService: AssetService, + private entityViewService: EntityViewService, private fb: FormBuilder) { this.subTypeFormGroup = this.fb.group({ subType: [null] @@ -203,13 +207,13 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor, if (!this.subTypes) { switch (this.entityType) { case EntityType.ASSET: - // TODO: + this.subTypes = this.assetService.getAssetTypes(false, true); break; case EntityType.DEVICE: this.subTypes = this.deviceService.getDeviceTypes(false, true); break; case EntityType.ENTITY_VIEW: - // TODO: + this.subTypes = this.entityViewService.getEntityViewTypes(false, true); break; } if (this.subTypes) { diff --git a/ui-ngx/src/app/shared/components/entity/entity-subtype-select.component.ts b/ui-ngx/src/app/shared/components/entity/entity-subtype-select.component.ts index db31c53f56..ceed407498 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-subtype-select.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-subtype-select.component.ts @@ -32,6 +32,8 @@ import {TranslateService} from '@ngx-translate/core'; import {DeviceService} from '@core/http/device.service'; import {EntitySubtype, EntityType} from '@app/shared/models/entity-type.models'; import {BroadcastService} from '@app/core/services/broadcast.service'; +import {AssetService} from '@core/http/asset.service'; +import {EntityViewService} from '@core/http/entity-view.service'; @Component({ selector: 'tb-entity-subtype-select', @@ -83,6 +85,8 @@ export class EntitySubTypeSelectComponent implements ControlValueAccessor, OnIni private broadcast: BroadcastService, public translate: TranslateService, private deviceService: DeviceService, + private assetService: AssetService, + private entityViewService: EntityViewService, private fb: FormBuilder) { this.subTypeFormGroup = this.fb.group({ subType: [null] @@ -202,13 +206,13 @@ export class EntitySubTypeSelectComponent implements ControlValueAccessor, OnIni if (!this.subTypes) { switch (this.entityType) { case EntityType.ASSET: - // TODO: + this.subTypes = this.assetService.getAssetTypes(false, true); break; case EntityType.DEVICE: this.subTypes = this.deviceService.getDeviceTypes(false, true); break; case EntityType.ENTITY_VIEW: - // TODO: + this.subTypes = this.entityViewService.getEntityViewTypes(false, true); break; } if (this.subTypes) { From e33c9d08cbb0d93eeaefc75a7f80f8190bc3e6c8 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 16 Aug 2019 21:22:54 +0300 Subject: [PATCH 014/133] Add Assets page --- ...d-entities-to-customer-dialog.component.ts | 3 +- .../assign-to-customer-dialog.component.ts | 3 +- .../home/pages/asset/asset-routing.module.ts | 50 +++ .../asset/asset-table-header.component.html | 23 + .../asset/asset-table-header.component.scss | 36 ++ .../asset/asset-table-header.component.ts | 42 ++ .../home/pages/asset/asset.component.html | 88 ++++ .../home/pages/asset/asset.component.scss | 19 + .../home/pages/asset/asset.component.ts | 93 ++++ .../modules/home/pages/asset/asset.module.ts | 41 ++ .../asset/assets-table-config.resolver.ts | 420 ++++++++++++++++++ .../pages/customer/customer-routing.module.ts | 17 + .../device/devices-table-config.resolver.ts | 4 +- .../modules/home/pages/home-pages.module.ts | 2 + ui-ngx/src/app/shared/models/constants.ts | 4 +- .../app/shared/models/entity-type.models.ts | 40 ++ .../assets/locale/locale.constant-en_US.json | 10 +- 17 files changed, 888 insertions(+), 7 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/pages/asset/asset-routing.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/asset/asset-table-header.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/asset/asset-table-header.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/asset/asset-table-header.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/asset/asset.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/asset/asset.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/asset/asset.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/asset/asset.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts diff --git a/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.ts b/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.ts index 7420ddaf7f..6a4535f170 100644 --- a/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.ts @@ -71,7 +71,8 @@ export class AddEntitiesToCustomerDialogComponent extends PageComponent implemen this.assignToCustomerText = 'device.assign-device-to-customer-text'; break; case EntityType.ASSET: - // TODO: + this.assignToCustomerTitle = 'asset.assign-asset-to-customer'; + this.assignToCustomerText = 'asset.assign-asset-to-customer-text'; break; case EntityType.ENTITY_VIEW: // TODO: diff --git a/ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.ts b/ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.ts index b7bc4dbc09..1d080ce53f 100644 --- a/ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.ts @@ -70,7 +70,8 @@ export class AssignToCustomerDialogComponent extends PageComponent implements On this.assignToCustomerText = 'device.assign-to-customer-text'; break; case EntityType.ASSET: - // TODO: + this.assignToCustomerTitle = 'asset.assign-asset-to-customer'; + this.assignToCustomerText = 'asset.assign-to-customer-text'; break; case EntityType.ENTITY_VIEW: // TODO: diff --git a/ui-ngx/src/app/modules/home/pages/asset/asset-routing.module.ts b/ui-ngx/src/app/modules/home/pages/asset/asset-routing.module.ts new file mode 100644 index 0000000000..8ded96dc19 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/asset/asset-routing.module.ts @@ -0,0 +1,50 @@ +/// +/// Copyright © 2016-2019 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 {EntitiesTableComponent} from '@shared/components/entity/entities-table.component'; +import {Authority} from '@shared/models/authority.enum'; +import {AssetsTableConfigResolver} from './assets-table-config.resolver'; + +const routes: Routes = [ + { + path: 'assets', + component: EntitiesTableComponent, + data: { + auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], + title: 'asset.assets', + assetsType: 'tenant', + breadcrumb: { + label: 'asset.assets', + icon: 'domain' + } + }, + resolve: { + entitiesTableConfig: AssetsTableConfigResolver + } + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], + providers: [ + AssetsTableConfigResolver + ] +}) +export class AssetRoutingModule { } diff --git a/ui-ngx/src/app/modules/home/pages/asset/asset-table-header.component.html b/ui-ngx/src/app/modules/home/pages/asset/asset-table-header.component.html new file mode 100644 index 0000000000..38421eb64b --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/asset/asset-table-header.component.html @@ -0,0 +1,23 @@ + + + diff --git a/ui-ngx/src/app/modules/home/pages/asset/asset-table-header.component.scss b/ui-ngx/src/app/modules/home/pages/asset/asset-table-header.component.scss new file mode 100644 index 0000000000..cb7fe8d04b --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/asset/asset-table-header.component.scss @@ -0,0 +1,36 @@ +/** + * Copyright © 2016-2019 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 { + flex: 1; + display: flex; + justify-content: flex-start; +} + +:host ::ng-deep { + tb-entity-subtype-select { + mat-form-field { + font-size: 16px; + + .mat-form-field-wrapper { + padding-bottom: 0; + } + + .mat-form-field-underline { + bottom: 0; + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/asset/asset-table-header.component.ts b/ui-ngx/src/app/modules/home/pages/asset/asset-table-header.component.ts new file mode 100644 index 0000000000..20b638ef5a --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/asset/asset-table-header.component.ts @@ -0,0 +1,42 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import {Component} from '@angular/core'; +import {Store} from '@ngrx/store'; +import {AppState} from '@core/core.state'; +import {EntityTableHeaderComponent} from '@shared/components/entity/entity-table-header.component'; +import {EntityType} from '@shared/models/entity-type.models'; +import {AssetInfo} from '@shared/models/asset.models'; + +@Component({ + selector: 'tb-asset-table-header', + templateUrl: './asset-table-header.component.html', + styleUrls: ['./asset-table-header.component.scss'] +}) +export class AssetTableHeaderComponent extends EntityTableHeaderComponent { + + entityType = EntityType; + + constructor(protected store: Store) { + super(store); + } + + assetTypeChanged(assetType: string) { + this.entitiesTableConfig.componentsData.assetType = assetType; + this.entitiesTableConfig.table.updateData(); + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/asset/asset.component.html b/ui-ngx/src/app/modules/home/pages/asset/asset.component.html new file mode 100644 index 0000000000..299bbbc5dc --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/asset/asset.component.html @@ -0,0 +1,88 @@ + +
+ + + + +
+ +
+
+
+ + asset.assignedToCustomer + + +
+ {{ 'asset.asset-public' | translate }} +
+
+
+ + asset.name + + + {{ 'asset.name-required' | translate }} + + + + +
+ + asset.description + + +
+
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/asset/asset.component.scss b/ui-ngx/src/app/modules/home/pages/asset/asset.component.scss new file mode 100644 index 0000000000..d18a4874d0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/asset/asset.component.scss @@ -0,0 +1,19 @@ +/** + * Copyright © 2016-2019 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 { + +} diff --git a/ui-ngx/src/app/modules/home/pages/asset/asset.component.ts b/ui-ngx/src/app/modules/home/pages/asset/asset.component.ts new file mode 100644 index 0000000000..6265126dbf --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/asset/asset.component.ts @@ -0,0 +1,93 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import {Component} from '@angular/core'; +import {Store} from '@ngrx/store'; +import {AppState} from '@core/core.state'; +import {EntityComponent} from '@shared/components/entity/entity.component'; +import {FormBuilder, FormGroup, Validators} from '@angular/forms'; +import {EntityType} from '@shared/models/entity-type.models'; +import {NULL_UUID} from '@shared/models/id/has-uuid'; +import {ActionNotificationShow} from '@core/notification/notification.actions'; +import {TranslateService} from '@ngx-translate/core'; +import {AssetInfo} from '@app/shared/models/asset.models'; + +@Component({ + selector: 'tb-asset', + templateUrl: './asset.component.html', + styleUrls: ['./asset.component.scss'] +}) +export class AssetComponent extends EntityComponent { + + entityType = EntityType; + + assetScope: 'tenant' | 'customer' | 'customer_user'; + + constructor(protected store: Store, + protected translate: TranslateService, + public fb: FormBuilder) { + super(store); + } + + ngOnInit() { + this.assetScope = this.entitiesTableConfig.componentsData.assetScope; + super.ngOnInit(); + } + + hideDelete() { + if (this.entitiesTableConfig) { + return !this.entitiesTableConfig.deleteEnabled(this.entity); + } else { + return false; + } + } + + isAssignedToCustomer(entity: AssetInfo): boolean { + return entity && entity.customerId && entity.customerId.id !== NULL_UUID; + } + + buildForm(entity: AssetInfo): FormGroup { + return this.fb.group( + { + name: [entity ? entity.name : '', [Validators.required]], + type: [entity ? entity.type : null, [Validators.required]], + additionalInfo: this.fb.group( + { + description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''], + } + ) + } + ); + } + + updateForm(entity: AssetInfo) { + this.entityForm.patchValue({name: entity.name}); + this.entityForm.patchValue({type: entity.type}); + this.entityForm.patchValue({additionalInfo: {description: entity.additionalInfo ? entity.additionalInfo.description : ''}}); + } + + + onAssetIdCopied($event) { + this.store.dispatch(new ActionNotificationShow( + { + message: this.translate.instant('asset.idCopiedMessage'), + type: 'success', + duration: 750, + verticalPosition: 'bottom', + horizontalPosition: 'right' + })); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/asset/asset.module.ts b/ui-ngx/src/app/modules/home/pages/asset/asset.module.ts new file mode 100644 index 0000000000..04c401979e --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/asset/asset.module.ts @@ -0,0 +1,41 @@ +/// +/// Copyright © 2016-2019 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 {CommonModule} from '@angular/common'; +import {SharedModule} from '@shared/shared.module'; +import {HomeDialogsModule} from '../../dialogs/home-dialogs.module'; +import {AssetComponent} from './asset.component'; +import {AssetTableHeaderComponent} from './asset-table-header.component'; +import {AssetRoutingModule} from './asset-routing.module'; + +@NgModule({ + entryComponents: [ + AssetComponent, + AssetTableHeaderComponent + ], + declarations: [ + AssetComponent, + AssetTableHeaderComponent + ], + imports: [ + CommonModule, + SharedModule, + HomeDialogsModule, + AssetRoutingModule + ] +}) +export class AssetModule { } diff --git a/ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts new file mode 100644 index 0000000000..1d0c1545af --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts @@ -0,0 +1,420 @@ +/// +/// Copyright © 2016-2019 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 {Injectable} from '@angular/core'; + +import {ActivatedRouteSnapshot, Resolve, Router} from '@angular/router'; +import { + CellActionDescriptor, + checkBoxCell, + DateEntityTableColumn, + EntityTableColumn, + EntityTableConfig, + GroupActionDescriptor, + HeaderActionDescriptor +} from '@shared/components/entity/entities-table-config.models'; +import {TranslateService} from '@ngx-translate/core'; +import {DatePipe} from '@angular/common'; +import {EntityType, entityTypeResources, entityTypeTranslations} from '@shared/models/entity-type.models'; +import {EntityAction} from '@shared/components/entity/entity-component.models'; +import {forkJoin, Observable, of} from 'rxjs'; +import {select, Store} from '@ngrx/store'; +import {selectAuthUser} from '@core/auth/auth.selectors'; +import {map, mergeMap, take, tap} from 'rxjs/operators'; +import {AppState} from '@core/core.state'; +import {Authority} from '@app/shared/models/authority.enum'; +import {CustomerService} from '@core/http/customer.service'; +import {Customer} from '@app/shared/models/customer.model'; +import {NULL_UUID} from '@shared/models/id/has-uuid'; +import {BroadcastService} from '@core/services/broadcast.service'; +import {MatDialog} from '@angular/material'; +import {DialogService} from '@core/services/dialog.service'; +import { + AssignToCustomerDialogComponent, + AssignToCustomerDialogData +} from '@modules/home/dialogs/assign-to-customer-dialog.component'; +import { + AddEntitiesToCustomerDialogComponent, + AddEntitiesToCustomerDialogData +} from '../../dialogs/add-entities-to-customer-dialog.component'; +import {Asset, AssetInfo} from '@app/shared/models/asset.models'; +import {AssetService} from '@app/core/http/asset.service'; +import {AssetComponent} from '@modules/home/pages/asset/asset.component'; +import {AssetTableHeaderComponent} from '@modules/home/pages/asset/asset-table-header.component'; +import {AssetId} from '@app/shared/models/id/asset-id'; + +@Injectable() +export class AssetsTableConfigResolver implements Resolve> { + + private readonly config: EntityTableConfig = new EntityTableConfig(); + + private customerId: string; + + constructor(private store: Store, + private broadcast: BroadcastService, + private assetService: AssetService, + private customerService: CustomerService, + private dialogService: DialogService, + private translate: TranslateService, + private datePipe: DatePipe, + private router: Router, + private dialog: MatDialog) { + + this.config.entityType = EntityType.ASSET; + this.config.entityComponent = AssetComponent; + this.config.entityTranslations = entityTypeTranslations.get(EntityType.ASSET); + this.config.entityResources = entityTypeResources.get(EntityType.ASSET); + + this.config.deleteEntityTitle = asset => this.translate.instant('asset.delete-asset-title', { assetName: asset.name }); + this.config.deleteEntityContent = () => this.translate.instant('asset.delete-asset-text'); + this.config.deleteEntitiesTitle = count => this.translate.instant('asset.delete-assets-title', {count}); + this.config.deleteEntitiesContent = () => this.translate.instant('asset.delete-assets-text'); + + this.config.loadEntity = id => this.assetService.getAssetInfo(id.id); + this.config.saveEntity = asset => { + return this.assetService.saveAsset(asset).pipe( + tap(() => { + this.broadcast.broadcast('assetSaved'); + }), + mergeMap((savedAsset) => this.assetService.getAssetInfo(savedAsset.id.id) + )); + }; + this.config.onEntityAction = action => this.onAssetAction(action); + + this.config.headerComponent = AssetTableHeaderComponent; + + } + + resolve(route: ActivatedRouteSnapshot): Observable> { + const routeParams = route.params; + this.config.componentsData = { + assetScope: route.data.assetsType, + assetType: '' + }; + this.customerId = routeParams.customerId; + return this.store.pipe(select(selectAuthUser), take(1)).pipe( + tap((authUser) => { + if (authUser.authority === Authority.CUSTOMER_USER) { + this.config.componentsData.assetScope = 'customer_user'; + this.customerId = authUser.customerId; + } + }), + mergeMap(() => + this.customerId ? this.customerService.getCustomer(this.customerId) : of(null as Customer) + ), + map((parentCustomer) => { + if (parentCustomer) { + if (parentCustomer.additionalInfo && parentCustomer.additionalInfo.isPublic) { + this.config.tableTitle = this.translate.instant('customer.public-assets'); + } else { + this.config.tableTitle = parentCustomer.title + ': ' + this.translate.instant('asset.assets'); + } + } else { + this.config.tableTitle = this.translate.instant('asset.assets'); + } + this.config.columns = this.configureColumns(this.config.componentsData.assetScope); + this.configureEntityFunctions(this.config.componentsData.assetScope); + this.config.cellActionDescriptors = this.configureCellActions(this.config.componentsData.assetScope); + this.config.groupActionDescriptors = this.configureGroupActions(this.config.componentsData.assetScope); + this.config.addActionDescriptors = this.configureAddActions(this.config.componentsData.assetScope); + this.config.addEnabled = this.config.componentsData.assetScope !== 'customer_user'; + this.config.entitiesDeleteEnabled = this.config.componentsData.assetScope === 'tenant'; + this.config.deleteEnabled = () => this.config.componentsData.assetScope === 'tenant'; + return this.config; + }) + ); + } + + configureColumns(assetScope: string): Array> { + const columns: Array> = [ + new DateEntityTableColumn('createdTime', 'asset.created-time', this.datePipe, '150px'), + new EntityTableColumn('name', 'asset.name'), + new EntityTableColumn('type', 'asset.asset-type'), + ]; + if (assetScope === 'tenant') { + columns.push( + new EntityTableColumn('customerTitle', 'customer.customer'), + new EntityTableColumn('customerIsPublic', 'asset.public', '60px', + entity => { + return checkBoxCell(entity.customerIsPublic); + }, () => ({}), false), + ); + } + return columns; + } + + configureEntityFunctions(assetScope: string): void { + if (assetScope === 'tenant') { + this.config.entitiesFetchFunction = pageLink => + this.assetService.getTenantAssetInfos(pageLink, this.config.componentsData.assetType); + this.config.deleteEntity = id => this.assetService.deleteAsset(id.id); + } else { + this.config.entitiesFetchFunction = pageLink => + this.assetService.getCustomerAssetInfos(this.customerId, pageLink, this.config.componentsData.assetType); + this.config.deleteEntity = id => this.assetService.unassignAssetFromCustomer(id.id); + } + } + + configureCellActions(assetScope: string): Array> { + const actions: Array> = []; + if (assetScope === 'tenant') { + actions.push( + { + name: this.translate.instant('asset.make-public'), + icon: 'share', + isEnabled: (entity) => (!entity.customerId || entity.customerId.id === NULL_UUID), + onAction: ($event, entity) => this.makePublic($event, entity) + }, + { + name: this.translate.instant('asset.assign-to-customer'), + icon: 'assignment_ind', + isEnabled: (entity) => (!entity.customerId || entity.customerId.id === NULL_UUID), + onAction: ($event, entity) => this.assignToCustomer($event, [entity.id]) + }, + { + name: this.translate.instant('asset.unassign-from-customer'), + icon: 'assignment_return', + isEnabled: (entity) => (entity.customerId && entity.customerId.id !== NULL_UUID && !entity.customerIsPublic), + onAction: ($event, entity) => this.unassignFromCustomer($event, entity) + }, + { + name: this.translate.instant('asset.make-private'), + icon: 'reply', + isEnabled: (entity) => (entity.customerId && entity.customerId.id !== NULL_UUID && entity.customerIsPublic), + onAction: ($event, entity) => this.unassignFromCustomer($event, entity) + } + ); + } + if (assetScope === 'customer') { + actions.push( + { + name: this.translate.instant('asset.unassign-from-customer'), + icon: 'assignment_return', + isEnabled: (entity) => (entity.customerId && entity.customerId.id !== NULL_UUID && !entity.customerIsPublic), + onAction: ($event, entity) => this.unassignFromCustomer($event, entity) + }, + { + name: this.translate.instant('asset.make-private'), + icon: 'reply', + isEnabled: (entity) => (entity.customerId && entity.customerId.id !== NULL_UUID && entity.customerIsPublic), + onAction: ($event, entity) => this.unassignFromCustomer($event, entity) + } + ); + } + return actions; + } + + configureGroupActions(assetScope: string): Array> { + const actions: Array> = []; + if (assetScope === 'tenant') { + actions.push( + { + name: this.translate.instant('asset.assign-assets'), + icon: 'assignment_ind', + isEnabled: true, + onAction: ($event, entities) => this.assignToCustomer($event, entities.map((entity) => entity.id)) + } + ); + } + if (assetScope === 'customer') { + actions.push( + { + name: this.translate.instant('asset.unassign-assets'), + icon: 'assignment_return', + isEnabled: true, + onAction: ($event, entities) => this.unassignAssetsFromCustomer($event, entities) + } + ); + } + return actions; + } + + configureAddActions(assetScope: string): Array { + const actions: Array = []; + if (assetScope === 'tenant') { + actions.push( + { + name: this.translate.instant('asset.add-asset-text'), + icon: 'insert_drive_file', + isEnabled: () => true, + onAction: ($event) => this.config.table.addEntity($event) + }, + { + name: this.translate.instant('asset.import'), + icon: 'file_upload', + isEnabled: () => true, + onAction: ($event) => this.importAssets($event) + } + ); + } + if (assetScope === 'customer') { + actions.push( + { + name: this.translate.instant('asset.assign-new-asset'), + icon: 'add', + isEnabled: () => true, + onAction: ($event) => this.addAssetsToCustomer($event) + } + ); + } + return actions; + } + + importAssets($event: Event) { + if ($event) { + $event.stopPropagation(); + } + // TODO: + } + + addAssetsToCustomer($event: Event) { + if ($event) { + $event.stopPropagation(); + } + this.dialog.open(AddEntitiesToCustomerDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + customerId: this.customerId, + entityType: EntityType.ASSET + } + }).afterClosed() + .subscribe((res) => { + if (res) { + this.config.table.updateData(); + } + }); + } + + makePublic($event: Event, asset: Asset) { + if ($event) { + $event.stopPropagation(); + } + this.dialogService.confirm( + this.translate.instant('asset.make-public-asset-title', {assetName: asset.name}), + this.translate.instant('asset.make-public-asset-text'), + this.translate.instant('action.no'), + this.translate.instant('action.yes'), + true + ).subscribe((res) => { + if (res) { + this.assetService.makeAssetPublic(asset.id.id).subscribe( + () => { + this.config.table.updateData(); + } + ); + } + } + ); + } + + assignToCustomer($event: Event, assetIds: Array) { + if ($event) { + $event.stopPropagation(); + } + this.dialog.open(AssignToCustomerDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + entityIds: assetIds, + entityType: EntityType.ASSET + } + }).afterClosed() + .subscribe((res) => { + if (res) { + this.config.table.updateData(); + } + }); + } + + unassignFromCustomer($event: Event, asset: AssetInfo) { + if ($event) { + $event.stopPropagation(); + } + const isPublic = asset.customerIsPublic; + let title; + let content; + if (isPublic) { + title = this.translate.instant('asset.make-private-asset-title', {assetName: asset.name}); + content = this.translate.instant('asset.make-private-asset-text'); + } else { + title = this.translate.instant('asset.unassign-asset-title', {assetName: asset.name}); + content = this.translate.instant('asset.unassign-asset-text'); + } + this.dialogService.confirm( + title, + content, + this.translate.instant('action.no'), + this.translate.instant('action.yes'), + true + ).subscribe((res) => { + if (res) { + this.assetService.unassignAssetFromCustomer(asset.id.id).subscribe( + () => { + this.config.table.updateData(); + } + ); + } + } + ); + } + + unassignAssetsFromCustomer($event: Event, assets: Array) { + if ($event) { + $event.stopPropagation(); + } + this.dialogService.confirm( + this.translate.instant('asset.unassign-assets-title', {count: assets.length}), + this.translate.instant('asset.unassign-assets-text'), + this.translate.instant('action.no'), + this.translate.instant('action.yes'), + true + ).subscribe((res) => { + if (res) { + const tasks: Observable[] = []; + assets.forEach( + (asset) => { + tasks.push(this.assetService.unassignAssetFromCustomer(asset.id.id)); + } + ); + forkJoin(tasks).subscribe( + () => { + this.config.table.updateData(); + } + ); + } + } + ); + } + + onAssetAction(action: EntityAction): boolean { + switch (action.action) { + case 'makePublic': + this.makePublic(action.event, action.entity); + return true; + case 'assignToCustomer': + this.assignToCustomer(action.event, [action.entity.id]); + return true; + case 'unassignFromCustomer': + this.unassignFromCustomer(action.event, action.entity); + return true; + } + return false; + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts b/ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts index 317b894402..224a94a74b 100644 --- a/ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts @@ -22,6 +22,7 @@ import {Authority} from '@shared/models/authority.enum'; import {UsersTableConfigResolver} from '../user/users-table-config.resolver'; import {CustomersTableConfigResolver} from './customers-table-config.resolver'; import {DevicesTableConfigResolver} from '@modules/home/pages/device/devices-table-config.resolver'; +import {AssetsTableConfigResolver} from '../asset/assets-table-config.resolver'; const routes: Routes = [ { @@ -74,6 +75,22 @@ const routes: Routes = [ resolve: { entitiesTableConfig: DevicesTableConfigResolver } + }, + { + path: ':customerId/assets', + component: EntitiesTableComponent, + data: { + auth: [Authority.TENANT_ADMIN], + title: 'customer.assets', + assetsType: 'customer', + breadcrumb: { + label: 'customer.assets', + icon: 'domain' + } + }, + resolve: { + entitiesTableConfig: AssetsTableConfigResolver + } } ] } 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 eb7b1a808e..ad1bb3a820 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 @@ -141,8 +141,8 @@ export class DevicesTableConfigResolver implements Resolve> { - const columns: Array> = [ + configureColumns(deviceScope: string): Array> { + const columns: Array> = [ new DateEntityTableColumn('createdTime', 'device.created-time', this.datePipe, '150px'), new EntityTableColumn('name', 'device.name'), new EntityTableColumn('type', 'device.device-type'), 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 ab24696552..38e776ab79 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 @@ -24,6 +24,7 @@ import { CustomerModule } from '@modules/home/pages/customer/customer.module'; // import { AuditLogModule } from '@modules/home/pages/audit-log/audit-log.module'; import { UserModule } from '@modules/home/pages/user/user.module'; import {DeviceModule} from '@modules/home/pages/device/device.module'; +import {AssetModule} from '@modules/home/pages/asset/asset.module'; @NgModule({ exports: [ @@ -32,6 +33,7 @@ import {DeviceModule} from '@modules/home/pages/device/device.module'; ProfileModule, TenantModule, DeviceModule, + AssetModule, CustomerModule, // AuditLogModule, UserModule diff --git a/ui-ngx/src/app/shared/models/constants.ts b/ui-ngx/src/app/shared/models/constants.ts index f92a20eed2..c6976ff660 100644 --- a/ui-ngx/src/app/shared/models/constants.ts +++ b/ui-ngx/src/app/shared/models/constants.ts @@ -60,7 +60,9 @@ export const HelpLinks = { tenants: helpBaseUrl + '/docs/user-guide/ui/tenants', customers: helpBaseUrl + '/docs/user-guide/customers', users: helpBaseUrl + '/docs/user-guide/ui/users', - devices: helpBaseUrl + '/docs/user-guide/ui/devices' + devices: helpBaseUrl + '/docs/user-guide/ui/devices', + assets: helpBaseUrl + '/docs/user-guide/ui/assets', + entityViews: helpBaseUrl + '/docs/user-guide/ui/entity-views' } }; diff --git a/ui-ngx/src/app/shared/models/entity-type.models.ts b/ui-ngx/src/app/shared/models/entity-type.models.ts index 475d24d67f..28f8db7a41 100644 --- a/ui-ngx/src/app/shared/models/entity-type.models.ts +++ b/ui-ngx/src/app/shared/models/entity-type.models.ts @@ -108,6 +108,34 @@ export const entityTypeTranslations = new Map search: 'device.search', selectedEntities: 'device.selected-devices' } + ], + [ + EntityType.ASSET, + { + type: 'entity.type-asset', + typePlural: 'entity.type-assets', + list: 'entity.list-of-assets', + nameStartsWith: 'entity.asset-name-starts-with', + details: 'asset.asset-details', + add: 'asset.add', + noEntities: 'asset.no-assets-text', + search: 'asset.search', + selectedEntities: 'asset.selected-assets' + } + ], + [ + EntityType.ENTITY_VIEW, + { + type: 'entity.type-entity-view', + typePlural: 'entity.type-entity-views', + list: 'entity.list-of-entity-views', + nameStartsWith: 'entity.entity-view-name-starts-with', + details: 'entity-view.entity-view-details', + add: 'entity-view.add', + noEntities: 'entity-view.no-entity-views-text', + search: 'entity-view.search', + selectedEntities: 'entity-view.selected-entity-views' + } ] ] ); @@ -137,6 +165,18 @@ export const entityTypeResources = new Map( { helpLinkId: 'devices' } + ], + [ + EntityType.ASSET, + { + helpLinkId: 'assets' + } + ], + [ + EntityType.ENTITY_VIEW, + { + helpLinkId: 'entityViews' + } ] ] ); 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 63ce8dbe30..30b6d1e295 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -228,6 +228,7 @@ "no-asset-types-matching": "No asset types matching '{{entitySubtype}}' were found.", "asset-type-list-empty": "No asset types selected.", "asset-types": "Asset types", + "created-time": "Created time", "name": "Name", "name-required": "Name is required.", "description": "Description", @@ -264,7 +265,9 @@ "asset-required": "Asset is required", "name-starts-with": "Asset name starts with", "import": "Import assets", - "asset-file": "Asset file" + "asset-file": "Asset file", + "search": "Search assets", + "selected-assets": "{ count, plural, 1 {1 asset} other {# assets} } selected" }, "attribute": { "attributes": "Attributes", @@ -867,6 +870,7 @@ "no-entity-view-types-matching": "No entity view types matching '{{entitySubtype}}' were found.", "entity-view-type-list-empty": "No entity view types selected.", "entity-view-types": "Entity View types", + "created-time": "Created time", "name": "Name", "name-required": "Name is required.", "description": "Description", @@ -900,7 +904,9 @@ "make-public-entity-view-title": "Are you sure you want to make the entity view '{{entityViewName}}' public?", "make-public-entity-view-text": "After the confirmation the entity view and all its data will be made public and accessible by others.", "make-private-entity-view-title": "Are you sure you want to make the entity view '{{entityViewName}}' private?", - "make-private-entity-view-text": "After the confirmation the entity view and all its data will be made private and won't be accessible by others." + "make-private-entity-view-text": "After the confirmation the entity view and all its data will be made private and won't be accessible by others.", + "search": "Search entity views", + "selected-entity-views": "{ count, plural, 1 {1 entity view} other {# entity views} } selected" }, "event": { "event-type": "Event type", From 133ab9827492d7038eee2d95e5870221860dceb6 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 19 Aug 2019 20:09:41 +0300 Subject: [PATCH 015/133] Entity views page --- .../server/controller/BaseController.java | 4 +- .../server/common/data/EntityView.java | 8 + ui-ngx/package-lock.json | 3 +- ui-ngx/package.json | 1 + ui-ngx/src/app/core/http/entity.service.ts | 71 +++- ...d-entities-to-customer-dialog.component.ts | 3 +- .../assign-to-customer-dialog.component.ts | 3 +- .../entity-view/entity-view-routing.module.ts | 50 +++ .../entity-view-table-header.component.html | 23 + .../entity-view-table-header.component.scss | 36 ++ .../entity-view-table-header.component.ts | 42 ++ .../entity-view/entity-view.component.html | 151 +++++++ .../entity-view/entity-view.component.scss | 30 ++ .../entity-view/entity-view.component.ts | 138 ++++++ .../pages/entity-view/entity-view.module.ts | 41 ++ .../entity-views-table-config.resolver.ts | 400 ++++++++++++++++++ .../modules/home/pages/home-pages.module.ts | 2 + .../entity/add-entity-dialog.component.html | 2 +- .../entity/entities-table-config.models.ts | 1 + .../entity/entity-autocomplete.component.ts | 36 +- .../entity/entity-keys-list.component.html | 43 ++ .../entity/entity-keys-list.component.ts | 209 +++++++++ .../entity/entity-list.component.ts | 5 + .../entity/entity-select.component.html | 35 ++ .../entity/entity-select.component.scss | 17 + .../entity/entity-select.component.ts | 149 +++++++ .../entity-subtype-autocomplete.component.ts | 5 + .../entity/entity-subtype-select.component.ts | 16 +- .../entity/entity-type-select.component.html | 29 ++ .../entity/entity-type-select.component.scss | 17 + .../entity/entity-type-select.component.ts | 134 ++++++ .../components/time/datetime.component.html | 40 ++ .../components/time/datetime.component.scss | 26 ++ .../components/time/datetime.component.ts | 111 +++++ .../app/shared/models/entity-type.models.ts | 23 +- ui-ngx/src/app/shared/models/id/entity-id.ts | 4 +- .../models/telemetry/telemetry.models.ts | 23 + ui-ngx/src/app/shared/shared.module.ts | 12 + .../assets/locale/locale.constant-en_US.json | 7 +- ui-ngx/src/styles.scss | 7 + 40 files changed, 1916 insertions(+), 41 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/pages/entity-view/entity-view-routing.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/entity-view/entity-view-table-header.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/entity-view/entity-view-table-header.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/entity-view/entity-view-table-header.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/entity-view/entity-view.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/entity-view/entity-view.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/entity-view/entity-view.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/entity-view/entity-view.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/entity-view/entity-views-table-config.resolver.ts create mode 100644 ui-ngx/src/app/shared/components/entity/entity-keys-list.component.html create mode 100644 ui-ngx/src/app/shared/components/entity/entity-keys-list.component.ts create mode 100644 ui-ngx/src/app/shared/components/entity/entity-select.component.html create mode 100644 ui-ngx/src/app/shared/components/entity/entity-select.component.scss create mode 100644 ui-ngx/src/app/shared/components/entity/entity-select.component.ts create mode 100644 ui-ngx/src/app/shared/components/entity/entity-type-select.component.html create mode 100644 ui-ngx/src/app/shared/components/entity/entity-type-select.component.scss create mode 100644 ui-ngx/src/app/shared/components/entity/entity-type-select.component.ts create mode 100644 ui-ngx/src/app/shared/components/time/datetime.component.html create mode 100644 ui-ngx/src/app/shared/components/time/datetime.component.scss create mode 100644 ui-ngx/src/app/shared/components/time/datetime.component.ts create mode 100644 ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts 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 c363a11a56..5b18603cf9 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -402,7 +402,7 @@ public abstract class BaseController { validateId(entityViewId, "Incorrect entityViewId " + entityViewId); EntityViewInfo entityView = entityViewService.findEntityViewInfoById(getCurrentUser().getTenantId(), entityViewId); checkNotNull(entityView); - accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE, operation, entityViewId, entityView); + accessControlService.checkPermission(getCurrentUser(), Resource.ENTITY_VIEW, operation, entityViewId, entityView); return entityView; } catch (Exception e) { throw handleException(e, false); @@ -426,7 +426,7 @@ public abstract class BaseController { validateId(assetId, "Incorrect assetId " + assetId); AssetInfo asset = assetService.findAssetInfoById(getCurrentUser().getTenantId(), assetId); checkNotNull(asset); - accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE, operation, assetId, asset); + accessControlService.checkPermission(getCurrentUser(), Resource.ASSET, operation, assetId, asset); return asset; } catch (Exception e) { throw handleException(e, false); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java index 4075fca000..ef31a0175d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java @@ -55,6 +55,14 @@ public class EntityView extends SearchTextBasedWithAdditionalInfo public EntityView(EntityView entityView) { super(entityView); + this.entityId = entityView.getEntityId(); + this.tenantId = entityView.getTenantId(); + this.customerId = entityView.getCustomerId(); + this.name = entityView.getName(); + this.type = entityView.getType(); + this.keys = entityView.getKeys(); + this.startTimeMs = entityView.getStartTimeMs(); + this.endTimeMs = entityView.getEndTimeMs(); } @Override diff --git a/ui-ngx/package-lock.json b/ui-ngx/package-lock.json index 5500e96dfb..00f8dabb3f 100644 --- a/ui-ngx/package-lock.json +++ b/ui-ngx/package-lock.json @@ -3377,8 +3377,7 @@ "deep-equal": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", - "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", - "dev": true + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=" }, "deep-freeze-strict": { "version": "1.1.1", diff --git a/ui-ngx/package.json b/ui-ngx/package.json index 378f6e3f6d..b31fbebec4 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -33,6 +33,7 @@ "ace-builds": "^1.4.5", "compass-sass-mixins": "^0.12.7", "core-js": "^3.1.4", + "deep-equal": "^1.0.1", "font-awesome": "^4.7.0", "hammerjs": "^2.0.8", "material-design-icons": "^3.0.1", diff --git a/ui-ngx/src/app/core/http/entity.service.ts b/ui-ngx/src/app/core/http/entity.service.ts index ef4b306710..8533b78225 100644 --- a/ui-ngx/src/app/core/http/entity.service.ts +++ b/ui-ngx/src/app/core/http/entity.service.ts @@ -15,10 +15,10 @@ /// import {Injectable} from '@angular/core'; -import {Observable, throwError, of, empty, EMPTY, forkJoin} from 'rxjs/index'; +import {EMPTY, forkJoin, Observable, of, throwError} from 'rxjs/index'; import {HttpClient} from '@angular/common/http'; import {PageLink} from '@shared/models/page/page-link'; -import {EntityType} from '@shared/models/entity-type.models'; +import {AliasEntityType, EntityType} from '@shared/models/entity-type.models'; import {BaseData} from '@shared/models/base-data'; import {EntityId} from '@shared/models/id/entity-id'; import {DeviceService} from '@core/http/device.service'; @@ -37,6 +37,9 @@ import {concatMap, expand, map, toArray} from 'rxjs/operators'; import {Customer} from '@app/shared/models/customer.model'; import {AssetService} from '@core/http/asset.service'; import {EntityViewService} from '@core/http/entity-view.service'; +import {DataKeyType} from '@shared/models/telemetry/telemetry.models'; +import {DeviceInfo} from '@shared/models/device.models'; +import {defaultHttpOptions} from '@core/http/http-utils'; @Injectable({ providedIn: 'root' @@ -339,4 +342,68 @@ export class EntityService { } } } + + public prepareAllowedEntityTypesList(allowedEntityTypes: Array, + useAliasEntityTypes: boolean): Array { + const authUser = getCurrentAuthUser(this.store); + const entityTypes: Array = []; + switch (authUser.authority) { + case Authority.SYS_ADMIN: + entityTypes.push(EntityType.TENANT); + break; + case Authority.TENANT_ADMIN: + entityTypes.push(EntityType.DEVICE); + entityTypes.push(EntityType.ASSET); + entityTypes.push(EntityType.ENTITY_VIEW); + entityTypes.push(EntityType.TENANT); + entityTypes.push(EntityType.CUSTOMER); + entityTypes.push(EntityType.DASHBOARD); + if (useAliasEntityTypes) { + entityTypes.push(AliasEntityType.CURRENT_CUSTOMER); + } + break; + case Authority.CUSTOMER_USER: + entityTypes.push(EntityType.DEVICE); + entityTypes.push(EntityType.ASSET); + entityTypes.push(EntityType.ENTITY_VIEW); + entityTypes.push(EntityType.CUSTOMER); + entityTypes.push(EntityType.DASHBOARD); + if (useAliasEntityTypes) { + entityTypes.push(AliasEntityType.CURRENT_CUSTOMER); + } + break; + } + if (allowedEntityTypes && allowedEntityTypes.length) { + for (let index = entityTypes.length - 1; index >= 0; index--) { + if (allowedEntityTypes.indexOf(entityTypes[index]) === -1) { + entityTypes.splice(index, 1); + } + } + } + return entityTypes; + } + + public getEntityKeys(entityId: EntityId, query: string, type: DataKeyType, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + let url = `/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/keys/`; + if (type === DataKeyType.timeseries) { + url += 'timeseries'; + } else if (type === DataKeyType.attribute) { + url += 'attributes'; + } + return this.http.get>(url, + defaultHttpOptions(ignoreLoading, ignoreErrors)) + .pipe( + map( + (dataKeys) => { + if (query) { + const lowercaseQuery = query.toLowerCase(); + return dataKeys.filter((dataKey) => dataKey.toLowerCase().indexOf(lowercaseQuery) === 0); + } else { + return dataKeys; + } + } + ) + ); + } } diff --git a/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.ts b/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.ts index 6a4535f170..69276418d5 100644 --- a/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.ts @@ -75,7 +75,8 @@ export class AddEntitiesToCustomerDialogComponent extends PageComponent implemen this.assignToCustomerText = 'asset.assign-asset-to-customer-text'; break; case EntityType.ENTITY_VIEW: - // TODO: + this.assignToCustomerTitle = 'entity-view.assign-entity-view-to-customer'; + this.assignToCustomerText = 'entity-view.assign-entity-view-to-customer-text'; break; } } diff --git a/ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.ts b/ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.ts index 1d080ce53f..b76cadad02 100644 --- a/ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.ts @@ -74,7 +74,8 @@ export class AssignToCustomerDialogComponent extends PageComponent implements On this.assignToCustomerText = 'asset.assign-to-customer-text'; break; case EntityType.ENTITY_VIEW: - // TODO: + this.assignToCustomerTitle = 'entity-view.assign-entity-view-to-customer'; + this.assignToCustomerText = 'entity-view.assign-to-customer-text'; break; } } diff --git a/ui-ngx/src/app/modules/home/pages/entity-view/entity-view-routing.module.ts b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view-routing.module.ts new file mode 100644 index 0000000000..b130ae9b3c --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view-routing.module.ts @@ -0,0 +1,50 @@ +/// +/// Copyright © 2016-2019 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 {EntitiesTableComponent} from '@shared/components/entity/entities-table.component'; +import {Authority} from '@shared/models/authority.enum'; +import {EntityViewsTableConfigResolver} from '@modules/home/pages/entity-view/entity-views-table-config.resolver'; + +const routes: Routes = [ + { + path: 'entityViews', + component: EntitiesTableComponent, + data: { + auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], + title: 'entity-view.entity-views', + entityViewsType: 'tenant', + breadcrumb: { + label: 'entity-view.entity-views', + icon: 'view_quilt' + } + }, + resolve: { + entitiesTableConfig: EntityViewsTableConfigResolver + } + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], + providers: [ + EntityViewsTableConfigResolver + ] +}) +export class EntityViewRoutingModule { } diff --git a/ui-ngx/src/app/modules/home/pages/entity-view/entity-view-table-header.component.html b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view-table-header.component.html new file mode 100644 index 0000000000..0a5e358485 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view-table-header.component.html @@ -0,0 +1,23 @@ + + + diff --git a/ui-ngx/src/app/modules/home/pages/entity-view/entity-view-table-header.component.scss b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view-table-header.component.scss new file mode 100644 index 0000000000..cb7fe8d04b --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view-table-header.component.scss @@ -0,0 +1,36 @@ +/** + * Copyright © 2016-2019 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 { + flex: 1; + display: flex; + justify-content: flex-start; +} + +:host ::ng-deep { + tb-entity-subtype-select { + mat-form-field { + font-size: 16px; + + .mat-form-field-wrapper { + padding-bottom: 0; + } + + .mat-form-field-underline { + bottom: 0; + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/entity-view/entity-view-table-header.component.ts b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view-table-header.component.ts new file mode 100644 index 0000000000..ae5f34f806 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view-table-header.component.ts @@ -0,0 +1,42 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import {Component} from '@angular/core'; +import {Store} from '@ngrx/store'; +import {AppState} from '@core/core.state'; +import {EntityTableHeaderComponent} from '@shared/components/entity/entity-table-header.component'; +import {EntityType} from '@shared/models/entity-type.models'; +import {EntityViewInfo} from '@app/shared/models/entity-view.models'; + +@Component({ + selector: 'tb-entity-view-table-header', + templateUrl: './entity-view-table-header.component.html', + styleUrls: ['./entity-view-table-header.component.scss'] +}) +export class EntityViewTableHeaderComponent extends EntityTableHeaderComponent { + + entityType = EntityType; + + constructor(protected store: Store) { + super(store); + } + + entityViewTypeChanged(entityViewType: string) { + this.entitiesTableConfig.componentsData.entityViewType = entityViewType; + this.entitiesTableConfig.table.updateData(); + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/entity-view/entity-view.component.html b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view.component.html new file mode 100644 index 0000000000..af0ce120a6 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view.component.html @@ -0,0 +1,151 @@ + +
+ + + + +
+ +
+
+
+ + entity-view.assignedToCustomer + + +
+ {{ 'entity-view.entity-view-public' | translate }} +
+
+
+ + entity-view.name + + + {{ 'entity-view.name-required' | translate }} + + + + +
+ + + +
+ +
+ + + +
entity-view.attributes-propagation
+
+
+
entity-view.attributes-propagation-hint
+ + + + + + +
+ + + +
entity-view.timeseries-data
+
+
+
entity-view.timeseries-data-hint
+ + +
+
+ + +
+ + entity-view.description + + +
+
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/entity-view/entity-view.component.scss b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view.component.scss new file mode 100644 index 0000000000..3b135d0989 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view.component.scss @@ -0,0 +1,30 @@ +/** + * Copyright © 2016-2019 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 { + mat-expansion-panel { + margin-bottom: 16px; + } +} + +.tb-dialog { + :host { + mat-expansion-panel { + margin-left: 6px; + margin-right: 6px; + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/entity-view/entity-view.component.ts b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view.component.ts new file mode 100644 index 0000000000..cfb1b6c276 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view.component.ts @@ -0,0 +1,138 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import {Component} from '@angular/core'; +import {Store} from '@ngrx/store'; +import {AppState} from '@core/core.state'; +import {EntityComponent} from '@shared/components/entity/entity.component'; +import {FormBuilder, FormGroup, Validators} from '@angular/forms'; +import {EntityType} from '@shared/models/entity-type.models'; +import {NULL_UUID} from '@shared/models/id/has-uuid'; +import {ActionNotificationShow} from '@core/notification/notification.actions'; +import {TranslateService} from '@ngx-translate/core'; +import {EntityViewInfo} from '@app/shared/models/entity-view.models'; +import {Observable} from 'rxjs'; +import {map} from 'rxjs/operators'; +import {DataKeyType} from '@shared/models/telemetry/telemetry.models'; +import {EntityId} from '@app/shared/models/id/entity-id'; + +@Component({ + selector: 'tb-entity-view', + templateUrl: './entity-view.component.html', + styleUrls: ['./entity-view.component.scss'] +}) +export class EntityViewComponent extends EntityComponent { + + entityType = EntityType; + + dataKeyType = DataKeyType; + + entityViewScope: 'tenant' | 'customer' | 'customer_user'; + + allowedEntityTypes = [EntityType.DEVICE, EntityType.ASSET]; + + maxStartTimeMs: Observable; + minEndTimeMs: Observable; + + selectedEntityId: Observable; + + constructor(protected store: Store, + protected translate: TranslateService, + public fb: FormBuilder) { + super(store); + } + + ngOnInit() { + this.entityViewScope = this.entitiesTableConfig.componentsData.entityViewScope; + super.ngOnInit(); + this.maxStartTimeMs = this.entityForm.get('endTimeMs').valueChanges; + this.minEndTimeMs = this.entityForm.get('startTimeMs').valueChanges; + this.selectedEntityId = this.entityForm.get('entityId').valueChanges; + } + + hideDelete() { + if (this.entitiesTableConfig) { + return !this.entitiesTableConfig.deleteEnabled(this.entity); + } else { + return false; + } + } + + isAssignedToCustomer(entity: EntityViewInfo): boolean { + return entity && entity.customerId && entity.customerId.id !== NULL_UUID; + } + + buildForm(entity: EntityViewInfo): FormGroup { + return this.fb.group( + { + name: [entity ? entity.name : '', [Validators.required]], + type: [entity ? entity.type : null, [Validators.required]], + entityId: [entity ? entity.entityId : null, [Validators.required]], + startTimeMs: [entity ? entity.startTimeMs : null], + endTimeMs: [entity ? entity.endTimeMs : null], + keys: this.fb.group( + { + attributes: this.fb.group( + { + cs: [entity && entity.keys && entity.keys.attributes ? entity.keys.attributes.cs : null], + sh: [entity && entity.keys && entity.keys.attributes ? entity.keys.attributes.sh : null], + ss: [entity && entity.keys && entity.keys.attributes ? entity.keys.attributes.ss : null], + } + ), + timeseries: [entity && entity.keys && entity.keys.timeseries ? entity.keys.timeseries : null] + } + ), + additionalInfo: this.fb.group( + { + description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''], + } + ) + } + ); + } + + updateForm(entity: EntityViewInfo) { + this.entityForm.patchValue({name: entity.name}); + this.entityForm.patchValue({type: entity.type}); + this.entityForm.patchValue({entityId: entity.entityId}); + this.entityForm.patchValue({startTimeMs: entity.startTimeMs}); + this.entityForm.patchValue({endTimeMs: entity.endTimeMs}); + this.entityForm.patchValue({ + keys: + { + attributes: { + cs: entity.keys && entity.keys.attributes ? entity.keys.attributes.cs : null, + sh: entity.keys && entity.keys.attributes ? entity.keys.attributes.sh : null, + ss: entity.keys && entity.keys.attributes ? entity.keys.attributes.ss : null, + }, + timeseries: entity.keys && entity.keys.timeseries ? entity.keys.timeseries : null + } + }); + this.entityForm.patchValue({additionalInfo: {description: entity.additionalInfo ? entity.additionalInfo.description : ''}}); + } + + + onEntityViewIdCopied($event) { + this.store.dispatch(new ActionNotificationShow( + { + message: this.translate.instant('entity-view.idCopiedMessage'), + type: 'success', + duration: 750, + verticalPosition: 'bottom', + horizontalPosition: 'right' + })); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/entity-view/entity-view.module.ts b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view.module.ts new file mode 100644 index 0000000000..056507b833 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view.module.ts @@ -0,0 +1,41 @@ +/// +/// Copyright © 2016-2019 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 {CommonModule} from '@angular/common'; +import {SharedModule} from '@shared/shared.module'; +import {HomeDialogsModule} from '../../dialogs/home-dialogs.module'; +import {EntityViewComponent} from '@modules/home/pages/entity-view/entity-view.component'; +import {EntityViewTableHeaderComponent} from './entity-view-table-header.component'; +import {EntityViewRoutingModule} from './entity-view-routing.module'; + +@NgModule({ + entryComponents: [ + EntityViewComponent, + EntityViewTableHeaderComponent + ], + declarations: [ + EntityViewComponent, + EntityViewTableHeaderComponent + ], + imports: [ + CommonModule, + SharedModule, + HomeDialogsModule, + EntityViewRoutingModule + ] +}) +export class EntityViewModule { } diff --git a/ui-ngx/src/app/modules/home/pages/entity-view/entity-views-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/entity-view/entity-views-table-config.resolver.ts new file mode 100644 index 0000000000..fcaa8aa4c5 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/entity-view/entity-views-table-config.resolver.ts @@ -0,0 +1,400 @@ +/// +/// Copyright © 2016-2019 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 {Injectable} from '@angular/core'; + +import {ActivatedRouteSnapshot, Resolve, Router} from '@angular/router'; +import { + CellActionDescriptor, + checkBoxCell, + DateEntityTableColumn, + EntityTableColumn, + EntityTableConfig, + GroupActionDescriptor, + HeaderActionDescriptor +} from '@shared/components/entity/entities-table-config.models'; +import {TranslateService} from '@ngx-translate/core'; +import {DatePipe} from '@angular/common'; +import {EntityType, entityTypeResources, entityTypeTranslations} from '@shared/models/entity-type.models'; +import {EntityAction} from '@shared/components/entity/entity-component.models'; +import {forkJoin, Observable, of} from 'rxjs'; +import {select, Store} from '@ngrx/store'; +import {selectAuthUser} from '@core/auth/auth.selectors'; +import {map, mergeMap, take, tap} from 'rxjs/operators'; +import {AppState} from '@core/core.state'; +import {Authority} from '@app/shared/models/authority.enum'; +import {CustomerService} from '@core/http/customer.service'; +import {Customer} from '@app/shared/models/customer.model'; +import {NULL_UUID} from '@shared/models/id/has-uuid'; +import {BroadcastService} from '@core/services/broadcast.service'; +import {MatDialog} from '@angular/material'; +import {DialogService} from '@core/services/dialog.service'; +import { + AssignToCustomerDialogComponent, + AssignToCustomerDialogData +} from '@modules/home/dialogs/assign-to-customer-dialog.component'; +import { + AddEntitiesToCustomerDialogComponent, + AddEntitiesToCustomerDialogData +} from '../../dialogs/add-entities-to-customer-dialog.component'; +import {EntityView, EntityViewInfo} from '@app/shared/models/entity-view.models'; +import {EntityViewService} from '@core/http/entity-view.service'; +import {EntityViewComponent} from '@modules/home/pages/entity-view/entity-view.component'; +import {EntityViewTableHeaderComponent} from '@modules/home/pages/entity-view/entity-view-table-header.component'; +import {EntityViewId} from '@shared/models/id/entity-view-id'; + +@Injectable() +export class EntityViewsTableConfigResolver implements Resolve> { + + private readonly config: EntityTableConfig = new EntityTableConfig(); + + private customerId: string; + + constructor(private store: Store, + private broadcast: BroadcastService, + private entityViewService: EntityViewService, + private customerService: CustomerService, + private dialogService: DialogService, + private translate: TranslateService, + private datePipe: DatePipe, + private router: Router, + private dialog: MatDialog) { + + this.config.entityType = EntityType.ENTITY_VIEW; + this.config.entityComponent = EntityViewComponent; + this.config.entityTranslations = entityTypeTranslations.get(EntityType.ENTITY_VIEW); + this.config.entityResources = entityTypeResources.get(EntityType.ENTITY_VIEW); + + this.config.addDialogStyle = {maxWidth: '800px'}; + + this.config.deleteEntityTitle = entityView => + this.translate.instant('entity-view.delete-entity-view-title', { entityViewName: entityView.name }); + this.config.deleteEntityContent = () => this.translate.instant('entity-view.delete-entity-view-text'); + this.config.deleteEntitiesTitle = count => this.translate.instant('entity-view.delete-entity-views-title', {count}); + this.config.deleteEntitiesContent = () => this.translate.instant('entity-view.delete-entity-views-text'); + + this.config.loadEntity = id => this.entityViewService.getEntityViewInfo(id.id); + this.config.saveEntity = entityView => { + return this.entityViewService.saveEntityView(entityView).pipe( + tap(() => { + this.broadcast.broadcast('entityViewSaved'); + }), + mergeMap((savedEntityView) => this.entityViewService.getEntityViewInfo(savedEntityView.id.id) + )); + }; + this.config.onEntityAction = action => this.onEntityViewAction(action); + + this.config.headerComponent = EntityViewTableHeaderComponent; + + } + + resolve(route: ActivatedRouteSnapshot): Observable> { + const routeParams = route.params; + this.config.componentsData = { + entityViewScope: route.data.entityViewsType, + entityViewType: '' + }; + this.customerId = routeParams.customerId; + return this.store.pipe(select(selectAuthUser), take(1)).pipe( + tap((authUser) => { + if (authUser.authority === Authority.CUSTOMER_USER) { + this.config.componentsData.entityViewScope = 'customer_user'; + this.customerId = authUser.customerId; + } + }), + mergeMap(() => + this.customerId ? this.customerService.getCustomer(this.customerId) : of(null as Customer) + ), + map((parentCustomer) => { + if (parentCustomer) { + if (parentCustomer.additionalInfo && parentCustomer.additionalInfo.isPublic) { + this.config.tableTitle = this.translate.instant('customer.public-entity-views'); + } else { + this.config.tableTitle = parentCustomer.title + ': ' + this.translate.instant('entity-view.entity-views'); + } + } else { + this.config.tableTitle = this.translate.instant('entity-view.entity-views'); + } + this.config.columns = this.configureColumns(this.config.componentsData.entityViewScope); + this.configureEntityFunctions(this.config.componentsData.entityViewScope); + this.config.cellActionDescriptors = this.configureCellActions(this.config.componentsData.entityViewScope); + this.config.groupActionDescriptors = this.configureGroupActions(this.config.componentsData.entityViewScope); + this.config.addActionDescriptors = this.configureAddActions(this.config.componentsData.entityViewScope); + this.config.addEnabled = this.config.componentsData.entityViewScope !== 'customer_user'; + this.config.entitiesDeleteEnabled = this.config.componentsData.entityViewScope === 'tenant'; + this.config.deleteEnabled = () => this.config.componentsData.entityViewScope === 'tenant'; + return this.config; + }) + ); + } + + configureColumns(entityViewScope: string): Array> { + const columns: Array> = [ + new DateEntityTableColumn('createdTime', 'entity-view.created-time', this.datePipe, '150px'), + new EntityTableColumn('name', 'entity-view.name'), + new EntityTableColumn('type', 'entity-view.entity-view-type'), + ]; + if (entityViewScope === 'tenant') { + columns.push( + new EntityTableColumn('customerTitle', 'customer.customer'), + new EntityTableColumn('customerIsPublic', 'entity-view.public', '60px', + entity => { + return checkBoxCell(entity.customerIsPublic); + }, () => ({}), false), + ); + } + return columns; + } + + configureEntityFunctions(entityViewScope: string): void { + if (entityViewScope === 'tenant') { + this.config.entitiesFetchFunction = pageLink => + this.entityViewService.getTenantEntityViewInfos(pageLink, this.config.componentsData.entityViewType); + this.config.deleteEntity = id => this.entityViewService.deleteEntityView(id.id); + } else { + this.config.entitiesFetchFunction = pageLink => + this.entityViewService.getCustomerEntityViewInfos(this.customerId, pageLink, this.config.componentsData.entityViewType); + this.config.deleteEntity = id => this.entityViewService.unassignEntityViewFromCustomer(id.id); + } + } + + configureCellActions(entityViewScope: string): Array> { + const actions: Array> = []; + if (entityViewScope === 'tenant') { + actions.push( + { + name: this.translate.instant('entity-view.make-public'), + icon: 'share', + isEnabled: (entity) => (!entity.customerId || entity.customerId.id === NULL_UUID), + onAction: ($event, entity) => this.makePublic($event, entity) + }, + { + name: this.translate.instant('entity-view.assign-to-customer'), + icon: 'assignment_ind', + isEnabled: (entity) => (!entity.customerId || entity.customerId.id === NULL_UUID), + onAction: ($event, entity) => this.assignToCustomer($event, [entity.id]) + }, + { + name: this.translate.instant('entity-view.unassign-from-customer'), + icon: 'assignment_return', + isEnabled: (entity) => (entity.customerId && entity.customerId.id !== NULL_UUID && !entity.customerIsPublic), + onAction: ($event, entity) => this.unassignFromCustomer($event, entity) + }, + { + name: this.translate.instant('entity-view.make-private'), + icon: 'reply', + isEnabled: (entity) => (entity.customerId && entity.customerId.id !== NULL_UUID && entity.customerIsPublic), + onAction: ($event, entity) => this.unassignFromCustomer($event, entity) + } + ); + } + if (entityViewScope === 'customer') { + actions.push( + { + name: this.translate.instant('entity-view.unassign-from-customer'), + icon: 'assignment_return', + isEnabled: (entity) => (entity.customerId && entity.customerId.id !== NULL_UUID && !entity.customerIsPublic), + onAction: ($event, entity) => this.unassignFromCustomer($event, entity) + }, + { + name: this.translate.instant('entity-view.make-private'), + icon: 'reply', + isEnabled: (entity) => (entity.customerId && entity.customerId.id !== NULL_UUID && entity.customerIsPublic), + onAction: ($event, entity) => this.unassignFromCustomer($event, entity) + } + ); + } + return actions; + } + + configureGroupActions(entityViewScope: string): Array> { + const actions: Array> = []; + if (entityViewScope === 'tenant') { + actions.push( + { + name: this.translate.instant('entity-view.assign-entity-views'), + icon: 'assignment_ind', + isEnabled: true, + onAction: ($event, entities) => this.assignToCustomer($event, entities.map((entity) => entity.id)) + } + ); + } + if (entityViewScope === 'customer') { + actions.push( + { + name: this.translate.instant('entity-view.unassign-entity-views'), + icon: 'assignment_return', + isEnabled: true, + onAction: ($event, entities) => this.unassignEntityViewsFromCustomer($event, entities) + } + ); + } + return actions; + } + + configureAddActions(entityViewScope: string): Array { + const actions: Array = []; + if (entityViewScope === 'customer') { + actions.push( + { + name: this.translate.instant('entity-view.assign-new-entity-view'), + icon: 'add', + isEnabled: () => true, + onAction: ($event) => this.addEntityViewsToCustomer($event) + } + ); + } + return actions; + } + + addEntityViewsToCustomer($event: Event) { + if ($event) { + $event.stopPropagation(); + } + this.dialog.open(AddEntitiesToCustomerDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + customerId: this.customerId, + entityType: EntityType.ENTITY_VIEW + } + }).afterClosed() + .subscribe((res) => { + if (res) { + this.config.table.updateData(); + } + }); + } + + makePublic($event: Event, entityView: EntityView) { + if ($event) { + $event.stopPropagation(); + } + this.dialogService.confirm( + this.translate.instant('entity-view.make-public-entity-view-title', {entityViewName: entityView.name}), + this.translate.instant('entity-view.make-public-entity-view-text'), + this.translate.instant('action.no'), + this.translate.instant('action.yes'), + true + ).subscribe((res) => { + if (res) { + this.entityViewService.makeEntityViewPublic(entityView.id.id).subscribe( + () => { + this.config.table.updateData(); + } + ); + } + } + ); + } + + assignToCustomer($event: Event, entityViewIds: Array) { + if ($event) { + $event.stopPropagation(); + } + this.dialog.open(AssignToCustomerDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + entityIds: entityViewIds, + entityType: EntityType.ENTITY_VIEW + } + }).afterClosed() + .subscribe((res) => { + if (res) { + this.config.table.updateData(); + } + }); + } + + unassignFromCustomer($event: Event, entityView: EntityViewInfo) { + if ($event) { + $event.stopPropagation(); + } + const isPublic = entityView.customerIsPublic; + let title; + let content; + if (isPublic) { + title = this.translate.instant('entity-view.make-private-entity-view-title', {entityViewName: entityView.name}); + content = this.translate.instant('entity-view.make-private-entity-view-text'); + } else { + title = this.translate.instant('entity-view.unassign-entity-view-title', {entityViewName: entityView.name}); + content = this.translate.instant('entity-view.unassign-entity-view-text'); + } + this.dialogService.confirm( + title, + content, + this.translate.instant('action.no'), + this.translate.instant('action.yes'), + true + ).subscribe((res) => { + if (res) { + this.entityViewService.unassignEntityViewFromCustomer(entityView.id.id).subscribe( + () => { + this.config.table.updateData(); + } + ); + } + } + ); + } + + unassignEntityViewsFromCustomer($event: Event, entityViews: Array) { + if ($event) { + $event.stopPropagation(); + } + this.dialogService.confirm( + this.translate.instant('entity-view.unassign-entity-views-title', {count: entityViews.length}), + this.translate.instant('entity-view.unassign-entity-views-text'), + this.translate.instant('action.no'), + this.translate.instant('action.yes'), + true + ).subscribe((res) => { + if (res) { + const tasks: Observable[] = []; + entityViews.forEach( + (entityView) => { + tasks.push(this.entityViewService.unassignEntityViewFromCustomer(entityView.id.id)); + } + ); + forkJoin(tasks).subscribe( + () => { + this.config.table.updateData(); + } + ); + } + } + ); + } + + onEntityViewAction(action: EntityAction): boolean { + switch (action.action) { + case 'makePublic': + this.makePublic(action.event, action.entity); + return true; + case 'assignToCustomer': + this.assignToCustomer(action.event, [action.entity.id]); + return true; + case 'unassignFromCustomer': + this.unassignFromCustomer(action.event, action.entity); + return true; + } + return false; + } + +} 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 38e776ab79..7ff956ddc1 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 @@ -25,6 +25,7 @@ import { CustomerModule } from '@modules/home/pages/customer/customer.module'; import { UserModule } from '@modules/home/pages/user/user.module'; import {DeviceModule} from '@modules/home/pages/device/device.module'; import {AssetModule} from '@modules/home/pages/asset/asset.module'; +import {EntityViewModule} from '@modules/home/pages/entity-view/entity-view.module'; @NgModule({ exports: [ @@ -34,6 +35,7 @@ import {AssetModule} from '@modules/home/pages/asset/asset.module'; TenantModule, DeviceModule, AssetModule, + EntityViewModule, CustomerModule, // AuditLogModule, UserModule diff --git a/ui-ngx/src/app/shared/components/entity/add-entity-dialog.component.html b/ui-ngx/src/app/shared/components/entity/add-entity-dialog.component.html index f3f9c3ec9a..024ebb8b64 100644 --- a/ui-ngx/src/app/shared/components/entity/add-entity-dialog.component.html +++ b/ui-ngx/src/app/shared/components/entity/add-entity-dialog.component.html @@ -15,7 +15,7 @@ limitations under the License. --> -
+

{{ translations.add }}

diff --git a/ui-ngx/src/app/shared/components/entity/entities-table-config.models.ts b/ui-ngx/src/app/shared/components/entity/entities-table-config.models.ts index c3dbaf77ec..536b5289a1 100644 --- a/ui-ngx/src/app/shared/components/entity/entities-table-config.models.ts +++ b/ui-ngx/src/app/shared/components/entity/entities-table-config.models.ts @@ -117,6 +117,7 @@ export class EntityTableConfig, P extends PageLink = P entityTranslations: EntityTypeTranslation; entityResources: EntityTypeResource; entityComponent: Type>; + addDialogStyle = {}; defaultSortOrder: SortOrder = {property: 'createdTime', direction: Direction.ASC}; columns: Array> = []; cellActionDescriptors: Array> = []; diff --git a/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts b/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts index d6830ee882..1325a9e88a 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts @@ -52,6 +52,7 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit if (this.entityTypeValue !== entityType) { this.entityTypeValue = entityType; this.load(); + this.reset(); } } @@ -210,21 +211,36 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; + if (this.disabled) { + this.selectEntityFormGroup.disable(); + } else { + this.selectEntityFormGroup.enable(); + } } - writeValue(value: string | null): void { + writeValue(value: string | EntityId | null): void { this.searchText = ''; if (value != null) { - let targetEntityType = this.entityTypeValue; - if (targetEntityType === AliasEntityType.CURRENT_CUSTOMER) { - targetEntityType = EntityType.CUSTOMER; - } - this.entityService.getEntity(targetEntityType, value).subscribe( - (entity) => { - this.modelValue = entity.id.id; - this.selectEntityFormGroup.get('entity').patchValue(entity, {emitEvent: true}); + if (typeof value === 'string') { + let targetEntityType = this.entityTypeValue; + if (targetEntityType === AliasEntityType.CURRENT_CUSTOMER) { + targetEntityType = EntityType.CUSTOMER; } - ); + this.entityService.getEntity(targetEntityType, value).subscribe( + (entity) => { + this.modelValue = entity.id.id; + this.selectEntityFormGroup.get('entity').patchValue(entity, {emitEvent: true}); + } + ); + } else { + const targetEntityType = value.entityType as EntityType; + this.entityService.getEntity(targetEntityType, value.id).subscribe( + (entity) => { + this.modelValue = entity.id.id; + this.selectEntityFormGroup.get('entity').patchValue(entity, {emitEvent: true}); + } + ); + } } else { this.modelValue = null; this.selectEntityFormGroup.get('entity').patchValue('', {emitEvent: true}); diff --git a/ui-ngx/src/app/shared/components/entity/entity-keys-list.component.html b/ui-ngx/src/app/shared/components/entity/entity-keys-list.component.html new file mode 100644 index 0000000000..12ce39061e --- /dev/null +++ b/ui-ngx/src/app/shared/components/entity/entity-keys-list.component.html @@ -0,0 +1,43 @@ + + + + + {{key}} + close + + + + + + + + + diff --git a/ui-ngx/src/app/shared/components/entity/entity-keys-list.component.ts b/ui-ngx/src/app/shared/components/entity/entity-keys-list.component.ts new file mode 100644 index 0000000000..69ae45fb15 --- /dev/null +++ b/ui-ngx/src/app/shared/components/entity/entity-keys-list.component.ts @@ -0,0 +1,209 @@ +/// +/// Copyright © 2016-2019 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 {COMMA, ENTER, SEMICOLON} from '@angular/cdk/keycodes'; +import {AfterViewInit, Component, ElementRef, forwardRef, Input, OnInit, SkipSelf, ViewChild} from '@angular/core'; +import { + ControlValueAccessor, + FormBuilder, + FormControl, + FormGroup, + FormGroupDirective, + NG_VALUE_ACCESSOR, NgForm +} from '@angular/forms'; +import {Observable, of} from 'rxjs'; +import {map, mergeMap, startWith, tap, share, pairwise, filter} from 'rxjs/operators'; +import {Store} from '@ngrx/store'; +import {AppState} from '@app/core/core.state'; +import {TranslateService} from '@ngx-translate/core'; +import {AliasEntityType, EntityType} from '@shared/models/entity-type.models'; +import {BaseData} from '@shared/models/base-data'; +import {EntityId} from '@shared/models/id/entity-id'; +import {EntityService} from '@core/http/entity.service'; +import {ErrorStateMatcher, MatAutocomplete, MatAutocompleteSelectedEvent, MatChipList, MatChipInputEvent} from '@angular/material'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import {DataKeyType} from '@shared/models/telemetry/telemetry.models'; +import * as equal from 'deep-equal'; + +@Component({ + selector: 'tb-entity-keys-list', + templateUrl: './entity-keys-list.component.html', + styleUrls: [], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => EntityKeysListComponent), + multi: true + } + ] +}) +export class EntityKeysListComponent implements ControlValueAccessor, OnInit, AfterViewInit { + + keysListFormGroup: FormGroup; + + modelValue: Array | null; + + entityIdValue: EntityId; + + @Input() + set entityId(entityId: EntityId) { + if (!equal(this.entityIdValue, entityId)) { + this.entityIdValue = entityId; + this.reset(); + } + } + + @Input() + keysText: string; + + @Input() + dataKeyType: DataKeyType; + + private requiredValue: boolean; + get required(): boolean { + return this.requiredValue; + } + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + @Input() + disabled: boolean; + + @ViewChild('keyInput', {static: false}) keyInput: ElementRef; + @ViewChild('keyAutocomplete', {static: false}) matAutocomplete: MatAutocomplete; + @ViewChild('chipList', {static: false}) chipList: MatChipList; + + filteredKeys: Observable>; + + separatorKeysCodes: number[] = [ENTER, COMMA, SEMICOLON]; + + private searchText = ''; + + private propagateChange = (v: any) => { }; + + constructor(private store: Store, + public translate: TranslateService, + private entityService: EntityService, + private fb: FormBuilder) { + this.keysListFormGroup = this.fb.group({ + key: [null] + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + ngOnInit() { + this.filteredKeys = this.keysListFormGroup.get('key').valueChanges + .pipe( + map((value) => value ? value : ''), + mergeMap(name => this.fetchKeys(name) ), + share() + ); + } + + ngAfterViewInit(): void {} + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.keysListFormGroup.disable(); + } else { + this.keysListFormGroup.enable(); + } + } + + writeValue(value: Array | null): void { + this.searchText = ''; + if (value != null) { + this.modelValue = [...value]; + } else { + this.modelValue = []; + } + } + + reset() { + this.keysListFormGroup.get('key').patchValue(null, {emitEvent: true}); + } + + addKey(key: string): void { + if (!this.modelValue || this.modelValue.indexOf(key) === -1) { + if (!this.modelValue) { + this.modelValue = []; + } + this.modelValue.push(key); + if (this.required) { + this.chipList.errorState = false; + } + } + this.propagateChange(this.modelValue); + } + + add(event: MatChipInputEvent): void { + if (!this.matAutocomplete.isOpen) { + const value = event.value; + if ((value || '').trim()) { + this.addKey(value.trim()); + } + this.clear(''); + } + } + + remove(key: string) { + const index = this.modelValue.indexOf(key); + if (index >= 0) { + this.modelValue.splice(index, 1); + if (!this.modelValue.length) { + if (this.required) { + this.chipList.errorState = true; + } + } + this.propagateChange(this.modelValue.length ? this.modelValue : null); + } + } + + selected(event: MatAutocompleteSelectedEvent): void { + this.addKey(event.option.viewValue); + this.clear(''); + } + + displayKeyFn(key?: string): string | undefined { + return key ? key : undefined; + } + + fetchKeys(searchText?: string): Observable> { + this.searchText = searchText; + return this.entityIdValue ? this.entityService.getEntityKeys(this.entityIdValue, searchText, + this.dataKeyType, false, true).pipe( + map((data) => data ? data : [])) : of([]); + } + + clear(value: string = '') { + this.keyInput.nativeElement.value = value; + this.keysListFormGroup.get('key').patchValue(null, {emitEvent: true}); + setTimeout(() => { + this.keyInput.nativeElement.blur(); + this.keyInput.nativeElement.focus(); + }, 0); + } + +} diff --git a/ui-ngx/src/app/shared/components/entity/entity-list.component.ts b/ui-ngx/src/app/shared/components/entity/entity-list.component.ts index e8bb92fb83..2d8463f93d 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-list.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-list.component.ts @@ -124,6 +124,11 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; + if (this.disabled) { + this.entityListFormGroup.disable(); + } else { + this.entityListFormGroup.enable(); + } } writeValue(value: Array | null): void { diff --git a/ui-ngx/src/app/shared/components/entity/entity-select.component.html b/ui-ngx/src/app/shared/components/entity/entity-select.component.html new file mode 100644 index 0000000000..20fa952061 --- /dev/null +++ b/ui-ngx/src/app/shared/components/entity/entity-select.component.html @@ -0,0 +1,35 @@ + +
+ + + + +
diff --git a/ui-ngx/src/app/shared/components/entity/entity-select.component.scss b/ui-ngx/src/app/shared/components/entity/entity-select.component.scss new file mode 100644 index 0000000000..bb18c2c7b6 --- /dev/null +++ b/ui-ngx/src/app/shared/components/entity/entity-select.component.scss @@ -0,0 +1,17 @@ +/** + * Copyright © 2016-2019 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 { +} 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 new file mode 100644 index 0000000000..8c0c55ba5c --- /dev/null +++ b/ui-ngx/src/app/shared/components/entity/entity-select.component.ts @@ -0,0 +1,149 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import {AfterViewInit, Component, forwardRef, Input, OnInit} from '@angular/core'; +import {ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR} from '@angular/forms'; +import {Store} from '@ngrx/store'; +import {AppState} from '@app/core/core.state'; +import {TranslateService} from '@ngx-translate/core'; +import {AliasEntityType, EntityType, entityTypeTranslations} from '@app/shared/models/entity-type.models'; +import {EntityService} from '@core/http/entity.service'; +import {EntityId} from '@app/shared/models/id/entity-id'; +import {coerceBooleanProperty} from '@angular/cdk/coercion'; + +@Component({ + selector: 'tb-entity-select', + templateUrl: './entity-select.component.html', + styleUrls: ['./entity-select.component.scss'], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => EntitySelectComponent), + multi: true + }] +}) +export class EntitySelectComponent implements ControlValueAccessor, OnInit, AfterViewInit { + + entitySelectFormGroup: FormGroup; + + modelValue: EntityId = {entityType: null, id: null}; + + @Input() + allowedEntityTypes: Array; + + @Input() + useAliasEntityTypes: boolean; + + private requiredValue: boolean; + get required(): boolean { + return this.requiredValue; + } + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + @Input() + disabled: boolean; + + private defaultEntityType: EntityType | AliasEntityType = null; + + private displayEntityTypeSelect: boolean; + + private propagateChange = (v: any) => { }; + + constructor(private store: Store, + private entityService: EntityService, + public translate: TranslateService, + private fb: FormBuilder) { + + const entityTypes = this.entityService.prepareAllowedEntityTypesList(this.allowedEntityTypes, + this.useAliasEntityTypes); + if (entityTypes.length === 1) { + this.displayEntityTypeSelect = false; + this.defaultEntityType = entityTypes[0]; + } else { + this.displayEntityTypeSelect = true; + } + + this.entitySelectFormGroup = this.fb.group({ + entityType: [this.defaultEntityType], + entityId: [null] + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + ngOnInit() { + this.entitySelectFormGroup.get('entityType').valueChanges.subscribe( + (value) => { + this.updateView(value, this.modelValue.id); + } + ); + this.entitySelectFormGroup.get('entityId').valueChanges.subscribe( + (value) => { + const id = value ? (typeof value === 'string' ? value : value.id) : null; + this.updateView(this.modelValue.entityType, id); + } + ); + } + + ngAfterViewInit(): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.entitySelectFormGroup.disable(); + } else { + this.entitySelectFormGroup.enable(); + } + } + + writeValue(value: EntityId | null): void { + if (value != null) { + this.modelValue = value; + this.entitySelectFormGroup.get('entityType').patchValue(value.entityType, {emitEvent: true}); + this.entitySelectFormGroup.get('entityId').patchValue(value, {emitEvent: true}); + } 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}); + } + } + + updateView(entityType: EntityType | AliasEntityType | null, entityId: string | null) { + if (this.modelValue.entityType !== entityType || + this.modelValue.id !== entityId) { + this.modelValue = { + entityType, + id: this.modelValue.entityType !== entityType ? null : entityId + }; + if (this.modelValue.entityType && this.modelValue.id) { + this.propagateChange(this.modelValue); + } else { + this.propagateChange(null); + } + } + } +} diff --git a/ui-ngx/src/app/shared/components/entity/entity-subtype-autocomplete.component.ts b/ui-ngx/src/app/shared/components/entity/entity-subtype-autocomplete.component.ts index 3cc79ab416..47746f06e0 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-subtype-autocomplete.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-subtype-autocomplete.component.ts @@ -161,6 +161,11 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor, setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; + if (this.disabled) { + this.subTypeFormGroup.disable(); + } else { + this.subTypeFormGroup.enable(); + } } writeValue(value: string | null): void { diff --git a/ui-ngx/src/app/shared/components/entity/entity-subtype-select.component.ts b/ui-ngx/src/app/shared/components/entity/entity-subtype-select.component.ts index ceed407498..86cca08f3f 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-subtype-select.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-subtype-select.component.ts @@ -66,8 +66,6 @@ export class EntitySubTypeSelectComponent implements ControlValueAccessor, OnIni @Input() typeTranslatePrefix: string; - @ViewChild('subTypeInput', {static: true}) subTypeInput: ElementRef; - entitySubtypeTitle: string; entitySubtypeRequiredText: string; @@ -158,6 +156,11 @@ export class EntitySubTypeSelectComponent implements ControlValueAccessor, OnIni setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; + if (this.disabled) { + this.subTypeFormGroup.disable(); + } else { + this.subTypeFormGroup.enable(); + } } writeValue(value: string | null): void { @@ -230,13 +233,4 @@ export class EntitySubTypeSelectComponent implements ControlValueAccessor, OnIni } return this.subTypes; } - - clear() { - this.subTypeFormGroup.get('subType').patchValue(null, {emitEvent: true}); - setTimeout(() => { - this.subTypeInput.nativeElement.blur(); - this.subTypeInput.nativeElement.focus(); - }, 0); - } - } diff --git a/ui-ngx/src/app/shared/components/entity/entity-type-select.component.html b/ui-ngx/src/app/shared/components/entity/entity-type-select.component.html new file mode 100644 index 0000000000..c01683d02d --- /dev/null +++ b/ui-ngx/src/app/shared/components/entity/entity-type-select.component.html @@ -0,0 +1,29 @@ + + + {{ 'entity.type' | translate }} + + + {{ displayEntityTypeFn(type) }} + + + + {{ 'entity.type-required' | translate }} + + diff --git a/ui-ngx/src/app/shared/components/entity/entity-type-select.component.scss b/ui-ngx/src/app/shared/components/entity/entity-type-select.component.scss new file mode 100644 index 0000000000..bb18c2c7b6 --- /dev/null +++ b/ui-ngx/src/app/shared/components/entity/entity-type-select.component.scss @@ -0,0 +1,17 @@ +/** + * Copyright © 2016-2019 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 { +} diff --git a/ui-ngx/src/app/shared/components/entity/entity-type-select.component.ts b/ui-ngx/src/app/shared/components/entity/entity-type-select.component.ts new file mode 100644 index 0000000000..386a8bd424 --- /dev/null +++ b/ui-ngx/src/app/shared/components/entity/entity-type-select.component.ts @@ -0,0 +1,134 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import {AfterViewInit, Component, forwardRef, Input, OnInit} from '@angular/core'; +import {ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR} from '@angular/forms'; +import {Store} from '@ngrx/store'; +import {AppState} from '@app/core/core.state'; +import {TranslateService} from '@ngx-translate/core'; +import {AliasEntityType, EntityType, entityTypeTranslations} from '@app/shared/models/entity-type.models'; +import {EntityService} from '@core/http/entity.service'; +import {coerceBooleanProperty} from "@angular/cdk/coercion"; + +@Component({ + selector: 'tb-entity-type-select', + templateUrl: './entity-type-select.component.html', + styleUrls: ['./entity-type-select.component.scss'], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => EntityTypeSelectComponent), + multi: true + }] +}) +export class EntityTypeSelectComponent implements ControlValueAccessor, OnInit, AfterViewInit { + + entityTypeFormGroup: FormGroup; + + modelValue: EntityType | AliasEntityType | null; + + @Input() + allowedEntityTypes: Array; + + @Input() + useAliasEntityTypes: boolean; + + @Input() + showLabel: boolean; + + private requiredValue: boolean; + get required(): boolean { + return this.requiredValue; + } + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + @Input() + disabled: boolean; + + entityTypes: Array; + + private propagateChange = (v: any) => { }; + + constructor(private store: Store, + private entityService: EntityService, + public translate: TranslateService, + private fb: FormBuilder) { + this.entityTypeFormGroup = this.fb.group({ + entityType: [null] + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + ngOnInit() { + this.entityTypes = this.entityService.prepareAllowedEntityTypesList(this.allowedEntityTypes, this.useAliasEntityTypes); + this.entityTypeFormGroup.get('entityType').valueChanges.subscribe( + (value) => { + let modelValue; + if (!value || value === '') { + modelValue = null; + } else { + modelValue = value; + } + this.updateView(modelValue); + } + ); + } + + ngAfterViewInit(): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.entityTypeFormGroup.disable(); + } else { + this.entityTypeFormGroup.enable(); + } + } + + writeValue(value: EntityType | AliasEntityType | null): void { + if (value != null) { + this.modelValue = value; + this.entityTypeFormGroup.get('entityType').patchValue(value, {emitEvent: true}); + } else { + this.modelValue = null; + this.entityTypeFormGroup.get('entityType').patchValue(null, {emitEvent: true}); + } + } + + updateView(value: EntityType | AliasEntityType | null) { + if (this.modelValue !== value) { + this.modelValue = value; + this.propagateChange(this.modelValue); + } + } + + displayEntityTypeFn(entityType?: EntityType | AliasEntityType | null): string | undefined { + if (entityType) { + return this.translate.instant(entityTypeTranslations.get(entityType as EntityType).type); + } else { + return ''; + } + } +} diff --git a/ui-ngx/src/app/shared/components/time/datetime.component.html b/ui-ngx/src/app/shared/components/time/datetime.component.html new file mode 100644 index 0000000000..90b7ae1bed --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/datetime.component.html @@ -0,0 +1,40 @@ + +
+ + {{ dateText | translate }} + + + + + + {{ timeText | translate }} + + + + +
diff --git a/ui-ngx/src/app/shared/components/time/datetime.component.scss b/ui-ngx/src/app/shared/components/time/datetime.component.scss new file mode 100644 index 0000000000..fbc1b776d6 --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/datetime.component.scss @@ -0,0 +1,26 @@ +/** + * Copyright © 2016-2019 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 ::ng-deep { + .mat-form-field-wrapper { + padding-bottom: 8px; + } + .mat-form-field-underline { + bottom: 8px; + } + .mat-form-field-infix { + width: 150px; + } +} diff --git a/ui-ngx/src/app/shared/components/time/datetime.component.ts b/ui-ngx/src/app/shared/components/time/datetime.component.ts new file mode 100644 index 0000000000..a34dfc0617 --- /dev/null +++ b/ui-ngx/src/app/shared/components/time/datetime.component.ts @@ -0,0 +1,111 @@ +/// +/// Copyright © 2016-2019 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} from '@angular/forms'; +import {coerceBooleanProperty} from '@angular/cdk/coercion'; + +@Component({ + selector: 'tb-datetime', + templateUrl: './datetime.component.html', + styleUrls: ['./datetime.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => DatetimeComponent), + multi: true + } + ] +}) +export class DatetimeComponent implements OnInit, ControlValueAccessor { + + private requiredValue: boolean; + get required(): boolean { + return this.requiredValue; + } + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + @Input() + disabled: boolean; + + @Input() + dateText: string; + + @Input() + timeText: string; + + minDateValue: Date | null; + + @Input() + set minDate(minDate: number | null) { + this.minDateValue = minDate ? new Date(minDate) : null; + } + + maxDateValue: Date | null; + + @Input() + set maxDate(maxDate: number | null) { + this.maxDateValue = maxDate ? new Date(maxDate) : null; + } + + modelValue: number; + + date: Date; + + private propagateChange = (v: any) => { }; + + constructor() { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + ngOnInit(): void { + } + + writeValue(datetime: number | null): void { + this.modelValue = datetime; + if (this.modelValue) { + this.date = new Date(this.modelValue); + } else { + this.date = null; + } + } + + updateView(value: number | null) { + if (this.modelValue !== value) { + this.modelValue = value; + this.propagateChange(this.modelValue); + } + } + + onDateChange() { + const value = this.date ? this.date.getTime() : null; + this.updateView(value); + } + +} diff --git a/ui-ngx/src/app/shared/models/entity-type.models.ts b/ui-ngx/src/app/shared/models/entity-type.models.ts index 28f8db7a41..70588c2e8b 100644 --- a/ui-ngx/src/app/shared/models/entity-type.models.ts +++ b/ui-ngx/src/app/shared/models/entity-type.models.ts @@ -37,21 +37,21 @@ export enum AliasEntityType { export interface EntityTypeTranslation { type: string; - typePlural: string; + typePlural?: string; list: string; - nameStartsWith: string; - details: string; - add: string; - noEntities: string; - selectedEntities: string; - search: string; + nameStartsWith?: string; + details?: string; + add?: string; + noEntities?: string; + selectedEntities?: string; + search?: string; } export interface EntityTypeResource { helpLinkId: string; } -export const entityTypeTranslations = new Map( +export const entityTypeTranslations = new Map( [ [ EntityType.TENANT, @@ -136,6 +136,13 @@ export const entityTypeTranslations = new Map search: 'entity-view.search', selectedEntities: 'entity-view.selected-entity-views' } + ], + [ + AliasEntityType.CURRENT_CUSTOMER, + { + type: 'entity.type-entity-view', + list: 'entity.type-current-customer' + } ] ] ); diff --git a/ui-ngx/src/app/shared/models/id/entity-id.ts b/ui-ngx/src/app/shared/models/id/entity-id.ts index 55bbd2f0c1..dbe3f72c87 100644 --- a/ui-ngx/src/app/shared/models/id/entity-id.ts +++ b/ui-ngx/src/app/shared/models/id/entity-id.ts @@ -14,9 +14,9 @@ /// limitations under the License. /// -import { EntityType } from '@shared/models/entity-type.models'; +import {AliasEntityType, EntityType} from '@shared/models/entity-type.models'; import { HasUUID } from '@shared/models/id/has-uuid'; export interface EntityId extends HasUUID { - entityType: EntityType; + entityType: EntityType | AliasEntityType; } diff --git a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts new file mode 100644 index 0000000000..2e91be8e11 --- /dev/null +++ b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts @@ -0,0 +1,23 @@ +/// +/// Copyright © 2016-2019 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 enum DataKeyType { + timeseries = 'timeseries', + attribute = 'attribute', + function = 'function', + alarm = 'alarm' +} diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index 23d19c39a5..f1ea7825ce 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -84,6 +84,10 @@ import {EntitySubTypeAutocompleteComponent} from '@shared/components/entity/enti import {EntitySubTypeSelectComponent} from './components/entity/entity-subtype-select.component'; import {EntityAutocompleteComponent} from './components/entity/entity-autocomplete.component'; import {EntityListComponent} from '@shared/components/entity/entity-list.component'; +import {EntityTypeSelectComponent} from './components/entity/entity-type-select.component'; +import {EntitySelectComponent} from './components/entity/entity-select.component'; +import {DatetimeComponent} from '@shared/components/time/datetime.component'; +import {EntityKeysListComponent} from './components/entity/entity-keys-list.component'; @NgModule({ providers: [ @@ -122,12 +126,16 @@ import {EntityListComponent} from '@shared/components/entity/entity-list.compone TimewindowPanelComponent, TimeintervalComponent, DatetimePeriodComponent, + DatetimeComponent, // ValueInputComponent, DashboardAutocompleteComponent, EntitySubTypeAutocompleteComponent, EntitySubTypeSelectComponent, EntityAutocompleteComponent, EntityListComponent, + EntityTypeSelectComponent, + EntitySelectComponent, + EntityKeysListComponent, NospacePipe, MillisecondsToTimeStringPipe, EnumToArrayPipe, @@ -193,11 +201,15 @@ import {EntityListComponent} from '@shared/components/entity/entity-list.compone TimewindowPanelComponent, TimeintervalComponent, DatetimePeriodComponent, + DatetimeComponent, DashboardAutocompleteComponent, EntitySubTypeAutocompleteComponent, EntitySubTypeSelectComponent, EntityAutocompleteComponent, EntityListComponent, + EntityTypeSelectComponent, + EntitySelectComponent, + EntityKeysListComponent, // ValueInputComponent, MatButtonModule, MatCheckboxModule, 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 30b6d1e295..53d03ac648 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -6,7 +6,9 @@ "access-forbidden": "Access Forbidden", "access-forbidden-text": "You haven't access rights to this location!
Try to sign in with different user if you still wish to gain access to this location.", "refresh-token-expired": "Session has expired", - "refresh-token-failed": "Unable to refresh session" + "refresh-token-failed": "Unable to refresh session", + "permission-denied": "Permission Denied", + "permission-denied-text": "You don't have permission to perform this operation!" }, "action": { "activate": "Activate", @@ -826,6 +828,7 @@ "duplicate-alias-error": "Duplicate alias found '{{alias}}'.
Entity View aliases must be unique whithin the dashboard.", "configure-alias": "Configure '{{alias}}' alias", "no-entity-views-matching": "No entity views matching '{{entity}}' were found.", + "public": "Public", "alias": "Alias", "alias-required": "Entity View alias is required.", "remove-alias": "Remove entity view alias", @@ -837,6 +840,7 @@ "entity-view-name-filter-required": "Entity view name filter is required.", "entity-view-name-filter-no-entity-view-matched": "No entity views starting with '{{entityView}}' were found.", "add": "Add Entity View", + "entity-view-public": "Entity view is public", "assign-to-customer": "Assign to customer", "assign-entity-view-to-customer": "Assign Entity View(s) To Customer", "assign-entity-view-to-customer-text": "Please select the entity views to assign to the customer", @@ -877,6 +881,7 @@ "events": "Events", "details": "Details", "copyId": "Copy entity view Id", + "idCopiedMessage": "Entity view Id has been copied to clipboard", "assignedToCustomer": "Assigned to customer", "unable-entity-view-device-alias-title": "Unable to delete entity view alias", "unable-entity-view-device-alias-text": "Device alias '{{entityViewAlias}}' can't be deleted as it used by the following widget(s):
{{widgetsList}}", diff --git a/ui-ngx/src/styles.scss b/ui-ngx/src/styles.scss index bd55482c9b..bb221f4b8d 100644 --- a/ui-ngx/src/styles.scss +++ b/ui-ngx/src/styles.scss @@ -216,6 +216,13 @@ div { } } +.tb-hint { + padding-bottom: 15px; + font-size: 12px; + line-height: 14px; + color: #808080; +} + pre.tb-highlight { display: block; padding: 15px; From b8d837abbbe6389c24f35902c7a3c43532db61e2 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 20 Aug 2019 20:42:48 +0300 Subject: [PATCH 016/133] Rule Chains, Widgets Bundles and Dashboards pages --- .../controller/DashboardController.java | 11 +- .../BaseDashboardControllerTest.java | 4 +- .../dao/dashboard/DashboardService.java | 2 +- .../dao/dashboard/DashboardInfoDao.java | 2 +- .../dao/dashboard/DashboardServiceImpl.java | 24 +- .../dashboard/DashboardInfoRepository.java | 9 + .../sql/dashboard/JpaDashboardInfoDao.java | 21 +- .../dao/service/BaseDashboardServiceTest.java | 8 +- ui-ngx/package-lock.json | 8 + ui-ngx/package.json | 1 + ui-ngx/src/app/core/http/dashboard.service.ts | 86 +++- ui-ngx/src/app/core/http/device.service.ts | 2 +- ui-ngx/src/app/core/http/entity.service.ts | 6 +- .../src/app/core/http/rule-chain.service.ts | 56 ++ ui-ngx/src/app/core/http/widget.service.ts | 54 ++ ui-ngx/src/app/core/utils.ts | 11 + ...d-entities-to-customer-dialog.component.ts | 10 +- .../asset/assets-table-config.resolver.ts | 1 + .../pages/customer/customer-routing.module.ts | 17 + .../customers-table-config.resolver.ts | 9 + .../dashboard/dashboard-form.component.html | 110 ++++ .../dashboard/dashboard-form.component.scss | 19 + .../dashboard/dashboard-form.component.ts | 110 ++++ .../dashboard/dashboard-routing.module.ts | 57 +++ .../home/pages/dashboard/dashboard.module.ts | 41 ++ .../dashboards-table-config.resolver.ts | 484 ++++++++++++++++++ ...-dashboard-customers-dialog.component.html | 58 +++ ...ge-dashboard-customers-dialog.component.ts | 129 +++++ .../device/devices-table-config.resolver.ts | 1 + .../entity-view/entity-view.component.html | 1 - .../entity-views-table-config.resolver.ts | 1 + .../modules/home/pages/home-pages.module.ts | 6 + .../rulechain/rulechain-routing.module.ts | 56 ++ .../pages/rulechain/rulechain.component.html | 75 +++ .../pages/rulechain/rulechain.component.scss | 19 + .../pages/rulechain/rulechain.component.ts | 81 +++ .../home/pages/rulechain/rulechain.module.ts | 36 ++ .../rulechains-table-config.resolver.ts | 176 +++++++ .../widget/widget-library-routing.module.ts | 57 +++ .../pages/widget/widget-library.module.ts | 36 ++ .../widget/widgets-bundle.component.html | 44 ++ .../widget/widgets-bundle.component.scss | 19 + .../pages/widget/widgets-bundle.component.ts | 55 ++ .../widgets-bundles-table-config.resolver.ts | 158 ++++++ .../entity/entity-autocomplete.component.html | 1 + .../entity/entity-autocomplete.component.ts | 30 +- .../entity/entity-keys-list.component.html | 1 + .../entity/entity-keys-list.component.ts | 15 +- .../entity/entity-list.component.html | 6 +- .../entity/entity-list.component.ts | 2 +- ...entity-subtype-autocomplete.component.html | 3 +- .../entity-subtype-autocomplete.component.ts | 68 ++- .../entity-subtype-select.component.html | 3 +- .../entity/entity-subtype-select.component.ts | 7 +- .../socialshare-panel.component.html | 53 ++ .../components/socialshare-panel.component.ts | 61 +++ ui-ngx/src/app/shared/models/constants.ts | 5 +- .../src/app/shared/models/customer.model.ts | 2 +- .../src/app/shared/models/dashboard.models.ts | 32 +- .../app/shared/models/entity-type.models.ts | 60 ++- .../app/shared/models/rule-chain.models.ts | 9 +- ui-ngx/src/app/shared/shared.module.ts | 8 +- .../assets/locale/locale.constant-en_US.json | 17 +- 63 files changed, 2412 insertions(+), 142 deletions(-) create mode 100644 ui-ngx/src/app/core/http/rule-chain.service.ts create mode 100644 ui-ngx/src/app/core/http/widget.service.ts create mode 100644 ui-ngx/src/app/modules/home/pages/dashboard/dashboard-form.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/dashboard/dashboard-form.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/dashboard/dashboard-form.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/dashboard/dashboard-routing.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/dashboard/dashboard.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts create mode 100644 ui-ngx/src/app/modules/home/pages/dashboard/manage-dashboard-customers-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/dashboard/manage-dashboard-customers-dialog.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/rulechain/rulechain.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/rulechain/rulechain.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/rulechain/rulechain.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/rulechain/rulechain.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts create mode 100644 ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/widget/widget-library.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/widget/widgets-bundle.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/widget/widgets-bundle.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/widget/widgets-bundle.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/widget/widgets-bundles-table-config.resolver.ts create mode 100644 ui-ngx/src/app/shared/components/socialshare-panel.component.html create mode 100644 ui-ngx/src/app/shared/components/socialshare-panel.component.ts diff --git a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java index a7c427b35e..d7dd9fe482 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java @@ -460,17 +460,16 @@ public class DashboardController extends BaseController { @PathVariable("customerId") String strCustomerId, @RequestParam int pageSize, @RequestParam int page, - @RequestParam(required = false) Long startTime, - @RequestParam(required = false) Long endTime, - @RequestParam(required = false, defaultValue = "false") boolean ascOrder) throws ThingsboardException { + @RequestParam(required = false) String textSearch, + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder) throws ThingsboardException { checkParameter("customerId", strCustomerId); try { TenantId tenantId = getCurrentUser().getTenantId(); CustomerId customerId = new CustomerId(toUUID(strCustomerId)); checkCustomerId(customerId, Operation.READ); - TimePageLink pageLink = createTimePageLink(pageSize, page, "", - "createdTime", ascOrder ? "asc" : "desc", startTime, endTime); - return checkNotNull(dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink).get()); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + return checkNotNull(dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink)); } catch (Exception e) { throw handleException(e); } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java index 2997b3d7e5..3f5d9ef882 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java @@ -323,10 +323,10 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest } List loadedDashboards = new ArrayList<>(); - TimePageLink pageLink = new TimePageLink(21); + PageLink pageLink = new PageLink(21); PageData pageData = null; do { - pageData = doGetTypedWithTimePageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?", + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?", new TypeReference>(){}, pageLink); loadedDashboards.addAll(pageData.getData()); if (pageData.hasNext()) { diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java index bb49599aa4..41d91cc723 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java @@ -47,7 +47,7 @@ public interface DashboardService { void deleteDashboardsByTenantId(TenantId tenantId); - ListenableFuture> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink); + PageData findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink); void unassignCustomerDashboards(TenantId tenantId, CustomerId customerId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardInfoDao.java index 002a07045c..867cde7328 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardInfoDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardInfoDao.java @@ -47,6 +47,6 @@ public interface DashboardInfoDao extends Dao { * @param pageLink the page link * @return the list of dashboard objects */ - ListenableFuture> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TimePageLink pageLink); + PageData findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, PageLink pageLink); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java index 63ac5e93ca..3a5e869bfe 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java @@ -188,7 +188,7 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb } @Override - public ListenableFuture> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink) { + public PageData findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink) { log.trace("Executing findDashboardsByTenantIdAndCustomerId, tenantId [{}], customerId [{}], pageLink [{}]", tenantId, customerId, pageLink); Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId); Validator.validateId(customerId, "Incorrect customerId " + customerId); @@ -250,7 +250,7 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb } }; - private class CustomerDashboardsUnassigner extends TimePaginatedRemover { + private class CustomerDashboardsUnassigner extends PaginatedRemover { private Customer customer; @@ -259,13 +259,8 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb } @Override - protected PageData findEntities(TenantId tenantId, Customer customer, TimePageLink pageLink) { - try { - return dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(customer.getTenantId().getId(), customer.getId().getId(), pageLink).get(); - } catch (InterruptedException | ExecutionException e) { - log.warn("Failed to get dashboards by tenantId [{}] and customerId [{}].", customer.getTenantId().getId(), customer.getId().getId()); - throw new RuntimeException(e); - } + protected PageData findEntities(TenantId tenantId, Customer customer, PageLink pageLink) { + return dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(customer.getTenantId().getId(), customer.getId().getId(), pageLink); } @Override @@ -275,7 +270,7 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb } - private class CustomerDashboardsUpdater extends TimePaginatedRemover { + private class CustomerDashboardsUpdater extends PaginatedRemover { private Customer customer; @@ -284,13 +279,8 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb } @Override - protected PageData findEntities(TenantId tenantId, Customer customer, TimePageLink pageLink) { - try { - return dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(customer.getTenantId().getId(), customer.getId().getId(), pageLink).get(); - } catch (InterruptedException | ExecutionException e) { - log.warn("Failed to get dashboards by tenantId [{}] and customerId [{}].", customer.getTenantId().getId(), customer.getId().getId()); - throw new RuntimeException(e); - } + protected PageData findEntities(TenantId tenantId, Customer customer, PageLink pageLink) { + return dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(customer.getTenantId().getId(), customer.getId().getId(), pageLink); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java index 34b2937150..089c5b0aad 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java @@ -38,4 +38,13 @@ public interface DashboardInfoRepository extends PagingAndSortingRepository findByTenantIdAndCustomerId(@Param("tenantId") String tenantId, + @Param("customerId") String customerId, + @Param("searchText") String searchText, + Pageable pageable); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java index 2b2dbf5820..0799b09e1e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java @@ -80,19 +80,12 @@ public class JpaDashboardInfoDao extends JpaAbstractSearchTextDao> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TimePageLink pageLink) { - log.debug("Try to find dashboards by tenantId [{}], customerId[{}] and pageLink [{}]", tenantId, customerId, pageLink); - - ListenableFuture> relations = relationDao.findRelations(new TenantId(tenantId), new CustomerId(customerId), EntityRelation.CONTAINS_TYPE, RelationTypeGroup.DASHBOARD, EntityType.DASHBOARD, pageLink); - - return Futures.transformAsync(relations, input -> { - List> dashboardFutures = new ArrayList<>(input.getData().size()); - for (EntityRelation relation : input.getData()) { - dashboardFutures.add(findByIdAsync(new TenantId(tenantId), relation.getTo().getId())); - } - return Futures.transform(Futures.successfulAsList(dashboardFutures), dashboards -> { - return new PageData(dashboards, input.getTotalPages(), input.getTotalElements(), input.hasNext()); - }); - }); + public PageData findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, PageLink pageLink) { + return DaoUtil.toPageData(dashboardInfoRepository + .findByTenantIdAndCustomerId( + UUIDConverter.fromTimeUUID(tenantId), + UUIDConverter.fromTimeUUID(customerId), + Objects.toString(pageLink.getTextSearch(), ""), + DaoUtil.toPageable(pageLink))); } } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java index ee4bfe02cb..6c7091e178 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java @@ -302,10 +302,10 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest { } List loadedDashboards = new ArrayList<>(); - TimePageLink pageLink = new TimePageLink(23); + PageLink pageLink = new PageLink(23); PageData pageData = null; do { - pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink).get(); + pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink); loadedDashboards.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); @@ -319,8 +319,8 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest { dashboardService.unassignCustomerDashboards(tenantId, customerId); - pageLink = new TimePageLink(42); - pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink).get(); + pageLink = new PageLink(42); + pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertTrue(pageData.getData().isEmpty()); diff --git a/ui-ngx/package-lock.json b/ui-ngx/package-lock.json index 00f8dabb3f..20cfca4c3f 100644 --- a/ui-ngx/package-lock.json +++ b/ui-ngx/package-lock.json @@ -992,6 +992,14 @@ } } }, + "@ngx-share/core": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@ngx-share/core/-/core-7.1.2.tgz", + "integrity": "sha512-i54tu5rS+8yxu2v+AFnssSW2FUQJEWFLUiMqXtDIzkXqlPffFyWzpkhx+vfVJi6D7zXiEq1Spb4kubeTJwZpdg==", + "requires": { + "tslib": "^1.9.0" + } + }, "@ngx-translate/core": { "version": "11.0.1", "resolved": "https://registry.npmjs.org/@ngx-translate/core/-/core-11.0.1.tgz", diff --git a/ui-ngx/package.json b/ui-ngx/package.json index b31fbebec4..6908849900 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -28,6 +28,7 @@ "@ngrx/effects": "^8.2.0", "@ngrx/store": "^8.2.0", "@ngrx/store-devtools": "^8.2.0", + "@ngx-share/core": "^7.1.2", "@ngx-translate/core": "^11.0.1", "@ngx-translate/http-loader": "^4.0.0", "ace-builds": "^1.4.5", diff --git a/ui-ngx/src/app/core/http/dashboard.service.ts b/ui-ngx/src/app/core/http/dashboard.service.ts index 3419f6ad8a..ee30421842 100644 --- a/ui-ngx/src/app/core/http/dashboard.service.ts +++ b/ui-ngx/src/app/core/http/dashboard.service.ts @@ -14,15 +14,14 @@ /// limitations under the License. /// -import { Injectable } from '@angular/core'; -import { defaultHttpOptions } from './http-utils'; -import { Observable } from 'rxjs/index'; -import { HttpClient } from '@angular/common/http'; -import { PageLink } from '@shared/models/page/page-link'; -import { PageData } from '@shared/models/page/page-data'; -import { Tenant } from '@shared/models/tenant.model'; -import {DashboardInfo, Dashboard} from '@shared/models/dashboard.models'; -import {map} from 'rxjs/operators'; +import {Inject, Injectable} from '@angular/core'; +import {defaultHttpOptions} from './http-utils'; +import {Observable} from 'rxjs/index'; +import {HttpClient} from '@angular/common/http'; +import {PageLink} from '@shared/models/page/page-link'; +import {PageData} from '@shared/models/page/page-data'; +import {Dashboard, DashboardInfo} from '@shared/models/dashboard.models'; +import {WINDOW} from '@core/services/window.service'; @Injectable({ providedIn: 'root' @@ -30,7 +29,8 @@ import {map} from 'rxjs/operators'; export class DashboardService { constructor( - private http: HttpClient + private http: HttpClient, + @Inject(WINDOW) private window: Window ) { } public getTenantDashboards(pageLink: PageLink, ignoreErrors: boolean = false, @@ -48,14 +48,7 @@ export class DashboardService { public getCustomerDashboards(customerId: string, pageLink: PageLink, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { return this.http.get>(`/api/customer/${customerId}/dashboards${pageLink.toQuery()}`, - defaultHttpOptions(ignoreLoading, ignoreErrors)).pipe( - map( dashboards => { - dashboards.data = dashboards.data.filter(dashboard => { - return dashboard.title.toUpperCase().includes(pageLink.textSearch.toUpperCase()); - }); - return dashboards; - } - )); + defaultHttpOptions(ignoreLoading, ignoreErrors)); } public getDashboard(dashboardId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { @@ -74,4 +67,61 @@ export class DashboardService { return this.http.delete(`/api/dashboard/${dashboardId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); } + public assignDashboardToCustomer(customerId: string, dashboardId: string, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.post(`/api/customer/${customerId}/dashboard/${dashboardId}`, + null, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public unassignDashboardFromCustomer(customerId: string, dashboardId: string, + ignoreErrors: boolean = false, ignoreLoading: boolean = false) { + return this.http.delete(`/api/customer/${customerId}/dashboard/${dashboardId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public makeDashboardPublic(dashboardId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.post(`/api/customer/public/dashboard/${dashboardId}`, null, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public makeDashboardPrivate(dashboardId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.delete(`/api/customer/public/dashboard/${dashboardId}`, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public updateDashboardCustomers(dashboardId: string, customerIds: Array, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.post(`/api/dashboard/${dashboardId}/customers`, customerIds, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public addDashboardCustomers(dashboardId: string, customerIds: Array, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.post(`/api/dashboard/${dashboardId}/customers/add`, customerIds, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public removeDashboardCustomers(dashboardId: string, customerIds: Array, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.post(`/api/dashboard/${dashboardId}/customers/remove`, customerIds, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public getPublicDashboardLink(dashboard: DashboardInfo): string | null { + if (dashboard && dashboard.assignedCustomers && dashboard.assignedCustomers.length > 0) { + const publicCustomers = dashboard.assignedCustomers + .filter(customerInfo => customerInfo.public); + if (publicCustomers.length > 0) { + const publicCustomerId = publicCustomers[0].customerId.id; + let url = this.window.location.protocol + '//' + this.window.location.hostname; + const port = this.window.location.port; + if (port !== '80' && port !== '443') { + url += ':' + port; + } + url += `/dashboard/${dashboard.id.id}?publicId=${publicCustomerId}`; + return url; + } + } + return null; + } + } diff --git a/ui-ngx/src/app/core/http/device.service.ts b/ui-ngx/src/app/core/http/device.service.ts index d233e6650f..adc4bf0ac2 100644 --- a/ui-ngx/src/app/core/http/device.service.ts +++ b/ui-ngx/src/app/core/http/device.service.ts @@ -107,7 +107,7 @@ export class DeviceService { } public assignDeviceToCustomer(customerId: string, deviceId: string, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { return this.http.post(`/api/customer/${customerId}/device/${deviceId}`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); } diff --git a/ui-ngx/src/app/core/http/entity.service.ts b/ui-ngx/src/app/core/http/entity.service.ts index 8533b78225..1622de0d20 100644 --- a/ui-ngx/src/app/core/http/entity.service.ts +++ b/ui-ngx/src/app/core/http/entity.service.ts @@ -40,6 +40,7 @@ import {EntityViewService} from '@core/http/entity-view.service'; import {DataKeyType} from '@shared/models/telemetry/telemetry.models'; import {DeviceInfo} from '@shared/models/device.models'; import {defaultHttpOptions} from '@core/http/http-utils'; +import {RuleChainService} from '@core/http/rule-chain.service'; @Injectable({ providedIn: 'root' @@ -55,6 +56,7 @@ export class EntityService { private tenantService: TenantService, private customerService: CustomerService, private userService: UserService, + private ruleChainService: RuleChainService, private dashboardService: DashboardService ) { } @@ -85,7 +87,7 @@ export class EntityService { observable = this.userService.getUser(entityId, ignoreErrors, ignoreLoading); break; case EntityType.RULE_CHAIN: - // TODO: + observable = this.ruleChainService.getRuleChain(entityId, ignoreErrors, ignoreLoading); break; case EntityType.ALARM: console.error('Get Alarm Entity is not implemented!'); @@ -274,7 +276,7 @@ export class EntityService { break; case EntityType.RULE_CHAIN: pageLink.sortOrder.property = 'name'; - // TODO: + entitiesObservable = this.ruleChainService.getRuleChains(pageLink, ignoreErrors, ignoreLoading); break; case EntityType.DASHBOARD: pageLink.sortOrder.property = 'title'; diff --git a/ui-ngx/src/app/core/http/rule-chain.service.ts b/ui-ngx/src/app/core/http/rule-chain.service.ts new file mode 100644 index 0000000000..95cb1e9900 --- /dev/null +++ b/ui-ngx/src/app/core/http/rule-chain.service.ts @@ -0,0 +1,56 @@ +/// +/// Copyright © 2016-2019 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 {Injectable} from '@angular/core'; +import {defaultHttpOptions} from './http-utils'; +import {Observable} from 'rxjs/index'; +import {HttpClient} from '@angular/common/http'; +import {PageLink} from '@shared/models/page/page-link'; +import {PageData} from '@shared/models/page/page-data'; +import {RuleChain} from '@shared/models/rule-chain.models'; + +@Injectable({ + providedIn: 'root' +}) +export class RuleChainService { + + constructor( + private http: HttpClient + ) { } + + public getRuleChains(pageLink: PageLink, ignoreErrors: boolean = false, + ignoreLoading: boolean = false): Observable> { + return this.http.get>(`/api/ruleChains${pageLink.toQuery()}`, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public getRuleChain(ruleChainId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.get(`/api/ruleChain/${ruleChainId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public saveRuleChain(ruleChain: RuleChain, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.post('/api/ruleChain', ruleChain, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public deleteRuleChain(ruleChainId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { + return this.http.delete(`/api/ruleChain/${ruleChainId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public setRootRuleChain(ruleChainId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.post(`/api/ruleChain/${ruleChainId}/root`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + +} diff --git a/ui-ngx/src/app/core/http/widget.service.ts b/ui-ngx/src/app/core/http/widget.service.ts new file mode 100644 index 0000000000..efa85f5516 --- /dev/null +++ b/ui-ngx/src/app/core/http/widget.service.ts @@ -0,0 +1,54 @@ +/// +/// Copyright © 2016-2019 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 {Injectable} from '@angular/core'; +import {defaultHttpOptions} from './http-utils'; +import {Observable} from 'rxjs/index'; +import {HttpClient} from '@angular/common/http'; +import {PageLink} from '@shared/models/page/page-link'; +import {PageData} from '@shared/models/page/page-data'; +import {WidgetsBundle} from '@shared/models/widgets-bundle.model'; + +@Injectable({ + providedIn: 'root' +}) +export class WidgetService { + + constructor( + private http: HttpClient + ) { } + + public getWidgetBundles(pageLink: PageLink, ignoreErrors: boolean = false, + ignoreLoading: boolean = false): Observable> { + return this.http.get>(`/api/widgetsBundles${pageLink.toQuery()}`, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public getWidgetsBundle(widgetsBundleId: string, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.get(`/api/widgetsBundle/${widgetsBundleId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public saveWidgetsBundle(widgetsBundle: WidgetsBundle, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.post('/api/widgetsBundle', widgetsBundle, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public deleteWidgetsBundle(widgetsBundleId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { + return this.http.delete(`/api/widgetsBundle/${widgetsBundleId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + +} diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index b25fa860e9..bf848e6ceb 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -41,6 +41,17 @@ export function onParentScrollOrWindowResize(el: Node): Observable { return shared; } +export function isLocalUrl(url: string): boolean { + const parser = document.createElement('a'); + parser.href = url; + const host = parser.hostname; + if (host === 'localhost' || host === '127.0.0.1') { + return true; + } else { + return false; + } +} + const scrollRegex = /(auto|scroll)/; function parentNodes(node: Node, nodes: Node[]): Node[] { diff --git a/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.ts b/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.ts index 69276418d5..cab5cd2b57 100644 --- a/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.ts @@ -21,11 +21,11 @@ import {Store} from '@ngrx/store'; import {AppState} from '@core/core.state'; import {FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators} from '@angular/forms'; import {DeviceService} from '@core/http/device.service'; -import {EntityId} from '@shared/models/id/entity-id'; import {EntityType} from '@shared/models/entity-type.models'; import {forkJoin, Observable} from 'rxjs'; import {AssetService} from '@core/http/asset.service'; import {EntityViewService} from '@core/http/entity-view.service'; +import {DashboardService} from '@core/http/dashboard.service'; export interface AddEntitiesToCustomerDialogData { customerId: string; @@ -54,6 +54,7 @@ export class AddEntitiesToCustomerDialogComponent extends PageComponent implemen private deviceService: DeviceService, private assetService: AssetService, private entityViewService: EntityViewService, + private dashboardService: DashboardService, @SkipSelf() private errorStateMatcher: ErrorStateMatcher, public dialogRef: MatDialogRef, public fb: FormBuilder) { @@ -78,6 +79,10 @@ export class AddEntitiesToCustomerDialogComponent extends PageComponent implemen this.assignToCustomerTitle = 'entity-view.assign-entity-view-to-customer'; this.assignToCustomerText = 'entity-view.assign-entity-view-to-customer-text'; break; + case EntityType.DASHBOARD: + this.assignToCustomerTitle = 'dashboard.assign-dashboard-to-customer'; + this.assignToCustomerText = 'dashboard.assign-dashboard-to-customer-text'; + break; } } @@ -118,6 +123,9 @@ export class AddEntitiesToCustomerDialogComponent extends PageComponent implemen case EntityType.ENTITY_VIEW: return this.entityViewService.assignEntityViewToCustomer(customerId, entityId); break; + case EntityType.DASHBOARD: + return this.dashboardService.assignDashboardToCustomer(customerId, entityId); + break; } } diff --git a/ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts index 1d0c1545af..d217bc57c2 100644 --- a/ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts @@ -93,6 +93,7 @@ export class AssetsTableConfigResolver implements Resolve this.onAssetAction(action); + this.config.detailsReadonly = () => this.config.componentsData.assetScope === 'customer_user'; this.config.headerComponent = AssetTableHeaderComponent; diff --git a/ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts b/ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts index 224a94a74b..d405c8cf12 100644 --- a/ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts @@ -23,6 +23,7 @@ import {UsersTableConfigResolver} from '../user/users-table-config.resolver'; import {CustomersTableConfigResolver} from './customers-table-config.resolver'; import {DevicesTableConfigResolver} from '@modules/home/pages/device/devices-table-config.resolver'; import {AssetsTableConfigResolver} from '../asset/assets-table-config.resolver'; +import {DashboardsTableConfigResolver} from '@modules/home/pages/dashboard/dashboards-table-config.resolver'; const routes: Routes = [ { @@ -91,6 +92,22 @@ const routes: Routes = [ resolve: { entitiesTableConfig: AssetsTableConfigResolver } + }, + { + path: ':customerId/dashboards', + component: EntitiesTableComponent, + data: { + auth: [Authority.TENANT_ADMIN], + title: 'customer.assets', + dashboardsType: 'customer', + breadcrumb: { + label: 'customer.dashboards', + icon: 'dashboard' + } + }, + resolve: { + entitiesTableConfig: DashboardsTableConfigResolver + } } ] } diff --git a/ui-ngx/src/app/modules/home/pages/customer/customers-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/customer/customers-table-config.resolver.ts index fa558cf0c3..7f340b9afe 100644 --- a/ui-ngx/src/app/modules/home/pages/customer/customers-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/customer/customers-table-config.resolver.ts @@ -155,6 +155,15 @@ export class CustomersTableConfigResolver implements Resolve +
+ + + + + + + +
+
+ + dashboard.assignedToCustomers + + +
+ + +
+ + dashboard.public-link + + + +
+
+ +
+ + dashboard.title + + + {{ 'dashboard.title-required' | translate }} + + +
+ + dashboard.description + + +
+
+ +
diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-form.component.scss b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-form.component.scss new file mode 100644 index 0000000000..d18a4874d0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-form.component.scss @@ -0,0 +1,19 @@ +/** + * Copyright © 2016-2019 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 { + +} diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-form.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-form.component.ts new file mode 100644 index 0000000000..6914a4f0c0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-form.component.ts @@ -0,0 +1,110 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import {Component} from '@angular/core'; +import {Store} from '@ngrx/store'; +import {AppState} from '@core/core.state'; +import {EntityComponent} from '@shared/components/entity/entity.component'; +import {FormBuilder, FormGroup, Validators} from '@angular/forms'; +import {ActionNotificationShow} from '@core/notification/notification.actions'; +import {TranslateService} from '@ngx-translate/core'; +import { + Dashboard, + isPublicDashboard, + getDashboardAssignedCustomersText, + isCurrentPublicDashboardCustomer, + DashboardInfo +} from '@shared/models/dashboard.models'; +import {DashboardService} from '@core/http/dashboard.service'; + +@Component({ + selector: 'tb-dashboard-form', + templateUrl: './dashboard-form.component.html', + styleUrls: ['./dashboard-form.component.scss'] +}) +export class DashboardFormComponent extends EntityComponent { + + dashboardScope: 'tenant' | 'customer' | 'customer_user'; + customerId: string; + + publicLink: string; + assignedCustomersText: string; + + constructor(protected store: Store, + protected translate: TranslateService, + private dashboardService: DashboardService, + public fb: FormBuilder) { + super(store); + } + + ngOnInit() { + this.dashboardScope = this.entitiesTableConfig.componentsData.dashboardScope; + this.customerId = this.entitiesTableConfig.componentsData.customerId; + super.ngOnInit(); + } + + isPublic(entity: Dashboard): boolean { + return isPublicDashboard(entity); + } + + isCurrentPublicCustomer(entity: Dashboard): boolean { + return isCurrentPublicDashboardCustomer(entity, this.customerId); + } + + hideDelete() { + if (this.entitiesTableConfig) { + return !this.entitiesTableConfig.deleteEnabled(this.entity); + } else { + return false; + } + } + + buildForm(entity: Dashboard): FormGroup { + this.updateFields(entity); + return this.fb.group( + { + title: [entity ? entity.title : '', [Validators.required]], + configuration: this.fb.group( + { + description: [entity && entity.configuration ? entity.configuration.description : ''], + } + ) + } + ); + } + + updateForm(entity: Dashboard) { + this.updateFields(entity); + this.entityForm.patchValue({title: entity.title}); + this.entityForm.patchValue({configuration: {description: entity.configuration ? entity.configuration.description : ''}}); + } + + onPublicLinkCopied($event) { + this.store.dispatch(new ActionNotificationShow( + { + message: this.translate.instant('dashboard.public-link-copied-message'), + type: 'success', + duration: 750, + verticalPosition: 'bottom', + horizontalPosition: 'right' + })); + } + + private updateFields(entity: Dashboard): void { + this.assignedCustomersText = getDashboardAssignedCustomersText(entity); + this.publicLink = this.dashboardService.getPublicDashboardLink(entity); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-routing.module.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-routing.module.ts new file mode 100644 index 0000000000..bda080e3bd --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-routing.module.ts @@ -0,0 +1,57 @@ +/// +/// Copyright © 2016-2019 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 {EntitiesTableComponent} from '@shared/components/entity/entities-table.component'; +import {Authority} from '@shared/models/authority.enum'; +import {DashboardsTableConfigResolver} from './dashboards-table-config.resolver'; + +const routes: Routes = [ + { + path: 'dashboards', + data: { + breadcrumb: { + label: 'dashboard.dashboards', + icon: 'dashboard' + } + }, + children: [ + { + path: '', + component: EntitiesTableComponent, + data: { + auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], + title: 'dashboard.dashboards', + dashboardsType: 'tenant' + }, + resolve: { + entitiesTableConfig: DashboardsTableConfigResolver + } + } + ] + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], + providers: [ + DashboardsTableConfigResolver + ] +}) +export class DashboardRoutingModule { } diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard.module.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard.module.ts new file mode 100644 index 0000000000..c358088524 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard.module.ts @@ -0,0 +1,41 @@ +/// +/// Copyright © 2016-2019 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 {CommonModule} from '@angular/common'; +import {SharedModule} from '@shared/shared.module'; +import {HomeDialogsModule} from '../../dialogs/home-dialogs.module'; +import {DashboardFormComponent} from '@modules/home/pages/dashboard/dashboard-form.component'; +import {ManageDashboardCustomersDialogComponent} from '@modules/home/pages/dashboard/manage-dashboard-customers-dialog.component'; +import {DashboardRoutingModule} from './dashboard-routing.module'; + +@NgModule({ + entryComponents: [ + DashboardFormComponent, + ManageDashboardCustomersDialogComponent + ], + declarations: [ + DashboardFormComponent, + ManageDashboardCustomersDialogComponent + ], + imports: [ + CommonModule, + SharedModule, + HomeDialogsModule, + DashboardRoutingModule + ] +}) +export class DashboardModule { } diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts new file mode 100644 index 0000000000..b03df9a57a --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts @@ -0,0 +1,484 @@ +/// +/// Copyright © 2016-2019 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 {Injectable} from '@angular/core'; + +import {ActivatedRouteSnapshot, Resolve, Router} from '@angular/router'; +import { + CellActionDescriptor, + checkBoxCell, + DateEntityTableColumn, + EntityTableColumn, + EntityTableConfig, + GroupActionDescriptor, + HeaderActionDescriptor +} from '@shared/components/entity/entities-table-config.models'; +import {TranslateService} from '@ngx-translate/core'; +import {DatePipe} from '@angular/common'; +import {EntityType, entityTypeResources, entityTypeTranslations} from '@shared/models/entity-type.models'; +import {EntityAction} from '@shared/components/entity/entity-component.models'; +import {forkJoin, Observable, of} from 'rxjs'; +import {select, Store} from '@ngrx/store'; +import {selectAuthUser} from '@core/auth/auth.selectors'; +import {map, mergeMap, take, tap} from 'rxjs/operators'; +import {AppState} from '@core/core.state'; +import {Authority} from '@app/shared/models/authority.enum'; +import {CustomerService} from '@core/http/customer.service'; +import {Customer} from '@app/shared/models/customer.model'; +import {MatDialog} from '@angular/material'; +import {DialogService} from '@core/services/dialog.service'; +import { + AddEntitiesToCustomerDialogComponent, + AddEntitiesToCustomerDialogData +} from '../../dialogs/add-entities-to-customer-dialog.component'; +import { + Dashboard, + DashboardInfo, + getDashboardAssignedCustomersText, + isCurrentPublicDashboardCustomer, + isPublicDashboard +} from '@app/shared/models/dashboard.models'; +import {DashboardService} from '@app/core/http/dashboard.service'; +import {DashboardFormComponent} from '@modules/home/pages/dashboard/dashboard-form.component'; +import { + ManageDashboardCustomersActionType, + ManageDashboardCustomersDialogComponent, + ManageDashboardCustomersDialogData +} from './manage-dashboard-customers-dialog.component'; + +@Injectable() +export class DashboardsTableConfigResolver implements Resolve> { + + private readonly config: EntityTableConfig = new EntityTableConfig(); + + constructor(private store: Store, + private dashboardService: DashboardService, + private customerService: CustomerService, + private dialogService: DialogService, + private translate: TranslateService, + private datePipe: DatePipe, + private router: Router, + private dialog: MatDialog) { + + this.config.entityType = EntityType.DASHBOARD; + this.config.entityComponent = DashboardFormComponent; + this.config.entityTranslations = entityTypeTranslations.get(EntityType.DASHBOARD); + this.config.entityResources = entityTypeResources.get(EntityType.DASHBOARD); + + this.config.deleteEntityTitle = dashboard => + this.translate.instant('dashboard.delete-dashboard-title', { dashboardTitle: dashboard.title }); + this.config.deleteEntityContent = () => this.translate.instant('dashboard.delete-dashboard-text'); + this.config.deleteEntitiesTitle = count => this.translate.instant('dashboard.delete-dashboards-title', {count}); + this.config.deleteEntitiesContent = () => this.translate.instant('dashboard.delete-dashboards-text'); + + this.config.loadEntity = id => this.dashboardService.getDashboard(id.id); + this.config.saveEntity = dashboard => { + return this.dashboardService.saveDashboard(dashboard as Dashboard); + }; + this.config.onEntityAction = action => this.onDashboardAction(action); + this.config.detailsReadonly = () => this.config.componentsData.dashboardScope === 'customer_user'; + } + + resolve(route: ActivatedRouteSnapshot): Observable> { + const routeParams = route.params; + this.config.componentsData = { + dashboardScope: route.data.dashboardsType, + customerId: routeParams.customerId + }; + return this.store.pipe(select(selectAuthUser), take(1)).pipe( + tap((authUser) => { + if (authUser.authority === Authority.CUSTOMER_USER) { + this.config.componentsData.dashboardScope = 'customer_user'; + this.config.componentsData.customerId = authUser.customerId; + } + }), + mergeMap(() => + this.config.componentsData.customerId ? + this.customerService.getCustomer(this.config.componentsData.customerId) : of(null as Customer) + ), + map((parentCustomer) => { + if (parentCustomer) { + if (parentCustomer.additionalInfo && parentCustomer.additionalInfo.isPublic) { + this.config.tableTitle = this.translate.instant('customer.public-dashboards'); + } else { + this.config.tableTitle = parentCustomer.title + ': ' + this.translate.instant('dashboard.dashboards'); + } + } else { + this.config.tableTitle = this.translate.instant('dashboard.dashboards'); + } + this.config.columns = this.configureColumns(this.config.componentsData.dashboardScope); + this.configureEntityFunctions(this.config.componentsData.dashboardScope); + this.config.cellActionDescriptors = this.configureCellActions(this.config.componentsData.dashboardScope); + this.config.groupActionDescriptors = this.configureGroupActions(this.config.componentsData.dashboardScope); + this.config.addActionDescriptors = this.configureAddActions(this.config.componentsData.dashboardScope); + this.config.addEnabled = this.config.componentsData.dashboardScope !== 'customer_user'; + this.config.entitiesDeleteEnabled = this.config.componentsData.dashboardScope === 'tenant'; + this.config.deleteEnabled = () => this.config.componentsData.dashboardScope === 'tenant'; + return this.config; + }) + ); + } + + configureColumns(dashboardScope: string): Array> { + const columns: Array> = [ + new DateEntityTableColumn('createdTime', 'dashboard.created-time', this.datePipe, '150px'), + new EntityTableColumn('title', 'dashboard.title') + ]; + if (dashboardScope === 'tenant') { + columns.push( + new EntityTableColumn('customersTitle', 'dashboard.assignedToCustomers', + '100%', entity => { + return getDashboardAssignedCustomersText(entity); + }, () => ({}), false), + new EntityTableColumn('dashboardIsPublic', 'dashboard.public', '60px', + entity => { + return checkBoxCell(isPublicDashboard(entity)); + }, () => ({}), false), + ); + } + return columns; + } + + configureEntityFunctions(dashboardScope: string): void { + if (dashboardScope === 'tenant') { + this.config.entitiesFetchFunction = pageLink => + this.dashboardService.getTenantDashboards(pageLink); + this.config.deleteEntity = id => this.dashboardService.deleteDashboard(id.id); + } else { + this.config.entitiesFetchFunction = pageLink => + this.dashboardService.getCustomerDashboards(this.config.componentsData.customerId, pageLink); + this.config.deleteEntity = id => + this.dashboardService.unassignDashboardFromCustomer(this.config.componentsData.customerId, id.id); + } + } + + configureCellActions(dashboardScope: string): Array> { + const actions: Array> = []; + actions.push( + { + name: this.translate.instant('dashboard.open-dashboard'), + icon: 'dashboard', + isEnabled: () => true, + onAction: ($event, entity) => this.openDashboard($event, entity) + } + ); + if (dashboardScope === 'tenant') { + actions.push( + { + name: this.translate.instant('dashboard.export'), + icon: 'file_download', + isEnabled: () => true, + onAction: ($event, entity) => this.exportDashboard($event, entity) + }, + { + name: this.translate.instant('dashboard.make-public'), + icon: 'share', + isEnabled: (entity) => !isPublicDashboard(entity), + onAction: ($event, entity) => this.makePublic($event, entity) + }, + { + name: this.translate.instant('dashboard.make-private'), + icon: 'reply', + isEnabled: (entity) => isPublicDashboard(entity), + onAction: ($event, entity) => this.makePrivate($event, entity) + }, + { + name: this.translate.instant('dashboard.manage-assigned-customers'), + icon: 'assignment_ind', + isEnabled: () => true, + onAction: ($event, entity) => this.manageAssignedCustomers($event, entity) + } + ); + } + if (dashboardScope === 'customer') { + actions.push( + { + name: this.translate.instant('dashboard.export'), + icon: 'file_download', + isEnabled: () => true, + onAction: ($event, entity) => this.exportDashboard($event, entity) + }, + { + name: this.translate.instant('dashboard.make-private'), + icon: 'reply', + isEnabled: (entity) => isCurrentPublicDashboardCustomer(entity, this.config.componentsData.customerId), + onAction: ($event, entity) => this.makePrivate($event, entity) + }, + { + name: this.translate.instant('dashboard.unassign-from-customer'), + icon: 'assignment_return', + isEnabled: (entity) => !isCurrentPublicDashboardCustomer(entity, this.config.componentsData.customerId), + onAction: ($event, entity) => this.unassignFromCustomer($event, entity, this.config.componentsData.customerId) + } + ); + } + return actions; + } + + configureGroupActions(dashboardScope: string): Array> { + const actions: Array> = []; + if (dashboardScope === 'tenant') { + actions.push( + { + name: this.translate.instant('dashboard.assign-dashboards'), + icon: 'assignment_ind', + isEnabled: true, + onAction: ($event, entities) => this.assignDashboardsToCustomers($event, entities.map((entity) => entity.id.id)) + } + ); + actions.push( + { + name: this.translate.instant('dashboard.unassign-dashboards'), + icon: 'assignment_return', + isEnabled: true, + onAction: ($event, entities) => this.unassignDashboardsFromCustomers($event, entities.map((entity) => entity.id.id)) + } + ); + } + if (dashboardScope === 'customer') { + actions.push( + { + name: this.translate.instant('dashboard.unassign-dashboards'), + icon: 'assignment_return', + isEnabled: true, + onAction: ($event, entities) => + this.unassignDashboardsFromCustomer($event, entities.map((entity) => entity.id.id), this.config.componentsData.customerId) + } + ); + } + return actions; + } + + configureAddActions(dashboardScope: string): Array { + const actions: Array = []; + if (dashboardScope === 'tenant') { + actions.push( + { + name: this.translate.instant('dashboard.create-new-dashboard'), + icon: 'insert_drive_file', + isEnabled: () => true, + onAction: ($event) => this.config.table.addEntity($event) + }, + { + name: this.translate.instant('dashboard.import'), + icon: 'file_upload', + isEnabled: () => true, + onAction: ($event) => this.importDashboard($event) + } + ); + } + if (dashboardScope === 'customer') { + actions.push( + { + name: this.translate.instant('dashboard.assign-new-dashboard'), + icon: 'add', + isEnabled: () => true, + onAction: ($event) => this.addDashboardsToCustomer($event) + } + ); + } + return actions; + } + + openDashboard($event: Event, dashboard: DashboardInfo) { + if ($event) { + $event.stopPropagation(); + } + // TODO: + // this.router.navigateByUrl(`customers/${customer.id.id}/users`); + } + + importDashboard($event: Event) { + if ($event) { + $event.stopPropagation(); + } + // TODO: + } + + exportDashboard($event: Event, dashboard: DashboardInfo) { + if ($event) { + $event.stopPropagation(); + } + // TODO: + } + + addDashboardsToCustomer($event: Event) { + if ($event) { + $event.stopPropagation(); + } + this.dialog.open(AddEntitiesToCustomerDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + customerId: this.config.componentsData.customerId, + entityType: EntityType.DASHBOARD + } + }).afterClosed() + .subscribe((res) => { + if (res) { + this.config.table.updateData(); + } + }); + } + + makePublic($event: Event, dashboard: DashboardInfo) { + if ($event) { + $event.stopPropagation(); + } + this.dashboardService.makeDashboardPublic(dashboard.id.id).subscribe( + (publicDashboard) => { + // TODO: + + this.config.table.updateData(); + } + ); + } + + makePrivate($event: Event, dashboard: DashboardInfo) { + if ($event) { + $event.stopPropagation(); + } + this.dialogService.confirm( + this.translate.instant('dashboard.make-private-dashboard-title', {dashboardTitle: dashboard.title}), + this.translate.instant('dashboard.make-private-dashboard-text'), + this.translate.instant('action.no'), + this.translate.instant('action.yes'), + true + ).subscribe((res) => { + if (res) { + this.dashboardService.makeDashboardPrivate(dashboard.id.id).subscribe( + () => { + this.config.table.updateData(); + } + ); + } + } + ); + } + + manageAssignedCustomers($event: Event, dashboard: DashboardInfo) { + const assignedCustomersIds = dashboard.assignedCustomers ? + dashboard.assignedCustomers.map(customerInfo => customerInfo.customerId.id) : []; + this.showManageAssignedCustomersDialog($event, [dashboard.id.id], 'manage', assignedCustomersIds); + } + + assignDashboardsToCustomers($event: Event, dashboardIds: Array) { + this.showManageAssignedCustomersDialog($event, dashboardIds, 'assign'); + } + + unassignDashboardsFromCustomers($event: Event, dashboardIds: Array) { + this.showManageAssignedCustomersDialog($event, dashboardIds, 'unassign'); + } + + showManageAssignedCustomersDialog($event: Event, dashboardIds: Array, + actionType: ManageDashboardCustomersActionType, + assignedCustomersIds?: Array) { + if ($event) { + $event.stopPropagation(); + } + this.dialog.open(ManageDashboardCustomersDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + dashboardIds, + actionType, + assignedCustomersIds + } + }).afterClosed() + .subscribe((res) => { + if (res) { + this.config.table.updateData(); + } + }); + } + + unassignFromCustomer($event: Event, dashboard: DashboardInfo, customerId: string) { + if ($event) { + $event.stopPropagation(); + } + this.dialogService.confirm( + this.translate.instant('dashboard.unassign-dashboard-title', {dashboardTitle: dashboard.title}), + this.translate.instant('dashboard.unassign-dashboard-text'), + this.translate.instant('action.no'), + this.translate.instant('action.yes'), + true + ).subscribe((res) => { + if (res) { + this.dashboardService.unassignDashboardFromCustomer(customerId, dashboard.id.id).subscribe( + () => { + this.config.table.updateData(); + } + ); + } + } + ); + } + + unassignDashboardsFromCustomer($event: Event, dashboardIds: Array, customerId: string) { + if ($event) { + $event.stopPropagation(); + } + this.dialogService.confirm( + this.translate.instant('dashboard.unassign-dashboards-title', {count: dashboardIds.length}), + this.translate.instant('dashboard.unassign-dashboards-text'), + this.translate.instant('action.no'), + this.translate.instant('action.yes'), + true + ).subscribe((res) => { + if (res) { + const tasks: Observable[] = []; + dashboardIds.forEach( + (dashboardId) => { + tasks.push(this.dashboardService.unassignDashboardFromCustomer(customerId, dashboardId)); + } + ); + forkJoin(tasks).subscribe( + () => { + this.config.table.updateData(); + } + ); + } + } + ); + } + + onDashboardAction(action: EntityAction): boolean { + switch (action.action) { + case 'open': + this.openDashboard(action.event, action.entity); + return true; + case 'export': + this.exportDashboard(action.event, action.entity); + return true; + case 'makePublic': + this.makePublic(action.event, action.entity); + return true; + case 'makePrivate': + this.makePrivate(action.event, action.entity); + return true; + case 'manageAssignedCustomers': + this.manageAssignedCustomers(action.event, action.entity); + return true; + case 'unassignFromCustomer': + this.unassignFromCustomer(action.event, action.entity, this.config.componentsData.customerId); + return true; + } + return false; + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/manage-dashboard-customers-dialog.component.html b/ui-ngx/src/app/modules/home/pages/dashboard/manage-dashboard-customers-dialog.component.html new file mode 100644 index 0000000000..6f3cbb9171 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/manage-dashboard-customers-dialog.component.html @@ -0,0 +1,58 @@ + +
+ +

{{ titleText | translate }}

+ + +
+ + +
+
+
+ {{ labelText | translate }} + + +
+
+
+ + + +
+
diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/manage-dashboard-customers-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/manage-dashboard-customers-dialog.component.ts new file mode 100644 index 0000000000..402f20a958 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/manage-dashboard-customers-dialog.component.ts @@ -0,0 +1,129 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import {Component, Inject, OnInit, SkipSelf} from '@angular/core'; +import {ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef} from '@angular/material'; +import {PageComponent} from '@shared/components/page.component'; +import {Store} from '@ngrx/store'; +import {AppState} from '@core/core.state'; +import {FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm} from '@angular/forms'; +import {EntityType} from '@shared/models/entity-type.models'; +import {DashboardService} from '@core/http/dashboard.service'; +import {forkJoin, Observable} from 'rxjs'; + +export type ManageDashboardCustomersActionType = 'assign' | 'manage' | 'unassign'; + +export interface ManageDashboardCustomersDialogData { + actionType: ManageDashboardCustomersActionType; + dashboardIds: Array; + assignedCustomersIds?: Array; +} + +@Component({ + selector: 'tb-manage-dashboard-customers-dialog', + templateUrl: './manage-dashboard-customers-dialog.component.html', + providers: [{provide: ErrorStateMatcher, useExisting: ManageDashboardCustomersDialogComponent}], + styleUrls: [] +}) +export class ManageDashboardCustomersDialogComponent extends PageComponent implements OnInit, ErrorStateMatcher { + + dashboardCustomersFormGroup: FormGroup; + + submitted = false; + + entityType = EntityType; + + titleText: string; + labelText: string; + actionName: string; + + assignedCustomersIds: string[]; + + constructor(protected store: Store, + @Inject(MAT_DIALOG_DATA) public data: ManageDashboardCustomersDialogData, + private dashboardService: DashboardService, + @SkipSelf() private errorStateMatcher: ErrorStateMatcher, + public dialogRef: MatDialogRef, + public fb: FormBuilder) { + super(store); + + this.assignedCustomersIds = data.assignedCustomersIds || []; + switch (data.actionType) { + case 'assign': + this.titleText = 'dashboard.assign-to-customers'; + this.labelText = 'dashboard.assign-to-customers-text'; + this.actionName = 'action.assign'; + break; + case 'manage': + this.titleText = 'dashboard.manage-assigned-customers'; + this.labelText = 'dashboard.assigned-customers'; + this.actionName = 'action.update'; + break; + case 'unassign': + this.titleText = 'dashboard.unassign-from-customers'; + this.labelText = 'dashboard.unassign-from-customers-text'; + this.actionName = 'action.unassign'; + break; + } + } + + ngOnInit(): void { + this.dashboardCustomersFormGroup = this.fb.group({ + assignedCustomerIds: [[...this.assignedCustomersIds]] + }); + } + + isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { + const originalErrorState = this.errorStateMatcher.isErrorState(control, form); + const customErrorState = !!(control && control.invalid && this.submitted); + return originalErrorState || customErrorState; + } + + cancel(): void { + this.dialogRef.close(false); + } + + submit(): void { + this.submitted = true; + const customerIds: Array = this.dashboardCustomersFormGroup.get('assignedCustomerIds').value; + const tasks: Observable[] = []; + + this.data.dashboardIds.forEach( + (dashboardId) => { + tasks.push(this.getManageDashboardCustomersTask(dashboardId, customerIds)); + } + ); + forkJoin(tasks).subscribe( + () => { + this.dialogRef.close(true); + } + ); + } + + private getManageDashboardCustomersTask(dashboardId: string, customerIds: Array): Observable { + switch (this.data.actionType) { + case 'assign': + return this.dashboardService.addDashboardCustomers(dashboardId, customerIds); + break; + case 'manage': + return this.dashboardService.updateDashboardCustomers(dashboardId, customerIds); + break; + case 'unassign': + return this.dashboardService.removeDashboardCustomers(dashboardId, customerIds); + break; + } + } +} 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 ad1bb3a820..4c22a3b440 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 @@ -96,6 +96,7 @@ export class DevicesTableConfigResolver implements Resolve this.onDeviceAction(action); + this.config.detailsReadonly = () => this.config.componentsData.deviceScope === 'customer_user'; this.config.headerComponent = DeviceTableHeaderComponent; diff --git a/ui-ngx/src/app/modules/home/pages/entity-view/entity-view.component.html b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view.component.html index af0ce120a6..de884e1b9a 100644 --- a/ui-ngx/src/app/modules/home/pages/entity-view/entity-view.component.html +++ b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view.component.html @@ -85,7 +85,6 @@ formControlName="entityId"> -
diff --git a/ui-ngx/src/app/modules/home/pages/entity-view/entity-views-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/entity-view/entity-views-table-config.resolver.ts index fcaa8aa4c5..60a2421c7b 100644 --- a/ui-ngx/src/app/modules/home/pages/entity-view/entity-views-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/entity-view/entity-views-table-config.resolver.ts @@ -96,6 +96,7 @@ export class EntityViewsTableConfigResolver implements Resolve this.onEntityViewAction(action); + this.config.detailsReadonly = () => this.config.componentsData.entityViewScope === 'customer_user'; this.config.headerComponent = EntityViewTableHeaderComponent; 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 7ff956ddc1..ee66f2bb76 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 @@ -26,6 +26,9 @@ import { UserModule } from '@modules/home/pages/user/user.module'; import {DeviceModule} from '@modules/home/pages/device/device.module'; import {AssetModule} from '@modules/home/pages/asset/asset.module'; import {EntityViewModule} from '@modules/home/pages/entity-view/entity-view.module'; +import {RuleChainModule} from '@modules/home/pages/rulechain/rulechain.module'; +import {WidgetLibraryModule} from '@modules/home/pages/widget/widget-library.module'; +import {DashboardModule} from '@modules/home/pages/dashboard/dashboard.module'; @NgModule({ exports: [ @@ -37,6 +40,9 @@ import {EntityViewModule} from '@modules/home/pages/entity-view/entity-view.modu AssetModule, EntityViewModule, CustomerModule, + RuleChainModule, + WidgetLibraryModule, + DashboardModule, // AuditLogModule, UserModule ] diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts new file mode 100644 index 0000000000..207c1b6225 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts @@ -0,0 +1,56 @@ +/// +/// Copyright © 2016-2019 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 {EntitiesTableComponent} from '@shared/components/entity/entities-table.component'; +import {Authority} from '@shared/models/authority.enum'; +import {RuleChainsTableConfigResolver} from '@modules/home/pages/rulechain/rulechains-table-config.resolver'; + +const routes: Routes = [ + { + path: 'ruleChains', + data: { + breadcrumb: { + label: 'rulechain.rulechains', + icon: 'settings_ethernet' + } + }, + children: [ + { + path: '', + component: EntitiesTableComponent, + data: { + auth: [Authority.TENANT_ADMIN], + title: 'rulechain.rulechains' + }, + resolve: { + entitiesTableConfig: RuleChainsTableConfigResolver + } + } + ] + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], + providers: [ + RuleChainsTableConfigResolver + ] +}) +export class RuleChainRoutingModule { } diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.component.html b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.component.html new file mode 100644 index 0000000000..7a2f485261 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.component.html @@ -0,0 +1,75 @@ + +
+ + + + +
+ +
+
+
+
+
+ + rulechain.name + + + {{ 'rulechain.name-required' | translate }} + + + + {{ 'rulechain.debug-mode' | translate }} + +
+ + rulechain.description + + +
+
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.component.scss b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.component.scss new file mode 100644 index 0000000000..d18a4874d0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.component.scss @@ -0,0 +1,19 @@ +/** + * Copyright © 2016-2019 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 { + +} diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.component.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.component.ts new file mode 100644 index 0000000000..f055099a87 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.component.ts @@ -0,0 +1,81 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import {Component} from '@angular/core'; +import {Store} from '@ngrx/store'; +import {AppState} from '@core/core.state'; +import {EntityComponent} from '@shared/components/entity/entity.component'; +import {FormBuilder, FormGroup, Validators} from '@angular/forms'; +import {EntityType} from '@shared/models/entity-type.models'; +import {NULL_UUID} from '@shared/models/id/has-uuid'; +import {ActionNotificationShow} from '@core/notification/notification.actions'; +import {TranslateService} from '@ngx-translate/core'; +import {AssetInfo} from '@app/shared/models/asset.models'; +import {RuleChain} from "@shared/models/rule-chain.models"; + +@Component({ + selector: 'tb-rulechain', + templateUrl: './rulechain.component.html', + styleUrls: ['./rulechain.component.scss'] +}) +export class RuleChainComponent extends EntityComponent { + + constructor(protected store: Store, + protected translate: TranslateService, + public fb: FormBuilder) { + super(store); + } + + hideDelete() { + if (this.entitiesTableConfig) { + return !this.entitiesTableConfig.deleteEnabled(this.entity); + } else { + return false; + } + } + + buildForm(entity: RuleChain): FormGroup { + return this.fb.group( + { + name: [entity ? entity.name : '', [Validators.required]], + debugMode: [entity ? entity.debugMode : false], + additionalInfo: this.fb.group( + { + description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''], + } + ) + } + ); + } + + updateForm(entity: RuleChain) { + this.entityForm.patchValue({name: entity.name}); + this.entityForm.patchValue({debugMode: entity.debugMode}); + this.entityForm.patchValue({additionalInfo: {description: entity.additionalInfo ? entity.additionalInfo.description : ''}}); + } + + + onRuleChainIdCopied($event) { + this.store.dispatch(new ActionNotificationShow( + { + message: this.translate.instant('rulechain.idCopiedMessage'), + type: 'success', + duration: 750, + verticalPosition: 'bottom', + horizontalPosition: 'right' + })); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.module.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.module.ts new file mode 100644 index 0000000000..75b72cb789 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.module.ts @@ -0,0 +1,36 @@ +/// +/// Copyright © 2016-2019 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 {CommonModule} from '@angular/common'; +import {SharedModule} from '@shared/shared.module'; +import {RuleChainComponent} from '@modules/home/pages/rulechain/rulechain.component'; +import {RuleChainRoutingModule} from '@modules/home/pages/rulechain/rulechain-routing.module'; + +@NgModule({ + entryComponents: [ + RuleChainComponent + ], + declarations: [ + RuleChainComponent + ], + imports: [ + CommonModule, + SharedModule, + RuleChainRoutingModule + ] +}) +export class RuleChainModule { } diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts new file mode 100644 index 0000000000..9eea7a6780 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts @@ -0,0 +1,176 @@ +/// +/// Copyright © 2016-2019 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 {Injectable} from '@angular/core'; + +import {Resolve, Router} from '@angular/router'; +import { + checkBoxCell, + DateEntityTableColumn, + EntityTableColumn, + EntityTableConfig +} from '@shared/components/entity/entities-table-config.models'; +import {TranslateService} from '@ngx-translate/core'; +import {DatePipe} from '@angular/common'; +import {EntityType, entityTypeResources, entityTypeTranslations} from '@shared/models/entity-type.models'; +import {EntityAction} from '@shared/components/entity/entity-component.models'; +import {RuleChain} from '@shared/models/rule-chain.models'; +import {RuleChainService} from '@core/http/rule-chain.service'; +import {RuleChainComponent} from '@modules/home/pages/rulechain/rulechain.component'; +import {DialogService} from '@core/services/dialog.service'; + +@Injectable() +export class RuleChainsTableConfigResolver implements Resolve> { + + private readonly config: EntityTableConfig = new EntityTableConfig(); + + constructor(private ruleChainService: RuleChainService, + private dialogService: DialogService, + private translate: TranslateService, + private datePipe: DatePipe, + private router: Router) { + + this.config.entityType = EntityType.RULE_CHAIN; + this.config.entityComponent = RuleChainComponent; + this.config.entityTranslations = entityTypeTranslations.get(EntityType.RULE_CHAIN); + this.config.entityResources = entityTypeResources.get(EntityType.RULE_CHAIN); + + this.config.columns.push( + new DateEntityTableColumn('createdTime', 'rulechain.created-time', this.datePipe, '150px'), + new EntityTableColumn('name', 'rulechain.name'), + new EntityTableColumn('root', 'rulechain.root', '60px', + entity => { + return checkBoxCell(entity.root); + }), + ); + + this.config.addActionDescriptors.push( + { + name: this.translate.instant('rulechain.create-new-rulechain'), + icon: 'insert_drive_file', + isEnabled: () => true, + onAction: ($event) => this.config.table.addEntity($event) + }, + { + name: this.translate.instant('rulechain.import'), + icon: 'file_upload', + isEnabled: () => true, + onAction: ($event) => this.importRuleChain($event) + } + ); + + this.config.cellActionDescriptors.push( + { + name: this.translate.instant('rulechain.open-rulechain'), + icon: 'settings_ethernet', + isEnabled: () => true, + onAction: ($event, entity) => this.openRuleChain($event, entity) + }, + { + name: this.translate.instant('rulechain.export'), + icon: 'file_download', + isEnabled: () => true, + onAction: ($event, entity) => this.exportRuleChain($event, entity) + }, + { + name: this.translate.instant('rulechain.set-root'), + icon: 'flag', + isEnabled: (ruleChain) => !ruleChain.root, + onAction: ($event, entity) => this.setRootRuleChain($event, entity) + } + ); + + this.config.deleteEntityTitle = ruleChain => this.translate.instant('rulechain.delete-rulechain-title', + { ruleChainName: ruleChain.name }); + this.config.deleteEntityContent = () => this.translate.instant('rulechain.delete-rulechain-text'); + this.config.deleteEntitiesTitle = count => this.translate.instant('rulechain.delete-rulechains-title', {count}); + this.config.deleteEntitiesContent = () => this.translate.instant('rulechain.delete-rulechains-text'); + + this.config.entitiesFetchFunction = pageLink => this.ruleChainService.getRuleChains(pageLink); + this.config.loadEntity = id => this.ruleChainService.getRuleChain(id.id); + this.config.saveEntity = ruleChain => this.ruleChainService.saveRuleChain(ruleChain); + this.config.deleteEntity = id => this.ruleChainService.deleteRuleChain(id.id); + this.config.onEntityAction = action => this.onRuleChainAction(action); + this.config.deleteEnabled = (ruleChain) => ruleChain && !ruleChain.root; + this.config.entitySelectionEnabled = (ruleChain) => ruleChain && !ruleChain.root; + } + + resolve(): EntityTableConfig { + this.config.tableTitle = this.translate.instant('rulechain.rulechains'); + + return this.config; + } + + importRuleChain($event: Event) { + if ($event) { + $event.stopPropagation(); + } + // TODO: + } + + openRuleChain($event: Event, ruleChain: RuleChain) { + if ($event) { + $event.stopPropagation(); + } + // TODO: + // this.router.navigateByUrl(`customers/${customer.id.id}/users`); + } + + exportRuleChain($event: Event, ruleChain: RuleChain) { + if ($event) { + $event.stopPropagation(); + } + // TODO: + } + + setRootRuleChain($event: Event, ruleChain: RuleChain) { + if ($event) { + $event.stopPropagation(); + } + this.dialogService.confirm( + this.translate.instant('rulechain.set-root-rulechain-title', {ruleChainName: ruleChain.name}), + this.translate.instant('rulechain.set-root-rulechain-text'), + this.translate.instant('action.no'), + this.translate.instant('action.yes'), + true + ).subscribe((res) => { + if (res) { + this.ruleChainService.setRootRuleChain(ruleChain.id.id).subscribe( + () => { + this.config.table.updateData(); + } + ); + } + } + ); + } + + onRuleChainAction(action: EntityAction): boolean { + switch (action.action) { + case 'open': + this.openRuleChain(action.event, action.entity); + return true; + case 'export': + this.exportRuleChain(action.event, action.entity); + return true; + case 'setRoot': + this.setRootRuleChain(action.event, action.entity); + return true; + } + return false; + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts b/ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts new file mode 100644 index 0000000000..787ea3d420 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts @@ -0,0 +1,57 @@ +/// +/// Copyright © 2016-2019 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 {EntitiesTableComponent} from '@shared/components/entity/entities-table.component'; +import {Authority} from '@shared/models/authority.enum'; +import {RuleChainsTableConfigResolver} from '@modules/home/pages/rulechain/rulechains-table-config.resolver'; +import {WidgetsBundlesTableConfigResolver} from '@modules/home/pages/widget/widgets-bundles-table-config.resolver'; + +const routes: Routes = [ + { + path: 'widgets-bundles', + data: { + breadcrumb: { + label: 'widgets-bundle.widgets-bundles', + icon: 'now_widgets' + } + }, + children: [ + { + path: '', + component: EntitiesTableComponent, + data: { + auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN], + title: 'widgets-bundle.widgets-bundles' + }, + resolve: { + entitiesTableConfig: WidgetsBundlesTableConfigResolver + } + } + ] + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], + providers: [ + WidgetsBundlesTableConfigResolver + ] +}) +export class WidgetLibraryRoutingModule { } diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-library.module.ts b/ui-ngx/src/app/modules/home/pages/widget/widget-library.module.ts new file mode 100644 index 0000000000..727f985738 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-library.module.ts @@ -0,0 +1,36 @@ +/// +/// Copyright © 2016-2019 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 {CommonModule} from '@angular/common'; +import {SharedModule} from '@shared/shared.module'; +import {WidgetsBundleComponent} from '@modules/home/pages/widget/widgets-bundle.component'; +import {WidgetLibraryRoutingModule} from '@modules/home/pages/widget/widget-library-routing.module'; + +@NgModule({ + entryComponents: [ + WidgetsBundleComponent + ], + declarations: [ + WidgetsBundleComponent + ], + imports: [ + CommonModule, + SharedModule, + WidgetLibraryRoutingModule + ] +}) +export class WidgetLibraryModule { } diff --git a/ui-ngx/src/app/modules/home/pages/widget/widgets-bundle.component.html b/ui-ngx/src/app/modules/home/pages/widget/widgets-bundle.component.html new file mode 100644 index 0000000000..1af44ff331 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/widget/widgets-bundle.component.html @@ -0,0 +1,44 @@ + +
+ + +
+
+
+
+ + widgets-bundle.title + + + {{ 'widgets-bundle.title-required' | translate }} + + +
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/widget/widgets-bundle.component.scss b/ui-ngx/src/app/modules/home/pages/widget/widgets-bundle.component.scss new file mode 100644 index 0000000000..d18a4874d0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/widget/widgets-bundle.component.scss @@ -0,0 +1,19 @@ +/** + * Copyright © 2016-2019 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 { + +} diff --git a/ui-ngx/src/app/modules/home/pages/widget/widgets-bundle.component.ts b/ui-ngx/src/app/modules/home/pages/widget/widgets-bundle.component.ts new file mode 100644 index 0000000000..16f908c1b0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/widget/widgets-bundle.component.ts @@ -0,0 +1,55 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import {Component} from '@angular/core'; +import {Store} from '@ngrx/store'; +import {AppState} from '@core/core.state'; +import {EntityComponent} from '@shared/components/entity/entity.component'; +import {FormBuilder, FormGroup, Validators} from '@angular/forms'; +import {WidgetsBundle} from '@shared/models/widgets-bundle.model'; + +@Component({ + selector: 'tb-widgets-bundle', + templateUrl: './widgets-bundle.component.html', + styleUrls: ['./widgets-bundle.component.scss'] +}) +export class WidgetsBundleComponent extends EntityComponent { + + constructor(protected store: Store, + public fb: FormBuilder) { + super(store); + } + + hideDelete() { + if (this.entitiesTableConfig) { + return !this.entitiesTableConfig.deleteEnabled(this.entity); + } else { + return false; + } + } + + buildForm(entity: WidgetsBundle): FormGroup { + return this.fb.group( + { + title: [entity ? entity.title : '', [Validators.required]] + } + ); + } + + updateForm(entity: WidgetsBundle) { + this.entityForm.patchValue({title: entity.title}); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/widget/widgets-bundles-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/widget/widgets-bundles-table-config.resolver.ts new file mode 100644 index 0000000000..aea0fedff3 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/widget/widgets-bundles-table-config.resolver.ts @@ -0,0 +1,158 @@ +/// +/// Copyright © 2016-2019 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 {Injectable} from '@angular/core'; + +import {Resolve, Router} from '@angular/router'; +import { + checkBoxCell, + DateEntityTableColumn, + EntityTableColumn, + EntityTableConfig +} from '@shared/components/entity/entities-table-config.models'; +import {TranslateService} from '@ngx-translate/core'; +import {DatePipe} from '@angular/common'; +import {EntityType, entityTypeResources, entityTypeTranslations} from '@shared/models/entity-type.models'; +import {EntityAction} from '@shared/components/entity/entity-component.models'; +import {WidgetsBundle} from '@shared/models/widgets-bundle.model'; +import {WidgetService} from '@app/core/http/widget.service'; +import {WidgetsBundleComponent} from '@modules/home/pages/widget/widgets-bundle.component'; +import {NULL_UUID} from '@shared/models/id/has-uuid'; +import {Store} from '@ngrx/store'; +import {AppState} from '@core/core.state'; +import {getCurrentAuthUser} from '@app/core/auth/auth.selectors'; +import {Authority} from '@shared/models/authority.enum'; + +@Injectable() +export class WidgetsBundlesTableConfigResolver implements Resolve> { + + private readonly config: EntityTableConfig = new EntityTableConfig(); + + constructor(private store: Store, + private widgetsService: WidgetService, + private translate: TranslateService, + private datePipe: DatePipe, + private router: Router) { + + this.config.entityType = EntityType.WIDGETS_BUNDLE; + this.config.entityComponent = WidgetsBundleComponent; + this.config.entityTranslations = entityTypeTranslations.get(EntityType.WIDGETS_BUNDLE); + this.config.entityResources = entityTypeResources.get(EntityType.WIDGETS_BUNDLE); + + this.config.columns.push( + new DateEntityTableColumn('createdTime', 'widgets-bundle.created-time', this.datePipe, '150px'), + new EntityTableColumn('title', 'widgets-bundle.title'), + new EntityTableColumn('tenantId', 'widgets-bundle.system', '60px', + entity => { + return checkBoxCell(entity.tenantId.id === NULL_UUID); + }), + ); + + this.config.addActionDescriptors.push( + { + name: this.translate.instant('widgets-bundle.create-new-widgets-bundle'), + icon: 'insert_drive_file', + isEnabled: () => true, + onAction: ($event) => this.config.table.addEntity($event) + }, + { + name: this.translate.instant('widgets-bundle.import'), + icon: 'file_upload', + isEnabled: () => true, + onAction: ($event) => this.importWidgetsBundle($event) + } + ); + + this.config.cellActionDescriptors.push( + { + name: this.translate.instant('widgets-bundle.open-widgets-bundle'), + icon: 'now_widgets', + isEnabled: () => true, + onAction: ($event, entity) => this.openWidgetsBundle($event, entity) + }, + { + name: this.translate.instant('widgets-bundle.export'), + icon: 'file_download', + isEnabled: () => true, + onAction: ($event, entity) => this.exportWidgetsBundle($event, entity) + } + ); + + this.config.deleteEntityTitle = widgetsBundle => this.translate.instant('widgets-bundle.delete-widgets-bundle-title', + { widgetsBundleTitle: widgetsBundle.title }); + this.config.deleteEntityContent = () => this.translate.instant('widgets-bundle.delete-widgets-bundle-text'); + this.config.deleteEntitiesTitle = count => this.translate.instant('widgets-bundle.delete-widgets-bundles-title', {count}); + this.config.deleteEntitiesContent = () => this.translate.instant('widgets-bundle.delete-widgets-bundles-text'); + + this.config.entitiesFetchFunction = pageLink => this.widgetsService.getWidgetBundles(pageLink); + this.config.loadEntity = id => this.widgetsService.getWidgetsBundle(id.id); + this.config.saveEntity = widgetsBundle => this.widgetsService.saveWidgetsBundle(widgetsBundle); + this.config.deleteEntity = id => this.widgetsService.deleteWidgetsBundle(id.id); + this.config.onEntityAction = action => this.onWidgetsBundleAction(action); + } + + resolve(): EntityTableConfig { + this.config.tableTitle = this.translate.instant('widgets-bundle.widgets-bundles'); + const authUser = getCurrentAuthUser(this.store); + this.config.deleteEnabled = (widgetsBundle) => this.isWidgetsBundleEditable(widgetsBundle, authUser.authority); + this.config.entitySelectionEnabled = (widgetsBundle) => this.isWidgetsBundleEditable(widgetsBundle, authUser.authority); + this.config.detailsReadonly = (widgetsBundle) => !this.isWidgetsBundleEditable(widgetsBundle, authUser.authority); + return this.config; + } + + isWidgetsBundleEditable(widgetsBundle: WidgetsBundle, authority: Authority): boolean { + if (authority === Authority.TENANT_ADMIN) { + return widgetsBundle && widgetsBundle.tenantId && widgetsBundle.tenantId.id !== NULL_UUID; + } else { + return authority === Authority.SYS_ADMIN; + } + } + + importWidgetsBundle($event: Event) { + if ($event) { + $event.stopPropagation(); + } + // TODO: + } + + openWidgetsBundle($event: Event, widgetsBundle: WidgetsBundle) { + if ($event) { + $event.stopPropagation(); + } + // TODO: + // this.router.navigateByUrl(`customers/${customer.id.id}/users`); + } + + exportWidgetsBundle($event: Event, widgetsBundle: WidgetsBundle) { + if ($event) { + $event.stopPropagation(); + } + // TODO: + } + + onWidgetsBundleAction(action: EntityAction): boolean { + switch (action.action) { + case 'open': + this.openWidgetsBundle(action.event, action.entity); + return true; + case 'export': + this.exportWidgetsBundle(action.event, action.entity); + return true; + } + return false; + } + +} diff --git a/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.html b/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.html index d7b21e99d3..0884a92e53 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.html +++ b/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.html @@ -19,6 +19,7 @@ + + + +
diff --git a/ui-ngx/src/app/shared/components/socialshare-panel.component.ts b/ui-ngx/src/app/shared/components/socialshare-panel.component.ts new file mode 100644 index 0000000000..55de4477cd --- /dev/null +++ b/ui-ngx/src/app/shared/components/socialshare-panel.component.ts @@ -0,0 +1,61 @@ +/// +/// Copyright © 2016-2019 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, Input, OnDestroy, OnInit } from '@angular/core'; +import { User } from '@shared/models/user.model'; +import { Authority } from '@shared/models/authority.enum'; +import { select, Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { selectAuthUser, selectUserDetails } from '@core/auth/auth.selectors'; +import { map } from 'rxjs/operators'; +import { AuthService } from '@core/auth/auth.service'; +import { Router } from '@angular/router'; +import {isLocalUrl} from '@core/utils'; + +@Component({ + selector: 'tb-social-share-panel', + templateUrl: './socialshare-panel.component.html', + styleUrls: [] +}) +export class SocialSharePanelComponent implements OnInit { + + @Input() + shareTitle: string; + + @Input() + shareText: string; + + @Input() + shareLink: string; + + @Input() + shareHashTags: string; + + constructor() { + } + + ngOnInit(): void { + } + + isShareLinkLocal(): boolean { + if (this.shareLink && this.shareLink.length > 0) { + return isLocalUrl(this.shareLink); + } else { + return true; + } + } + +} diff --git a/ui-ngx/src/app/shared/models/constants.ts b/ui-ngx/src/app/shared/models/constants.ts index c6976ff660..b3538306d7 100644 --- a/ui-ngx/src/app/shared/models/constants.ts +++ b/ui-ngx/src/app/shared/models/constants.ts @@ -62,7 +62,10 @@ export const HelpLinks = { users: helpBaseUrl + '/docs/user-guide/ui/users', devices: helpBaseUrl + '/docs/user-guide/ui/devices', assets: helpBaseUrl + '/docs/user-guide/ui/assets', - entityViews: helpBaseUrl + '/docs/user-guide/ui/entity-views' + entityViews: helpBaseUrl + '/docs/user-guide/ui/entity-views', + rulechains: helpBaseUrl + '/docs/user-guide/ui/rule-chains', + dashboards: helpBaseUrl + '/docs/user-guide/ui/dashboards', + widgetsBundles: helpBaseUrl + '/docs/user-guide/ui/widget-library#bundles' } }; diff --git a/ui-ngx/src/app/shared/models/customer.model.ts b/ui-ngx/src/app/shared/models/customer.model.ts index adfa684fe0..502abad934 100644 --- a/ui-ngx/src/app/shared/models/customer.model.ts +++ b/ui-ngx/src/app/shared/models/customer.model.ts @@ -27,5 +27,5 @@ export interface Customer extends ContactBased { export interface ShortCustomerInfo { customerId: CustomerId; title: string; - isPublic: boolean; + public: boolean; } diff --git a/ui-ngx/src/app/shared/models/dashboard.models.ts b/ui-ngx/src/app/shared/models/dashboard.models.ts index ce99407541..6204818ca4 100644 --- a/ui-ngx/src/app/shared/models/dashboard.models.ts +++ b/ui-ngx/src/app/shared/models/dashboard.models.ts @@ -26,10 +26,40 @@ export interface DashboardInfo extends BaseData { } export interface DashboardConfiguration { - widgets: Array; + [key: string]: any; // TODO: } export interface Dashboard extends DashboardInfo { configuration: DashboardConfiguration; } + +export function isPublicDashboard(dashboard: DashboardInfo): boolean { + if (dashboard && dashboard.assignedCustomers) { + return dashboard.assignedCustomers + .filter(customerInfo => customerInfo.public).length > 0; + } else { + return false; + } +} + +export function getDashboardAssignedCustomersText(dashboard: DashboardInfo): string { + if (dashboard && dashboard.assignedCustomers && dashboard.assignedCustomers.length > 0) { + return dashboard.assignedCustomers + .filter(customerInfo => !customerInfo.public) + .map(customerInfo => customerInfo.title) + .join(', '); + } else { + return null; + } +} + +export function isCurrentPublicDashboardCustomer(dashboard: DashboardInfo, customerId: string): boolean { + if (customerId && dashboard && dashboard.assignedCustomers) { + return dashboard.assignedCustomers.filter(customerInfo => { + return customerInfo.public && customerId === customerInfo.customerId.id; + }).length > 0; + } else { + return false; + } +} diff --git a/ui-ngx/src/app/shared/models/entity-type.models.ts b/ui-ngx/src/app/shared/models/entity-type.models.ts index 70588c2e8b..1d8b72cb0f 100644 --- a/ui-ngx/src/app/shared/models/entity-type.models.ts +++ b/ui-ngx/src/app/shared/models/entity-type.models.ts @@ -36,9 +36,9 @@ export enum AliasEntityType { } export interface EntityTypeTranslation { - type: string; + type?: string; typePlural?: string; - list: string; + list?: string; nameStartsWith?: string; details?: string; add?: string; @@ -137,6 +137,44 @@ export const entityTypeTranslations = new Map( { helpLinkId: 'entityViews' } + ], + [ + EntityType.RULE_CHAIN, + { + helpLinkId: 'rulechains' + } + ], + [ + EntityType.DASHBOARD, + { + helpLinkId: 'dashboards' + } + ], + [ + EntityType.WIDGETS_BUNDLE, + { + helpLinkId: 'widgetsBundles' + } ] ] ); diff --git a/ui-ngx/src/app/shared/models/rule-chain.models.ts b/ui-ngx/src/app/shared/models/rule-chain.models.ts index 52d396d888..100214a8cc 100644 --- a/ui-ngx/src/app/shared/models/rule-chain.models.ts +++ b/ui-ngx/src/app/shared/models/rule-chain.models.ts @@ -15,23 +15,16 @@ /// import {BaseData} from '@shared/models/base-data'; -import {AssetId} from '@shared/models/id/asset-id'; import {TenantId} from '@shared/models/id/tenant-id'; -import {CustomerId} from '@shared/models/id/customer-id'; import {RuleChainId} from '@shared/models/id/rule-chain-id'; import {RuleNodeId} from '@shared/models/id/rule-node-id'; -export interface RuleChainConfiguration { - todo: Array; - // TODO: -} - export interface RuleChain extends BaseData { tenantId: TenantId; name: string; firstRuleNodeId: RuleNodeId; root: boolean; debugMode: boolean; - configuration: RuleChainConfiguration; + configuration?: any; additionalInfo?: any; } diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index f1ea7825ce..feab000ddc 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -55,6 +55,7 @@ import { MatDatetimepickerModule, MatNativeDatetimeModule } from '@mat-datetimep import { FlexLayoutModule } from '@angular/flex-layout'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { RouterModule } from '@angular/router'; +import { ShareModule as ShareButtonsModule } from '@ngx-share/core'; import { UserMenuComponent } from '@shared/components/user-menu.component'; import { NospacePipe } from './pipe/nospace.pipe'; import { TranslateModule } from '@ngx-translate/core'; @@ -88,6 +89,7 @@ import {EntityTypeSelectComponent} from './components/entity/entity-type-select. import {EntitySelectComponent} from './components/entity/entity-select.component'; import {DatetimeComponent} from '@shared/components/time/datetime.component'; import {EntityKeysListComponent} from './components/entity/entity-keys-list.component'; +import {SocialSharePanelComponent} from './components/socialshare-panel.component'; @NgModule({ providers: [ @@ -136,6 +138,7 @@ import {EntityKeysListComponent} from './components/entity/entity-keys-list.comp EntityTypeSelectComponent, EntitySelectComponent, EntityKeysListComponent, + SocialSharePanelComponent, NospacePipe, MillisecondsToTimeStringPipe, EnumToArrayPipe, @@ -179,7 +182,8 @@ import {EntityKeysListComponent} from './components/entity/entity-keys-list.comp FlexLayoutModule.withConfig({addFlexToParent: false}), FormsModule, ReactiveFormsModule, - OverlayModule + OverlayModule, + ShareButtonsModule ], exports: [ FooterComponent, @@ -210,6 +214,7 @@ import {EntityKeysListComponent} from './components/entity/entity-keys-list.comp EntityTypeSelectComponent, EntitySelectComponent, EntityKeysListComponent, + SocialSharePanelComponent, // ValueInputComponent, MatButtonModule, MatCheckboxModule, @@ -246,6 +251,7 @@ import {EntityKeysListComponent} from './components/entity/entity-keys-list.comp FormsModule, ReactiveFormsModule, OverlayModule, + ShareButtonsModule, NospacePipe, MillisecondsToTimeStringPipe, EnumToArrayPipe, 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 53d03ac648..e1f090b863 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -445,6 +445,7 @@ "no-dashboards-text": "No dashboards found", "no-widgets": "No widgets configured", "add-widget": "Add new widget", + "created-time": "Created time", "title": "Title", "select-widget-title": "Select widget", "select-widget-subtitle": "List of available widget types", @@ -562,7 +563,9 @@ "show-details": "Show details", "hide-details": "Hide details", "select-state": "Select target state", - "state-controller": "State controller" + "state-controller": "State controller", + "search": "Search dashboards", + "selected-dashboards": "{ count, plural, 1 {1 dashboard} other {# dashboards} } selected" }, "datakey": { "settings": "Settings", @@ -1282,6 +1285,7 @@ "rulechains": "Rule chains", "root": "Root", "delete": "Delete rule chain", + "created-time": "Created time", "name": "Name", "name-required": "Name is required.", "description": "Description", @@ -1312,7 +1316,10 @@ "no-rulechains-matching": "No rule chains matching '{{entity}}' were found.", "rulechain-required": "Rule chain is required", "management": "Rules management", - "debug-mode": "Debug mode" + "debug-mode": "Debug mode", + "search": "Search rule chains", + "selected-rulechains": "{ count, plural, 1 {1 rule chain} other {# rule chains} } selected", + "open-rulechain": "Open rule chain" }, "rulenode": { "details": "Details", @@ -1565,6 +1572,7 @@ "widgets-bundles": "Widgets Bundles", "add": "Add Widgets Bundle", "delete": "Delete widgets bundle", + "created-time": "Created time", "title": "Title", "title-required": "Title is required.", "add-widgets-bundle-text": "Add new widgets bundle", @@ -1585,7 +1593,10 @@ "export-failed-error": "Unable to export widgets bundle: {{error}}", "create-new-widgets-bundle": "Create new widgets bundle", "widgets-bundle-file": "Widgets bundle file", - "invalid-widgets-bundle-file-error": "Unable to import widgets bundle: Invalid widgets bundle data structure." + "invalid-widgets-bundle-file-error": "Unable to import widgets bundle: Invalid widgets bundle data structure.", + "search": "Search widget bundles", + "selected-widgets-bundles": "{ count, plural, 1 {1 widgets bundle} other {# widgets bundles} } selected", + "open-widgets-bundle": "Open widgets bundle" }, "widget-config": { "data": "Data", From 77eb33df961deeeec691c7b7fee58b7ad2c7a2d9 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 21 Aug 2019 13:36:39 +0300 Subject: [PATCH 017/133] Improvements --- ui-ngx/src/app/core/core.module.ts | 7 +- .../src/app/core/services/dialog.service.ts | 10 +++ .../dialog/todo-dialog.component.html | 24 ++++++ .../dialog/todo-dialog.component.scss | 23 ++++++ .../services/dialog/todo-dialog.component.ts | 31 +++++++ .../asset/assets-table-config.resolver.ts | 1 + .../home/pages/dashboard/dashboard.module.ts | 7 +- .../dashboards-table-config.resolver.ts | 21 ++++- ...ake-dashboard-public-dialog.component.html | 64 +++++++++++++++ .../make-dashboard-public-dialog.component.ts | 77 ++++++++++++++++++ .../device/devices-table-config.resolver.ts | 1 + .../entity-view/entity-view.component.html | 4 + .../rulechains-table-config.resolver.ts | 3 + .../widget/widgets-bundle.component.html | 6 ++ .../widgets-bundles-table-config.resolver.ts | 5 ++ .../dashboard-autocomplete.component.html | 5 +- .../entity/entity-autocomplete.component.html | 4 +- .../entity/entity-keys-list.component.html | 15 ++-- .../entity/entity-keys-list.component.scss | 27 ++++++ .../entity/entity-keys-list.component.ts | 2 +- .../entity/entity-list.component.html | 3 +- ...entity-subtype-autocomplete.component.html | 5 +- .../socialshare-panel.component.scss | 18 ++++ .../components/socialshare-panel.component.ts | 2 +- ui-ngx/src/assets/coming-soon.jpg | Bin 0 -> 283697 bytes ui-ngx/src/styles.scss | 23 ++++++ 26 files changed, 370 insertions(+), 18 deletions(-) create mode 100644 ui-ngx/src/app/core/services/dialog/todo-dialog.component.html create mode 100644 ui-ngx/src/app/core/services/dialog/todo-dialog.component.scss create mode 100644 ui-ngx/src/app/core/services/dialog/todo-dialog.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/dashboard/make-dashboard-public-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/dashboard/make-dashboard-public-dialog.component.ts create mode 100644 ui-ngx/src/app/shared/components/entity/entity-keys-list.component.scss create mode 100644 ui-ngx/src/app/shared/components/socialshare-panel.component.scss create mode 100644 ui-ngx/src/assets/coming-soon.jpg diff --git a/ui-ngx/src/app/core/core.module.ts b/ui-ngx/src/app/core/core.module.ts index d07c7e7383..c47572f4a3 100644 --- a/ui-ngx/src/app/core/core.module.ts +++ b/ui-ngx/src/app/core/core.module.ts @@ -38,6 +38,7 @@ import { FlexLayoutModule } from '@angular/flex-layout'; import { TranslateDefaultCompiler } from '@core/translate/translate-default-compiler'; import { AlertDialogComponent } from '@core/services/dialog/alert-dialog.component'; import { WINDOW_PROVIDERS } from '@core/services/window.service'; +import {TodoDialogComponent} from "@core/services/dialog/todo-dialog.component"; export function HttpLoaderFactory(http: HttpClient) { return new TranslateHttpLoader(http, './assets/locale/locale.constant-', '.json'); @@ -46,11 +47,13 @@ export function HttpLoaderFactory(http: HttpClient) { @NgModule({ entryComponents: [ ConfirmDialogComponent, - AlertDialogComponent + AlertDialogComponent, + TodoDialogComponent ], declarations: [ ConfirmDialogComponent, - AlertDialogComponent + AlertDialogComponent, + TodoDialogComponent ], imports: [ CommonModule, diff --git a/ui-ngx/src/app/core/services/dialog.service.ts b/ui-ngx/src/app/core/services/dialog.service.ts index 955e062dce..ada1baf26c 100644 --- a/ui-ngx/src/app/core/services/dialog.service.ts +++ b/ui-ngx/src/app/core/services/dialog.service.ts @@ -20,6 +20,7 @@ import { MatDialog, MatDialogConfig } from '@angular/material'; import { ConfirmDialogComponent } from '@core/services/dialog/confirm-dialog.component'; import { TranslateService } from '@ngx-translate/core'; import { AlertDialogComponent } from '@core/services/dialog/alert-dialog.component'; +import {TodoDialogComponent} from "@core/services/dialog/todo-dialog.component"; @Injectable( { @@ -67,4 +68,13 @@ export class DialogService { return dialogRef.afterClosed(); } + todo(): Observable { + const dialogConfig: MatDialogConfig = { + disableClose: true, + panelClass: ['tb-fullscreen-dialog'] + }; + const dialogRef = this.dialog.open(TodoDialogComponent, dialogConfig); + return dialogRef.afterClosed(); + } + } diff --git a/ui-ngx/src/app/core/services/dialog/todo-dialog.component.html b/ui-ngx/src/app/core/services/dialog/todo-dialog.component.html new file mode 100644 index 0000000000..67607c875c --- /dev/null +++ b/ui-ngx/src/app/core/services/dialog/todo-dialog.component.html @@ -0,0 +1,24 @@ + +

Coming soon!

+
+ +
+
+ +
diff --git a/ui-ngx/src/app/core/services/dialog/todo-dialog.component.scss b/ui-ngx/src/app/core/services/dialog/todo-dialog.component.scss new file mode 100644 index 0000000000..2dc404f108 --- /dev/null +++ b/ui-ngx/src/app/core/services/dialog/todo-dialog.component.scss @@ -0,0 +1,23 @@ +/** + * Copyright © 2016-2019 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 { + .mat-dialog-content { + padding: 0 24px 24px; + img { + max-width: 500px; + } + } +} diff --git a/ui-ngx/src/app/core/services/dialog/todo-dialog.component.ts b/ui-ngx/src/app/core/services/dialog/todo-dialog.component.ts new file mode 100644 index 0000000000..54e365eb84 --- /dev/null +++ b/ui-ngx/src/app/core/services/dialog/todo-dialog.component.ts @@ -0,0 +1,31 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import {Component} from '@angular/core'; +import {MatDialogRef} from '@angular/material'; + +@Component({ + selector: 'tb-todo-dialog', + templateUrl: './todo-dialog.component.html', + styleUrls: ['./todo-dialog.component.scss'] +}) +export class TodoDialogComponent { + + comingSoon = require('../../../../assets/coming-soon.jpg'); + + constructor(public dialogRef: MatDialogRef) { + } +} diff --git a/ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts index d217bc57c2..016004a493 100644 --- a/ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts @@ -279,6 +279,7 @@ export class AssetsTableConfigResolver implements Resolve> { @@ -299,6 +303,7 @@ export class DashboardsTableConfigResolver implements Resolve { - // TODO: - - this.config.table.updateData(); + this.dialog.open + (MakeDashboardPublicDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + dashboard: publicDashboard + } + }).afterClosed() + .subscribe(() => { + this.config.table.updateData(); + }); } ); } diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/make-dashboard-public-dialog.component.html b/ui-ngx/src/app/modules/home/pages/dashboard/make-dashboard-public-dialog.component.html new file mode 100644 index 0000000000..7d53d8b1e7 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/make-dashboard-public-dialog.component.html @@ -0,0 +1,64 @@ + +
+ +

{{ 'dashboard.public-dashboard-title' | translate }}

+ + +
+ + +
+
+ + +
+
{{ publicLink }}
+ +
+
+ + +
+
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/make-dashboard-public-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/make-dashboard-public-dialog.component.ts new file mode 100644 index 0000000000..26c782ed8c --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/make-dashboard-public-dialog.component.ts @@ -0,0 +1,77 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import {Component, Inject, OnInit, SkipSelf} from '@angular/core'; +import {ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef} from '@angular/material'; +import {PageComponent} from '@shared/components/page.component'; +import {Store} from '@ngrx/store'; +import {AppState} from '@core/core.state'; +import {FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm} from '@angular/forms'; +import {EntityType} from '@shared/models/entity-type.models'; +import {DashboardService} from '@core/http/dashboard.service'; +import {forkJoin, Observable} from 'rxjs'; +import {DashboardInfo} from '@app/shared/models/dashboard.models'; +import {ActionNotificationShow} from '@core/notification/notification.actions'; +import {TranslateService} from '@ngx-translate/core'; + +export interface MakeDashboardPublicDialogData { + dashboard: DashboardInfo; +} + +@Component({ + selector: 'tb-make-dashboard-public-dialog', + templateUrl: './make-dashboard-public-dialog.component.html', + styleUrls: [] +}) +export class MakeDashboardPublicDialogComponent extends PageComponent implements OnInit { + + dashboard: DashboardInfo; + + publicLink: string; + + constructor(protected store: Store, + @Inject(MAT_DIALOG_DATA) public data: MakeDashboardPublicDialogData, + public translate: TranslateService, + private dashboardService: DashboardService, + public dialogRef: MatDialogRef, + public fb: FormBuilder) { + super(store); + + this.dashboard = data.dashboard; + this.publicLink = dashboardService.getPublicDashboardLink(this.dashboard); + } + + ngOnInit(): void { + } + + close(): void { + this.dialogRef.close(); + } + + + onPublicLinkCopied($event) { + this.store.dispatch(new ActionNotificationShow( + { + message: this.translate.instant('dashboard.public-link-copied-message'), + type: 'success', + target: 'makeDashboardPublicDialogContent', + duration: 750, + verticalPosition: 'bottom', + horizontalPosition: 'left' + })); + } + +} 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 4c22a3b440..95e6edc67b 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 @@ -311,6 +311,7 @@ export class DevicesTableConfigResolver implements Resolve
entity-view.attributes-propagation-hint
+ + +
entity-view.timeseries-data-hint
+
+ - + diff --git a/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.html b/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.html index 0884a92e53..cbc06bd824 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.html +++ b/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.html @@ -28,7 +28,9 @@ (click)="clear()"> close - + diff --git a/ui-ngx/src/app/shared/components/entity/entity-keys-list.component.html b/ui-ngx/src/app/shared/components/entity/entity-keys-list.component.html index 264eb89d72..f0f30ba830 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-keys-list.component.html +++ b/ui-ngx/src/app/shared/components/entity/entity-keys-list.component.html @@ -15,27 +15,32 @@ limitations under the License. --> - - + + {{key}} close - - diff --git a/ui-ngx/src/app/shared/components/entity/entity-keys-list.component.scss b/ui-ngx/src/app/shared/components/entity/entity-keys-list.component.scss new file mode 100644 index 0000000000..fbe3187168 --- /dev/null +++ b/ui-ngx/src/app/shared/components/entity/entity-keys-list.component.scss @@ -0,0 +1,27 @@ +/** + * Copyright © 2016-2019 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 { + +} + +:host ::ng-deep { + .mat-form-field-flex { + padding-top: 0; + .mat-form-field-infix { + border-top: 0; + } + } +} diff --git a/ui-ngx/src/app/shared/components/entity/entity-keys-list.component.ts b/ui-ngx/src/app/shared/components/entity/entity-keys-list.component.ts index 4ffec6d989..a9aca0725a 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-keys-list.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-keys-list.component.ts @@ -41,7 +41,7 @@ import * as equal from 'deep-equal'; @Component({ selector: 'tb-entity-keys-list', templateUrl: './entity-keys-list.component.html', - styleUrls: [], + styleUrls: ['./entity-keys-list.component.scss'], providers: [ { provide: NG_VALUE_ACCESSOR, diff --git a/ui-ngx/src/app/shared/components/entity/entity-list.component.html b/ui-ngx/src/app/shared/components/entity/entity-list.component.html index cb9ed2a5c9..435427716e 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-list.component.html +++ b/ui-ngx/src/app/shared/components/entity/entity-list.component.html @@ -25,7 +25,7 @@ {{entity.name}} close - diff --git a/ui-ngx/src/app/shared/components/entity/entity-subtype-autocomplete.component.html b/ui-ngx/src/app/shared/components/entity/entity-subtype-autocomplete.component.html index e86e72a1b6..79268979ec 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-subtype-autocomplete.component.html +++ b/ui-ngx/src/app/shared/components/entity/entity-subtype-autocomplete.component.html @@ -29,7 +29,10 @@ (click)="clear()"> close - + diff --git a/ui-ngx/src/app/shared/components/socialshare-panel.component.scss b/ui-ngx/src/app/shared/components/socialshare-panel.component.scss new file mode 100644 index 0000000000..28e2cace12 --- /dev/null +++ b/ui-ngx/src/app/shared/components/socialshare-panel.component.scss @@ -0,0 +1,18 @@ +/** + * Copyright © 2016-2019 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 { + display: block; +} diff --git a/ui-ngx/src/app/shared/components/socialshare-panel.component.ts b/ui-ngx/src/app/shared/components/socialshare-panel.component.ts index 55de4477cd..1cac8b9008 100644 --- a/ui-ngx/src/app/shared/components/socialshare-panel.component.ts +++ b/ui-ngx/src/app/shared/components/socialshare-panel.component.ts @@ -28,7 +28,7 @@ import {isLocalUrl} from '@core/utils'; @Component({ selector: 'tb-social-share-panel', templateUrl: './socialshare-panel.component.html', - styleUrls: [] + styleUrls: ['./socialshare-panel.component.scss'] }) export class SocialSharePanelComponent implements OnInit { diff --git a/ui-ngx/src/assets/coming-soon.jpg b/ui-ngx/src/assets/coming-soon.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3fc8e85352a9635e94286a8db0deef30bff1769e GIT binary patch literal 283697 zcmb4qWl$VE7w+Ox+)8m<99p2b6nEFcBE?x?akm03?(P)#rN|0Sad&5t;tqutDbT*R z-=BMb-zPJfBqzx^^UNfZlbrmw`rmf|iMq0yG5`$?06_bf0RN!?3IGiB|KWc*#y?EpZM<^fD{*f77KuZ#sWYmMZ+LP`)?3H4*&qLG5#6)|A2;$fsKQS zg^Tx3>_-ClpJFsD3~X#%0up>Q%70Q!02Ve5DH$`lfSjH+E(HrEtDwAR1eK7!OqPy^r6gjItlEghj-Z-rHsOFYI$DI}Gk{ zevZm4hpSW#>|U!n`d3u`(@Xb{JS?>TvG{-SKgIv_;{KEBlm4^nUlscwg}8W_=otSx zqLX4^k}(Tl{WA%hoW(ODjY6=rxo-}KRbGGRhmBWcdJ83$&|AY#^Ve*`A_|HIW&Hy$ z*}XGbcY(GG<^Qb%2>y}qZw^wxJHRC+R)t#yC$V9g)=M(&KG{FFTCY_-oW^-t9n3my zO$w#z4lAk`fd>+)(jut&gdYs|1nv%Z`AeLi>BHNVBRpvz@2(|QCfKJx{ZVgzobvcf z=Q8_vnk3#jnv%$qSRW*x{B22$`E(!1=y{g`(`G=wjc6$gTT=1;vR_hg**mN~b&xOV zkE(gC*p3>Hx#Jf@tC^nqqF=9bm9qN*um4cmfzkS&(jCfWrBuNgNZQt*0zn)hiTa(c z7D5s4>gs;hm~9hsk@;-vxOIlILZ-9(o-$ZC|2S9c)#pdf_w&olAn(aH%;9#~apk|b z4rfklk|*2UAbkG;&K)}`s)LM#(s1r|#+=~OxG>v0YJR_Oy#=;OBblg1pf;@i_yyRG z;jiSUL+-Ir*9Q3repfZ2)S9yk`OpJ(0M_j)qtiqViVlfgPiKc#H>W$;SluRPO3(_2n#Aoh5>ct%r_RqB#N^XD$*tNXIbdB2bZ)AHMi zkO#!39j*8Cyk~Uz@Q9y&0x5R>m4Vj2zOL_pOfXAVA@BQzoAmqwb<2pKC!Y@#MwM2V zcdT*xR#ub_XYGH?eOP>ngb_~3Kk`YJ=K>pF-H3&FP&~f1T7n55$u*E(%&fO>{mI-F z|1(VZA3#MIeTiJO1%c#i+Ux>0zYa}kvL6X44b6x<#?xV683JE2t>(H_Oil1QTu`kE zBg>^~On6)$)SWF5HX;|S(%;fX-EfUjd_Zm;1<=M5>A=l^n|K(XLw6Gp+gxk-h0ZfS zTXpw`v=z5KBju0he-r6ji=1fN8AK(7;{5I%M;a3#CN)mlLwwAC=bD)-F01AZhHQQ4 zk(lr8Aw@8-;Cd;q|0;hB+LyYc{ST*-(&^rB)Cj!YIK>uSNqYpZF{B=jaxd}q3P!r@ zRtg9oC$h{1UPL7cfr!}WH~aE%Q?utw5F7f+C+GKSt7)T)WIu*WLBcK+*+t%od+k_N zCt&lhead9fS&Xfj>l7m{LmKZd{H)~B@s+Fa)nx!8EW8WYA)XGe0p8Y?xo$OE^JwD*&m^TeN7 zH?MU?!g`9CwTc!2Y(@q6!UgZm-Y)pyG~qgMG0(Ep)9MaP4%?=^;&f?Gb~jUq{&1DF z?UPnkSkAcoG;U12;$r9&Ps`RGGjx%2)?)Rbf?95+?zK92?>Ek#jq$MF~78&H(8vi^3ujTvSo$_1vnbCyG7iQMO7D2+Sg-9=7&P z{v3iYoBTM4$XfKFvwGK9Q4ZraY^p~~UwQK%KuH@Q_Nj${RVaeBMi^euK%&xc;l=t9 z&-CUz|0lj=`u>?ROEikM<}XJ5FSM&rol2J_o0TS0KjWHR=ORz~1g&}!Phd1+Q~kmI zoM=<~`>mwmt`w;Idzn|GssVwR3B_o#h6gZWB76-wMj96*I-x&^U=5-E1mtk)P=a0iB0*)(v%Z`WYeAIo5Zn zf`z6a2DsOsZ!fbw})!*S}q_Y$&zrio7gq5HhH45F9(|ovwnt zv<^tek1*~(Z~96wfWkFlG{ zBbqkL1JyKe!A(*}5ZhXXl7Fj#K=--FzKZ^9_Z!vE)Nwo z6x%GvZLC1a1$IsTg46Y;D$_6seLO`!XeA)Xy;%t8K=ZjqU(oTEgTZ1-6NYPFduO80 zR6#!mspz5I+fbEUF=@WK%y+ResalX-kT&puSCjU?0Ij8R6%QcEKlhCbxg9Ss!`|;Q z(60+>oA!yHUZ})p2vw59taL8d_CpQ2HUxt|51TiSHmR8=`DU=jvkn`A@qJ@b&juSZ z!^yIE;?S7%*Cuac)R$;-tM2*>Bj?zC z*|ntwx5Ez~RNLQix!cV*zJI>8ST9r_+1A|~`~LlrQY+4!1ad<{M* zWgRK|jd3Z`Y#AxVl-)hUH}fKM5(|zcA=|3UG&$TXz*h7a&Jkdc#Mlo0nHtR(e(1h+ z!{CzPA9iCrHTSya@~m+%065rC9KkH555Op)bfQKMr%Y|-TaG}AV@03FRqHa?XE#t1 zpBH&YWCkj;MS0a+u+eQ_NCzT;`U$a?f4NxlGAg!aa}OdC!U!*TJlh%tJDNYj2K6`G ztG~tBBkAN;UGe)z%nlll-&+};ac#X3A9-`v^{__~{-b|Z?8?mNi)DgGM_RG?F+bb*3`#nT>C#8-P zYDs;v@caoK;k<0nAd%6Hx?U??IlW@e+)F==G3>_%lQz6 zPC=kO#}>%IQPXJN?5wq|{8LLA4*ts}%H5*Ut6|V~Mlb=oQ-o8d=Q)^AR+47I`PjuD z7DoK~R^wAaFk`o>i4=1f;hqPm2$nNr-y^bl$Ykv4k_sRJS1PX{Oq|>te8D-n^9+ zVo|LnV|r}NKE$K1s%$(fhXh)0l|)kVkMP~0)-Uq&3Y_`A2y~6fhD{HO z*Cu^{S6ayD+G}7f5$*nb&Q@Io(k}_PzZvip%}-<1^{l4$bh?MV3yhCK*I7`$^!I<3 z$uhS5Q_!5qzF&BSHUI3$s^gHkd3Z& zXvLN3tJ@ONEX?vvwW6ZgltXd$tPjW0^L=s3QcG}U3TS3}MTDvOY4xrutF&A^&Ip(! zXuBc-M3aY}Xwqn=)%8p`F113_wldyT`HDZ;vz>uoIbe1)puE$A?4cO5*BO`gE0@5$ z9K=TEi35fXC0T9>!Vu2(Q{P-2du*dP&t?7P+FzyGn#5D#EKROVk!)t>`_8x5t3YPN zj30`))%j~^37aWwhJD~QO-O=|R4KzX*0JTOrXR$BB0BU=2lIJBOP!Ys3xrjZnfy6T zbl$w44RoI*HhE*;Ki;gw*0Tp;&b?Q4(IeCW!$V2R@QovW(P8?Nf+St$5Jjzi<>2-P z!E@%F3cB~^$EQW{+GG~*oX|47>cPvPMUjUHk8vA+RfeXd-(DO+4bc3lByi$ysxI~G z(4HMpM{_R)^bs%6>WMCodE2zu%(Q|o{^6Jjd)5+WMds&Iky81^=Oe0uio3$m-e{Cj zfWrYTc?PFy==Th4q4QeR1Ik6<%8QtQT!?J=LnW->uy^Vrz~fiC)(`@%Kr@dNN7Eg4Im3XerDu`zF8L$|ebk>!dF5 zu`R%j`I}4``oDIt*8cFCP`8~ZU|7t(K3KD_1!}2UA6d!iRD2jj^5@z5!-R&Y8_Nc zHCC{zPYv6DLvx?#&mP0vPRNymeUPL7u8c);maio!ljNxL;py`{AeLiOqpRd zJKX_~q46cj`D?G0YNKh2iD?cyB3UtlpA!~af`X|C361>IU88)nhI|D`_P+V>aV_75 zkaBq)c`|n4xx||jGlPk;j32kFL|HlYf!KAj{qCY03+UgV2%jNCFyt@Qbm$4#+paWv ze$tLo3Z9zj$!v~TFMnW=@8+IpEp!oX-`Iq;X5Z=lEQp+kA-P6Lvvcnm03PZEoOr1! zlr;=W+^nH465Yh6g8nV_#-iTi!{X}%R*XoF&+k(pL;eYw)qeI_X>Hcs%X^UYM!IZ~ z)HU&ytsL#mZe2ppfY;NqOz6(mV2sat;8jHSJWQ;7X>zj(7p+GresD&DB#xg=;`->xDcI{K`YSIdQg&$vBg=_3i+aE?_3)Zj^d9{aLBNUq zg#Eary|#)c9)>^3*#|GlEW)hzE$D_7=urtV=S_Y7p60L_+P^Uo>WHqP{ZiHmU7_@m zoR)L3Pd}VZF;z{_s|fp=)pRqvSxv{q6l&(3e{yGA=hggF^-k~&!WO{^dFjT#X<@df za=%o(&`g{I;!@rqneSmK77qIGccz`6G5*_7-b!Jm04s8AxeUY?MI_Za=6u0uJwitp z2Y@PGuLc+errQe$O&JctOR0`~KT-emxE43D5$E&QD70J@!_l5S6%_^;VI;^WD|*St0^Suf?`7xV{AfRO-AL+Bskn zll-$hA{1QWm!6&ise(Q$DKSKoQR@3!(_)qDnnz#7i#@%Px7o9gi)h}EsqRa>=_h8` zFY3~AYp@(M6DQ2(Q1lnZ_r>-8*#G-@+k^PZMQ6=~LJ}l!Q44SXS*x#Z!Tsl2{j`#? z_jf%g-ki=$kWY|IVX`7x=SK}bmt;_+$UEN;?4OSxvgeQ?tfn7NgKs*K71PL;bD9_R}XlL9qXb zsbMUlAR@KwU*{w4$HM75 zO9Pns5J$@Hk z#n+SuzjL0p00+yooAb>!YQhGew8bu<9vL2xOiBuw1?>_A>%CcEa7XvN$v_y(P)?5X z=)96MBuWCpj&%@S0$QNj6v6xt5S-fls%_MXYp`_qi>VqutJm7bg~=kCgT3XgfBOi} zA-!hU3fu(z+pWfZ>SIlS5iqqkWp8W#{h`Ftw9a?<>3-qRlQZ+{)|50Zr0vk=K#Oi1 z&kGN>`>y4^y4qiU2N1Y!OZOY4H1nC_F=EUoHVW)Y-p>UP< zFy&ppLa#pJMJRIjF(+E3NCa4_N0aoSw-b2wNgG$~Uy-s%Cj&ZJx6pJsk z?XdP~73Mz6cOt4_W&Of8=DyK%Ud2ONhVG~7Z%^VpXulmM06!j8gSAM_h3N?jgnj#_B(CHZRipo-3jAGT#Ap z1qu~eeBg!FaxExxN!M-waOR@T&x#5Cm}xStA3Z&K2BLrZ)l>Z2s3Jl1rFqUf9-b)y zhxqRfG}{h|9Z($v|d(!AVWb))c_6Q2L>pf)Onw;rJy+^>uQ)AOrFAPoJeeEwjPIT)$K!Cn zey?%Nuu|qbvo_F`i|;L&_738kydQ>hbuyh@u=w^?QpPD8K_^v==O?fDE$ydf^Y-h|4i9T3*O}TC5DXHJ=u~Sl-(b3HiR%gU+LAUO6M*r zvYXT`}yP>Fy6x_vu?gbU8|{8gh%)|pweRIm=5^1RO6 zJY>t<>o+>h?wak4fo)*b8z-nzCYDHgdW-fq%q;GPXGtCD^0(szqj&YuU+m}k$&3U; zr(N@Y@Cy~|NgGxI4|U0k(leQWco-jw|NK5%%Q57#FhA!ZP6qF*2vzn%mD=8hA;48ppC1S2k7a@w+80anGwnIqGAVY?B1LX8M}v z7TG2~Yl}748W;CNM>y`*3=I;RA?W8(nhfrF6_}m8Mdxy-B&dHRu;9p&O%2JTC2v-z z<5s&;-5@tR?5_vUms^Hc6Y_gNG}+YH3i)3P#1Is6nR*C_Pt{ly;mLKNqq z3L|FzTDRrou)~M*&lb%;xsRD{vR5UQL#xNd-`=R_>*}6NGJM8whV7YLJZ43&x42-k zNwS=eYk1~|z93M==k(3-V=F;?*Vl;;N!3>f~nTmG@;3C#~#Y?>#I zFDyJmrD)H5R6n8z-}wywdc3CkgC8cBDw*eQ7sXY!lttoY5^q3yL>rG%6);Q=Mx_0y zE#02Hu$gc?|5K`ksA`ZKIOt+h)GZFRJ{{fOv%@Dcn69?V>*6PQ} zc9xABD>F)P?+@U^RC~`w9Ii^2P&1t+crc{`OAfB0d2Io_^BupSemCJ&w$7LKg(V~| z`LiUA!E3(d^wZ3B^z10!N-kPT`nsFNdWY9_xs`iFxff#*%xXcx{cnw4LRvxZy`&JM zr&+LF{pO=)`oYC4erlQ#gW{U@DQu?jIh_|J+8^#(7Ds0@=d=b3Enyg2YJxJ1`((O8GJQ>6mTEG^TU-$Or5$PT3F`DLDoHagP z4w{EEVZv<9SVUWr|Hya_`7ro`L5*Y|WRtKOoR3TCGZ98ZuuIv~p!(^OZkI3m+7*Hy zJ#^qQt2=hyGPn3sg{>+Y?4F&%fmO5yISPVp5yhDNABc800cO0GG#umftOXH#+ zkW>?KQu%?cEd}o{b^il!c-CbYxsjPJ7QO5iw=Nvdo2Wwl6&CGSV4vajqz^G%ms0UH zL%;Ut+OWxx2pqLzysEve?W`|T*_0gF_ELAdkO_E3KjPC`dS0QPgwqF#2tdX?%=wp` zZGJ-R=V*3O>3e*Z*OZR&R$vzcRS>?^$eXG$*0_{l2}{QLdTb82vBvZL00*0=kHCXj z_6AY6p5ErLC1Dpm<1z2UJl|=_%o90>{<`pTsk(Tcx+)#+6bM;owkO`}gyI=2 z(}vrCv~G^^Y%~LTFTSTHXFeOc%=yxMpaZ0BlC#F83bBg6!B7TMQh&d9XZ^X~iA{jb zH)lZgPB1fRJiDxt&yt&CFdqtwaMN1U0=M%W(aC#@Egwc^mSxWll`*naFnl_qGnMqu zFzEB^azh(Dd{%KwfA#L!$ixY2-mKJ_aOEgdfbWG#hc7o#@D2z<;wVAVx z;%=1u#xNU(f1skVjw1}Vdm*p|-$0Mm5Yj~z&_^hQmw(+dFl2<$C!Yk(v^-HZ?(j<$ zY$aM0M@G||sTQGsk1;a?NWbi?rA_X1m8`q$(aH0GO+sJ)Z7G-@` zGRZSKDZhA8fy{ATm@v^`*K;yc_;A^>OSkv*F?i7-O?Lf-%cEL7d5377 zm-jC0NkB66<;&jAmLLW_M%`Z%tNjsD(rm8faZ<;JD{3%fzL>REJx^g zf^;n26;KT$^c3Oc76?x6id+53+tCO0ZtH_A2iDWHGBL)cVD`s{>(a8z3k&c|l`E?U*o$PJ-ueZH39vBgio(f75ehZb=sRny4BS6wv`r=7HwC%A;; zsoMVmBqo(dK1BNC2kP+DstOw&o9tk}#8A1#IpE?#o>s>Bfe+=E5uk3IFIi}AZaNH` zmC|%IkZa~IawcWl13o#b6*jr>o)B6;@uVq5?5)HmIJT`=%Wm9hWvwh!{aQaxSaWc; z>!VRp8l+E=+%Ht@ONEWvpj66KkV!VTJg>=dB^ zsxtzN&pJpv5o+^6#ne3m!CM`{8?M5@VKx|gZrUKu*ytzF)=CP^rsIl8B+tgNBBtFixb(FWvMiZgD9z?kS@#@A|gxr}Yb3~(J=;w+@->8c4B-n(b z+1q-gSSVw%PyAt9yt}@;9)6>JeB){G9b`5+go2}pssE!X8 zRz)>F{!GzaehMpbR+=E~F?Fq*D2A;w_iMZ~j7@Gv_51$nXr`dDy&8x|n*H{)GVnhr zMp0|O-4B=oCs&H?XI84F`HWb?T{S+fw$dSpz?O)d4i6%7WSu%T&!5XCsBeHe{ z7-0wh0r(~nTw{q+Ia_>{nQzg9?NS@HYI)2|R+3EDG8<1B8V@r-nu*CIs-I%_?NUG7 zI}0r#iK-+GH`K0HyjokjfylvyLh^qr1RpN3lVLB!C-&1RD_f1XAHrLvG&DE_R;3kv zRsHP+{%J+JmRX*A>t&*_y~9DgS>r0-?)*uXYezZ4ofHQ4tX-u-?P28 z`@@@Q(^GTu`M11vPW|q!y ztpo!o8oomP$R)VSMQEqs1#W3Oo_nhH#~b7}Nlm(FYNxs(=urlbQAuXf#Vt;6-m+^L zxUGz2Fw7WW^sUAI^@(BO?Z-q%Q@MUR?%B&M6BYhFWPoTza5B) z4XQ)TgbRvP>@VmyLQnY^Q7*c6Vh5cZ-k1Zq(SslER2arWMj=1h#AnOI>=CK!zg@*7 zx{;DtJUtaLtTSH4YQ1*F^CoxN+d}c*1)6KdQDY-LAs9#N_W{dc`2$fGgh6Rf-UkbF zA6t6z8cuwY=W3SiDy@WpcV9M5j2PlIQ{u%qc2rq9fVzTI{{cu--~M1!611-P^`x(4 z+}VL>4XD;mbwOUyHKlK;bWuOG2B>_klX=P2tFix)kKtPUu3a#442!ibKvtzNRA*v^ZtC{7!3=wrkcnl(fxYN$dn-r{x7*L@s%J5eymQXmwt1 zLek**!w<9g-YG=R=oX#UkNzoue^he)4-mB+lE0SGGKnK&5r2_q5Y_DBA-+y!={f&w z{FjYgLl)ZHY(|v~GB+b+B^!lL@XGa!rp19T)_45-A(x;H1&i*fdr}cEV^Ixg%Ol&c zy{v1oTiUh--docGPjZ2$PQcr;T~@>o$RTlj?g3IqWTroqd*bfXov(IPfg;@~tLGF{ zP#AC+kh@vvTDpQHPY&+k$$4A8hEnjonRQD#E%w=*CV`wq6|=>PAa`y~qdt*#7g>j@ zGeXoMwr!EPRPfH#d({h*fb7_nZq>}_HN!lH>!OXjy`E88kzLtMP;r63p`@Mzez^X7{XINkFvE-<3N z{FPDr^=@-ZuMHo*7fSH>yzp6OLb23Qv`s9~pQw?~k}WKLc;HXAfClBX_=qs8?gi-= zwR@~ayI0mjC9W-svGrG{%74+n(p7m?Nli7suHR6au3qDWjQZH0t zr#E-o+O7@~M6H%PP62Y?VriOyRyscz>VG9|lQiP5uj;gNHM6yy-?T~lqGGcs^xJQ; zY<&>yL5}u)G=bf~S;u`?rHiA*AF@PoV~H|x*%Va1G&WKSA79}%w?(n}kfKQ$R(>H& zej@s5?&&<4w}8@aJq7s%r16@q*!NBjxVe^;t^h%^=hpPWzp&qXdeh*o@*D+Ext>qN zFM+e$OH#e4LA9foJN)CscGrfNV0Z_fmyr$N0ehhk(&U^x^~B zQs-(pOn=MS8TEn|7MQIsE16~?+A#aWTt(r!TWItRt%G+!5F3FD#R!K8MQzU85e-P@ z*OR8`eu}dcrixO+nom7TdQQW+r*}yQ9gV}q2gV^Cp_RtQUaG%YzcQayAo;+dBQ_iU z25Ap3#Y%}v2Qxq4=t?^uzz688ya1S!=$H(%(~9=R*NxAKvUg(E)*LsKwbkOJ6e(nBjR< z_g;BqDAyV4R5yzv=&xFQ{Z)HT^*{coXGDGEHB@bfE*C|-iDVjJlac#d{+0YO9hQdH-I>Rx+@$!EtK z{S9MK_M-HN)1qKj^kBs`&!$}Cmse&!9!9UdvB~fX9~kE&gW-$z`6g1?5eYK*uPfG@ zp(}*a9ZrZGaE}(Z8*^})L+^pe6SVO_5anu+EKVO=RPh=1yoe~7GX)|ZydIW*Byik4 z{izW^_Nza?Iq&_H#xp_s*RQ z>QWK6!1cXGi*~6&Qoh1jnWKKr`#Aa>cEAek*lA_yId?<{rB{x)+;2=~|quM^$gTa?pz({+m2cWjNll*~vP*)1#cw_(zg_dndxN2nXr z>;LR(G#0o}s1*bGC4NNf5D;97Hirz2@w7p*UsE#?cxe*)JIRxiZ-OG=qT@)TZ_ToOYd+52}vD>8Mw?9aZjat z)uw6Po%k1#w(B;dyzR2LJS%>IzCwYBS)I8Hx?|FArS4R^e&4Evyd&|Y>@`-Q(aZBq zH$U*38CKTJ8f| z$$qcvn+2LLv88tibL#7>W(IkH@jqWme+Pd*$W;O#+Ny~1NnQ(BhH@-Uu=9iTGQtMk zMacyR`~!**$=R7m>oJd_)V4k~GX%c9a-~FRY%t$RXkNmhNm+p52@+=kd(I^;elW|c zOVh+qIevw3!{a_Hif#Z6u6*Z2l(uQf=@tjz9D-9}pvv8(V4?;ZE92r+)xd6z}~_T2@|fwcCW`(2p# zo4I|nx3?n3jd?zuz;AMG<8ds4Sx)(9)=07|_UxQ>$TvO20c&U<)9%Dqs!RQaks!^m^ms74ew7=VPyDYp^r%H-KZ*; zEG9slEJPy9NT7Zm_M{GrJZKd2>vH}pX9p6wYdD;%(FiTbjLH7@_#C=i^gZ*utovR+ zAPw<^U)G%oFEj z;9jR+d!xScm?=_a-+1BEci-wrkiL$-1gCBH`Obwxkq!DpUGW;S%1#2hos_uuV`qdD zBpS`bB@+Iv832chrLLw1TVZz};CulZRHf|z)sKZG}%IiH4e|>Zg%K8dn>$>sN{~>O|(tQaCOy0 z0nA>z^LI|7NqB41lXKwB84YjSI1o|M*4eesd{!_dz2n-#I*f@f3PRB*AKav3RWI`M_0)^Ip=^TqNJm=-tSIeveUq$#=pRD zqG-z5u&@}{Z;lSOyUr~0JOM#ur(mFx>0&+db?q_b3tOG;^4x~Jk*ub$6RyM`?++q0 z_IF?O(WQUQ433OOxw?en5Zx7wvT7DyBEaiOKWEY>Z#NR84N+h&_VDYlR3#G z{#g78T5y-^vBj4Yg*Cfn{x&$XVF?)aRQVUn_E1uMBbg*d@tHs9F1tBc)%0zdCTRHI zZx{|Q(;wHywx}jwd0*$YQ8&?V_dR^7y6Ve~vY}Q)kMO0co_VN>BB#t;+oEzVTGf-Z zn0&l$+91bnzKREB>53Q`zgc-{8(rtdNu=M0zpD5Axa!mhIbUm8wvR;5A0^s9VnI&- z#5<>x2>V^tJl3Wm zF9c|KX5C4yoQlHIMz{UT*ZTN+-&i0GF#6gnHPp>4<3P=WzfhONtM3bESexhjT z-l#7(4QOt-YZbUel8O0-=*UYSjY+ktqqHGUw6oC)nqHC_Sd7X`Z|&OC3nb6XS9caS*_wM>Zo|BdAUF+$%o1c6 z4CDl7R(qi7BlVLYu-?_nkyKRvba%~y&x|)H+~c1^(Jd*SK!Ag*52+>0UJHn5_6_|) z+1^V!h~M#RfPCPZmi0eCL@qSFkID|Yp&(KpEI_&zb*-a3FGa4JBUqmh)^(Kg4K$x( zAF_2gvs4gbAhs_kMLBNMy`%P*2lLU$`{JF?UAeb>E8cJW39W0|KxFB5hAX(puf6R{ zfD;L@?L8RUPPj~?`{Vj#S=1%{;-b7I`qx-GF%`AEnnBFum%6mwa>HJDRATq2n`mb>!0(mQ3eveKiBUwblvvG4V}iwZ}txZ!6E2xk%PvpRemp&UIIgc?Sl zz{O5P+qO*<=dYjjnW#UvQ@WczxFp%QRD8p zRoBxcNZd_vV&nZOM4??Fy-$t7*LeF+-7oA-_o4)I;WL~znNtkSclf7yZqSw{3OYzQ z+zW5Jh91n!83ox}?NWi<9UYAp?=^~`8kr{Es{nmjV`bp#!7QkEcs|6q23tQWV2exI z`FoeOE7=^QXsL!@0obQEtHw7f@ZnT*#sud9j)R;Ws$G)Z3o)ZDHH8o1Wa+G*Z%jNS z&m~V0ce=~>p8o+H&n0PL$H{ee__33)I1k9*X9LwK2Q z{UY%%_f~({;MFrcJ+qS4u7WKP4X!`q5)JKvhh&}ig<4}AH~!8wWxBenL~iN+p-lD_ zU6W8x+}^qp4Kio_RR^qRfbu(IqR%V z-9`MN&}eb7yLErJeu~NWNU{3%R0zbVz%}-6Op(Rtmtopg=_6PUNvyj*`V)`6=H=V| zD_`1unPrI?>Hd$uTy*(r-}yMPTHoof&4vnQ%70x8gWK`2h8`XIr+&IC%>l{LOUC>C zDkB9#g7qacKWeeHmEWeE)U{7yfYK%h?^1js0rK`c*+*i(Z{_6Pe(iI2Vh_ZIw^Dxe z5__6{vGMNsfW7CvNajbF(0KKKfWvMQor}7RM~-mWMIpa_HI2M47TiI)r*t0bGjnLAajL#@5#! z_b}FZT>2_T$0<{n7(6R&3fl!YD93YF@$^mh<*6>f;lQ^d;^~ z!_Btelra}LPO#=U+d7-IS1k=kuhipd0jX|$pEgg7rN{e-6x`t?)YCtKFVvTsB=1Rk zbwiYN*SE8spBtQqr=FCoVR7pENeeHiUGe)Cn< zziPEy@`%ZJzoYIYS>pE^mO~aUcZrYV^SJ0OJ=;waOwns@KL2btmIBWI>6T#2sc)|7 zn^{9q;fsmUTj`Qq;V?expsLdPx3pOp3yTws0*!G3u&^L(Zn!9S5+w!=sNkho)SXZV zYaPkJ^4RtA86x&lWbB*@&jay$7yD0;zi(L)VYzy%h0+fKRfzJ<_x-Q6Zl`u)o{Nm> z3nw32{SwV#GekYFN&NWw{iGooEQ&80MiV@jhUGI`*CdK7(!(CNRqq0tK&j`2I--lg z=9K_|qhM}HiOkpOtUstLv+mc+2}WNvnouE7>BS-Hwu|<%pVT4MYu=yjIMS(-`qTY3 zNVjmI4ZUC~ywp8#>mgKT6$_jZv!R=feD2|+@98tx>~!xqEdkf;#2x_qIihXIogRse z*ZK*FG_Z|}w&(1^wCdMc_4eB7lJ+*XBHok^rpn`b$?X3+_CM251~%3w?q>`ChV|oa z@c)b*;O7m~6IfnTz1mwA`en`mC(9-o0^U>7-AWr)A3hDh9& z>c9W6?W@16>Bd|3HoHO)rB08-gct$(T)iEcUoXw=rYpRk1%65ExvYu z(#$~pO_EVPS70vrWV$lxPZ8TxhC zom0!I!-t}tqmaA6f{$>^4=VX9wjygD;KL0FH%?37AZgFjHOeE%=tE+HBJJ2_vl$E} z-hU&<=drlOM`W%deL{Gb;NqkMjiFN~W(m(Ck&@zpRJIqMXHYxJ3p_7xCdm$1WPy9_ z9QFsiwi5GnaIY`AvYO3Sae2}fMTo|*ER^P0wSCGi!dt7B?;vTep6Hg5d`RpC7V;CR zG`^hlvSucp-3v9KVlgOENut{&G|u!F_X020FcycDpQ!k1;deVZg3(mF>;ul>p6k*J z4>n>LcIUTn++>;l+MPO6B0+;onkz59lyYLJZ`Cyy2-GXhH}m(ml7DWAhTsPU{2w!D zGhHpO$(iCHUwHNj;@U2t$2+b()*b{kwQ8K)P7Jba^rO~1lt__Jpi-##*BKDRhJdxrg1DMLTi^GaZ5~E-Dond5rZzSo6fzd-CYdmHuV0R(#vi|iw`X`FOj;&jH66Z765 zze*4ywjhS2Hl@<2f}Z>mPvB|Y6+^jb8>QAmV!Hfz_9%-xGl|P@H*|a*4c0neJ4GB{ z)M`4`5M#~dx{4LYQ6I5pl3cFSLU z!E=`zS|do66AQPj%(`O{))g7qZ@JkNGPaPAg3^|!H{1^XgL=?iZe@CKqwclrq__uB$K;A0Fwtn_$@wlA2ZdwsaXS%8%6gTf{d0eqFVvhZ7m@7 zuq|s(RH|IA3wlDns0^TV2Xo6a3oXjapJ%wW$_tkpj$wvfG4)MG$#zoVu%tGmj({Mk z&O^X9t?Jj)+kU;^c9}0p)0|52cfMR5ycZusJ91K zL@+lC*g*!RGT=nWX>&#y%(R&$`sH2C28Wr%`I!V~gvRKYImYqB^xGJ}N^2&@GZ%5J!W&(jOY}(zTZ&Kc zGFvB%h6(RNx{maaYaTYRLXL$ZR*Y(Nbg4zfFYs|P!iu_;6*A(t-@dUQ*WEa6!qvjj zb+o7xxxC|3qmTP6EBR}JZ4It#n$(#0ILRbKrge}c5O=pQM&-C)Yk)Dt0naFnBYgAK zKZ)u!z+RZZ^(x@JZzy}DCz^Q=+HYWecuy#FS?@92A;kD{9cslyd(SZ284C_8eS#Mm zB}DmrCw=Od%=Kg0>i+Vt_>p2<9yzNmK|KD2tNsBvJmHJ4|0>S2gFrt8s~U& z*$sKlVJ+P&Qrr%r*?WT{B#R934GW`@)Lc}4r zigN_G{mD6z+U)=!^ybj)&OTu2`q7~0?8;WtU+_!^Rx+7Wt!ZxOkyxXKZ`YVpy~@jM zj@Tp{jHLyntpJ^yXLm^GcIjCfQstivX48~iG^0Jq)op-=tao3MSur8R2MORZX~>*u1Z&3s%e2~<&#w++pe+k#_hUSYhP)OW9*HSd2tHaGv*;Z13^P}nLSDP z-0QHbIz_6{7G=rnga<=xDUi?sX;PM?ld^ot=~nLc^KGq0$8mGR1Q1Sb+*=^zbAr*? zXzUzh5+kK#YE(B_3Evv?%xTQ#br7PA`%LNUE;Mg;{HD&OxH??1+06oX>$-zPefye) z=$5qURkItG(|P!3nigxaj6q!OJ&~F zDYj89U@)Ejj{EgKbzSIQEo!{z&N%(mvnWkO*-J~ow^FXwYwP;SJMXyrYa*RplG_^5 zP2gju4{ROP4qc5h&ph(aji)x`)^BKN?cCeVOzqj^TjB$ji>--hS@=}k(I9 zEq9yU)&Br;vf3d+Wtmq=WNsTu+e%6hk?}a`=!61KfH>GkvX(Dow<6;Njy;< zt-)h~EtG(uNhYvk+OJuzm}2^YVqz;glY_~A*Ca?hnwL3+b`s%8-1TNh1grB@<7*w& zwZ3~V$$E2nUNNhAyc~)IMh3p+-EI*RdAq9lX^Qj}+>ynCSaLh9ptkElw-TTN$x4FK6guo#ww}}LZY(ZCi%5O~ z*OJi~9KhDJ%{MfGkjOQxBSLCbCf^GyQ;Et!8Se&3^?~z}c?*K;mf3Z45yW)c9BxE+ zb6bO{$znc^5^c~RtT}RF;q_Ep?4x>z4&5r~o~7C*{JRYWEGuNoX|eQDIwi!9%^L&h zTsLae?3TfsWO~zNdmXC>X|4QkdL zkxfUqdo*1l_U#9d^70srH1kKfX-mK;q=FCFYOdbs_0_9c)CToJNe+8haXJf!Y_yys z3uF(n9RQe8SzpqqPC3PtKqn!llc|&A2xlhm?CEO0YEGTRYIYp@9h-8;J4B&2`L6VC zYz_JZE!88nkKA|Wjg`7ea9tm@Uo?jgonXq76D74r`9jT=z~b_eDesO+SA)^sbxsFa zS3*3L0SV8Xx?eJ!7fW7(sH{kwtgduZAulP2(3ArVDM;u-rMTH1jgecMzBhT%+mzhj zPOWk-c;x3ytd|yyoz|6~#$0X4@O$ez!1!GNll*{@}kIXN>2X(O$y<`))&!3 zbf^LW6Z}I^?iYuym9H=pxT?yX zfh-NCYEd#U9<80G%#jm^7GhhL5OmbsuIya3drEFB zINH79*?*3eH1BBB?5-mZA=cKPg=kl2xI5HqUZ0zzw@3A76?zM8W&Cmb%n5G>*>Zc0 zsS$2Fbq9iZCGqu|NLP9)F_~kl(;UZ7GOay>dYfTQ+Fv|!0|8*ZY9OBnT#dv#28Xyb!>D_RSSf2?1>|kPnZTJYIJ`Ku;xC= z(KMm49xBbAWu{8lJrQtfL#g+)pC8=~CBUKSQw=G|C2EcdVacuq$WtRh5atAQTQ^oz zTa-S;xZ1jR5TRA(hQA9A%5seYW*gSj#>_YCWZGTIS;B zjR0KtyRN6amee`G6D7d3onr%)TT!Q}9t*3@hCNu#N zr9R}xmK%(wk~dR@XQDP8;GUJu8Gfban!kLLfasZ#9?sEnM9agw#8743_gdZhabqZN z#1GvM@9-tGe02`WHHzP_`4a7!c6S37#BH(yg|-<~`6_UA92#W{_qkuw6g}O7eh})% zA!_1z?jvHc#K-jePP5C6wJ&blkujH$hh!_No_+a6u>0!iy4H67O1*8c>TCvz^{C^n zVqKvR$wPN>l)*hq8MC6a04OBA$EhQQ2~_La@~ZFe0$Zc8Y>N{Ek|XR zxex~w1nv4Nmc;Sf7OG)dm7nE3vTb>^=hq5S4$FyawS_kY_SN-Nml}3LPnvA1G2L~N?b7WcHImnFv*U2#H2leK22hf<_Kt*rrL63rc%bzi^{Uma53%6-H7?Nu zHP0@Rdl)#)mOQML_Tr)04>uXKh8f_6AO)!LP*B))-ly4ibZD`WaxvC>TGGb2kQ!R! z>xM^Yu+E*6s*2{b-b~ZUfRYIt#0@8O<;_Vwma_;7(gqpJC#u_#MUwRU@w_Mmh))Nabt?pUD0igP((&yBp|%v2T{U+UK?>u+o023ftMWoxS_U)`!Qnn z3$>Fa(oHtQr_Q(bmdk~=BMN#P*p4J65*DWeiVm=}rDKTQNK()?+-)+!G&aSKT;pZ= zW3cipqB1zekehQ+AX?ITmf%A`;KF}BT%A_kRpCjrKalRKzi66|BB3t8Ftx$NnICKh z&S0GyKmo)!1-JCtZEe00*#J&tN3u`HgoRw)wdX0++l$t0CmhsFcNt0G<<8lO$bR*6 zPmjA7LRf;?>+`M^x#X1h*83Tgea0Y1#&pNohW`L=Vb}gpmhJle0u$E-r2aNbyx)D* zl@Lmn3F*?Wer`CR6ynvtnu)DlpJ{Wh7j1_XxFa%l{q4So^s9f-RgT%W>bj~H!L-C= zT;nHkC8dSOQYK?OAPkhAhf^Lm)Fs?tW!*C#9x9&Ba1CtK$6{Y**!1@ay;_o`TW|30 z)8<{R!@PvJ(``2EXRhrz$+dW*rmak8o8@W#j)#>+cx~89>!s+3#)Uz6IS?I z;XI`gvWiP5eS(4MT?>#nv{_^}*W)Av#&7t`P*3xkqID-z*P35vX?qr2?%9IDY(>6Y ztysL-60f7GRR$uE*$l@aKREgLmXcE5!1AqKk9l7F3I>Y}CePUpEE#&G;pRy>H!w_? z8Ms!vpvoPv^N90_?}+pN040Sd3%=^EO#Ul`Yc{4_EY`^>Zy?KFzjiTDv>%QQxtPti z+wY#`x_51%xEz;i!_cmqrlZ@+fTnM-&#Jr^isIaxW zzT1_wli#&!VrDZmu|j5pV0x7^8VlY07UhS}ZYz7M<6X8&!7&xNDlIRQN;~buo$Gn1 z9@Q9q%TIHAWtR=k&E6HM=rdu<#}?7INR-iOq{-y3gegu0g#c2cw#4sR;B9epL|J0$ zBmxIynIImp2#J^xEE5eS*omf1uT<{3G448!SWo2f$`;Gm$ynXPODEjy_}nXVsmd=Y z1rSivfrn%lUIzHztC3q*8?-I> zqHVTvLS!a7$N;v!Wu^QIBcU+6=A`EOvz;dk*PH_uOW535mO*0~7s(yH-dYe9A|#0k zax=1{v&XVPc?t#av9a6>KU_0h(oPnv=J}vmRxfqK-AwBp{j2?{a-d9FlG;#)(&CHl zyrm_l7$AoNw%dJ(!P4x9F-|pi(2S$mxtxn6hU1rfvK8kew>FEMiOwn4CpO!tP<4eY z@JiFa0)T=+PhOtdp){R*`c%v2C9Y#jPug&ETmf!iK{$bM7=VIvDu_KubEB9!@DN76 z%(PQ!a&;pp_DaqynzxKxtoc>97rQ`~W)_w5hhr6LW@1#*mV??QC@TQ$918=c)n&v} z7c36dmooE=Op|1VAiTlbw(TRfc&K+M60f$o@@5U~Cyd%>T6CLDmuJUk#FG|oF_Gr+ zuC}(LxYClZbXN~}il7uoN?cQY!-%b39qlol+I0T_PO%H#j>5D-on*7OY`-=Nz^ zfmx;LC$wIe=nh?*#JS`4Yc)@adpKkwQ`=)lMwsD90~VyFN$_b9ov3H)f&c^>V9jM zXIdiMd{$mhhQhPAi_Lof04PhcMu78@SxhM%fcK>`W;V;kTnGi7`?syDDh)JEy>~P@ ztvJlIkv))gpQ{ca&Y{JlK&n=8FBKRJ6PCUH*!Zrn(Vz6n&zDLaMn|fd3*i1!EJ+Q1 zf$rN?;of(yTlOK;2J*Z;j4J+#5pw)V>$J<@oKQj< zOE-9>Yf^~-0D`hlkTqTF9lq^Us(1x0Qhhjngs16ws8A(4&0r`Tqz zi*<%|{vj8e^NLVYq7}UD?(veyTaHPVKuQMTy~=AW(fLc77>-*P(YT$_4#6)Rzo(po1;83uvA2BhxF0U=hVDmjf{F**Jx$@IUZ4H zW<VK+9YYMiF#MA(5G6Z>HEmV-OK}J>onyy!dDf?og1zqoncKWDtH7a@3QyY3oF@T z8<4%5^wlpQrO7ztX^sQ@BmACUUmuCXRBKw)w4nCCLgvBTL_0C;KJ_~)inSU?10%^= zfhkbgKoEKme5#Vq54dIeAwACfUVA#m;oqoCt4!vQKJ(e1xK-V?asL3e#&33wF2|(0 zXym7L3hk2GueOy#Sl-;eF0z*w(CoK4I8rv;A-5bm1od5)MAxs?jo{B9K?I&cQr)`) zF)ao1E!S1`8(eTa7f`I$d@~8Xx2O|tidtN0c>spVY8-^96sj9ipuFqEZ@CFh`=+d& z2fxqO`(5+eCgE;nSB$nfwnd1Sl(wMOqD{5q1d0GIsSPoKU|9zpJyFN$=i9y=RbyAocEW`>m_kikdfe#y0+L> zw_BOii*AtFbk|EU8{O7b+9X>&%5Abtc&68BZImWciF!iNhTU6Ww%I>KsZ08!dJ9nf zqcsarv{Np@>^dtJb(^t|X@4oT#QaPQ}pb(NyMzrU;l{zh>OmIJErQ^BQ7KCONB-!t_`HWcDj+LQHd6tlrrMH<| z%^{Pqbo2w^CZ(D$m*sfQo?9+hFSBM_kVVQGMfz3!%%;xlyUd5u6sdAnT?Dv6+m65> zBp?KY?N%$j%wuk8v4Pv9u#$H;xSb$m24sUIjbf$)k5)+|$K0gYhp~sVb{lGqXk$(z z-WrU1P49AEnwK6R1w2_=yAZW(Hz`2!?xp4U{ew*Pe@JpHR>f?v+ow#rVY1q`#cCsD zO^fdl!9!^S@$kUkzjb=n(R#tDO*|WO*=tc0ld&si<7RLH{{ZDv8rS1mJ_ zbXT+9q}m?D?iTE&nexT3$6&ye+v`%xa@4LRq`H&EC+Su#FHN|v1$vl+q-OAaU_|=c zrd8^lOKJ8S1ajcIYqoo0J6AS71xC4l+h*sdn52C&$g?~?-ExG+YRvnwRjs)TRjxE= zRU|BN-DufpB`2XFHDk4}7nvl!-?N6Uh8$07(}=dfaVtUUk~qsdf(SLvp4M0GPSxaE zn8dV0Q>|9$_DjS$(ylo2R9P?ky2(pubzAP-WwuG%Zqvu8rtE7fjJ;v20lCS0h!Al3()oMHkkLFfvOUJZ^pckLgtz$FrxM*J--zLN2FsGj741)Ep_le)F(C+ScUuDf7ILXrRJaqN0D30n9SBn{jlbCum9f~BHaqL1X8{KP00wv;H#H^;$8W9l zn?Ix|ef7CZjv)LFDL#QltA8j`Eb8jg1*d@`4pUYNeMbFetvhb;ECPrFaJ zDM=+MLRywv2fv$xZ=!aXXI{4tX(myNThnaEt!n+Q zC$@k>Q*N3Q?oKH9@R-wEsP!g7i9JnSZ$yV9bjpUyr+tW9X;<=!<%~Z-?K;Wq=VrOt zH5XB>lkzNWha85ivP8H)DzuTXTT**Xl9_CSvI=(N(`u9HENyhpG~XT1Y+liBxb4mm zGcEZZ9|l;BHMJ?ZESDpe96TOFiRww%3QMbYJ(6nnm`h1;bm~aZJ2}O_KoU}@=9mn~ z<{}ldmE(4_u*0_asA5~sp@!RR0EbGC)*c%T&g06dO=!?uV^U+ulUnN=Trn+UCVU{tbUec?P58Ry-W8a?Q_atq>56OKX2{BIKCTa%g`Z5rM z@t&%F?)}lQ4 zIzc5xi1Gpz4nXfzHT=t`a>AQYEuBi6IoIv$X;Z@Sx;rPBI!`J06I?bj# z&~DiEyBb~1K{2iCit<#64=d^*GQ+`V#3_Dtkq{v45z>hx0eycC08}=aU1dk1Hm0B^I$FvqndALT0 z5xl#M>2jXzJpk~e<(W0R*~3&XciXelqTyPg+UK@nLR_WA z%GqrwTP(7Hy4N*;g(U;M<`?-!t9>59m!GdS)IC*+bYX?(H)+;EH#a(in+>}05`fp!aIn}_?>f(NQMtsO#Ic0X~3=?(eWZm z&#aQ|I#AQ?&w*gLv%=++yq>MhxCMMsVh)9=M&(*BKU1 zXgyhBm^kcIBJ+geLFB(4-i4=hT_-6!q4~rgn?T(W(nj03N`_7;r+Hqc-(qV9wA%f~ zWDuvY{58dgS;x5#hFq<_7p^N$LVBfyl7w~SqZ!cY7uy}DEYZj|1>RGpx91(VTZGo5 zDqXI5GT@GG{2>h$zSmyz(DJF8^6EvnrQqgx1Capv3Fnqtp=glkfUhYJKrqtEH3`rh_OsJeZSrw6fGQjla{oe1r1 zOFz<_f~M5#hoVC-6Vz5tqx(;5#uFam^Pb7KNbqfpDn-Wd_E8p8ND;s#Z#W`_FNzWh zhy#6xb#oIsrRU68s2+A+A&-neiC$k=QcL3+n7bW3AMp_FMHjo98&CA=~)Ry ztRXwC*AzQ7fTqb_$yYwyGHpcEjd^Pk(@k~8a(s&!wV^gEovOnHi!4)c08W(po^|cg z%P4KMhWR|Tyrckx^Hp@7IqX#q&pzvr-L=vEI zO2Se$=ucX-VvHKD&ode4J{m-Olzy~^kI&6w`#mz8y5n@!%)=)|%T^)0?Uwll>1|L} zC;=zr=uDuV#xj$##Dwl-jh0q^ifx;|ty!^z7jis<6=9_GQr){E9^Z4sZB&M+dk$iA zVtyFlOT1t63wqjl=s8A7GF*I;Ghl;nlIq;#-svZ?a2=a-VnInFr1~+V{>$0EEZ$Xw z=~b5A>oOM#;R1b@Kgu?l?R0t^km?k)JcH2n*+8X9B`O4?jVJwNk~l`5-z4VZX~Lm; z-|ZuzbFwWpz1lSs9)9Oz&uy~jGjF-KH+Y6Lrd)ABfUDR4VH>`P4$(IT1gv2)Dl`a~lxJ&8l@g(o`!0sM3Fnuz{m_C1kY&#KLNw2n?TQauf zSNmp!d_QvLdYC&GVWa+3nz~jVYU^&mda5zN#$C< zlV!OC-8uGs$di7&Ky%&iS!Kc2(T`)R->MW)0i7&(MriR#{9=*;2b(Ur9!%Ccw=yi| z?fXV9Mfv%5_B_)yZ4e1N?0N?n_KEgb#xWZ`zYNrUDpi6s zX47xCz>4ZzjM45%j?xyk)_U$1kfL|rb5b^$X6Z;>iB51W7}IPCxz~AF#Jm1yc|iGN zExKG1yNu<5@TcJ+EU=UG7;Gi9j{Uti7xc5`rh?yuPL@sv0wJR9HuzefVNDG51 zd_6SICHs_Qc8HOBlX8d?Zs0-OnQ|1+Pk!Dz5NmIwG4g+89Jc9ZEvo1(@L9g(mb+|1 z8^2jyAWFDEX(M4a*DY?gw!j^gf8%6PG8e=TTx8snq7+9spAcG1# z+w$NxJ@vO!+6I#$G24;YtaDSX1?{;ay5m||_NT}#){RxS=;jM+z3H>EZ;)9=T_WQ$ zbW*~TGTs|RL?Oh!*G@eCn{AP~0<9AY5V|JQsF=0>(+d5MPm^@ET%$KK6v$CrahWVQ zozS!>4TvN8$9OeV;P>Wj{?U5Wrr=zUzFF}*ib`U*6q2aZndGhZ;y-haqsY}EVZ3X; zhho-@(+|yb+7^%M9l@K6b0OL%+~8tsni8hmLLGgzw-QUr__sZq4zK8;DpuR%W7@vG zea+jK?HZ^Py>S4vz$lz#}B2%9mwYOqZp} z)NLf3_7|3WvkhD&Hw`aY@2`S6Cg~6m93*i_eZDWJefbg6r21{H^C4bY(rs11=V5uf zidyZ~=j9>D7goE;w#`=*NC1+16>)hW>{52#ur7Yj`4n7HPhS0|^>$%xEh$XGxZCZm z61Assvk`fBpE|1b%h>+!a=SvyAZHo&u42~56DH+>-Sc;wl&2dY`^*^|gqM;5J&O1e zdL_j46yQQLT}hbc*1Hv%{{X{S@N94&Xr*smZ&6{Oc@SJs?mw(mD?8iAL7}nuJr5yg zHJ;Kv)%4ihLyS1BkJ6C&X@r6Ye`(*=smu>bbUF&=XVyI-gRR(fX)F0!J=)si`Q{?> z&gGd=WGJq4Nb9mfQt(dYCG_lTrm);I+1nwv+B5wp)7%p0VVJLRb=z(~A9mHg$tyu` zn{FXTiqNnWo%(^bK4B!1xkl&hO6}`g;bazvboU-cD~lU_u~}ABZhRFsVqRL3EeU)& z>RXK4Wv{p#WTcu$2@T*rz zEhxCn)@_4zJA;j}owqQpX^XtJFKNNN=Q@{uP*jyy5|kpA?PN1sQHp*ONfN# z7TGr`E0-JxBr=uVrG^8orAboTKneru^gCG^v8=k`h3KAC?W5U_<%rqiSmZ@&Osl=3 zA|b8hb}cOt<2vb4O58~Z8*V@qZC-Sj+5NpNkE3@8)=O?RZ3uc2QFLvIaAhS$NSxG? zzSRd%}OdNCv(3%>JF`3P0tbbU)2of9mvJcEVA5V=25N@lHJb722hmxE(l7v zN0yZ^(iOMOycSfD2vR_ZQ&!#fj?q%aWWGk{;({c8u)%%>ONJ?0XX`OAirN!;k4#FnrN>2No z`fXS@ziKaLwjwGnGpyFPSe&hwZ;uvSme;a@QE#(lMF*!)Iv%twzSG(gg`1_HJG(i0 zbt`@57{aeEvf>HqgrPAv0C*@GNg)|6>psaiRPx-+zfd(+`82i?>ZE7)B%uCi4LFXU zsMTlfMeL`bIF^Cu8FillE4;wBCD`+4-Db{pwU^ojK)&kw>`43S&Wr7Nrjo)LV;9t^ z64uypEJna?&KC!oH)L`ZUHeh%)wd@0e$FgWG_zIWnRethC0sC>aN{@g?Vd|<-XSBV z-F)Z>Fs`^h%%0CSISW;%MR2&P3fw6cNX0|!DnT^;+Z44~?$*mbA7gmx>|#H0w-F2@aoty^>>w3L&_r+e3lVsPZPLXO`504L733~J84zT+y5?3a9KDP4W{F|;&N zLFu?c)_T=T?5&+^r!@6P~U#MX?G)f z>elRbHoq5Uf}gTFKgmy~1-&0a-xWLgFoFElPR`H0r((u3WoZVP+LEM#k9^N$xJtZJ zvv7K!vZLzM{{U)PE-4M-dOvPYA;rh9*I7U($8dK(&I$LQUL6+0Mk^rS{d^%Mx z22ZuM(lTYlyl9FUTDmO|?(#k#F0LbDKkv0Vn-8_rqK|piO(cRj9)9nH&v^0h+nnU} zrAaE}=CSqEKKdM#d(=o2?4K}x%ZwH1^y+r%Iup@fZM$Nq}2GRepNtnxDLX)xg&`z%U5WHcyjSi+x}W@gPYcw z))T9=;aZYKZLvFpkuMFf74uTQZE5h|ZPI?<^46DsO4;M>xZKoTRSrY3qAPb4(m528 z4&6L<@!GWO!KUDND(uO^79X@Z{{T`r=luG{Km8)zS`O{nx%_p1Ema!#E-dUT8<7e^ z9&G1W$HbGr{*iB?@;_}_&YzugZ_8Km)^*NSG(r2%_c2XRI`57H;-dQovh~RTY~+N+ zSN2SDAFVZusL_)H709mLNc>Q99-e7KJ%*Sm`xXh^1Lee)w zVF}ok9}fMt@2pJNMvaYM)jmqDH&xlElp(S3u#*|isZVkTsp@HI1J=`baE>}3j;MF2 z;oi0SO62+aKZGsGe`UcYYjXoV$63qpdDp}I&rf;=|# zrfoMhmlZJPF+00oY47j}*5JCEXMjw?YvlI3IPb_io=aNZQkJvkqEA}UA|&s9xjzZT zd(3G)pi856-)&$kins3TzQ@4TZ|zmC^7JzTjVi$%m$EDTg~GNM)5)me2;}UAC+qPA zAt^lylsD)NZ~@otaW|D@16J~pJ!=b6#lb-Yj^WvUFKY5=3~dp@yC%0=2}k% zE-kG*nslUeK~M?@Zh=WVbUO>W1;vY;a`PaeXgnQheHPOEQlb3*HOg%pJFSg$`)88Z zu)%|&)DUnj~l@)B00EO!y*xT&p!R9e$SX>-(2dy0@zHRn+OYv=iEvv|?y% zWg;8^kYJ37!JBK;Qx2t3pfowa5;V-1Gs{EaxK35$*^fhJ*rmW$oZFds##W@vfE;2= zPVqZB(t^GPgWa(7?yg$Q6NBT{CE(F47Ww&ZH*1G^YH-D71ah^65DEE5I7HS4oBADBQ;+_(rY$A!dIquEB*3y zp~q16rQ9yl7>5l?R~55pj+ZxoU`j#d*^jKXo|R)apcpPceJ@+GO#S=okkcA<%^5s| z%$}{j6s<#Wl#Z$(Y?07xX{{yIYrWS2$1xp3Vmy1*+Q}Y8f^=s4IG3i75*%R(=o^;F zNlT16Xpv>rlmiTVVMz88rX(5GzQGP>-M?=sT zT2}sBn#F;q*`(WRG3?sisLiA>lsM#-Kyk2m^+FP3a#`@-f74bo_UThtc1`KTs3Em2 zxQ!FpAb@r%KV1H*faEv(-c;-zAHeQT!niwcW&D!%&r@QzxH=)T-S3Z$!)f}7jWUbQfue zfHmHI$BJh^m#AQ^#(Xi~YsBJ+3d{pJMB>9^ZEY()kEWEr%Xbh&y&;r&^&h zeKF0jJbzN;S*BxG7Vv23wp{(**!xeGZY#k)^7n6igX%qhmx?<*cDYjlsS zuL_9!KgOM`x_57}YGz!1M#Wi;YF0B5Ug|ClrJ$xCobXpS+f!r!(h5*`9$b$~>4;I_ z!G#{*YKs~)IL(Hm$BcxbrjR?5gr}lF@)gVNXIpH0nzr@mvZYW=c3c<+pq$1sMsNUb znYb7lHln4~Hq>L>){(>#eev()HB@Du!>KqzZaJ=Trx>R# zM(m=;9d>O4?J6}HkBekFvv;y*(tVd~%EZ}6y<6>3=D>*}bL6NAQq-nUwXwwR(_aWy z{FVl)eNg}^jq z-Ew718jg2Y2LR7J8J;BJC(Ie%sJ!b;6=1wa%&dOTPGfzaONE+tSqe3-Lp$|xwY4R; zBR}F4fKq<4PW#rb)vG!jZgQ0d+pJdUZ9ZB|TP9ZI)ikuE1r((ZtgLu*0HfBr4<69H zBWhs0r%Ws%Bl;LkjAgt>@QYLBXLH~@D>Cn;_~urZWERQ)0rEIo z9`5Z`T^BCBs@Cn#7`l5-1TxulCRLsj=osLCR^+6p_jNUYWLn92)2w>&XV+Nhx62mUX|`mR_Ex)P z<$a3ypeJHuN<9x^uY~pDNaKfW|_X@!d-($BjqNvt5J%Rz@zeP!S z0H!;^)Ek~*XMvYoV>b;dxM|h#9MsKM(Ak=Lm1*8RD+bBnUE|#B^KG`csO&oSYfoyz$^255diSX# z&wA}^^X|)On_oU_oW9JJ0In?#t{%RfKKifzO|$2E-1AS_;Xh*2n|HfB*^Jpk_i63? zNLNn1j6H_l3X$e3S7&sR)zv*Fr5b^>!F+YjaDk8*UI-!XA%sSR?p15-`mJwP*S1}k zr~%J>gE;{z4eZment6Ndv_-p=6CxaB!M35MTtqv=2yF6NT6f}Y#iMfLE8lf;*r*zs z;?R?)89Z$pySm}Wu8MK|4&b#C$uRNrvi;wf51eM#K;*5iKMVPOYs>!$HZ|oWlnL zoB@JMA5GOL&}-{QWMpG5Y<9dL9w1I}GA%nTX8M7dX4h${Q!=HDYR>w4bB%A^1!z7T zxYyXc$inI;@o zn7vr1Nhyz#_G4wq3s&Mh^&UzN{S}fDv9`(*d%g{4)I2KRXSuFaSus{5+b&R5!ezE% z6hb60zpA0!Q}k3fN`VHci|@3?a}oerK&8J*w1&%XgL%{x%W~X{&8aDV9Pp4nb}Izz zWpyFOmafT_B}!6?M;g0rQsZl|TWrQO1{Za#KB;Vzh@ZIL(EuDwB$k~EV5r)5t~D+R zGZ;wLcz2w+5HsI8jc>a9KV=y8<1uCIjl9dsw^+F>gay+m^RiZ>0`+-?|CeZOqlmArNm%ZYWg5~aEl zr7d4nTh?s?y;)>4sENv1k*Q3}6A=8*-PYy20>5*-r6-)HmB{ZUAw7`;vr1B(j>_Y3 ziFKE9)F*Mtaqrd$hS#Y0M4z0OF37N;sVP0ul*n4$W#oJ-Y^}bE*bbi4Ycj%!wb|8N z_JRQ=#ztTYZJ5JNz+i1n1IQ${K{@qpjc0YW+5zDj>Tw<}8kX>#b5~A138@bl(Uim% zS#2dLhSFFrFn)k!AOME8+ozn0@}K#Z@#LX4Y;Ev(|EQ=FG!bk z!sIT*i<>G7_m&$*N%-`<|~5HeAl*%^+y<9PdLw6|cE$8w2-ebQ&jCL8i`ccydbn$2Wj`+aL*&;j$!8J9E%sLW_eh%RWh&JuFLmSO_czit&Kejvl z)wxfNPJ1j3KhwWxoG#QhE#mmq-u~$0zRyWKf>_m?l z_d_tVYkNf&nTk>PQ!xi~Z+!InM6#lO6_2A?UeYcqewG|Q8-upauJ){9l_5ZdwJu6K zVhHa~yP@u*!0%REh>>SSdIadK$%`emH2V)W9BtOf*rjPuAxR^=5w&wxTY_Hn^H*?; zcSqsLzgjHqTr)$2Ot__}(91~?5~vS23Yt=krwSvjU5_M^3M*e^8+*&9)@T&!GG7`o z#iJ0{)Fh4ljPk$$2CdksLXl+RY0H?Of4{wT7iQ8=7LXF!eiRQRj(~oOaVZ`93W(|c zrs7(W7E#BgNMhk^SZ3*GxH4m0ZI1p#i0b%}=%Kn5-jxwjJGJ83j@l<*?{bqG-D`^M z*^eu^wi-cN)%PSC%k+A*YV|c5L%47JoyyRt$1@vFO1I2_)GcVm^yRdNZ5Ub`%`ySD z=v=N!Zq*rHy`XkM2f>sd8j|XEnT+v^eeT6~RfjXjTwI)f`r0`d?kIe0KA+#ByRb{G z9)xV6vXTvHy`QYtL5WM$Or9#3$aNNKvPtbP=|y40gu;pF8j6;ax8*5*b!jKUx2xPb z(z|KUKhX?4K|gPi1d=(n0m)LQOjf2{P5Ucy4qc86hCBZ-bDUE18ywCtWUcE(?d0ykXX!f*Xd8Br+yL`Q>Vvb0 z*-7h2dk9IgX3gdrkoDFJ_l76Rw8FJ8@pI%mTk_qSP?Zo0(INMiu1HH1~M45nEbAs&p-C?ZSFFJxJh{}9_&02Q<0B6a1bsjrj&sp5; zLAdE{v0=LNx>%)zfHzwUP;xre-lrH^dXh>_bN#P5ofl4-)hve^nT+Otg_msO8_Aps ze;v9P-Yh+Ba#}Z3R%AwCG6aO6ZkAl-)2CZ%)>Nz;O0z7*wQ+jQVV^ObhUC1K71;sm zTqqxXS9Tpq#R;}3fe(j19U$ourg`zF@ zRl4Q;cP}{u`+wiP!Xk74HeV;FG z>GjSQV$O2`{C^!o5)%1w=@HygmX{KCS{q>xI-qtb29bh| zMRSUfuff|2EyYpepq+=k%TiPj4^*K}x_++ps4~69uxs*{5x_OQnaF9F5d$+LGE?4Z z)2K^$d7kx^?6+c-tr~l$D!R1dwC7LY$9_96Hc*_8BMMWCeQ7^VBsHvrZZ>iK)zmgO z>CD?b#x(ZQ&Fd<-sf0|J)Cg?0+Mbg(TdLyQ3^bk8>xw6Rk~#oM*aB(^Zq0F>4bY4o z7g$d_DK~U7v}*^wxR%qLk!6suM#tm#hPdf3SI*+BA zqEmItEEuP}M4an=nsQ4+>Tld`&4JwOPnMGUN6>I@^ju9_xQ44(u6i>IQ1w3b3lO#0 zqq%LqTikp$;};l2u_`k6Je01{qrs$ZR1}Xow@_E7Y|zm?s5`^5?B%~Bmk1ga6MDB) zB%R(zdXHs3)^l+yQgPgiOy*g28sPFRwmVeGNOI43n&Q%g7`HLAOJyZ#Tb%eU2nTfU zw)JicKHH?W+>hGlKe>Jp{BrGfD}>H5{{RSX&VbX?+vU-g9(e*RVcgs(>R<1QCO-Yc zKlYu;AmgHNC=u?U{g*;5<{3y`3V)Iu91G*wv`UW8>I zFFU9hE%B{K#PqURvRQ2zl%*!wbC%MIXT5Anc{L5F>nUN9w7Qa}-Q7R{YW+KFdum0F zhLh_F)P2x*Ny{_$iID_CYCfxm{vSSno@->BhVg2$;2Bn&S)}B73mMWbc`l&cn06LB zKJ9pA%X_J3ml+bHAiT>>sibgAY&JP{tGgohTxV14HwaqqsQ0YK={7U0#|;l~v{~dR z5QpMh+dNdZ)v*hGPN?jAw>6{H?N-ONOGfj&0Z?vuo}ybUu3nE%`mKQt@0{B7njJZ8tHg>@7|* z+I9(H_Sr+|*a5}Oyej3@ZBDz`?vXU_P3_TdT8EG}x8THmN8N34IdNUic^y$51niXk zCs(o&;zvJwWZYqCwz6rqYmMJ5Y+rIKq!iq&*EiJEX=;L}6K=UlQUOzKFzl5fYv@*u zk`Kevq;$VaV`;1j&)hDq-}1}z!e&d6No*neq@Xq%By?$p$H3Dm_)vg5$t1^i+AO0^ z;EbIlH=aQL$RoUn9AVT;-BHx%OsA4wmuZ!+OKh?t#kkA7UhcBKQNLfOY-Qc&5wf0i zj)_PBCANpc+5k#Ne(>EH2-ED_Gth@eWeVK!%CS`Kur_(lsJg;EBg^ekw}KsRt~Ga6 zJ?#e5xQa2odbc#SKc|yatGXK-szyC8i&q2@uFj8oHKF_;<&0CufAzJ(Wh89m_xnmaw!lK?`KW=>|xVH0ZZA0`bvykgfp)WA< z9cjdcw&D)yS_s`L2grgC-%yQ4)$C(VpND37iPy}g=#r~OGiPW&a-K)l2;2sm2kAWN zOKI^w6`M|-N|gg?4%g@1$8xh#%?y_umY3PnC>I}R%u4T(&1O6HOHs7lBRbZ1zD>}8i$*G?_H#7?=Mb%@k-p1KrYiRLrFV~ba0W+u zLzQ0X7M04*GmQTL9M<->k6_5#$s^!#W{2#IuIkv79(`dcROKbqx;QvH6s;>MN!$_C z!6{2=X$eXYgoGg}03|5{U;rHe9zwJ)jSEYMIinMak@s31nEhi1^?5l~pek0T1i%?{ zKP5%`9^p8?52jNs7MnvX-z^v0t~+|TDLkc2Tq$lg$7tt&5XWQ117s(2#Uig>OC8Hl z`^{AvUH+157O>(RLK=~BwPlf$$B)4}5M|Ol{(2J0K5DC4sqH7at;?!7$#gsSfHC1P zr?qGlgFw?SB(URy_r>ky$kv}89fBuYPx}sQiAem#?6m&?d~0i3mXEt-5*z8pbTmEo zt+jDKsEVlE!3ts^h)>_4E_J3uEcAT|4>Ek5LFAv-Hv8))-#NR481tL@G8;>qd2Tw7 zxbIJbLEKiJ#ruv;!pwW4z1y-sr8)5pzQuAB4BAHz&&dZOw^V6SLXxrGk`zAbNLQU5 zHc+a6XOZ#~IsTl3$M8QlxwRP^{TOpfC&$=$Cuu7K_@XMX<1Qy<#aquM#v zF-(h3bwx5nbX%^g#_vnp{{TbXII^X$#HG1Oioerapmo;IW$)9hE zTm5F3wPWtS(@or%QxKylmSHRDI=%Y3_n9ZCPjIOqI}L!W_f!D(f6MXrD@|_{lM*_j z`;TYEjv)aEB>^CJZMSyb8&!3mGhwwsx*Wk4`?Z!l2W4Fi#wsKsFi?W01eB>KkO&8g z#>%O!%Ig0BynK&m2y%MUy40b%BOv{C`uQiusc&i<);En{R?R-W-Aq_HmB71_&RHej zkf5YWxi69!+^=C(X$ z%W%DB&}Lm5#^l40^qV|IpuO6y4Xc~cqcFAwvfG9H&I zL{ztJi8=MCt!&$qw+Kocl%IEaDX9RS*Wy<+_7!g%bk45j8_3BN zi#t@r{68J>xm#C1XMD3$tkbgXRmzawq`cZ6#tpgUn`Rj$w-WSp#2pK+c8F|0B}0>` zQdaI_1Bo>Xp z3HERs$dme0F|DfXRQGCBZBXzJWUV=Ne0zhU#N2?nh?b)w>wCIBcIKBpl!=e8U=n(G z4k=Gc{{T!aIWC}kRKJ~T9y@D~e6nEiZWb#T0mb6UlRfssYgy>@wIQOX9RLIs6r-Tz zdOIV=vE0JVGaJh+aBmi=$Sui-HH5m$sXl2wK16Iscp|w;82(+On(db-PT<~iizSm5 z$JkY_ZPc6G2+qiy<5BH$9UNq#vXv6Vg>q6<5|#WQ6|$%+GNhlZBU8YTHQmpT9YI>` zz~+y#y0-H&OU}1owYXC*iNr7X{lPJhDOWY_kT7K?FL?~&sx zi;_FS-Qd@9Rw|$N%J#F<8TUJugTTz=u|6VJtCi_&Jt3z_zJlmon;{5+|$cm4^8% zb&ZB;rWi9(w7c2s7Q!snxcNQH<(tcK;Wk`m!EI7yJlfq*E-STs#AQlSij-V)a(6YQ zM+GXtRG!Sh;Te*3&nb@Jcq)#>=8`w>_yx_{SFQfa7TqGoA!-hqJDy~&JtF0kPr6!d z{G?Bo;+Fb^i4LVlipWY-HUuYroAi)L)SqYQj)yeYPHtFv?K(Mb)z|F-Dm(YJ5mFEb zryaW2O=9+YJ_E1$W}Qp4!EVW^uoais{{V9~65YX&i1%2@B|G&=izO-i=ScCZhZdbF zR^tu#3S~fW#D9;&$fxM5rC7sk8cVr$Up-L&(4?={8_v-tf+nQHw_weq2ZfFCWs7#V$u~@25`^8hJeZdz32AOgPj1`ox`3Vs_rR2^;U=!kgE_g|A^f46 zT|Kq95M5JjSfIs6e^6Bkk;iZ23X}U_>3#DUz;n%2()Sx`E!qomGkXHmLlco5)|^e+ zI!8oKWi4#4s{4Hv@u4QBZtH$yuP(01?7He>*qtFaJx91jQGpMT-Avn~uVXe+ci5A& zEhucFqBzyzS+8Jw8UWwAH2m{W77$_Du2G$?p3RTs5uzs<*DQ|hIJ(0&?59wjwS~S? z6bs?Wh%wTwx~X6SJAvpbSEL#bA^ERo*>|!cv&*FQr?9uPncAe|_O^bZj+bS;%y@Ux#oIJ&{(M9r(sW5wG%Rl@ z_GFcN%D-c+lkMIerVvv5P`QV8kCxQ>;#&}|vMXnl@W95pKAl;dexcGD8$Z)vBkT$EI{ zT5+NhSnKe&z;=DfJa$NVxNC+XuZn1S4TAGO>gxLTDFYI0%ZP~uM&>^UT* zTWq0GteaM?8o92RHOoZvICkspvkc2)Et{pC*Icwr4_k3EY<5oRDjoH% z4XmEfm{zpTwcKrbdjl=hDec8?PnzuP9{RyGC^Z+0EZGw-YASOKS$q_!D#^ei%+ z)7ePr(Ig7-8gHLzSG298NU~pPo7{Mp3=OMR8+2+6GjN*AGXjOOL3yR4v~5aSn^)3W z5|o3u*xi>)=A&!Qk6ZOS9>617bsJ9dON`q)F>ZacF3h&cb}YC-!i2b{rZ9)uAc3)3 z2_Fck_Fn@8*WCXAOsJss6IDgxS{X^|aGEDxEJ8AV$wp-YXZ9}!9( zccj7;+ab~{`W?0SSs%2vu)6AkQq-8Q+S!ix#y=t-Ce#OkJ{|S5yZbOISX}gL-m_1V zpn_25TA1m5AZ@TyET@Sh#B`#>bq^*)JKl(D+=Mxv$|g(rT`k*`R3L6xSWyAgrDJ{zCv=~Lr@pD1_K#wC3?6!-{qIilS?;Fp zjPfQybe)Q3Z>7Fvi=q_zU5fjGBz9|BRmh&n)$~Po70od67G1engN$DwAzYYRc)Ui$ z7S^AwxFiBAn!Tnqe*2bbUPGi>KFmpM#bV!%<(KE%4@Qk9Y=&bZ(G5Ekzi?M=$!Dr# zq;8@XQUr~3rn#UlbJHAiOtl{p#k5t-Wvo*+mWQTfS!K&2kn3*;N+tWPohgu6cI4}F z5xN}NYy_;WD{{MuzhduZSXQQ>TQpx8L(!Yrmu4{I>QeR_snVtH;aAow>UZ4rs{a5z z_HLb}7*xz_P4URufu+hq({R&^2)Qj^Ro0+mcS{J>b_iWST{wKG5FS7Tq_&@#{6G6u_~`&gCuHZ0tmf ze0WP~vjq+|fEF88$aTc^8!fb`5~40F$5alz&Wnb7HDu1Zsh060DaJ&jZ_MD95nNLT#9aBd2WQc37z!22~aK&r3nys+Jv? z3f8UErIjd>PV|yMl%z`PNV<8a&`&5B4l@{zAa*u*$k-%okTyqv*bgemxI1aFw%Q_D z;x1$ADT^7H&81E+&!T!Hc&oymyWU&1Hrf!{q)d`{MvQ~;I|5u?zuJu1 zOLkh){J9H2;?rpf;)c*X5DL$!JxyfD?V=tKPQ@oaTO8V@ue<*eItKviP-DEk4V*EQ^GcGJ@3L zkb0?S+g*jkunJM>CYcIoz=$*o5(O4(!)Br|bx zYT!tW#ODQa-(xEz?tax^nH3}`|Q*L+r!6>rhc7}cC>vdT0VS)7r)^%kqpmkD( zLL+G|Z>*Bo$UUSTj~>d)qD8+>ZBmPt<`fR-a_J#E8-wXP9?WeW1awP>;-v?9K|6v* z={@xc&a|%2m)x1lEf-06#$^S*&K5^(2@(P~-?5N`@Je9q)u#dc(v#MvA#{AbMSPJ$ z=B=~(%1(pt8^Ib7cY`4B3)1RSZBd2eC0kkbV(W}uV&U53Z-vTDda&O4)3> zJW#L=ZspzwZm1TvM9FFTKr3e1^;*0>!Pcu>vbd?$RRj^JIY)fxrKaW<;%GIq=rI=Bhp4nT9@M*9QRjbdfTHhu+Hc6X#kclUeTTC{0 zVX%6F2iH3vq<-qqszWw?7e7AJ^YCV$ih+Q-yTIT+D@zT9Tn*9{i7iIAxxgt)LF7jR zuu4zm9=wev>dZuGh3-UVLKPyzIc}@HOJBP<4&?PcMQkjQQy(C@2x<+)WT7Rbozh$* zf7RFY*1qsN90M+bf7p<2je+^5JBm;0Ra6g}jRT8M`Sa0A;S48{Nzw3?XzmD=079{Q zP$2ga?v3;{wPc)rLSee2@@$3e9c1tB>p#7d`6s~mcUG%Mzt(#L{{Wiyukl9eRUw9D zM{+cl!;LzUm8AHJg7M+Ebz1EUo$o31gwkmm!1pd^_U_WGi#)(A`DQO~dVH)#L+(q6 z!V2A7HU(%4dz(p*c8tSy<0p6`h} zg>Us#H>Pu(`w+owpDboqW|)gVb{LLZFTZeFhvFGwAgT2$eYk*qb`{E*el>NE8bPtM zF9)~eZnpiGNn$@DRGf#3$#uyMM_Bnaw-R<&VxZbU-DkX4Jy1KPmyX&M!KCMypIMFq z1Oe+yT02KJ?{TIxSDHr5q$o4A2Z0mEc4tiw3`tq8cKqFFyk@vFFs8_`-?7v?d3C_$ zn$VXbNP_Eg972ML=OF~88~!h;091{PGUch$k#f`f{r%g(8>#z;vU z07ywIB&3nOb@nln+43w(*^^tA+i)aXW6DctNJ?34Y^7lVBcK2sfCFzT(c5>UWYMB+ z8!u~G$;g1-t%b5ghk{F>1JxKfgVlRFK9u)$rU4}Q$p^YJ-H2|5Pw9JBn{}f3XOU@< z7g6GM@uqicpm)d}oNO{CI+;EjCN;@P0wN(0# zi_Ei1J}ALwjt(ItEQ9q%5I-2tkJCorkQaoo?mMjc&N}Zx_0!n3f$CiTRd9hf{2d?&X-QBz*MwJAD}K1NN$m0ge zI_aIxxUt6?!}8}R#@v#bx04B$8asYWWSv34eCv=3bf+4Sl0XGjHRdI z%6x!`j5QVi0C||Aq5wlJEUc)I0psFDMRaE{fu=S*ivU!chH06*ncc%S+i(k+J>XhP zvRo?J5tuH8lH;T#4(rm zacDTsr)qOv`7-T2i?yYPF84*H`R}F6drmAZ-G<34Q6UN0AZ!5GRj>dFN)nW%C`d|D zkOESX+yET_9z&&9ns%-lr>koRy7gy2qSnn|ziQOKp?d-YmUyrnyxgr7Rqh7{y&*vSly!b;}LFnwf5IGYGHQCiE@0!eYXk} zp3%NeAc6+zSnJZK3dwSqyQ=H#+mWE8{VO%*1i(j_33au8s?X7maqW3+q0gmk&F`%3 zX@^;{#k{2V>XC27v=H?;)Vbu$gS6%n#pp30T_5a#~?s+Rsp zF@QhZfQ9#5v2}WXxTsb0bHMj$BoYu9#k3FFWW(w5cJZ#vg? z<-KbrG(EjhbA|^s-~iCb4Z~1&R`DL^Y?r@fC53LDL+K$v^+LhuP@ohKLMvzwfv$gE3#nFo#OC;ps^JXW zWUG6#Xvbwmk9iw(_oi8O#`{6w@&Z;oN{33gTgcvi*peW)E-PdOHp6Z$lsZPoiqw>n zka~hZJ6AKbc5NySYpp6{W4SmM@-o)XCjcaB8gg1RE*$VhR>2+6d!U~$eybk&lv{Co zW<#h~gT1>ShU$PF?gqytB|c?q;(_K(V`&aEamBD$Xvjb}`^&E1FRSrY!dkmmQ}!Sf zG(3Z1quIP9WO-g0qzwGz7FIH>gm^9-HwU{iuju+EKB7WG6t^5% z+>N^({3|D2@>qAQZQK^s?{4K2i#Hs4-v&8;UFj6ami#MsF>1TS+^k(LXg6wZ?dEkuGv8g*&C~JlU+8>x9Y6hwQh-L zR?^XwxSYi|0V772hM52wLP$9*`t>nYHq^x8Gsq029~ht3(L?f^zF}f%oXbq*&Aww8 zm{dDvVKy?}W-LXeeXX%-Sl;CoE+w+mrGSQ(6Ujw8n&{mav1!!YU5!G;CQ8Vqxzw%sa@ zyW3-XEzF&p;}+R(>8;{QT*xfZfm*CG6nKg8Q{u|E330CvgrxM^psBToNNxQFi%mMy z*@8Z_Yk|fGZx?B?40ETl9zic{sj+`t^NE}P07sbDD9G;vd}+UaD_Ezr9kxnV;-JmK zX9aGp%R#$@e!F;q+IRQu_Eo7e>20=KpS;|z5u#h-v}8Xm_d*$Iq13HQJpig>In=Bs z$*7nvqCI$Oem{1_FR@}d1*0Wb1u58zLo9^%tz1arcG!cOex4laGK^W)6BE&CRo zu_U_+J1Y-%t4g*#fg^;e3GsbEu+iVM4lHsF8xp3{qeklO-BmWS9O5-0?-*J&FYM{N zet8x2#@o5{=?qRMAUS|!o*AvR+f#FWXIr{)q^-Meuty3PTf^#Ha<&w;5T~3QE;9-T z;cY*<9v$794prsMv`>&60Q~#aae(AkTyH3E zP4!I2{3ms#8zt?g`zUz?WDkq6PjeEGI=aVs`f1%HfhErL@*ZYkULaj0STdV0CE2g= z&UI@Hjm8_vQSS^+%4_TLQgWxhEFCRN78Ly&JT$3 zs`uJ2Pt~Z}M|`$HWoGqiYvqeGtqq@MWMkS6$zADEl9eZ?c_YfAIwS2AyZq?dd#W!p z8zOm#ui9@YPY78AX$|&~-AVc_zO?QN4@E5t3HX6cYV8VjdPQtD&8vR3fHtc}W*Pt$ z1m;>>4RA6>5=??fuR+bQspyqYWaiV~t9$1$-x*}C%XP)q9X9)l64d6# z^Bxrs*IT8+Hl9hw?2i_&8I-vAwYf<@5*(PW4!A$5upVCi9BOMm+|9wHw&IrC%&U`6 zC!*S4nEr89pqTDguTU-bcUW#dn^|4Byr8dv%arg?+Q*S@BW^5?@!NS;9?D}Kap_T& zAYMl9&#F6|{4G2sT)A-!>i4@ZJ$yiu;HB-^qT#abP_d|iyK`~dx1ut7C$niGDFApL z3E%Ap&a^rQA*=P1D%1wl75H72+^00Io0zO9M2y4|cw5(IMSS9u-!>_KDN`iVwKjbD2({0^?Ee+mnR^_!HRy;Al_R5bPII zXVOhnlIyALnP1?tm`^p0lhx-D{{Zu^I~x;d2Vs38-_{ZDg}LdJB13`h&)lpFA@{eZ zUDfntz1gx)@J;j|r0#15;hMC|s$$z2FY)MIT8K$Mng>(%)E0iH<+1Vgz_j~6YA8!= zhC|Z+Ply26Zhk2Q_3Asj%|!-Zsf!=EG&W;?1K%>MYhPzXl@VHY+ZLr)8kI0cfEtv> zpSGZV<3NVb?z`lzyGB@;vShhBF5+9=s@!buQP2Q5p6~$sYHG`kyxJXQVQ5$iN`c%E zd~2LV_gWs@F{QYqao4Q8%|ws6sH*VwsUA9gqwbLuAw<)gdknZx61%<0?YHao8b^Wi z@2Re~EDp098~bfO;!|nfZi0Ld#4E%Dzj3PD01CZpR;lELJlnP#HYpuoleb|Q%6tzm zotOdjMQd5Z{iEi-hU90Odl3pyN$jn6IXyS)(Q*(wstKZ42CL_BOZKl+el%61gx!ZFsrG*MMwchKNW*W^NV3%Xu)2OCGimi9P zs{p2Qle=(@=bGu>Nl#TNBkdqnUxvwzde?nP$sdx)X2A;4h{aNzNF37I=E>XFl$Mfz ziCB#2#(wxhZobf0aGl3!xR}kYjfaWdXkOhq{nZE1O&_@Bns(NAH&`?1i_7ja?~865 z&{z*4*Vcw!U+!{4i)E%twjm(>5CueS1T~p8`4Vw&Xw)!|6x!9QRH(+_ zA|&SX`{lC_J_HT{F zM`-=QC`jl^%1{UP#8eR;%i0E`yZUn;Q0g|9aho7{_c-w#;XmCTxSkpS=lCsd zyXy80m7>w3F|(Uhx)xTYy^~xQb&SA`Vq}kaWezF81F}`dI!64st;KEAdZnl8-jQH$ z=2^xgo7=6q&7U~9xs9UM6D5nqu0v5)E%IAr?5IS7knaBgGPH6*+$knsrrIy484ar= z&2IQ^F{O6QmRjy%5bW^e-oyl>ggBAVw#<8SQrYCS@mtGr#j9j=t3%n>1H`oBJk=~C z8L+VqJ-%&+wq^zmcj%wudZmjj&l9jL(*;0Lu&7%5DCHMr82!czR9VQ6g z8v_s^XN0VG3b!=_-IVBa;g(r<_wii)sGia3*L-R%uOQCt*xicnklXEZFuOBP%bx5X zdF3VBgs|5l(wSvtv_?qe6qRyT#8vBS*6kd{@daYKbA{g^S>4?Yv1P*U%L-GSc@yry zIO2zTl%k?{B$_R6#xe`Pv<9DEbgCt#tm?yLyW|czIVwzhjAlD1%tCymjvUo3^tSm5 zQ%7<-5-QEYbvo6m@hx+#%AX2E39mDIw9O?GEi;%$#}-OA=%A%;74%k}l#)RPi<wj49(;ZZPB@_0Y>sgiMmJACQx@K8D&8u~$%@uOxNpU=b zX-Mg^d)A^zN>DnfL?m@JN`}w_4JtIZ=_HdP2>qb|lM=JqL*1M+b^?~X~Je%T!`JZ+@j+dmn6k?x74Jn5RD`P;7=yYi)bsC5nFf}ZQn-y zjwEB*ELiTO^A zYnG5-FPWZ8X+kjD5|~Rpyh-n>L##2!=HU|QOqZYKyFrqYcVQ_nX@$N8zelckW?>d^ z)OT(+tZL0RT+tzytO&^2j`KF`<;mLI-gJD+u$%y*deAI{;c2z0}wzVq5B@Wy1_<#Uy@WBO5tKm;< z;=pyq4!a}-*1G9>#qvE^k(XuJQ}OUEA-E>l4f%3PlXA=70SB^38@i~itTdFQ5?*s<|!EyCy$#uuAs_=T3P<@|u)_)Rr1-E$2H_D1+_M|>M_ z+kk6_8E_%3$Qa%myL=4^mk`oK8*eUTGBniu{{TD{kEio-%v)1aX2tTSq-pl9894?g zG6cQYk9$BWxj`L19@qmRDLW9fNKU}ko7$@hfq>!}Zlz-yjc&(pnN^len)7SDN<&dH z#+4myB_RnM{rw47%{+vXK;Y)-JEjfz1*rPQ<=-pQdwJ4~F8wh#m~!tC6zkJ62+s12 zd4~xEe3#l3@NvZ{NqGuVi)5Y6$?WHb>Bd)wwW+Py<Tq=vGAmZ<6 z%Tpxwj+sH=Cn`=d>_;<~kNGR&_iNXjYfGQ`o(YpL7PXX++bxAH zAefPgf>IB~CFQHuq;`E-vEaCD9X_^bmZsV4R;zHf#9-j_oa~uGHXEs;=Ovx@D*DPQ zJ~e#XZuY!_-xmF4p2YiY$&^foDTRf$`wk9BaVqf&Ne6n%3n=Q9D1C&AP{;g8`YdH@jBE1KsRhU@XL%SY=t`0qAu*LZ?_~Y z9`U_U`fcrRkZ7iu*ez{m7F_(tY5Ov9tHND@b+x-4$C+u=NlSbkgR@N%9Rb7zgoTsP zb_Qvx>C+evSz*JKe#-9`H=$zq^moc%x=30>oloyk+vHU1>iAngJ|qJM_G82*Vmg7V z(KBURE;-wms8=hM+7k+Gmm=IEB1mF77Nrz0i2_PS+ZB{^SHyZ>qgsVKXPT7BZbSm@ zbAm@^K$0Yk$q~54dW?IjfD9fms4FIU?D?Et^!n|TY6ZU~x@7aOa`Wl;#Ww-s+N}=8 zL?Fwgd#qF$1q?UKj_BvQmP)Gp&>eKf8^>m8u2Y}d?-!gJN-YiDo4LkyH;Zvj@MZ<2 z!j(9tKtrGsL#?lV@mQMqruc5B>t-X3-)z9D*@k2n#^u7}9#nY+t@J8V)&`|7I0bf?(w zRxB;#_y(Klwes4cKXltIY__(PN6~N~C2cEX^i%=sTP1rbdp-bDA5I&H2-|3PY4O16 zu-G>E0=deJw5HqSsY;$N~>P!9ehdy6b>gL^=I z586_9gkVKIWXciLhSs+>X|*@G=1&^7H0J=*%`=ZJb_t&QceMKJY?RcNecL+1 zWWiBOi78aeT%^2@ys<6cxdyGUq^sztAF$Uf>yC|Fu;wt_@;w9_9_3%+@O<(NAkg@a*wnmf_Q|s%IPOc5;#+B?@>-98s5QqjT#J-x&|sw=>Dna))e>Av#>a3wyMgbj z?(XNEBg=zfe*TF#eDJ0jOe$qbJnZBpx6D;_3*juq{ zb-|}$JktP3mh5Lj+K^PS$)tx86odc>*cEO`6*Bf;_UqJZE^%waHGcc}L5<$#CMU`g zT0&*pUwL~W-q8Sdjg_c!N`~Ma!RfK<9nqbkO39ZDjBFZ7n4DQ<6C~yF$z}R|2Uc@8 z9sbWH-WnI0&(E}@9msD<;d*X5t(T_Hdv_>{RC!50Upms2*TF;1r1uM?mt_2pWa6oW zZtKeGh^am*^H-98O;p&M^RVrB)D1ATJj-$N)G?gf)=ue+^0>viAP(ZL(YWtz=J_VB zOI@}#wm69m3OtV`tew{4&w7-6Nv>Y$tfg4JqbGKN;0}JxJHpYTOPL2zkxRAPShg|l z@q(7O70Jg})yI~8vX6;1#aVpJi`p;R=ID($jK$gxnnh;G0(`U=ESCF+XdaP1R^246 zr$R$9QhssPP^FZiz08-|N7}aKEvxpK=q!@-xE9GWZ~0qEE01q!D+~zGlsY{r*$YH; zsXL`EEQIc)o;Uu4>1-_<(tKx4;YVo?+aR^_g4zRl6V?KxI-)wQMZ%%E-0THw+lTCm zChDmA$K=p=4emY=0N`Zh6H=jV+xn)S$jp_Wrg~ps(+gy#Es|zkZx?rjyXE@iuC5$w&gL-1hx`)m*yirTQa1Hs@{s0J*a)$q5UOYEb})F-OsQ*2cRiv02|_jrUTNqt$CsB}!U}Qj|#~lTeC|w|8AzI)^Y0?gacB z;&Omrgj5(Qn?KWQ|+h9gLiA`V95ra??pFQcx!0Qbn9z)DCCwa`dFKR9~Q z=V^;{LPMoZJl5rx+vwwG(D;uqdK#;>A5ZLgMWviuIJ7QA1j-U4I}t^F)(m8C;)q>w=bckj}vEDqMrU+opEH>s&{Fr&f6V>HQG-Ncx0IyWCz@tf`AR3saY zr+X~jW7{urp33x{>7gt&93_Ob^3MI6VEho}o%b<`5uPOWN@-_-xOCfEbiNhqT(!dE zmqdw&BI#sgPJNZIq?RoWB}{fo0cGH|ly+NkzuHWk=@hp{?KZbjAB}WBuuzpyEmd+^GDFKh<5j65EW>)w%)<0>j@G!QRV{3H>MGU zA8Kp;lV9~~9+MXnwr>6fhfe^fvnQq6dOMc$y8_!sbfEzx@+P`A;RXfb44YNn3~BJD zw5DXml*(OY_S7~gS`-RYk~(Zg)nS){&4PowPTqd+AbELCnSfIb6IhpwhrLjG6itfb zm-{=RqQrX@^{dUIFdHGfe&7s*w%8pzHsq#5ASa*&AXJw~mNV^G)Lmp>&OnVNI2Ja) zs$tvKRXOAy;vJf|6S(gYlU(nt*p27epD2f_8V7H=TwqNu;W4q9GS;>rIW2p}b_0iX zwv zeX_RK%W$LLRx7vaje2?FkN*H^;7N}(#(Key%0D4*eWa~7Hn4blpNzy@jEhVzE#;6J zZ|TX7(o~7@M~M$ZTj|@yK^|2%x85ytZWgSf$1xIY*;FQ7j`}`}oRA!0&+OY)-*s~@pm(U&p=wSUrFi1>wdd$eAHs z5gs!r1#i5g(Ji17eFo;SZ#kX8y9@5wEvQBDOb>Tz>ePm!2ak)3X$w~Y)a;SD+hMl* zj(o>-r&%4%@G17tG+e8m*lq8WFEYh5?o)_Q5`D9yBhA* zLjZey{%N&dMokM3T;b}@w`mrCf6pRcuuMV*F>{}2#37D13GTHSZZ!SkNIX!0QV53G zBltxtZZ#}QG93#`TE(WgWyVgN-l0aHm*UYk<2TW zMs=q8fq&Edo<2kEV~|~tyB)+i0@Y`=N-K=>HxYCMT=dc9@Cw_;1>eb(Me+9>srn0Sc)0--Q zJlcBY7%y)T#cu9@MRVSu`!{Ijf#lJ1yN;IHat$Vw{C2Mq?r45{n1h?~6B$E`P;vu= z{Q3$?no;cmDJlu+tZzy*Zpj&mIEI;F5Tm+;yu;S(Bt)UaC~lRdX-C8eAc7B2X$z?^ z2Lebv-ddF5jm9^sr9RX832BgvU9(%&cZ2b6jCS89bBwH=`8rlBc_{GR6 zRy{1&e4q~Hw~I`O+WfXM%L8h?K}uNnRE{k1DD2WWr;nDypcSHGL)he`+wL-4de)1PWyJ7cq5^F2YsF{@3Q-7rpW*Jp(>Q*z2`* zzj5x{YN++GYMrS18;>OZKRJHpvA5KRXqvKrO2t^CatojcQNElBl@HeaJ&l=rQfbqc zJc8wjWHK=fmQz^`le2DMc014m34AFfGMPwqV6xyTAxF6EYBj9(_*q?=--CNy+T!B( zdvvCp!}W5&+L3Ew;1-oKx0J2dZE;IVIk538ET`dfaGk8|!0qj0rM)|_oVWZvwyo)~ z;N0Ozw%p-InGRb@W<+T%CDxv8LuDaoP$^PKBoIc`Gj4E(&ku@er6j_oJ&WYzDbeY+ z%+uYQ%dMG)Lf%BgZ~_^)J3Yw?DFboA#Jq=)K_H>T5Oy_RJ%(O1+uK`D@#wJ6;7&^R zQIJ^H6nMa$CK?>MKnWcJ6!Qp{f#&nTcXVqq!Y`i3+3lw{_J6WMlZR>r(mj(R#jckX zvl2A^1VC~emGbva)bAJt?$BD9N5YZ{bz1SLt!th`q_&MPyUoS*8y?E%OHGFO(TQ=l zT-vnBb~_T7KT12wXtG{GQ%Xyug&v};wZ+7^XjEiCgQ#5#?E$8-brTF`WE-flqS?o=@*>@imvfjSuE(=Nc)zuM_ ztAVGrAJ!UqDfIIC$CFW zKxgTU-6XILAGV8TH5H}BMY+b5+^7o~{_p6@drsi2@6s-=n^jA*c@=C!; zKYep9vfEh6Z|ZsHi_i9C7fng!BmoL~>CyHm(sVNSu{B3ksj17if|4Tl9@K z&16bgkEzT!>nWU>m5vvv#R`nXQ(5pEk6&V7_OT zES~DxrM{Nn-`<^hW!4R~#VDysODq5o;7goRo`;aGu=bG5Z8&~|XP2q$#h5<_Ci}Ek zke6GM>e?t|DGCKDPiW#h#?{7`tuVT2o~l^%Pfu_cHKF+0Vc6t&Zbd)$EtM-6Md4;|Z8~Bnz zAQC2;b7hqsn?_%nNxN&#J%^{^;zncG32}kZPwmX zXl2B>r&JKyTRhaY9^jBSDn5g)dk^~_x+|0D#)GKl_V+Efytd}MlA4*FdSZEZZT|OwYMqRGy7MPQjx`K;*g~c ziQ=CjS5)~6$mTm`i0ab;g>7(Z@_okef5LYWs^J-hfx#zM#Dw1ThYBDJ>&P07vTBIZ*f zGqD_8cAWYtT6W|%@$>6V>r&xGZ@%9eaV^`+w;N&~ZM58;VO)@&TPvX3wFB~p-&L=% zWwruZ$>@N3BkUb|)vY2jagNsg2Ij;YY*49F*_iM|R@p%0 z1jw-`u&C2xq4t;6NN{dG-$UzBb}?HI;UtJg2{GD++v#miD2`=hA0F+8 zb#3kU_N2)9HhgtJxB+t6U}wn~mB} zH4wx|$oSq_>Au5?cilxJtuRz1z)r#jYQt?TMPsvRw%KKrld#z+LPvS>0D09fKUs3k zSDzyn_Gym_TsZG4EzeMHOqXns7CJ2uPzX0U3G4I-VJB`&KSr@eooX(;5kFoMyk`qv z#T!oT(|2-WLHJdAmeKj``^-j9WI6?@&g#10!h>sr9@%S!9uz2TC5X{uG?tlRw2hL4 zAs{6w0000RQkt!2GLs2%hG~wC;+kN&x0uh*zh!rnq+YU{%c1+F$d2Tx&46j4^;l>n zw2tKX)c26^+MC>Y(Q*|TuZvXR$W&Jz+EUE>*UE^n4ab7O-h{>113zj($izb zN4)s~;(p54$-I2}pxW8@$!Ypv9h+;_YVi99TEkyq3SwNArCqNI8@AF1!pQU;$RC_~ z)rL|h*7D!@4rNAP!9p_aJ8vq=CFeNR>P`idw*|0n>>v?6kVs2&*xV*VNAtFEBW@}b zoL5w4rsHqZyzbEx2fa-t5T&-YF57)0#@_nC`NQ5>duK-d0=w#S77LRv&Mx|vUwOFA zx|ARu&9YQL9e$SEcb_`gWSG`HnAqiGSzVST+SM=s{7FsoZCiIJc%?w~3P}nC0tl^N z*6$06`kJEz|1{(RN#!TO_L8LxC@(VDQ{~_;-&PPol?ZLCoC{WpHbSvos2?QRMRpa+Lnh|)9rx^s7rD3-o|Ur zL2Ud<8^74lM5mHd3>QX8mvfbx0HxV#k`mY(}V-S!DX~dZK zhvweRM(#hH$1$gVN?D9Xb{p3gBq*u=Os&#?ElA{{_Y=3>TKv1_Ecv585;6HwQ}&3? zp;EP45H$Bz5`^ryO%0hrGwss5L zDMOHRi0BM1E3i%^VtPTTxzcxO-#zF}9c>e&DgcPA5kmIN5)|LjA zT(ezkMmwn3-aT!FY{Mc+gNoT6U&(g({`}x+iF|mbT7tJ(97BAS+@Lo-KFis(wtXs< zZ(Iztn8<;cn9O6$X9QCOX7?Fl%euIA!J-A0vQw8nt!%jz%Y6v<<> zVYv~|mxw7J6Jh3+@pSrqKsD0zhhSgWTUNPs92DY-2M{=Lv{S-!IgDE9NLlt5v}@F5 zF!^-4ayiMxI&$`_#_a+mD{CvBH3Z>Zj$+q?b~21 z_n#fB7CvFClHu{fw8Cr%jfFa^o?Ru2J1HGhrxegG$RKq)lqaC?7021x-r9j#?EpGK z#&EUGF(O<<%w+~ahX`;5YSpbqr(_wAyO{@elO8?CBCzt!M8WNucAjf%clhHKyjibS zc^3i*91Cca$`qdd)Hxl-!)M~=qMf&`-WzART&_#j?NN@4G{++;n+>*m$!)xT+OfGV zDMTbocxnRwK@7cpj?|Q_gp#Ff zqL5d&Y!TvboTEpuOKq{Nj@74^ty;ZG0mv@iZ=U+wfl1vjE$J)lT5N(2=s_obg0}XK z&$F_lN{efDoE=P&1hyPpX}Qic+QI|FUg^vLA)vS#bSu@Y_dK^D4ud)60l)y_Mp6eI zh-&2!6+7doXnjS|w=mI17Y zX_mpyBZN4|H7@JTKtKa98sJV~iTC7v{2NA|O~7#J#x3(v@_WU(S9QDE%tf)8 zk0M;xpCglsM05nCtM|u~u&#x+PQ>h1$W|*MOBVZVX5qt*%S%vcmeLZ0jfa2(_18Lk zJm9*Yb+;oshn!=!f0W@#k84u#<8e}L{{SdH;c=q3(3A(ATAVwD5|lN+bzH{+)VT@c zKiUf*N0_$QEwM+~3XtN5>8!q!vennMC_3S}ImOe~NpUf{lZR!XGz?h~;v*8Zvz<^< z59-cf0qW`d!&4_t92B?Z<`=kP1;OLt#wxo0nt@<4ZYtExT2yNU_msQ6q?GKGl>Y#w zO!w}7`pxYxU)EtgAUOqrPk z<*h+>)|R5;lInX!ZcfEp>^1|Stjii(KApYH)F-ee8$a)9adS7v4}!5-)!h1hpS8UH zi?{-%SOn{pyD`-(df`Q5hEmnRLR8c?5G32KpcWkz;XF7}bZ{w)Uc9?}AXy^`NGa_-ZFLJ* z#;$-0oLjG&i*kzF&1gjBmqA}`bIYNym2OGji$!F9inE^J%x4Nvgr&BKN>Y#lQk3if z1GoS-0C!UsB!pR@r){>5kL0Ad+m!0l4X19<#^lglQ!QkiFJLY@xW;?{00m{6n(1{z zCtREH2QCBSB~hBiA5`S&y~;JiPOxi4X_Bqb`KJ9MbbzO4GK2sF2}uP-ApBeLCvLT2 zyJXi#-gK`Z(<@Tc>dK5k2c2D_!iZH|cOP!&3{4^TD#ENvA z19>hD5C-x{19=KcwCGY2@_%{9*(GOMtax^oCi#70^K+XrODwAEcE~SEl38JpuFN|j z-ICL3=AsfjN!V^{K^pU&>br=Mg)Q3_KBKtGt}woN^nb;0oE^u)OXF`8p!e3w$E1$k zB}KpL%=noE=#B^BS)#V2SuJjODYjSEQ^WXo_dER*rmV8HEk#RFQ9b0;mlyPHo?g?} zRyw`ev_OFX`p^I!p@a;uyhD>|(0xG%u+9Tt6T8DcV=$`RzSCsUT`R~R!D6_Z{qM83 zvv8F1q))iq<*YKxk{@5j=}B2$+k~Vb@c;_dX$6b$5Mn}}>h39puz5zt<2jPf9v<@dDA1C9QlEKu?n&?!pQ+^-j!A{; zi$EeZqIcl^-9UF+CzQ68E77?rVSb#IS~ROveY0%plIoSgsV6gte?&Qw8bf(iP&thi z4OWP2J3a^2J`*RKRgDf)b7C_wucGn-$EN-jT~nd)zb)1pofWjci0a?tYC1{a>!>^O zaYQiBddi64U(;Hg;}g_S=JXUXok#DcHi6dY+K zNk0ir>G7!BgAkziHyOU zCdl0QsFLg@B~l~4q)CO#WGy}gACi0a^X{o68PTo`cJY=-2a3|6vXHM7sCXK$Fl@>u zUYotn+X7XyAhS&^-fRgld`u>Ru?@mL(JuPO|u_j>vhxVu1mJs9~@D8#jYwceoo)LDsAMx7;q3;d6xK* zxvq!F@rwp!V4I8OHny%dyMjA6OIw zvSpw|7dIOV1VMf#K#(~+E#(>LkA+Q*a6$xRMfY>(L07_H0pa4^L{{T^CLA7kpYO&42Bd|=2vh|1# zA{ysY#@2?o61F{MXO`8`qabaBwxv9|bbG}dQaCAGfLqO`EJIHeDwejX|DDkEz2*EG|7 z4^iQw7IBGM?Kk=KzsZ_>NR{VSWjoa$8Q5pY^W$ip$~0jcAMcM>yL22SW#blwz$=xMM`pHoJ2fCKTm+w=p$(0#QnC%&_Hlx49eir`#PgQJs z)+DAS-ds)fn5Zno*v4hVd{R&j>MHUdk}GFmU%jSH(uxq` zI5>o@JNGC8KS89myLGuDBoykDg+*D!k0NWqh-|6n70@LEf79hwJ&M>ZHrC=51vp7t zsvX5=zwRC1b!4=L+vwA1Kt23Y6Tj%EQ&U`CI^YI@0WzH+%+y;}@>7QCUSX7>L_2J; zO?{oW+`ZVTErGXP%G8~`YhkBY%5b~`8N?EhpOGxkpetdv>ur>!XYW@d_tZPu0Us)2 zRxB)XCoaouwsnM^iC{?*U;Dc#%LmZarls?7PJUX6{U)uy>h^s$?|a-&b3;QplRBw~6sncpCj?~U2tjSFqJGNb^*Ab-+2nv>!9O1-6!eJ_9ZPLAfz+vMQ3*&MWYvcQPR5NftK8>zjGEtQf0~RC>hFHw0QjGR$`!B4 z^1NpWu}I0Y3;YW$rZ3}WPJA@bJ_!Wu6NGDe&EgiJDK}kp{QvJCs#F0P!rF09x1svKdykW8}Jrl3rv9w>iiQ&}dVCvZf3^|$HTuBAdsLTN9aBfg)*R*rnB;qr_EVvFIETu`Z zQm5fF3w_l(V=SJ#ZVI_8^(53E6;fe)OX@6QkUmtz@tcBrclJ)tVMq0+^a{|*ldhU| zc(=#7MnMkRwp42LniQJuAuj%Tb z&bP~oWghm8F6Syu&j&Y0`Nf5-=de^P3CwcTrS>_22Rgl;eZa6E|z-ZRZ5 zI%2IcJIOyKi>Eq)k7|B4&Pk0K2py{7&4Vqz%4N1|XJ=wbX!UL6E1?IW8z>Tz37~V` zRg0+E&86FAjohZkXt}!H4UI=%AHytyF)4QB^3THEssi@`<$MZsDPxgCV6p9IjcOze zn%65qaIBhMMW~VFS+b?t7M5XLU@+GtqC=EbzKr7k03c(LvFz0v5{Nv&25Wb6I_#?QPACk67Z{ zXW1>v3vVRG8bB=uk8jlQiJjT}cJCI{p<}+=|G1<|s)=Rv0=E9PJ$qu%z zsaD6iL%4(0BoKBYsNTJ=c&|uyGz0F&K&d5Tn(iuIh&QcG)vuH#W)UwJOKf|6!Y{j`)YQJpRF{(j z0E+1oXPIjy%yw8qd+uRKB}|{<9`t^(S{p}b8dY6FnHrMgxzb6F;17w>%F2+Fvd{(L zsydr$(+ezlPur#5Zs;x`Pm4@9I7iN{s&wt%H5^w`q=)wu2vcE(=G zrp9v9ozH2!b{)Ij)bYzw;`c?RXl#P`<@1fck{pV(hk}w4pjYgRWX|soWI4{eV|gNV z8pR~yZCr1OL2bKhNI?z4Dfm^%U0-|;o?gldDcLDydUPvN^y8Fj+033tc;ZHWB$QKi zN^a^0f#9dwU#GX6Ycidk>P^Bm<2}l)!{22vpWL>#8(!(QLEoxlqmbmkr1Hl#cmS23 zkUgr9qs6v!d<3WH>|B*O~|dSsusNJM9Yjhv)j@v zb;%y^xNw%$!zFpQM*AuLv0&~2=z#LZ=i&#g7dG#3X?YE3YfPT$$ZB&thkPc|sL?n? zl1@q!h-k(&XVg31lP2$R#4{{%GH&=zRb_oF&zFt5vJnNb-kv98$4E_Nom4C5Avn}m-51xx!y0v44Nk=#Mrs$K0SSE<_=+XGyT?;z(BtY+CW z3Tig4V`LaU&lQ1Vzgcp7JY0txyh6KL=O8&2bSKjD4=2qlNg&o{=Bm5qgg}9Jm6&MW zLjIj(lGobH5Ex6gdz+v8ZMcxq$?;mapLCgpUBVchv`4irLznU!$YEzSr!@-au9xShFg^Iqv*CRF7UUSMz-r z&9R7yDwi_HDR9De+)8O3vjlJB+O~?`;CWLy#tn{U%qSM}3q(7Z+s)=uvOl&Yebu{Z z_FPL(wxm46sY=qcC@Bsg_@s`C1bB{>Q+qRVwrR}V4aE}f36fqhuB2`mYEqqz)63o8 zYi~hNhjo^2A929=I1qiUYImz#5xjer)7m=6BO}m!>fZ=&F^e7EEm|hMS|Cp!cZ~qFk{?VAoSq#NpOCpR#UOqnA+jVNK-NrS#>AH3mjBGK{Z$V zFvyT}I~uahaY~nr`{pqtlDAM@zFVKcloLI zO=VT;-Ptj>yfUuQt@;9X*r)FwuWGH|Y1qywnJulC5_Tw9*owC)4l>KT7g=r1ySbzt zzfDI*?9v!cVMzK|vkI}a*=Am!3`Ih_J@#?pJ`^X!@8{$y(2pIMtT!2y0ESW!f#d+# zQKcaX2tWx)?n*|$ebgznu1#afD=Fbo#m|?Hrt1zH9E)y=Z1p@_Y2T)z2T~HCdJj5v zNmy+*h&NZ{O}w;KDtcWzw;e!*Ej@Nf>#{)cr!ol!*D4k8GF%OnMRf_=cM!4gkL`f{ zCa|rnNX3?!Q(0(6oyXowY1`cvN#Z?Q>$jonQ?>HkThFxqbLOLSbAr5V)eSnx^p`!n zY5rFc$n&kTTuW+LAu^+t64ULI^w6~IcOE-~)Ee06hM449!KyYK9~qA7-F9O#Cdfib zOc;%kJmlZQ(fz5p{6&Dzn^(i7`tjsog6lBC@wgZElbqUgw7t0=mC)+-lRB;+L#C z{*Z0A%Y2J;vQFGub+VN!e-0q>-@|R|jWmkioaO1Z`%&64+g?PRIo9nLJ1jXx=WPXu zba%sFhFjHvi0LBjnZ^UAS9lim-laH=M9pK{S_p*-HNWO;)_BMr1Xg4?I1jF<&bWRh zlT+LNPmt^7=I5t+V|$Tl!tKwh4po`MUe8K&9iFyYm)unEw|YE7YB+f*HHbwJZ|Nz9Tsk5mo8qIf3T zQZ-s|QKfpoO=t$t@x%VXN_vJ+SyEKaHAuOY_>cHi6rhyj^#v>NhY?*JJU%u zgLJ$ID?~_)nN!~%GaV;xpOmxpQat|vMPs$jsOdS9uaFoaWnAvDt&E{grA|EQ;=>K~ zan*Swe?M@jz3L6hbh~>~q%^CH#}pA4mh8r7iQMh=lsox&k1 z!0|strjy_a>E?UQSDJ&UE5Pqo8+E?Q=2NJ-7rZ@e#^O2>wICykTz$jRG&YIdZ>z)* zkfjhSPSu6(!%s8$@&kD)wO0m-ay?qVV)|8&pTd~mE=9lcwnDMD6*A>t-B8@YF;7Ri z0!eeAY;L8H1#mL-TR*wy*~UP|oqNrX2*0x;`;Yvh{oWKnmfKr#5<iCn z$_B{hl2cuid$HwsRpJvY-=bxH(0Yp<*imllVEcwEY6N@J@fbVy5ZD9+@AHyhErpZ0 z33J)60Amlvb9-hg^vMw|I){2;g*f?K)hVFMYLVKj={mKgYFbait@;7dx?3x;VL{Jf zl4Fi}>NpKRCo*K^5OZp<+*A!YDK^QM6`jc;0OqLgqHi zH&Y5V!cqKVk=8*>DXSeEZD6qf0CT82_KDo+@3j0}^8IepePC^zUn;drQY1SiqYgaz zEufInn^`6-pf_1zM5SPJWo{(&s{2Mfb=KLmYB!IV=jWkQHnW-7sjlL&{i1Noj;r6S zx+7wm>~&$d{EX7!EvHHfF76dEXF)0d0NO*(kbbK}&a5kDIfeU9>82N|mj>rqa))iL z$b*ns-}hU!nqb`$S#97CBqj7Zl``N{O4x-Y(|*qJ)tKYCPO@BDQXkbjySFH1V`a5w zU>MfKdJe_1_uBb2hoDjyi0Ar|qw+Rwrfvw#Y|ZcRh?fGS%55XSH#Mh@-U0O0ht(}% zro#@W@%6o@$S8vWAk$i4eLOM!2URdIW zb=ssJzZ*cZN%-6P)Y?}pd5~~Bj^pz8cPBoXXoHyct;>2Dm|MT`548@iLm!bYMY7~S zOqhyS>!Mbh`$ESALiU`EH6!Sz^MJv8pG7H$S^Ywjr#j;T{{VB5hhDAsTSifBYqhvz zcb3xK-Xma<+aM&C=SJKeq(>?nZOA0_t6n77v*V&%H_-boeHEbY4L$c2sTg=3L*$y; zRNf`3oVns2)DGLE+x}kBi{OyM%qdUfR~Rm>D{V<2DYx86I}_l0>b=k>b;ZpJ)5(^t z7}IQm*m1}Wu<~;0HtCCL4TJ?ag+x*U_D@h2M*H=yuZ*B%HNWMj`D&c@LgbHT%n~D; zg+PTO-k?AP7A8K{+YaxcRvC9RTwDgDmgB+`9ZDV=30h00RjW4W;M{$cZ()wrv^uL+ zacr8}(4rEg_HV7SprE3iihOFauxds3*}io67tJiy{BuW$4?r>@$C7brZaNe0Xj3mb z9fIPP3uKU4SHsmR8+DFecgJNb@)Xkl07Hp2vbkIBx0n+xH+y_(&@K!)8eCZo2O5yd z!3%9^JrtD!K?lB{PW6C8m>}~%5>V<6Ivmw1eWfrRd!( zjlFqNQSDKo7i76?f7%ZNA=H$)9_rhWltKKU?-D3t*UP`NZbNU=48rR&UYcHHrXCrP z%6&^+mO_rr*=IO^dp*%#QR)C?XOKZv=`9V;Wl?SnwMFbv${gb!f=~K(rjOhMG!;$aWQ-;q=poJIg5f-ns65jjQ zKObjp5r5it78Nr~afnc%;1X^{Hn_&zB);{{wwZAIxj_yk46JlLc^^%sqgII|i4YIF zy*2H45LJ(tT<$6MsM9GDal4aBZw$)^W)g*!eD%A#W7os`VOGh?;Wl=YZo0AO+Cg65 zq;Fi$k$XL9rJveMPx0(+d1a0@#}mn=I=gmZbZz7~)35@2Hx&Am+DLI+l9Cdyhj4g` zv=Qw6J_u55zR_CI1An3%$k?8gebS@2Pu^+nsY?rcTex#Q=^KttN>iv2wD(i6i{_r? zQ`DPf{u7X-E3;c}GF8# z_=$0kAjgs$ml{VBLsC#9$x2S>Jk*jtm0wP2X1PX33s1Fe{op&3dBiOf2HqQ%n`KAz z*E#z^Y0kCbS>`>e67>@Zy|(s4irepRyNtPNj@sLr;kV3`gh_L>2wSRQN`7|7ZCK28&Wue*$)p(<6FS3o%3)Y+Yqk%0a_rm~EHNF3K_};& zdP~m)B|SM66R-q^ExIA2mV7p5r_03h?Esf*WyzO;XUPq@xS_BVwe20n!+o>1!Cjxl zzQrK)u4B@SFWL`GEP8dVciXNyF&nMI;Wo;~R?DtK#w6U_-ZbeR=n^9tK}7-X3q*4E z#3QvN6yC~=Ip?Q-_}&oUT32J}_63mJb1539tI(q(Im@A~sBqeIw2h2G=g;Jr>h3E& z9DUKZYUn>zDXSXEYPVi-I1#Q4hdU&=ggBG3+aYQn3cNnf>Zq<7m#wxYklv{1c1X~Q z>RxW=hguxXo%M3iOuje^Wvbv%}B%|2$l z03AX*IEo6`8r5o!rpUp4uJrFYmt|!(OU@@I#gNaA;$KTKq}2$thWJvw&g zqQ2PP(onQVS#-MPd&Z+)?s&{A&EYnSFw#c2m*%Ds+9Dc|iCT?3Xs*a2%;NV~ks6i1@Tv0#tjdTuiHcm3Hw~ z&f6Z*r#DGQQ@A5-x2mnVG@fa{a+j(P0l`t2MyOrmdsAe%o~kir!ZPfK5y!1Ci#IU+ zzUEnzHAUzTC{{R?fr#J?s;CiEAZzjMph*y{nEVQLEzAmun`b@miwsDtJ z;@Z*_JgNuneoy)twG3=`Hl%I;0Oc&zdR3yRY2u}OL17_U4=dhtMReUTARq1BU-_x6 zR#wc%uTUMgg(_4?uS#VJ?{P?4cTe646@fP|jc2{yoJ>VYko2UspNonThR_l>Jx_l9 z0IFM2wF>_LtC@rPM~S@cuG?>C-SeyUxzd-RMR#R*l_ba~YFV{|2C-V^MwZmDUt zv|DwZgm+{74l}oKK^?#k4(+P8i>NwTqSzQ><1Q{cjM>)JzQ+lc6kT-+8#M|nbOPh= z1G+*G6r@z-EUwP?dT952sVSv?n>V z&gn_|hoHkO>Hgr_T)Y-*9KJv;0FDJhj8HYZC%SBuAw!P!xrP0r4B}$<@V8X zrg}nmR;VdcV?kianEG05bljfvFgYhgEB~fx3#17oAV3`icE@grzYK zT3nQ+-EQg|w$ec2(DWV1ADnvDb2i7Nx|x^9Qz68jO~+5-T0dvjKBm5FZW==o^Z5^n z@LlPbW%=<%5l_rvF>EaSoo^rPl8u(@D;KR2xT8P&kXLYQd*0biKdXqpX z(}`6v)r+zj4}X6Yp|PsOXz!89{{WD+{ranC%g(hdtNl_*{{WFS{ranD51lPTnC>3r zH7}Y_3yNP5pNOk{wgbkf9K!}x$0nVhU>8JU`6R{jkg(4?ebpoAqI7YvS=e-W8#RR||rk7VDqL z5F@Rrc`FfAfS{YVViI16q#%WqC~;R3qOuKlJ2uXq)N5Jl2_#IIBQt}0o0n;))yU!} zvT?2|y4aU$e=$4{1HUmF)%B;^i*m{|#}~)+!6NScryIAodB5GCF>%WRkr@HRy5e}M zQ^)}B(`R5u#(=9dtDm}?<0C#MRc?=w=SW~%=H=tk}FT5$Bi=VCRU-c9k}j;Gh^ag-KLL`2D@`r za`b7|H6uSUl_kqt4z(USM(UF(&=DDd>bJ{j`^{5&o9wF-C(%nTI=jOR^`^m} zsUFFm(G1HGSkqZkP_j5mhcY{{mJf(?nCa)Z1bEfgrykDPAG6c=eyHUaR(}Yk)Ct|do?|XaV4JYc2+&uu^!^bhZ79C)Ni{m!rLABc%3`Sujo+d`0b59uGV`}spLcP(#RO(E(7pFhzYxcPC7 zMx*0N$@JSM#A2}0K2WjChZ(ds0uZjm_DM>6fC@?g9oq`3w7(-dagSnCWUv1KslIIn z6pL%?B#dh5A++*_;*ZgW^QB#yk4Bs&2V|)`ZT+G>q-@#~3LF@=*6%qr&9d}J3v{8D zSqEbC>K_*pcL_-9q7P68^~P4eYE4kJVDIU|BV%&0=b}r5Ad8aM`>GH+>HBRZrz~( z@;T3e))#HrchoINyys3Hc04H(@!WiUSMCZ<*BOJ)j>(LJ`L6BXSo)1VUE;xx={=TH zvF7FxmaCM8dM*u(gau7_AF8E-mJoj_NvP_Z_%{w7Z zI|@RO0#y1(XzObLcFph8niLG91B#p850z`Ax$@KaLur<5i4p+!TNFl}_g2YIa-2|G z>jS7ONIg?oce^^a%D#BHZWhtP+F~*!;=p)Jhg5LW5OYqd#VYN&qB^rB@?FbG_CrSa zgSl^X5-^3OnsS7)8AI5cN=so}fdn&oc{o9Etb*@wT??!>Xs4tN7u+x@S`_xiXyPL z3O6cIQi&^3?@{$MSlv^yZBDtEg+|0oXy&?fJK8dg(OR3U7T__2&#;e*t#DtI?pmjH z$(X@%dj*yJVimMGe1e#ZdP@@BSlJxZqwqXV(X{MUVDU(tb6LjG+Em04uR=i;Qp>#mF5_A0U8I_NFn(=eQVW6WUo*OeP2yT@5cy1bvKY>2Vm z*zSGN{Y@u+L|JiSM1c{A5F)b{Bvz8c3^IU*+HE6bp(z~#KT%$ylr48P#9@9LA;&rcNZTZA)_Fqqe!%uzc(Fz zLy-JaHzoFV>1!SgIDhP#RkU!o#X%oMgb({7v9D_=vrbSyyeNL2ljH;tg+dZ05y_ z+N53Xmq>9T#n?!jDc%v8QSM1sde(b`Xaz*{0;)|?&YsEhtS1n{Bf`92ZMhqg9D-j` zBF1qLWPCEEpcJ;^K`ShycQ~Z)u_C9~u9ZcFa*LK{cQoZWEI>?GoQ@i|+P5830xsn( ze^pl}Ro+xPtE8`IM$*pR+dz@oE({*AmmK&2Kpmr&_J_d>J(XWptwBIeU{2uxd7Sj~ zBUPq}8&LH{#LbND&kDxeANa~cLA&k2v;Nj$Tc+I7hwqC8@($o5zN|b?5yo(9LTp&2 znVWPzW!C};Q;)iSldm|UR_lcID_JDg)RIY2B$L#Vd+2iWtZTieQsuo%0A(@uMx2X| zt~|~{P7u+Ow@TboIL>b4^58!vN^l<|Sa%EchR*Xb?|BWX*yMr(X`4Bw6rg-k7NAev zQB7#iAJZ)@$g^ljZOOLyp)iu*-9GZ-(%WQ@5S1(_kIkmUiF5*TkpKcS=Mb{?w%KmXb7_X+ zaTBaSUW@5P%HKU1cDCQ&3bcEU&pigGfocmqzHCXCivaee1 z&uJDdd$wbi8-riFX5QtZ3zDM}Hg(?Ag*3@LREAPJcX|`qBdMr;l=3L|EkU(u?r52p ztZ)f88+E1UmgGh*O`s_c7CwuIiWCA>x>Bu_2K{TfF8I=N*1K$u*=l^B#YdY7sVQt9 z6>>@4f2dZL+qSmoj@l;anB4%=IyH?QmP25dxuLt@0whU-t6Dy$)3l0b2%ga1PNzJe z8Rsgc_I|TiqhPRZ82kXM!`-c>`z0@hy7SCBTT^_S99oJ9N|1yAHeUTnDyz=hW<{`v zWPDPBcylP?mecttPt_l+_|*Wd6u~Wexj!18f?1UxEwZNPXm%s7LR5KiHfmc%~u+gXU{FE!{skm z?iT?YFtz)-G8|qp>>pa3LLE}$_DXOLL=+M?-@I*8h}jmnLQ-}bU67?GVc6U!S|2-l zIG0e9`f4F+`+8ROXjNn2Iiws-4h6)PlJ>Ac4uhB@P-Fs^tq*(J_K_qV#Df~-PY;?@ zjCRSYjj^eEdy{;ZeQmbnSaK#B=gEf<3Zlzs?b}Bx1uJD=5)_hBQlVK`KB2hv@=ht1 zT9c1ttS7d(T1%YSP$ywJ6NmBf7JJC}n^EGh4^v!Qvpmm7a_i;p_oS0x;h7}J?n#Yl zyhOM+Z@fOPA?A+l@Mb~Vm{>>-Y(X4|sV1Q2T4|Zu+PCMp_Mu`E?eCYZmv`}LZOOIC z+iumD*-NO{_bDWlEjxt*D$QM=Rm5AprlkS~2QapynUE#Df^i0u+HT|8WCsxTHsPb| z0mA*@%R%R!leqXDBwSPN!dXO?UH5s6{weg0_Wacktdr$J-*Qv#Dh$?ydCd*f-+Ejh zl!M|wSgL1AJ)q``d;7M11{f=AusT;|Z(*E=q!qu#KAxWR}z5gyfv`H+x22d_?UEg6greBA4A!Nk)A}neebNP%1gp)}@XRZF4os5a`i(S_wV&-$YKyKQB*$0wdjebqpB?6niH1l5gV#PdAwF0*QGqp&g5SQPV^ z_BRdGsziR-92VumN`Dx&Ctzc?dnEMaRz~Bq+caBA^=QJ*~b6@sE z%Z#;?k!buuuWoe-?RROP44{qtG9_AR=}cU$?Oh~(C=J3 z99VG;LfRrk6{vdX>-f?gZ{d|BCKyQiu9Lw2jftmOX*lGzn?WMoD5bN%b1!YMKgCwx zrm+}dBPv`z)jf-Q+z{W0bx8?7<@@SD{AOlGax8}VwCYL}pD|Y+=F^7#mu7r)@=$AO>X#GscRk#PaTT~veL&_KZo*T+#3eYN3UNL3 z(tD}EdyP0Cs@pIk^{+Z@kEL6;Vz*xMsR`R~7umN4IX`KV{{Y^RR*F(oc1YaqT>C3i zVA{Q+bG9v*_1`PWAXzN9{j$wyWGP?}?70rU`u1isT;5?NZYUklc1n#(e`@UGQxV(i zjzrQePLNTCW0D+d-M4~f%J^QId$&mJ+WgfD>F(?>Q=#cDz{b;TZ*#FtPRnv9*NHe= zlwy^aM!dQGSj0<@aE!A)8>I4442M#s<{r?{TGAT}8T0sNH!d=sjc{$7UAE&sLK3x0 zm+@>8#9}x!m8_CL1E~|pdM55!HH-ecWJ%4YFtGG%A{a68>w3Z57UAwpm5pOp7m~~tnCe<=ye-y zW-wrDTZ`@HF*r}z?K!~ViOdpIcJ}>qOEU zk7^r6BIcoD`9584RD@daoW@%cBgu9wxbmVk5>pN|`$|iQT6;GDAnmf$JBbFy&b2zp zq*`T@YOX-bk|RTT$6AcqfLy0a!%Q-gd$})RzkuBMSD3o1?Ln;C!E!S)e320$w%n#8 zTW!oCsL|Mvj?Lz`Ag#cr{uLFdl@BiJ^gfAfOS}7nb4cN1C8Wt2mc$6&Bom_N_O0D2 zRQji8u85XKp6e&G)+gvwDP7P~`K3%hCw+>*n)b4PjY8!JZLL8>3wOWLO-*Yv-p znt28W%``U^t~Dd@Z=y;z&-xL0jxLBD!lU(!Dv(?Cscc{6+=VD+}#o zt@NLGj^~4oK9U`2f1HH_QEfVA*5%YTnTp$#n=BUL_o*D9EF>HWC;I zqAo2b=&SeSj?H~|s5CJxaMK_cr@3l14#Lp?0LkpWS7mc$0R0%ZRHXKuJCi92?jJZS zb%<-m5s}{8!>@_B;hB2+40k4ELYC$r&I}OK<-D;l9@l20N@{5kJX=Rn`ge#Ms zzHf-f8!qA!Ha9pZJrg|%9~*rHlqX~Jfh5`3rd5vT80Eh%#IG^#wtK50L^+Hffy5r2 z$Es8aAxS$aDg+P+H5F=2XcefLVeiOEs&Q_w~3aUy9PX#xfG~%1uH^_R5*nMtqUDe zq=GxD2c`Kn+1YnZ1l9)`x@6W!oveA%Sp}@3%2ma=!3UvrCyR0s^;`j8MQN=XS3Pqb zRl-bm#}T>X^nur&d4lCt^o=(LjE3XGmvX_v*n3|ysG7NTVP^%!X%%eolJ7t6%nVi8@w-o*sSXWA|nMm1WS~ZlpqeRwav}GhW4F`$!5kZ zura)L*u}cnYmD4DQ6r~|Y%+xLLJ~ZHAAfnLRk`X~9t1duX#1_>`p(>Wmt`$T%3WiN zbsNtU!?-NuG|C-y^fZLH`hwk9ND6U401A(Z*a5llsP3X_9yg{KyV+h!n`o8y^#| zIHD4SC#fgPb5M;sz_Og9Dq~u8irR)UR5asQ);NbdW^q_<4U`EV3zOYXJK*_1LAiMWCJxvkb1Uj?&edvd#2ah#p&Hz)`l5>F1SWqYjv3}2V!E7 z9O`06Q0TcU02kzrt5D#bkdO@b-7j{M1+Dd5Vvd3&h5YJAlWutZiv2YP$UUI37`K=3 z{Xe$cwHGOlt`AzVkcXRyul=%Wh>a8v5Ve*0AlA3`yw%-JNq1N?v5eUs+k8t1l;b_3 zJNQhxs3t?*Iux*$QR=F)@m1%nxgm|*4-ME`tZ+F4z0)ctmb77{F&;lMvNaE0up589 zVK%XK$umHn(Kx+=8JlutXdytp5_>491a(`>K-&~I*rcsw=Q>{34p2-C{ zBs^-w=f2#eDjLxxCPm1AmQ(|3P)uY36}QeEh&Sdtrg#P;kGG2AIF+`?ZEak`R`x?A ztoN-;9}?DlRI;LcYmL3Iy{$5hH$;sF!W98?pSP`x3zam4hX~$u8l%@E+0*g++BV>6ceN1M`}`{hM)II~L8gh+uZqL$t3T zwMLAf>fUw4rp*=AsmGjBoN-HA>RObw5>%p2=B=_sY_F!?_#6+2u)YMaI)|Z7WQomYZjEJOFX0J0_b*P$YZ_-6U72`1WgK&+)7`Qt_u& zyH)KCzQI`8X>In%;IZJOtZ_b|(|SX)?yM^cwT>pZ01VDypl3VGPvoW9_Y|tMwV4r| zc&VvZjUb;YB77+&UIMuHy=#V`PTd7FDlA#Co`pIyGGj?WY4#p$INPWrVwIslg#tUl z8&ibsNhi*rFM}!gWJ0J{qPN{TdCQ-(#!SvT9ZD{}huwpcaoH+8PhyZuw)LONV3z#a zH4f69QEwjCtn(4n+du7ncg<00zFY0LN$jnO(~!cU$}ps)gh)bzF^YExSs_565w&z_ zK9x&rJ~fWqb!SZ^cgaSfh zs|P6Q#obE5&1Uznz=aE!=P$F zalZhhRCff_>n=m>KI~TQw|siK_qOsB%*O&;X=_OcS5e(j z(6%OxV@257TRCbVgW4L5ffJi#z;R1buWgvb5PkV%eBQ(l<4wzRLHRak0?xVKad*I!_(j9q2*vQoFqd^!9elCY*$(9Ex(^d6c444Ytrw@w+bo(T4(?O9p}WK zQ&}8^Bb6TY7BX`w@B*kcUUbb;%9HHxjLd#KM`JfXJ-J3%O5oa7PiFxOA4WvZB|YGw zfa1QgYQ5D9tnC}h^F1=bVz+I3SZ$W(@VN1t;LKaWE!!rB(w>M7#WCdgbZJMvrt^6;?`g}&{@;~HFLCBPQbv7rZDS_leChzDb_uD55lT-MS7)ML&0 z5FNmmhO}DF^ zxVgu?tuD(N0MTSzF3EtbgkrrdD2U6Aj`B1^wX z!|HqzJO^;BRlSelkdm8~#ucrF9m})jw!w9NDpH(KNmZ)WWg#y) zsorg>H>;jvFfA&!MZV@uv>j;@xqO?Cu)d!t+?1Z6k-uXY9@XCe0EIiOAlh)xX3JFV zLdmYzEQa|tha_Kbl28|Q$Y-dbxX~FxdL$`o4Y3uykd6DgDMwMK8kwZ{1~S$)qY3MX~{jLu5%e^+;n3VORl9MJ-y2NA+l6F6YlM( z{p0Sc(?BteNWAHcX|c8IB(_1g-Y;_f&lkktwhGsCliDmul806~uXO+q!W0v^+j>(; z)hRvo9?(f|5&(xl5D49uazNnMG&QFw_-49(@r@$s+Dd}jQzdEH)RYp`fIEV~@$yjW zE0#U1aGXad#Na0SnAXgAGSW!c9l;xU)E^f1c+(t{UUC*}`elo;Y17+0 zdrQVzT)2rhxuFlNtxuKqijNsB02R0jkWbla!SwCj*7b+8oDJ7}CqDnlY-`1+MXD$C*PhulAYk+(o(?M;4azjC7F*Bq!(irRF}1=-76(Z|cOz zct)vd-nYb=9N8a~EEjpLx{|`JcTkiz`EVX3v`a)+0=L^|6nF2d8r6%pguzpG3nidJ zaV0YC&@IfyLXvtC+ES1XJ+|EYDBw?%8iv=L3vLjCciY z-iH!8INH0Eg#rjBy}i<$?VV54ELTxkj+o;kv_-hBl=n2_hy`dKfqa1U+MwFoCEG6x zgRwODK~mb?dQ5^Kr!^ByFr8Gj$F^oxl|1FW-+8uNV!pW73tW!};z?|W7;t%{?0b|5 z=~YzAzN=_MtC^iUFEPYhJ#!mW4p$gW`QB~Jjh{dr^CG8^+jX=$s;|(mIy9`MK}jSL z+($wxg)6ebf!Vi*%ZIy&DreD?iMbV{!*lBnTMpNCw%p*!i*n%XDUjr)_;XRq=`iws z6^}IZQ0^mof%_{17f4lXC@41Evnz$ih))!v`7?;y_@y4hTY*}7uoLn z)y~l+g6=zgaBaG!MMlMk&ckU@_&5($2M0>8eVBD-HIi0OrP*Xwtl3e`vKc=hxam@y zE=UhJh%%HC-136qD@t~B1tgU0YU5bimhUtS<_vEd{PpvV6c~$Y40$e;%J)_^asc%a z)UVOk`KxN%fo-8<>m?`kR+8a5726xQy$STZ*^1~pbSY3t=%L<|=xbqZceJ*|bC&$$ zwyysGG>WIz5Zu)GK`ES@8^^OedgF;@mWbC|HQqX+&V?l$n|-Bsq>lsKK^;K=jffRg z;CVg&07o*Lc8F?y;!Vocb6H~p%Vw{Vt_esaT4y=X3GJkO+RsT1r*ZR%P&Y3s(C1i2 zFR56a!wJl-f_19o*h{DjC~>u<@@?jpI=1Q*0?@SVQlJQ_=-mJ?RP|4@nEdDDM0F_> z+HR^x$b(b{b2{~j>)u0`-So3Qzu7K0g`&-FxMMb?&+K1jT$vAYNOU-o6ysZLosU}H zX|8vaVi{8!7p!+PGW@od?kke50k-m}Esu#S@J+##?b|i=$Ihd0R1F>;;OFv@sR6sn71`KYfOcqC$N&kyO0jsj?ces z>PtHR0A9h&d%|sIeOXWGwpo3+>E1`DS8J1*K3fJ9$ZpTUkBb zqFo++sj(#9RED<#6MlFSUMI~(#Rph9LuXz%?tFPi%4wbs=aO*1@yT~l|M zi!{j*aQw(QAu3b0=~4%{NgRgjTxo76;Q*+#`*Nd+gsneXlHEoimGLqXhh(kGuk!8YCOsPYNSrRlZi>7yBZ9Rtp8a;DiQ zg?`tW6vqnJC5F?GLQ{uEMc}%=(6wD?|u<9N!X2~rR=SXRmZtz-| zhTczlM{1MDk`F>QJu7vnx_^!8jv+S;%2|8eB@{Z$n9g=`V9IS=n3o;F)wh~h3qps8 z*zO4ixzbFg`+2xu^h;D_u->9$@1o$DthO3r`wMiXtE^=A0!ZNdWASbhK8ll61;<*` zQMnPX%zS?wr@?^XcALIS$}eb(Ogj#|>IH_|W@gg{)sRYq3JW1Yxbd4;7Lb5R**tb2 z4uh9Xg;%xPnt|+toqI`Q@_nTA;&T%6EUNzid6hQFHbPiK@ewt~VyZKQaZ1BXjncES zw*)8=v8FA4!`{?$<@Og1E`G-y9bKJwogIkH4ffoWtx8eyN>WMiuD*r+zgG@;1Y~6I z3oZ0tiy^AI{{UkvRxX&W_a)Pr@g%=y$rhLGDg);JT4|LnQG&aIos5Mq{(`*-utSoJ zl6;xcJ85W$(qB;e330+z!5^BGgpb!;-HrPyx1@f}Ug3jGZs(rZ=P{gHXKlmY?$LHw zX$jw=9b`EeC-PkD)SBpRG0JT@_AJJu*jA<6GNiS0gryCX7*k2&l@A3Z@<|>ACb^sY zx^!`aSAhvEmG5J;6DYgPLcG<;?2Ka>cT? zvJG344^MM9l!$P3XCH^YH;{mKZZ-ZAj?vbxgLhTL6Z29N(G`X5531KYR)=OW2-c@u=7zf_tKcDZE68~ zxYRAc4Kvyoi(rH}2_m|aOYRzPKM91LB{sBUP|0Dq&>AfJqrY9Z*-#EN{{H|>IysZM zB=^*nh_?~1&!50}&Z${74C(pYp3>bux8eDmT|><;F6H@)i80;2PGu-W7FW?s+vAPV z4Uze~xS{mcd#j`Os*@eI3)R)AOGzy8Qr{!$qhdc<+N>;cYqBkZJjH(Mg0)0{)kx?M z-ZnpdW!zks86{e$P8R2otwSmYEtWsx_I!V)Dz-f9Qp*1T)nvcxdq3e7RmH}tLdBhP zwL-e320~Pan(EAkj~7}#X;<@iR-SvSVSxuQj)`!$`MZlqAhvu8!0Ot+ICQB8A^!kz zL`qbWl_U_Mu>^D?nl58hb!$qAw<{ZIazfX3wUN3dH}O8weAWlkPv;J`x{ER_#t|wU zggnZ|=~@op{{Y|0w5znsw797_($bVD@^S;J*_EFXgZtXQRba&trb>2IfyG+xiR&s0 z$=NP{86DqZ(@JZDoTR}Dn#SI@Jp-Hu!WX~K?sr*VnvZ$k!nX-kRI&AoN##%FXU(8l zZ$$B3vh;^7i+`_F@RF1Qt^0~e6=K;h(Qi@E^l`CJ+;LD^7wUzlbf|rXycY7t;@tI> zSmw{(1RrjrfnA*%%%~xx9_6_T>EBKzRgRVK+-bzBsVUYYD*pgfl7Hk)e}1al1#jV6 zW=`1@kNFc{->R~1v(<06J0Y>j-MBQL%5hy2`p@dC4h8OOM({n#A&?~^-VMU>D&mpJ zxS+#e`i`zE^z`4-t4zC5{{Ri$nP<{?w%dWUb4}ARxjos~ETKP)1<*&nI-QQ~Hh3ic zHK)3vTKkkl!}2RWC!XFVT(GHDr9AF8VU&oorA-S1mC?eWq}$w5_U07Y0=hZKROZX0 z_Y9%Ce4SrStlF)iwc0RCDC1zZ1oc6906qE9(C(lb2dYuyT`e0MdkQq~?wujSvBNC1 z$hq6dC9D}spEwR{>r}4uLXgaB{(Fa+o8Aq=fdLs#aBkP_6?mHBd2rL%DN27SM(Fm!u`Swx zlw}0b#0|v?4^eOE+ekQdDec;**6V+P#cYfhqTa0Fk2%@2a8+- zhVi1Fz124cl3>Wl_a8CLNLiVs%5Hh7`2Hy^a4&eTpgy}?SvTQRmtITD`x#rUqdv&J<%3>i%L?#cH2M2Wx&)ryFU%y&up#zlJsM#*PTca z%7Ub!Bcn-JH1hT_P-zH z-I>Awk*vavt=zxGw<8(XErZ0*rb$%CqWMhPI2?^UG1I6yfc zR~Ah&9MK!ryD!l!%c~yHX*q7x>#w#mBSNw?uQ}N#c z!V;CK8|^!KgVza8YWzuj;m~ z{{W!h{*tr7N9ZW`R~YL`SJQQ92_{LVb9XY|zR|9jhG2pLde#+PN9yY7_qTZPKF;Dm zRumX5z+|K>xa)_T&a3G-uQU9@pDNy{Uuo}Y8G1VGTt5%=3fEr+Mn4Q@{RPMIzrIlWx(n!_@p3u81G|BIo4fX9S)0yTi z(JiHU1?>PSHVCU5gr|OrnCgEPkEjndcmpiPF^d)#V26z3a70UV6cn`IBBd!r?za}B z)ov+7sY*RbB$|%sx-ivgrIzJ_84g=+tr_mfmacN-%USvNWUuQyTnY+3-2fW^1x*R? z70c}^y+J!ZkS${gnaR#@$hpID8HTq;2^o^-J4W5O{=M0fGT;XR@jK5b0293tHIFvo z9v2iWsc$*>wp$#WU)VgoPeE0L{Xo6v6Q8hYln9rnW4KwFa&^TECAnk0F1(M5W5f6s zPY`-3VyoJ0>erIys|?maJ=hmhp3uk>yV*rWbmil!dZ_vAa1Uqni}eXqby(cwGciOsy}y-f7tH-0M(jW zV@f?e+3)(ZSvZFkW)*3y*wkF^!EnNE52=Xv2t^5WCw|G^T#)A8JXD08ytl3a$#jEK z@k^TRcRV`g`hv<^kp?p4$9!+mNLm5te2&C;iuAW9$F37wM98q-Tdb#wkPvrLd-{5h zDu|xmHzch~V`H+pFNVj+?nmpdOXzn@YxI8H(Vzp^b1fzz+(8_2oQ{J<&+T25%jbtY zkVgJXoc)C~LM1GdQ#nOk#ob{ZEHg&BrmzUx#b$2hv&^O!SyT%{h+2njWlmf$D~nh3B_rZ;B0+9zU)e`n zhi^0rm)qZro14Do@?C{=WfusJB>XE%fZUI?6szy8Y|TN_5Zlo%nf54HTWW4YPF`bd zrqq%YlkxWyllp3_(=Alf+;6G5SY2Lh`~s7UshAy;>c2i3HRJyP`V{xprAoBo5aGmXK4bV)?_8WyIE|KSRI%=P zhC1=$H#>X#X#2jMSW;p|b;Y+LD=uuM4~?*VeQJ%?yS)JIML6Beq;1W(uIYHuyT=zT)a*|g$RE37m%A)^R>`oC)A3chP~&Ulm89+G`i{hR zRd;I6t-30ycrL8!6#Gkdrjq@F_a*hO<%?lg6d-_=6-i+1(|`@skcIf2w=8S+jhoA| z+s<2Yr(?F>?OP`-@(Mfd106aV;JtUx?i!J*`4&YgW6s5HW038wtSteEZ7YjuN)!Q3 z`xOvCJE-qnZ$x@4d$%jpvf2%OIsu`?mXL7>0}de)M2AQqjYNi#m$f#P8%8#*0v!2_ zj#17%pDCW@7u^{4lC@34B4;*Cu`5AakfcN`Oh^Oqjg_Si0zWMv?c>s*u<#5I4!A_d z^-E51nGvKuAzHH*8da^r_WL3w!EuTb#FNv5<7VFc)pR^RY`tA^yOdmx#W~nlD_f#m zNi8d7wBkB~50_1d?jVqB)aj4favT;uU6I@E=GS(zF(UgCcL5b~U1ck9InUt%JdVWp zZR53R^pj)N=u@dsx2*SD)|eB8?Qo32W)YYpa0Vg6M+){AP1?cFdjNZUK^(!IJ(`W- zMxN2AklS2qP484JP3}Hw23+j6(xKh+a*Lm$s-0NBX75Wn7h=|V**(`Ibr$(n>&2ET zQ4SzTb^ic-mmMinYV4EM04Zupj^J($W;d8+_NzC#*m7)=-J)xlmRZbSi_Y%`5lICR z%Fk==JouqAR@qx=Jpo7y77m+Ycze2+b{KuS8Zm4h(%p^+V&`t$tBml8Go-2A$$7%i zjDq1MN0PeMMJLtSMb~%4Rlp0IEe?kQ&@s8;&TGTm(CT2~0!eTHVzS`dn59Hvk^#;# z-vB@&0f!MB2MX&eu1AXQ)NBc<1*v$8kqLsWO?e6dH4^ZS~GQIqF zHIH+&X>A)ZUgC>h>SQ*1L$GuJG3;m!a3F|*ji3nD-8*+%)xk}~2WPuc-U%`}?khX? z`Ng1rW-Gozk zCxgU!8fV)702OHZO)={O;rRR$*7=bxaM709r$n5k%rBD?LLc5a_&9=scldTY3f!r? zmEe%;Z`pC5nFPwO>WN%nWsY44_N{VBwh=s-$D7S#bw^y)&G4lkD#-dIQ@aAGWKVlZ!fa*zezOwd`wssp9TEW)b9l#Ghif zl4-%zRbJks>clXmT>zXwW52zhZW7Rg_{@7IfjklB66s}scGRWo?mKSetfvFie03-w zEy>uNms(G+X&bdp-+dt27lg$&hn@^`UVND1Qu<{b<~zY2@D-O16evrQ;)C-^2Re_xa9MaL|a5B?k&fH2SgAf0#$LhR(3w_aGJcTf zH#csM+u(csnskM7d@$OQha)Xr>j?@%kWWxK0!Z6#%So(0l5Fdpm9?u;x{O4ZNYIBF zk?!PjS9W!JcQ(y+Jphj+`cCxXPg-Fnok^^wIa(n+l^RUhLL35WvoWKFv5?|Ph55LakkJrN=nLi?;rp= z*OnlI%Wd~TDNrDSk~_f%s3XK4weFf!ckernyMs_1pV{+6b8FejGQB6pZg)kmv~p9> zKylT6Quyo^eO0}BH$}Q=&5cF&*A&TNID~2miPunLj$HR(Z7q=NrwWwao|&#D{f*r{ zyx5^X22kUS`R8&VnxE%1&u)tX z-wkMi5-e6y(=4{ycOeK!2?LUkbx1+lvUc{_cG6cjpq2(w*6{%s-j#YrQ`$oN!)IQ4+SKJ4+C33ck!ieeCsxyXH#%<3=pij zfx;XWN}V^=T{PL<8qdw<&dJ?4o@*u9EjE$$B3#EQEO!l(=y?OsRaYlh<7mctCnb9n zy9Wrvv66)JwsV|Z9mwOzx$&Dr8I=vkVO5R8-2o5BLb$>5P!M)h*c{Qo~7Ds1= zb+*bIJq~nqKEt1C76?CYeQeupc7lB=n1RTVkt3et#z@6KXew1~jY zT;hUrpI^LK-|Jw-<~Cr;YV+Ou5k*HzhaTw;1IRy)H@ z^ITVZooQ*@c8omU?YP-HC=h$n4N zk=1KaAt^}eqCgc?WnRqsj5j-r3GvQaZuuv zq7n!sR9gVi{MTM%N7Nr{6*qH-?*e8wgtWrp+n}}Agqd;q6}tM;cL;e(?9zIUM1;Ir zyv^3ob{5WK?UTSaagHzmgPuUATG91Z;!m)1T6|+vE;^&04MT~8+_TJz)K-jN7lQL` z*>MyoaWbw~C2t(uoJl;A*J`#CyJ=4@MA}Qz4DV6_b6#}?a&3-pz9EUY^}5r?@+8UJ zN}K8wrrSS}wOV%QFf9-mwAvs=g9a)CapJ{gEi%eSa6%FSQa-_19E~!*)|Ufr+!2sE zkU1UPWDHu%iOm%3qxwTXpKs?SiH~F0{v&3Oj$)P@eU{Y`JXnKojEL+uJrIBf{=Me5 z9I38UIeJ!Ws}~$jGD@}>8XyGXmAKn_^0)Ru^z!$Ldq7MFQ77JlO9`LT6#=TDE_Ecjku-I}LvF#Z(nho03bz#V|A2q2DG9J%DG-&%TAJQD?YhQxU?r7;Q|SES04;#!X#loh)BiaR9; zNb02%&;ee$=*AC}={K|PDS+gb;JLP_4aj}S>gVp#ROx+fEkBver;yxk4NI_{2GMOd zx*OgHrP$??;n!(#p@Q-^@8Go(QNQNxQ?fwwHKEcjm9uNLl&G+VG|qaHrtKp>GN7>8 zjG7sd))?A4Om&A4ArGK2)8+*mw@%Ma#;#hdpcnjG9dmJ5f{)dUy!Q6;yjp zCFpujBQlE8mn#Rl+cLI-6S{(7%586L_OM4XtJdU&c^MzbyL z$#(pQdh`igRlBVGOsnET)72#>s5||HRx|ea5~akrN`oEgP&-d`ea4cfBlAxc_G%+^ zavcuDZaeG6Y!_8^BTa9tWLQRLbhgc5K=JpLxWlO*QPz}796UQCKICNJ8~V;`Pm_o$@w%nEKHy5V`BM|3 zM7T#(X{@EjmEe32m_7&Aspql}Xo=({?2|{agsZ`pE;qN6>9Jb~C+wz^ifWzQfqM3h z%w9n|sjOoYfo(_Fmt8}y0rsfWt+B(K>a>3G_J)z|Og9DIs}HbzRjS*UTOkd}Zbi!9 z^&f2oZ!$cV2hty$I{DU;UC!BzMuC*%H;ZkpZ^~Nbs%*xir7bxatTflNaBe zD)3TVd?=+~Vc6A{w#|dLP|4uY2ge`7a%9$ajUIo8#Yk>`(;mPw9{$*~J zswXYLu`8~jvo7m;it^{&4b}|y&t-3)8+5XiG|HPoTu3e>Vt4AGZhvSCEt2VN$8?Sq zW|@0XZpepmc}gWr*&=}^;^9#}0co_eznkK$<{~Eic_IpJ_F=bX!W#{)N=J7shr-w! zY&>gLhR|(|xT!^frSru0Mnfk#z{{t_v&>a}xvx;Q?j$$fdH}rdfpLmicH( zS$<4;2_UfQqBz32B<`$ggT}HNX-80Fp*wg`gK11-MC^wIWEX*`zR35=TFL z=sD<(ts&EGUX-gQl<8(EWW(K%xRdiC6W?wZ)gGdZQuK}_ymke)Qb&7(YnB!_k3qgQX@_Qt)F_t324jS7 z+dwaZ5|DZT5;g#I09Tbh#@e?hNp;rCHdWF@Y?Q^Nb zhy>@Cy~^ zSumIv2zdNt$%i8N%Wkyp#Y#ewvIroOHZ_}0+UQ!mI-92xcODP@X0c_-^#Dsjg%)( zUu>q&^)|~*4Z7Im^InQL5mWb?x z@6;9T+-m(}STve*oMURH81mE#__vTmJv;h*=XFa`m1;KOm`u3Nhe43@IJ|;uUdPog zBtZD7RM`rR3PgmB)YD5?`$9>tF}<$+nlp=*ad6eF0?R7>4*SMU+8ji7lA_BA30tAR zq~mMYD3tC}u3L?WHR$Ry+yYt(8_w~ElHe#I7pSL}{Wj?3KxelO1hq99?{1RFSIoT3 z=2QxN0oh#$t2-k8uFk4zG-sq8;Wp-#JC@;}CFQ1_`!{O_q~6)I!NYF0cxrGaUDoRj z$g!d$e~D+b0Jxwx*$D)bw{YuTv}mQG1|N>b!K}f-Y_Z$pM2pYqacw*~A0n-}A5&T; z(K2$78cVX2ub>dV)T{Wr6TjV7CLN7Uv_o6-BRH8dh$=@D^JyK)KD|jbcWdpJ?FNAG z02%@BQCixz+~jjo5U*N3R9%Fh_PeL-L89!R9r51(0INUbtN~SB?D0mkx=7o+8lrGL zKf^Tv?Q6jE%yKQ>*qut1H7!2bN||x!5Z*(~xRMsrps19%f|Uc@YV%T72UeUPYDlW1 zN}Lh(k6fUH7XJX%4VG`wBXI4KC?|g(iM3VRTJCK+g{BAq{{Ym{@laM^bDKY$lydEA z)r}TQ($@9wQn0i(q%Lf9rOc+(#>e4(zKOfAk+;M+$w=$-n^lpf+MkJLxV5tJVZB40 zY>$ zg+3ddl*??^&g{#K;Og7WtEeNqZj-u66&BL%Nyx?d?HtwB$7emY#xkPLWxmQTQEo}& z@`P0VR|i1*9qp{{H$swX%;oB8llH*5VHoC%&(8A66QNx3hufQr+--_VZV@jkJR_DA zuA3~9mlCbern!XzDr;KZdxf*J#B$;Z_#NZo6m^+{>qhNW<=T4IzVB@}6gK>4z20aQM~S8)3r z`&uX3J0H4Q@{4>sUSW;4+tb^ON@GikvO;Ax6iI9a0+pna5=bEIdT8EkYt5fauY3?S zjotZt)D*8NR(1^+LE0}LC?&}9TK9R;h)k9b)jkSOnooE&O8a1Heko(pEZa>~CSCHJ zgA}DGh^r-Pe3M-o<>c@m|j{g8lvh2G~?z>l9Xy>rj8>E=Z8Mo>_aStq$ zvaVPA1>tt!{=}5DBM`Qf(g{3!M5#oQN$arQ+M8y@*3P8bfR_l%IOcyd$y2wrt&bKR z7f$mBxSFED=_DH2 zD+l(7#?oBIeV{E_1a^l0?V4h&sYN5e%YwF4I(Ml^QS_>3TyqU=ykc%(nI@Rnt_)_^ z`!$)#_?AQ3s!9_0ea2ahHMW!#<1yUG~UuAPH~q109q$0;d;oKoctcxD2;5xtAWjGd7Z@EUfveT@_5}QoM2LKCB z$K}lnLV}W0w|MEeAk>wqzI`JJgRdjs?mimIN^cQ@bX%mlv?&Wp&nCzzC$7XD#X)pl z_diVY47*8UJfuOl%KRy?M_Ku$+Gn_;^C=!3guv>0cL$MEXHeq@XWCn5^U_oNwOeZf z*Ne%NN#Pq<+?&`@Hn=PJ3|nxO=}j zt3L{!&CA+{I-eh-Z{QfhfIaE`*Kyn*P!k@uC}IC?=1XQ{CX;1X+j;fvPeQu6at<*t!gS(;2N|&o-|%9t=PJme#23X_HDW` z997Sab_j2eoRuj=l|6Knos|2_Dq0dlL0?RkeX7}N(W1HSy$f+r(PQMpe0NNBy)BQ2 ze*kzLytk_qZfVlEWeNv57>@Ip%e#zg#DEEn)ZF$Ol{jQRDs`(G0gCEf(X5q;voYMN z0Vdy(TG!c5h@;*D{{Vb!pQOuyO{?-j>sE?y$7;Su8Oy?3U`JrrCC4 zNq)hSl0Fqdn6IGW9Yj~StZ(p_2U@e9QMF{~aPF3F$n2a1ZN@`mbno`3aradrnCO(< zJI3zKX~a7=L#Hn?12h^QCKWILQy4k}`7bVzKE zE-M68X?oBNGKDAQ%xc3w8*m;^uNk?&Zd!bMRJPi?GDnCg*ow1~s&y+P#=KU~Xx&)l zLZg}vpb`P^169^lp-#81(0mnOas6V(Qi)a#XhrhYq=h&5O=4*+tfFLTB+Jl$Cd+t0eS=v98vs3~;}s7{@n zISolP%OH*A(l?kHs?TF+Hx@F{-#%EU$Yz91dee5Y7g3YWy5aAxD{b~!J*Gvp1qB}W zRFc-+M#rCD3c_nV$~DH|A?Td8E%@EJ)uv`;C6vBgbZ(NHbW`}n4!=I=4TIGwRcV^E z!|O1+7ywUZqZ)ZM;KdwVOJ{S$6M z(tLA|4EvPJ5Ys#}3$|P>mit+n?c^w4XI)kAb1ub0Wf^X-tAK(>4u}Bk5(Puz%9ZT> zC7Vs!OVYHuOQ~kxaCv3NaaUG$L|FyHu37oAq)6;kf|6hPmBKmWOS%C=VhK&b0%irX#ixPElE zrD;vpewk>_XDwQLDdgTCff$zJqI0~9%&EJeFxy+e-*Tj)o_4r3`ON>{hhONa?6C#LC8@7lRzW48NrucG=RR@T19 zqgvPP0gEtQi$`K033Td61nRgwsM?IWdn#_zgl>&sa$PIzyPE12USw&DT8k>v64emT zhT<)(Oq5y+30nwp?f4-)f`HS6WU4Tx3XhW8m3hUZFb|q5Y=%tcRW}T2W_^R)5=UnQyR-!w( zD4K1#ElOp%Y{+f)q#!!et*k8$phXawip zPGre4Nh_zfpv!h(XZfB#p5cdYmG+L+4*mPo z;Zf$qq^2rIft=_yoQFj_u>xCGTEaEZvscQN3eyD7Hnzk2@u^5Hhf2TK16sQN?Gc5 ztO>To7tH%(qBO!jBew0lJeE9u-ul&Eug(i=u^eS_Cn$s|vU+hn>O#Cn`D-n-dglR8 z@gIjjazl4%uHd577?5z7&rG;E`4JfH3Y?vrdegW3s$&V+mgeF86mjw;My&5nqdREC`uUFBhXaU zSDQjV^qg<{z-hC7vW@q;N9p9P>jvj;Z5yu61_n%qicG=}DQP>kB%UQ+rDsB(a!KM` zWcySa>8Cj`5sZy<)CWV7T5P2*L1=gPjxqWX-&x7htA`#hYfh*-K_`Eg8b|l6ir-vs z7Ps7m*-dR}Zc_k8?thP@yJ&i2T(_ zBTJ%7)`<40S@wSTZxE3zzPyRlzLL4^x4|wUAf*S6;ZpIUIJ%UDNXm5rK}Sv8 z%x4Z?ZxxEeg?E7FyIv=5>YI&)+vB9BI?3J1@2rmR%oRKr=t}68W~=Oqzw%Yxp|p0D zKBXs^z4m*YhTC=B?zJ%&#(u57;;*tQy3&Qm>TG{K2WxF}t<6vV$A5_)i);S?Ex)=xN;uAlXu~D4 z)|28}wl+^IQ%cx>WDRAvq?%69;@*~F$s6zg0EnWWF2-b7KLr!^{!W#Yo}yEY+QzaaYnsJBq!Bg4Yu1fb4^{ z`z9UU)g8q)^>a73KE%4fT$|&Ssl)Q_iL5Cq&<-`T)QowsG3};lAgP--Y!;|4+dB78+=bPwR|cI z*b;>hLbo8D`gFpPeferKORz)kM|CLT+RAu^B$2lJ@4nR!yy`AjbH2&Jw7C|W_4X?J zJDm3!U&%H<`9q;!I{yHacK2816pFpkyZxiF?#0&3b3qMyMkn)rv~9H2Ka6^ z->2yN>t}9szT<7&Qs+N%vSiJfS+Eo{kcaGtqZZcO~G zkYlk}0qbMW{NkL1fwySd5Kxt{elLTP4&!Q&>VgIFy9S-7sLHO06H+_HvL@0c6X>Fr zoBsf$+NVX;6_?2=R{XaIZNAEFI>OS-Q*%RwQzfTD*2`NQ4kG-Ifi;uH_)<#<)eD3Odzh z)O%{7zS27OcWZhI+ERU7idHu7J&&a^D-OtX>?=@o&9;=}(j;UUtFDHlUDSVnj891O zw(%bWyKJxy%B)=4>YZiMnfZ1C@{FurN-tLEjv%)=o0JuHi;E$%@i5|>4zy5uo?z}l z6_00UPMuO~IgIpXkaBfg>HMXkNbD0hiQN5wgHIDHjH51%TtZuK| zk6~Z|VC~Lg(t9@d@BsZKdUp>RY7M!D@4GgoI;c%{k3Qk#w;pq#-1;OQgS&dfvd@Kd zv0Jl@IE7lS@Y%Q`KKnM~E+8xvp5&|pvGXFfSj(-+g!J&Vso8I>*8n?|cqJ$7NFS!L zp3_0D07PU)`Q8t9e7sf0oMay#@>`+bTK@p(EQKeztD9U;^=A9ON>A#n@!eQg#vFRw zr=yMEh@{DD_{i!<-ZvFmi5Re*4qu-YBlMNEN|mZTGfNo>Ey?B(_~0QWgr6x#$Xk zJuA!0?m3X+_gAc0ErR1}xiGTZk!40&910!)ZSj4n+#d?|bQ)6HW#ysAnQg_l^c1uu zML%I9arak!(LSEsbZU#OT}V`>Oo2E^FagX0Ai&BzRwmQfjK6rO*I_zw$#GAxo{DBT zmD+Z&<(Rd~BNDCZw^`%3*lqU`;*zIi7SuwLjgXY5ak{rByKVb5IV={=(&JBzf2PG= z!|xl`z$mElQ6%`2OG=373G3@vdv9-4=>_wzZd(Mv0386(XFex+m2%F>wlqa5XZ+SH z84Zc^!MnU&`~sZfe?qpci%z!cL21M+d8I)A03~C)8Z?pnqBAao`E7-NEn=%2usE(? zC9;KiwYy}0YDG|KqU3P+zrQ44LXC;v}woLYphG1$~N7>GD?T&R`lX+_s0JK z5s{UTDG^ey);2X^m^VAle-#;?3t@A*#>{7UUSKbLpv+6@9!#E!$ec9^L6F5^`C< z(e5dvtA7etDu7f{VNUKvA1Y`o?W5)DDdM6`DbS)iq}3-b$1^~$F3Yl7T)Js4K`k>gP|G~P2%|F+gfk-?L*Hj?^>{{6XEPL5|qGgPPl?XF4NlX3-2-5BWBV<)H#vH z=_}llNhI|o(`7`R$=v$Tfqe|<^vCtYsE zJ#7*c7*|o=2aoEizkRxIS8W4dFDe?OjDnFx&M!>56)d$Wz51Y_AzOM8{Pi}0agTF` z+}MgGw+D}SJ|OuKRX*aR_r#kRpoAnmxXM2X+L(Q+fINe9xkHZI+M!n+q#8#zG) z>I`MeKxJ0EvKN%Px1R3x;yxRL<~oIakBs2>)ceiOOM$dA9hDTI@ARBm99u~5Dp3Fm z^~XgtqUWTVU5)AUSw_xUA@G*$ILJq9A!}2Ps4R{|b6SAkzzVS0WtjZLb{c3nc`vhV zDCB(3!>u{UR;Nh%V_R>vw1qInG|WySPLnV+&NAQSTEk?r%$GURPlDDfA7%dl=dVc-LuDj=oe$kn-A2e$fa(52skuV}Tef%(KMn#(E&IZ@OAM#-oLj1GJiO0Jy+}IX zv5_Uv2N}`_vyy@2QUvf(tQI-c-EPda7VR7TEW?{l4`g9SbeF+Ii<8H|p3tz*PvBHM zD#*|C+-ps7Nx6<&b3`~0z(^_uYkB1zn`NXY;&H{RV3Z^hO;xrlv~Ok&DaJ9}q`!Q+ zO>CvReZ3u}((QbFsre{nzuy6M#g#ZnOKBUZfGOK{on)C84PVu1uV-lTs_req_y?YLLc9SwSI zi(BtsBsTP0sv@@X)Z$xQVCLg}xo${3cBsZ{qZdp5K#f7f^9Hee>MKE}?{O{QqH{yK zEy0dlWwGVUXA$0o4(hS8x=B}Oz3s##?;wz4Oh9ITSQ+9Sltpv1g&5^vDhb*n|H6Wy|5Da4d5_gxaW9fYbceHHKy8=64!`l3QCMf2|FWk!~@fz>%Wa>?OVIM+lN-XBM|~4NSv_lBV6R> zr?sUdn~IoDQ)342AsDtrc}%YDN|ocN_djFjyR56Z?gbZj{cJAJjE$; z^sfwkOB_^O$KoiFw%vDbwEAU-Ui80DtQZ6vbQvkJ!EQ6M48?MjApCEk9iPkLOJ~;_dFNO(*t7LpaD}H|ljf(MpR01~H#<&^e&G687Duh~Ns_ zyZuc=>G~V3d;ZM+&ay*^dqXg7o;$CFk(6>f@AW|Osej730Qy+Ej^C3xvrGA6@IIC= zEBhHOALpjF_T*_${`caXXwpAt*q{!3CwnwH;B@GQCY7Z;Qd^3q_8y#6DYNX6?A{yf zn$ml8bn@hV-ektNJ8jgZ;k{UYE!>9Ov0IkjifI1;@lg4CQbFg9-G<&xp#K0Os!_Br zf9AXQkIggD=>5^;paa=cLeRCl7aF}pTE`wyva7Qs4^A?AcTW75{rgh!vB$3uTb zEa&&MC%=C0dGo2~;U^x7v0euM0Q)Zg0LW+zo42fN7BBKqXC&+w0uf1}a|B?ri~6EDi_k};f*+8-;t+2%G| zl>+OJStYdi{NSjgsPH3V2m}#T&HF~ZrZkStXZ@@-yCKXhR)m)2!jWdTzmPE)1F(kU z`yxtH)8-veR>Nc~xK~K0E5{YK*SNiW*R?vFj>5jFzCzo(tH4y0_MOccrM zt!cY3NIm6HxDH9F`R%oLiwCtuzEoyhF=Tf7c@?K1$?hyoWW1MRybsk_0QeF*)DPQ3 zNVWd}CD3WoF1n9r%%WrSSiQY&x8i=|DQIjdDsc`W_c;V1#Vn(aiJX5iDIsy`>5C{rULRGljS5V=;$2t*qc5X|mHXBA&mRzL!^~kp!R?B>KsB9XI z4W)#ok=_)dr7Z5H9nRI!R!dcj9JIu+W0u=}ve^l+)L3xaLrk)dBq2Ki=UUrerIhN? zbqiLI8i%+ii&+Bsh&Fi1DMemfB^sjgo{RAOxfi zfB~?rHdSJB=K^!q(i1W0G9s-9qGYzVl%5Lp>(KZgO5BC+Hv|#sTAZSL@S!k$HrB^rw-xFJ`wphImg_nrK5i49GcF4oDfNwl2jsS&s-M?cW!MjGSRJhs zX}8b5s6T#BVGt0e?dez2@-HZTBl)AHR$0R$_3b&PIMZoCi(|H3^7#$$_)@l+Oc=*; z-Lj=RN7+{)SoErM5t5t#0JT1%-L?h1V;QnzEhsB-+d8LbK0iaO1NK#`O6Th;eb%3B zH~!@fW_qpSecAjK)Q47q#SSSTjfe-K70W)+S`UNR@;Q3t`&ZerTNKnJXBMxMlH{hF zK-o8lWg9phX+Mr_3LT?kmfX?Tr}nMsK^tr|)tuorX`g~OC~ZNXA=jLe>lZW?a&k+nB}!Z1g?5TN6(vKbL0zuN zsKI*HT)IoA41i3H#GM>I?40~39^{iqnqoNr0GAGHlei;pJQKH;!n)_!_gt}=x4T{) z?H#D{PA`Nc-f>8`RjF`Oapn>gC3`7L3?!AXmp*N^DM!2`Zb&@=C!iiCyU+GA^m_jQ z9)3Zsx669t+9z9$ip(ExZ+_(JS0Uts-qL$CAS-Q@O1Annzqjhqqhk7%yf+y+WMJpv zq^eeIbL$4RcSbL$I4*R;W#M_XhW^^XP*t|%%!Aj=Aw+}rimLsnP+!nJTa@!e1wm#a zLX)=XZPs_&?@_5nQ|$Mk*@NbONu;>D(t+(2GCUN-k_a9Bvm6}#bzZ;hqi4!+cdRx| zR@B=ivhdFb?TNn|oFy~bg{3{5w5>@SB|k-JKDFl7we2a@ZCqpkMx&hS4=xLOvue48 zqz^TF#hm;bn(UV+ITe*I6XgCmzsNtHpVm6o!c^F>V!t|crXxm-(%yadoDH_iX#}Zj zQRtL_2_*CYRcWai?hAyl=+1SE8M1^ZyoRpQ*hGfZp1hEhs5!X)DXJpxtQW6nJdHL; z`ca)_nssSYPqzluY!KsR?hk8;vU@rTY}qA|Vl~d)`G{B>ILYo)4h1%$(5J?r?`Gc8ca;X4 z!?0a9i_s}^^(Ia7N>Vy>W+GZ)I}N%QNEGZ%DabPL6`p%c=2yfkVyHOt(ec=)(`d=} zf;#xSzTNdB>N=Z)y1>BQ0GD^>1`d#lSzM4zPrG5fO$}{)X4YtyZPQfN~ zM1`pD?l^w^BR_i2*Kk{L=8> z&b7+IwX-OW_0K%^hq&1+p*)u#5U??6E+>BEJo$b=2e=*5qrXbq>1L~7`m-i--I*>f zLu4gW@yokD>n_piwh26klc1>z?*^vv0`e(s-DIUg+`_p+WnaH5_+y?wc{04N<0ACyY47BrC2DLe z_mC0*?yHK~2Hj+Yi)E7C65S27grYn)Qo~6DxC9^oKSe8-jS*j3p<4d6!J3XIyp>^= z>}G*ciC2{~KJRL@#D0C&=?)ZV$S%J0htk_^Aa|)sQQ0HZ)C)1pESS9JG2*MvQIMg0Wv|HD{;ZBu& zQ=S`rHwOTIiacx0XxLJ1F>!YC)*UkKJM}>$FVZmGV3IJNfz{kyf{>HHfbTy6pN46x zhNR^hl*`ccwGY}cvV|r5zGMQmNl$T2x;nrMANxbQAa|uIrg2O|AH!n59>)=3>8tH$ zPLk<XXeBC1?*vwz zt@CP9bqjXjW?4*^a4s-09{|zzS|veJiED%2WbxtLH+%S!o=U0a97}IDw-6SDs1+oW zvXj42(y>CTF5MN$^O=a)-wc2MXbs%}hplcGNYd{$*el+u`ehtL+=&8TdoEhnOqLD+*=Hh$HuV)tfnyHE}j z$h41D!*S9wAheKTuWH)BWF%=Kd=C(R9aVQbMOSIJ$ZrGHJnJ4vX+^khT$6T$b{m1& zOba1B2hmvcG^mn2q3tmll+=t9HjLwGa0gKRifVxX_oudCtGst4R;m=qUeB^vvtDIC zFH5C5mMzyOmRxrhacbEyB&_aL1w=%>WmFsO+rztK*m`O7COja^;ui4kWe*1W3q*^A7?P4kRGc|pe zO;oXy1|PZ@fE4C0`s7+E#^MC|AX2^qg^*-yvt1S5i$F1dN zE0{R6;*5ip_NNd^U)F+8x+S7U6;3pQ%ZK#*=1VQKS_xK6c zk^9{Ess|f4(qu}D2rJoiA{jx-Ut%dat5}#)ox$Bta(Q)sD;D*%vF*9tjo^#~3C#pH5%a;_$4a=huCW{DW?pjSmW^iwdRc@`5wmKgQwx% zp$0^Daz)F?&)hRdXBK%6O2dpJ-OYP_mR-)lFTvuDa+gLUlDr%aYCryHM_VpV4K?jB zbLcyr6Fo5Br2zz%*Q;5#!+pLlXxh5^u3xv2P(QS67EwQEVc<}V;ZhhO*(GUP65OaJ z&U#zlS<$VoRxKaWMf|P&C}#rww4UB-l~kB_Yqo{q;PB%BG-VKub28e}6Bz}BweP_g z^|{%&iIOX_3GHS7{D}UQupIhbIc&X>udlC{$bnga~TW*_|t^`)Y5ipy;(K zNm(6+U_(TTK1A8{&odGX!|Ej8=a{gTwza5)>J@u7V2LgPM@07i@;$YewpsXz`)Tlx zb%Mp0U0AME+H-pQM<+(=QULQ*7rsb3^xj#6!C8N?hWWFprI+&e{A`|k>)f%cwgDHX zdf?}v0>UEgh^YeKyKZ3ojOT;s^E-gG3pKt0SFF z-Mv@IZrTr;>uQNSaK*BfCeBdWfLg;N;e1VJP#av5oDj@d989@!U6t_!oU$%~$9dc< z6Q_dBn?naBZPzf6qU{VrT<6Eu2G;nWhBrg>;@;>)=rMz&QcBAJ%#7_JLU{hzq|)Yv zjwr_pRSSB(nq_Z6*K@3Y;f8J91a}cpd%_4hc0^GKzPc&SyCwo=B9XeA!m?xH%P9(0WSgV|vXAGR6FC7g#a6 zQsQ>paJQ6Lgp#>_KVE>+^A)zfNGAT;DgMg9s?ycKq}1`CWmhv z(KjsfTag=zq2t*VKR863Kop4B}Z?FhF{_3PxBM`sB z7I#{0+)~t#bVu+)1R%^?)nf+k2MHfFw-f(e5zW*k`lOu1?dtKammkP(4_8$=;?J@j zwbqyGC>{QzE`ZF%*r|YIPeprIw$nM`7DmHz5V3Jd?t_uLD7t$;i&V*zdLfli+nfEc z6FNiVSuIaBQlpob!66(6`vf~lnjea4Odi6xVjNOr1Z&XC+yLe{STwp|a18)>Bn5au z-}k!XkgCTI`BSfw296_)FfEBo2E0_biwsB`CUwb0na9E{;r3jVd|;T4~< zmdiIQ1_&Cp(1#sKG3}~}rYr$5R-ef_=`?!^w2E5s3_~EZ#0{vu^aokxT=`=wxoTD> zJ`x3Rl=aP8H zPeBS)L?XdX@lFn9VU@MksR=cw5Zs(Z1Ymm?+T&7p=;+bfusD1^`!RYohVw&BYH@|5 z%r_i5?(B;{B0T5HgRSiy7zN(0T!6&FATirSnM8tz;f?~>6X-uAJBqrg@AEz&w&jIe z@`wW=PDv}-9^s()6T}9`-}j~t!C4250Yr5_^Kfm zDb0jvn9H6L-h4h_`mbn?Pe+D@U*p{&U{Eg|N@y6h_Ar+%8J=p;;Rb)zihWIm#hwOf z6nD3EHnkX5j%%LnLJbesnsB9VM2O1yBQyBs}fuOI=JB6>mOY z?Nw~>so9|$Q1^C@40n=6TKFyFB)vo#P;`UUymw(|OGa#y?AvXfP+`W*`fyq09crm3 zlvH0FE!`@HwItoi$^1~;rZ7Ad2b!X=S=A&?UYpA&|Gl-9&?{^u`!<;$8{_?@(s$|0UDCPqN& zR2kLD?NaEz-CKqgw}^jsGH5eJ+so?Wrj*$FzhyvkO=69;SoVABAI+pf%J80{#6xk) z?P8Hu4eb~BKCD^Gs=mnUfB)0~Dv^+okY|url~I&1VHvga)!EGC3fwY}+H)C1N3CB{ zj}&(#D&v@E>J7fs3)yFuebi03p{Yv6^vJbpWUJNYm0yH_}9jdpa=CGg&k=`)b%2uKOV|=Wp4?6OFNhbW*nrt(4N{NYlp4s znhm7T1<2#&%iz)@L&+KieP?#Iy}&lBozY{w(9i`F`~{Dn`*v(3(V{yxOp1YXepUUA zu$-oq`46mE`t(lqml;2Df9W`XZkctmsrNk`zzKd|N}8iDJO?aIVT!3rt}H`ls~(Sa zSJ=7JaNg_r(!)mYMkweD^1)<3$eQan`K(CM!%We`^nl4(jDJVv!C&gcRT4O@F0( zzv*2~3P$>_olW~Ud)~_Qp8!_M+0zJgYbx~BEpb8Ns+@zw8{z$?hvh0=jlMPNBcWtG zVnY;~{H8?hTbrE};T+5lYI*BPd-E>;A$_@g^K*`jQ+(p8gElouE3=iOzFwkb2%gL) z+qu(0j)*wMCm4&skkm|YxsbLYKe{B`VBfQpQZv_;r4u8_-0@iUdM^Et`51j4? zWTM2gaY8K5+1?_Le3PhqzI~iyO9Wk)uLcZmh`O|!&i~BcX28=qx}b0l9m1g0Ds`1= zPbhus_|+^kTlG0#V$(~w@(pe>71~@+b0P5}KRJJ0Kc()6AsT!E?-Pfvym77O?HekGs`yqQeu~Oq+#IpPwJgzCa?X09}P#I z$7jdw_jIqJ$FBWTFJrQ4a&>T{eJJGfm+5sxHECPVi#6#`Rm~}It)_mp#E`BxB$5O> zrIiF43VmDq1Ls8nC_1QXdiK?GE^?-<^@s~Pfr`YB{>DHMg$B8Nr>B{0&!7cLS|E0V z`Ei@I>yLjr&F_Hbs~L-mYWaER>TTbcpb`Q(q7y7fl|>2TW>8I?9LQjmi9c44gAYTD zL&|j>vidsSajKK`mjmb05dVKU3Y`Lpmx*6|wRP(eCScx*1d}isMCwVXYJ8^tHV>Z@ z=pSqD@vB{4oCW{jwaBBEu+YJR-Hkxg_kJJMi0^2U!k4NY72#PWoCVX%EV~kli0Hns zu=pTKBr2&iY*T}}wxg3K%F}5Y@i#l9;ABe53B;*WWa6vE%0^lJ`Gaj}RSQit7BF`s zE2Gr>bc?hsyE?sRC{jJ0sc7kpabIKtyzJ1Ky1+pbjw@8f{*Yi@IPb)H!=iQE^~MRL zuWFIerIcQX3ZUn=GNcxoA;u9d8%PFDXK1M)kd4Os_qt zP@b#eTp3Lnv!Z%ZS}AH7GdXJT3=*0Pm6rc>I+0iCPoYlZ3~U4@T=nXhJV=^7 z=tP|RVOAqk^y9ckBLuKt!qjKZtl>IIeDFD>#Cgq;K(`~ozeIE_6^`ZEGe0Fg@4pIm zop(_7xh;Eln(@$(jxlOjj=yS4%Rg@vH9QziOPI+>&S<#sm3AVszU z&?0*JS1Iu2K)MibOa6#!L(}d;MRlrpB{*U``)R-gM*eiO=4wHJ-38uLQlg{M@EanUm5j6*pu2k(&t*g+>5d+!{5}*VscRamn*Mt*ETxtK;>4^yA9B9Y6h&pe zc1YI%`{_9_gsuRuTwpw&Js~Q5ko|KOxq2Du z)UkSbw9r3+zeTEHkTf_ZyPLX#8`>%og$@c{@A5rr=c~4?sfK^!=}(l!6Z_okzF2=^ z*UHZEm8BsB_({etpbAT;t(7xql3ouX(HMRV6Cf%R?kr6#ZHDGO`&SDWEUUP`X}4dA zTp{?}f9c&~4s?oXQ3;!{?_JAY8g(2Y7~|AqbXi7c2K~K;s4J|P*+on>wXcwf%3`Zp zeq8o)mf%jy#DiVD zYAV#4PxvQy$8GQA`E4k)$bq^UDww4I@vj&y?k8`;?=~~*%Zi6xz71X@#{sBJmxCEx2 zrWIn}Zu@FE&6koKU+$57xs5D%ck_w0Sfv>+M63F3(eDLTJ)L3ywEvLa?{B0c8qj)6 z^sLIBIBi)NmcOv&%b6q@8J*P49XD=od&gF9IKvc^DX}ocfGmn1dVc}5e%fU;Zc96m zFE8$4q@RA^=joAep`prC_VV)~_q(*YrENvq$jp?cS=0& z>37iWx2NziyOs!H=ifW*Hte~D&K9?2MmgAx53cWw(@CL+qp~vlzKt zeB6o5w>4h*ejFBHcj7WPouT(_87UPce~UrI{L)LAG6jRBoBS0`Pn*?& zB#$`aBJ_1611&&mPtW6ebr}ST_2=#KNFEbL5}i#K0v>E8^Z+laiSisrPmlS@m0t3> zt82Vw(l|dI4}9E4EyN9DTuY1A{x1cCyESZ8T2^L51Qy&>{M}^_pG6uX zV5VuAMg~w{dKyMIH3^nd)9gU#JXxpFQZj*@nMh`lq$i8*@dA|9V>E!#^3zf}lHva< zp@j9hOH4)H8plV-giE|M7~9A<(O@(b)^JV$BxZU`lDadGkbgv_8MJs5ng=ua3pE};G&68e(Ufp-QxA`i8ZwAhc> zpIx(Mh+k&wVu9PYfe9eA;r zv(svOQw(7GB9*WU3y9xSjCN`FTkUd zd8T7sPqjYmT-LOs9Ir~Z;YTx;T*~cZ8#%(@T+dbA5@c)x4+U@H_H}68dvQ9?FR2YS zn;q`)2DCivr=H>O2|v@F3SPAPM?B$dAc66@SgmPR&IE~mq|&zXKTazesuk_d9VuZ+ z4I_Y`AEFYmhhTgTcf?Q~PNz^P)~ti_gJevwUvOqwaFm9j^wZ>q%* z5{he^8XYIq#hhprPdquoXaZ;-28J6mty+cZ%s%PsKOQr+)j#o=5&bUM7v6fp*#=4( zQ&$v~mG3q#fje=<`vXR4en#m<4dm}!80Bv5FRwq?nTM z^MxCkFgx`QRYb!{X4IYDSU@`9bBE(4YT4JXC8LYZqFsl%Yqx#D$h!J|6{ZO_e+x$ zXck26+dGb*eEg_7I-edb=@)zYH?P7+I3p?UHOQnA4z-9_|2b|glr-slKb8e!?h$V+ zyK#422(x37v!jhkca#0@Kv(RbuTJG$3wI592C671XsVWU7nvGkDgsfd9=pulq2`bp zxYla5$E1xs+WU=>PguZrEYORACDQ@vbCJZY8g{1=>r_{(Z|3(c@0>a!>%2)zs*Oh>a3-$nJ#K$pN}MKp9Js~Y9wcT?G|1!1Ash1|al7WJFhBg}qnL~h={wtZjQ?0L zj&&1_PIK1D?ua0$!q~k=(Fwul6|D{}FpMiu`s#M-&ex#Jo67Qh+g|dr@AtzE)w-Q> zpeKEt_TJeC)f0%#;j}hoCql2AK{nLq4_;v*YOVK65laFtxBZ8dP>y}_T;F-86L%0a6J&#pCi3SJ8SX}2&PZR|=uow65CI()=4V|W3t8}~sYh*9;O`*V_z?%V%$f(`huq_`=jjv1*6@Cg3 z!c#FXtUae?@;8b&f9N`-hif=kboAXOF(Q=W*C zWV;G8S7^5Vv!zK~Fe*A>2Y04NO$)Ka-74R6lG5SP7G6GU_}w1yL7bO&%Q&biHh z^Dd{1V@Ifn9SIQ<3JQ@=_1dp|AwOHJ$XnRq6HeLIi(k$?W%tXqZ~xNJB2KykIC~iA zFrQ??v!7ETgAyGkX zcS*S6l;}!j^WV-C(VWr%2Jm{);M~o>{$dAn?1uymM(StsJjdyYTGcU;Rd%f9J}LvW z0~gN`w*p^jGdYhQ)qEM{^)34Y7Yf;Y1z7x9TB3;rUh6Hgil?nn)Y-wKg@KAr8d7>$ z$%mzWU42WH4~tWw;eK{)f1X4e|E2yqvmO3%-(O+$tr8X*D8DA!5jfYt@dNb;lo-}j zFc+>fNjAe`P(IK+p||{-w?#-K<4sD@qrEBD{zq?kF^|P0oy(l+Z-PXx)6P^pa_T3|q85~f3l=d6@6-eBgmR4KI_YrRmW&C!&k)|P92&Uu##M0q z%O{X24DntgO4yFLb!P zP`)K4ZznfUTe6WkP?PD2@y67ah5nNqF|@3p@N=4C($G_f6P`fv{D#*ErCRS6NVb|C zB$wUSl;md{FW6p#A?s@8aA$00if@XGW_n`OwjeGh0m}%YISs9F#Y9KTiqug=9}o!r z0OGa4j#kSf5cEdwmj}uy=r`E@FWBYT6N53k0CkbGWsrwavc~83mV>>FBc1O^KJ_zQ z@rrJi__&DejP0-5NOs7QvqtMsxg1ylRZ^pR=)h4b)vAjdro4pOIfjfj#1Uu<_tud6 z19+3nuoezLB2@o}DQ0axSAx^q!BMAF;n~^LO0a7aTo>Nv(eMwU?29QzR~;nPek4JM zd_2g+{^M9(q0tDuK>bcO@vi8ja^?qDUyi+I&y*&Sv=lz^!knDadmY&+j^d(kvj*kQ zj%oWm4gSo9wh~b0gALnn^Ad9*;ElqH;IcBUQ8C0@RqC1q|FjBg50Y%ZJ!lT3YW_-A z#4MKrOFTe>$7sa}MfUr_NhOMyV3bq^H3UeN=t$+Q^Q!!WU6LqItMGWlX70|B{P<6* z6oR0~xeiC=OG3X_PZ3c7qYoxUI6s0^Ki^p1@9rXE<91W(c3m_IiYz9`eVo}jQaNRE zVwRr8n0m2++?1Y*>^q66v8?OI*p4B=$nR#*5B5Kl5=@S42388Pd`i!tNB`n?6Op2gcAAzY~XAt_;)<(WbHhJO#S;M9lDQ*pHy-lih@G-&Y_CZg@}46fF%Eiy>YbtjzG_-lw~3aF*~K zwP^=i2jy&+Q(=+x$^IB?JJ?+=Mj%d)q^w#mz501Wmp&Yr2TsI__oT^vXov%TjpO#m{nelK-xKUlxn^!x{I$u3DEc8CD z)e-PEbI(mo_(ya4BOY=EI?0T%?lcpEm8jU89b1bfQJG(+96xD(Vv?RqCp>#*)AbvR6I=ZdyzCcIKEy*23N zszE>XjI-bjWA{43t|E$=BIQKXRi~t*#^rvu8A?duiw%})tJ#|V9DtE2E~~MMr7VB2 zOMGCsa+T6~pt7_3M5b1hRBr(Jz0z5~20c8`givr&!%)y)e$B5b)=IX+5A#t5Ozd0` zbp_ny@&}*U1-u(8{5vrZexCU0JgVI^Q2_mA(ZpQx=*-T6>TnAF1LDMDOe$*UIkpMw z8OhV`xWu1sd6xtPX%QYP7t@Fab}O7B-`&c!iY?~My3b6J7G#)%ZPqKst!C{E!C2C` zJ)@a1YO*d;#D&5!?Pv48m!PX83sTBecqDBf{M1_)&e6eV1flS+$$NO4+Ua}ex`c~e z-L~H+cjRc`)b+W4)Ljk)WT!bJ#kiO?I}t5}p@QD1%ENk~%ce%(E2&P}j(Rj9CptaA zDd$JO;@hZfKCMVS5{xE_Pg99bYeSk}<+Z!{Ryhqz9f3w;Z+h&llBPn00 zdF$tUKDC+Zk_yIOp0Tq2;c$FcC&)gAft&I4vc_uuFZv_ZZthB8xTSTi8iXf)3VX^- zrvq$!e@H6+Gh#ykIZ0O^h?orMPyP3u{13d}GXMXrLxfKjvr^fk95|FAN%Jc4 z(AK=5S~b=T*%^{TgXzTCK!yL1Hq?%Ic5j=77*a_Yt&LZ+AhmpYCrTcr5ZV#Vabch+ zrtU`bLtcRWn2CL?423xig=LVfZE%I1SU%hCJ#kDbWzrG0(BZO8N(N{v7IaU8`hatzc&i36RoryZxEb)Vr{pTPPGr+J_!c!8Dw1ytxoZeJ_u>2M2K*W{-MroRdZukV(Wt#xi5aAfjx%ti zWdc%e9E&9#ZF<)xcSh45<7{OCL~I_IEM_p*enI3jaJy*_+no`*6%PTs|Zi}@c-@i=`6q- zZ#^r$T5R}oj~1eH843-IU6*7PgI%NcEV=O0AskavQOv^FGld{8N`CUcqO(0MtC2p7 zy7(jLoRyQL6dsqlZSh6jW$&S%KUO!2n;*$t!27kY1gF1rX`jwyyPgmauF^6{%YoFl z>e(?&`V|R&1CqR0&Urt1FQKR&`%XuGbGwR5xO3`}>NGxOyJZ`4%}PcAN}Oo)&HHSZ zCLu3Twu#y|7j9-PVptAy`b$la&0DujNPjSsR~>bvXN5l?z|)G+h)@WI&eP=UrnS~W zNE09@#F`U{U<(j+s&6Q6)vZ0EWw8o}7O!Js7n3>OM!9at%Fmk7F^UBtf2gOZgnoJB>0WIhLODt zPri!5CINij;BDZ;RU3D)^{vu3Bo zrM5Q_1Sgy1H0KA*0?If*wmXtK(h7|)CUnwC;J>>r;o@XOPU^?)+B_CADA z3{X0I&<0*>o;s{BvLoec^zt*0TUFQ8fehOl>gIqH*09Y+UG*w?4<5E zb=zFV*RSTyya|np}cdx-h5iLmG{ycVqsz3nR)t#2Eu3T5H zYF0#xg@CF>KHyAj0|~c835mls#Zx?QLR85SgVmrPl-$8sh%&jDtqX*NGshwwZ-q9U z;xKwVMx+EL-<*~YwK@Kie0m-IMmdYz>J+?Cq``Y63Mc>qA$y$>$7Vc5gX6l}<%S)|~vN&W}Ka z%jcrI#XUE_YRwMIDi782f+6^u9Bboi=d9ew~?u`p+2c(@A*=8 zeh-Gm2tpUrnyCKSX5xduQWIj8I@_gB`SF*>)l)j<;^|Fu!T*oUIU?pxWEm zly5ReH;Rj_e@Q4!bRAwB$96_sw6*TL-p0H9myA-SbO#y zaOAvjYPj8Tq)I>j+fLeZ-@OPG=T^6^s5dzW zg)v_t0PM3SnWK5Jk-8_%eFzcs!%mNtSFTQzuCQ`Ae2b=vyTZqjv2J}HB=S6@JYMJL zqUzVH|0I%%g(Q&S?U=;57Nvh6-ccjy3iPH=lfXY4WZx48SSk;Atpj zLtsR`QGMnHN~ywP5^K;Foij`#8-+38I-lSztNHkjcMPzs{AkbCppyO4f>Kdzxq5yd zZ;}C!*0QpjI*R!}Q1DW9eV$@L^gC;y`m*Z=io57_T zHlue;y@7=4ZRDRO8hELpMY)!zC$)<3wSm864UYMrU=PfB7o3mA#dTar&TzuFLtfqee19a9&Ez0pR8a%I#KK$~A2x zc9jF(na;f2-%fMhdXMeTe~GRm)8w^$?|GX0W_ejV-~O87d7{&_7dr_I{B;STXSVW} zJ*SfJnk&F-7lYu&9QV8r+I-qavg5_f=Ra9)idoijw{2p&r#@*l)f+5!yuHU^QTbW=mP%Dt zV;Sp4ZM$GWAKt^{R>8ZIW0TZE2xW~Wn5&ykp>!^Zt8r+Xu6zwV9A&ioo#$+4kn%`e z4uIT4X0IcQuBak%65S0s$|;wbEfKgIk3ig5R>xBy8du?DJ8rwePRZSl%?CMg&RD zgrFw6qPe0N6R}PR`AG$rXeM-HUapY$H$C?sFT{QvcG4w9xJ-HAmO+Wyl|dPEB8pKc zcGrIL=O4-*oh|yc>G1)Qma0lVeT5wyY_GAaB-bJc!r<78Nm`wca;XL0)%WMtS^#`LH< zfCLdN+9e`&qU@ew3B-Y_Hc~06%4LlkCm?z#rYfC?VZoTdC&^E>OYld^0oK7FcM7=# zXZNtswW3wQ{!05Z`^IG+}oPXnOWXb*olUNuD3|zr*nO-Rjoym+{P)c z>lKx!y{(34t)`5S0gikW{B(~WE7CV%wZFu5klOphh6Ov-k4#9=KN9$?^6fB__^J2m#2*ZVJ8iTq^nU_sJQFq9gpOv=VyEC;*VS2mWynlI;$h5T$s-?8)^o(> zh?{1G1y{&s?=M8R4WTz_Tie!pLW*+akGS5xN(UAzFTK6*{xA90BUTcIoO2mC!@2J= zaBj$aUA*V!yoB%OB@jMpe`ZZB{3mPQ1!jA62G$rB$7$rguZ*KHe-SNTcZ^-mWX)cf zK&cdT(T(`u#qmxF=~uDH4UIRs7EWU4LOQjg72!!2^g5p1*>7zx$faj;-8=?~SAA%i zq&f*p`rNlwfXL^?%C?Lz31_zv8FXWaG=Q#`qADVL1l7|myY@Zahft+=QSzy;f4!fi zr7MN;Bq=xMCEp4PAWYoUA7rvF(YG@w=eCP`=L0 zMIwd@2ZexXBmYARa(~iPd=Gp@o<30#5dkEn5h-Hbltx&5|FU;mp1y>*E4A-<0awIC zH+{IgF4-1grp+92=lEe`i%8Ylpm>GOhfAc$6Cxbj$cUV+#Q(iA7m6=JiWpTD7f6dE z74h04OiF#?E#Ar_O!BPf{v?gCb5F#GAza?^c&7g$y)$}+&femS2P3vnipLiy8vPza z9wXMTc8>GI&c+L09CrG{&bGf;xxEPSxGFiO{(lb@r(gcbc>M(XvtBehgwQCQwc*D- zns^J3FxvlpC55li7$OL8USdP?#y|lqhw}f9cFr6sqYqo~A~PO&^}JAIcyR=r(jnx}w&((UA3Ow^eWvnia?N zIhoumhVqxs&7q1)<{Oj*uR6(uvi$B?Aman`>ju(3n#9|$;1*YYEE&b%wsVWJ(7*2T zeHqaF(=`5@D&+A&#AWF~B^062eRp$UH?&h`iywZhB z>Kqxp*{2yKR=Nv4^j@I_i2IXTRs~ng?Q|TV*ta|FNMQo$ZjnB88 z=DsAP%n$Gxe~{5nY!0$#Z$GX2<^Pb#I=5if@BXE<4$zHni>H}LuBTZ2(X}%a{tpRt z*|un#oXEmifn~S2m=BQJ`VpW-Lc*}yTrI4N@7LjtJ#L^C&hQR<3?ZL-th0!Y8?S ztXKr4PWlj@;s*1cVzUVyN?^Et!#Rt(uInpV{ZiXd*$JP=HRa+C!Fk@;87J1h9e3Os zLS)m&_nAK}|K*r3KO?IxFfeIR>y?)6{OgpGfKMJhDX{gY@W!DQD50o|$e2(dk2#VX zlhdYgzMt^3z7{BYzG?m+;xcv3yH;^bRcH3xxWoJm2j%%DvBj&_vECbW*d{w5S%@N0 zYY1ywOto!xgP+5;Hb@LfjW!%CIBFF11!TOSL?)W9|Tb(eK& zEI49#b!f58I{daJ+XWN46)qPwU5-`kjjWooPQ7Qkr9wUe2RCA0L(3Fsv->7?vvXUN zm&<$<2b_uh7r9_rlby$|xAlUEOrwa0?klWaX1rlG#x3yoR?x;$?9B(!z_W{V(H z>QeZ{K$MDz>)XYCnq2~;)x+Z9ivH1n5^jf|ijL~86rfpC$GN&_FT=X2AIBo7F7iJ- zotpDhhmQHIn^*@9BW;@mV#7KT{h6@~@94!9z=mOEqK6ttIITt-P7p3W$Nb(6)1TEm zJ<6Xu_ulo4ua)-O+A;n^SBTqupxW@R911Tx;=LsH@iZO$wrKsbMGWZG8 z^@y)j#_jDtHi1RP__4E*lRvo8I|*BY*T;xBa~s-5$e_v$`buvV@wPDK_JnPJrlGD) z`q|&4Bb@wJM!dmbp7gNKr=tfBO=_uqZqOx5>M}p-1vzm{OR6f!FW@C+fcXP~ zAs6rrFwa#cA5J+2%ENA%^I=y!eFOF^kxNY68&iplHtuBK5@bDk)r~VQoKif(Y~87ns@H6oatqqn4x@ETbPndU*^rhye5p-g zl0{n5Y-vyHc0F0X_P4bvT&B%uUb~4LIJ!Pq1O>fJdWL@NR-W!!*Wp!gY!XhrI`oK9 zz+S;wBd5+ZS1jET>}sp~*}t+O9#yE9y}I2vM7oT~J8;0MEMpwr2{Jg6kKu2XVsee!>Qp}rLVEr;mD84HFIT4TGe$G}$Nv;K1{PzQ~Lo8+5` zc5>xE(?1UQJLWBVKh?-y54pB|)XDf7q!Ynj5q+_Il?rzYOxq_&YtnvM`)xF7wdFv- zbr@CTRq4=LAq(%8=G%U4C7Vq6N*rnCj=&aCIRU>o)ITK9L)?w#79uTF6nIm#u#aTC zk*b_Uyycy&pGvStF$M}}MI}i?iRhJ5G4tporVsAa0>!x3#1UWjkMbhMF|Nd?`fOWnVnG%>Zl4ppGAuj@$-n_HpAT#~h`+YPUFs@#&)fp2Hri^gunAGU6Av{pvT zI`b_lx_@dmI1|s&&-z@VPWS8Aop5b>;eS?pOx)Z1XDM^q5+1;u6#_SH|NRK9b4{cGhsr{;3dCcDPHoY%c# z)ht5+WV(BX5>}*pcG5A7{${%m)4QWEW_!7*;y%2}J=B&9E8d_JM-Wj-cRCNyK7TlB z_H$J$pqow3N5FN~&;_aOsrlVC1=?x|7h#C3$YKT$`90CyD@B9_tT^bin`yl})cQ=l zq=bzj^1-w{XXYo_c(xa5!&3#L*4b1AA1J7+rK7hapKH1KEJC@^-~O2HMm#MjAX0x6 zMw@Ot!86GZPhBIgxHoS)^RADT+YnCS-yTPJW=~O6sINWD(KAIE;S(M!Ms{Bw1%X<@ zg?*e>jH(G;w6fcRqPu)1tJ}~y50QZ;J%D46Oz;nRA7v>hz`V0treZ<%U5%1#Lu6;j zFW7LGQI^BY0RhX--@P1mYAn;TWF|GQxoL3f{dMS$lK^ekY3 z$%7_Gjby6=zMfif`lJ02huWm6Wv0QS!&6WCf}i%VcUr zfvanOMSHZ?(sq2KMBg2vVO#AVN1-Dr@^WnYfW~E8;zHv&vda?*-S~4dU;Q?DU++n!W;Lxr-BTu}1jjWHX!f zN5M!Vsz8}ei{^k4=}W^~NYwS_shPAoV#5J#DO4pypWhfZQb9lKg8`bly=F=^N(6qi_Lpi;&azJRfp~dubliqAkm$^>Ud{hDNFLQ&U zAhB0w`AYD9XGW+*6$EnsGlY=fn(A&w^H&*nGin$e2F;jl`6+JTdQI)KxyddclDRH9 z1sXfg?kC(rG{4_Mem)MoIncX>)Uw|~QuU~=DB{h^`%Rv`k*}Mf<@n4kY2~3)L~rHy z*#Q;=+6lpLBJ`RD8PP9W>_Mm+(zC%&%*8o~dNbY7CKPyFxc6v8?~?4g0EGAEk487y zE1r6ivu-Z`lJ5z6GOeN~h9}t0yAIDq8A&*Cw5=TNa+%zIXdj{W3QL~2<&e9luXL{( ziG9`oe)7k(jzh|ExX!8T4eoqZp;NyoS3`Km6d|)g(<8Zu4>0=i6(`ZwF((@{E`Ou= z*y=kVagD_5sOoMZb_ReA6u9tI8hO4)F-vp{=`Wh0D6zeToE>}arUEG-*l$~`JQu*b zc<`=R4e4fQc_nhwo%%=1!{7SMJ#)1>a6b_2a&C?hyP%ccn$k6O=PhKO@IPxh{p*w! zoZrc{@R{N*BwHDzsVgp7XLfXI^SE!{q$Q%-~qS*o;R-ok&nc0Avp6$@CV1;!l`7W$N^!SoO9fD zHROMWTK+rK(0_+o`d>pu-9pA7|DM?0X&M!uds18>ZXv^ro+&q^-~!)5B=06xa5HbS zu=kTC;^tuXKbKy7KlJZ8d;UG=ajS@l5+R`hRIs2;mmesQkMI|EAi`&I;EZ9Of|tDi zIY-)K(8a}ZrrPm;{d(Zv6(jr4ip?0Dvk=}wzC&&yvlPfg>05|Ti@{|x&J7S?f))FJ zqtOg(-A%0j|2Hvfu-?TQ$RGci8O}A~=Knouc?8n+*cRz@({T&&CJ!{&y^6>7zy&K7 zeej>fcc!>ns`s=5>mXDY0oLAG_!go>0iOEfDdB5Epg8`|^8Fpj5sUF!96#2oe9`p-fGLCoJ_rSFEF5>B}x zyM?6udpp=8_iC)>|K}vq&!xfDZ{+Vd1L)S#R*mq!dI5OCQNZODzTpb|J1%&$4Q_h* z%!|Fj`HQ79CeMFYsnHDZeg4NZaDEDaJ7Z3`7Tn;edf;C4DZhnm-4i|omo6Jzv_FNy zm(3A>FJC#hd?4T#Muz;~g$i4qd@UzeXmkdutl8ypGeUeqh+BnCFt*P z{GG-oCeB>3m#6g}pW$W(KJlcr)FEK5tox9sb1{H9-52UY_$^ot!*w9b!uX8i4@^uS z3Fmc&IbN-wp{aPwgtbzQFy`RbKE+fOXjwPY3Fk|+cwuyhNI2R2lJ=#S- zY($DycfV}A9PcE8W?G7yc^h^3a&B%-7|PY6_bo*Cr@r7ZlKuR`lt7v!rYU5vw(7dz zvgNnhIr;Run|VTTRAL(qei;AR>hkDKwkR?wNEg&xw`&I@uuu~{PnNB{G&J39}|jGHWR9guqs*&EAQbbItFCa^wIzN(4!@_R4+{! z;XcOIWQtLH^fn|%jj7`~&$UfzhwiGi_#68@jRu~8{sY1ll`!_7&xZ8`JNBhg*^7Gb zKkkZj`Gyz16?q^;XOY*Ix}>(vR#NWq54Ls73btp^Hg{W#$iwRI69X9MIKhXo!VR4S zODp5E!|z@z4tOn7aO~r8CLysDK=(}CTavEHzN>?A$nj@6GHFHX;X#&7`W+P%Z+)Xt z>()ckEk^vHwG zB4OK1EuY2z)u4F=JaF!hwDDp$v`T$}(u-@lxDZ8`AaZbq-#{x>4w&~{tQn!Ittx47oxp|aB9mVu(;x;*Wt8u zGy`@7zcr(%I46FEPc!T-`gt+#bR;_$wsjRrLuwzWG)KfE@P3lYiVYCN-Xp-VMz2@4 z6pu%!w`Td@Yk2qiPi+qC?|jvFjxvgBY0kJyry;{Huv#qA;Vo{gZB}A(Mm8EAzg#LD zISb-4X=tiCW}==4W)&0%PhucdG&T%)f~;a6<$8bB)NFiJr~Tz(e!WmD6 z-IcDRPNe5$qsyY8dJVBrpTi3gJgKK*9ec9E(LyCx1$L2eS}F_-s8lny7Y>y|+bFij zUdK%0))`$)&0N50RMWJ$El?TyHLL@=^xi7S9_O|OX%d;amOgTPS9d=^TOdhqpZl7vWgO6dx2Gi_JkLT4fA6u8j2`C`K~|U zJg_5>D^q#}HC18heXBf=maeb&=NxyFv2iHSM1(F|4BPp|9|dRZMDrGdS`w1cZzRS%1+V2zb}bw%MB|$ljSx&orWRSG>BOm@T~=PA z_OAZk&nL4oTBHrew%PoO+Amjcm}rmp^(fd&zpY94Y-hpgo!#M>K7%bL(3r++Rc?}t zZIdFYKz67^y5R3^bRVf^CzN#ohWjc44ImRor)Xhp!!e=80CL55fPDf16;pVWSQLgG zBgj1gE}36(X-3sFX}~huXRgpN9%oj@%`E1QoS-~KDiPcUd9j7D-PVzg9|EFkN1y8# z>$oQD&F0tr@JzMBCuge=un%73cGeH)JupsAI4jY7t2xru+1O|GzCygdc2fmFH4#H7 zb8sBR(p<*q4tqxWpNV8xY*7qQvg2Aw24ac4yHFcst1kH@^4=rZBKd~A_tP=Jha%@H zhC5Ki8znNw(GTmZNxys2rA;M!Ex9o#hMPiPCC!J73BddiP?`=y0kb|W-pUV*@{BY2 zn|Q6U@^7;dtd(EgYIC4d=Co90Q;bkjk%^Spb)xdsZ+a(+(5$I{+Uv|3N>wyulv_~e zR40^l#66PW{>%~IAWonddgL+&n^?+l6m4k{_CHkjtDY1S-l1bkGQWm zTSU<5+L->N?an?5QdVp`i7sl8TFY#+5&p3oKes)!ryUU~=5g<1i^L-xoV3cNUe@L+ zibWScJU;~OS&DRR?F?B#51*sh&uL4IoHDP+-Qw;_>(9KtRLpR^;%19^*H|IjE!2@%6im1nZvNy*si^97 zXcP~D@fpF~YJ{~MgWn&aa<>M?rqYP045cJA=RqHnhPG$?j<9b0ByfB=$i%g=q_f+u zN!J`@pPgjRTnu$fG^+B#c!^2Fm{c3};cosk@d#}3&H=w4X~v+i7m2U*@NcCkHh&sV zZIa6Ye}B)8ZC<@Nb#A!UTfLpvF+blt|BhHCBhS^4zYf~uZy{V`uju&Eb7_G6F~|7m zRnEIN7CU_d6^t8PeonPh;hQcm59aMF`J%IS(>H|H`h2`Vo)Ezd^@c{Lfh~-S65y+R zPh8t9Ka)r4f5u!ypv&QWAI@x)_8zgtBXl|aUG#d?iAfB=s3@{ZSE7U)RX^yob4VnjU&&)@4JL%xtKdT^z(Mg&#)6|E@QB!|8!;8E;uWNMOU!6=@58HYj3CM zXV)WdBEu*;RZ>a{#^l_BPxHpwY4pB0j4Y-JbTOEBKu8c3X<7Fj?^EtoF4mFzD1#z1 zEFbpKM1NxYPl~*biy>Vw+No0ikk0Wb$x65F%ce)#;%oC5mYNU0!m|JLjv)k+Z^B!v!Cs;cE1m9v zg70?xY>OnCFkHH`N1^7lc2uN3j%3%eMSB}UVX^$$>|V_8x;a^WUw6M35@IIFEY3Hr zb`Lt;Jui?U7E&E&*A~)o8vk;yb_l?0lPnN>nE;)w;Pojyan63NzMv+uR zEI*!%f~9l=*Hd|HY2;!OX*7Oa2+6Q+gYg9gssgqwCU@LYDLoIH!!Z#V#zXrwmt5>c zfqsNIyl2DUWA5JlGi%lY&%p}qnfi6m;i=|^(CrPwUFjcY0EapXM-H-$2O=fLOQz5PrIR}UU zvizu6)2SE~ZA2z;s_oKL8B2?q*i_$1(DN#-G;nt&uu1nt!+ttOWy}nJ(9aJ2N_HIW zx1nZg$XwMlOGlm#FT$SZ!&!y78w~AS*xOp$jEWJpG zB9%5!sY>9+OxY&x<|p*KdIu)qkJq&HXK;#qVF6_NSon^Odk#sZ4n*~W#x7=*g{G~| z1qWr(jCvL&)JU1o2LY-DT1(sWl+7V1UW>~Zw%gAh$+&8VaorVk5IiBlBrrT5+e;t(Pz|MAw9~o^1rUgK^u(4+?c*FkA1@g^^d6p2%!(|UOB<21RIYT(+cxMbfBh6*NuN+8`X<>PI z2=I9%_9CbwET*mK|BN5TXh$oDps$U@dd5$TM;F1=9%WqqChps?`O}M6D#r#cG=OMc z3X{FDkCG##(O>;$;x~jcYN-1(w@Sv5j^V)WDzi29zP+gMyMTFj^ZRf6gXmja$gdcU z1#qGRRa1v6$cjESHh&Zg=z%$C9%`gYl9;VR6ZrdbkPLL>YHi5zF4j)81b9$rhKf^@*bMWXV=?|=9O zPpVB-q6TgJX8cMKKuh}+2TugDv!bKq8ph&00^bx$^qr8Qaq7&D#Fn(qscL7>)_fYP z`R84NXXs>6)$}3YmR6MhTI6t0U-DvQM5E0!M?KR(AJ_HhL@FN!AQqimEi7ikTWgAF z$%9>VI!;bMQ`e9;My+!a!z&4i#%OB}mtNSv;JkT4#Ooqjw9WSz9;mRdlajOMKqgSO z`%MdN^^d=IlhIUd?Z&bATQlLd;*UE|d)$4-A9Q>&u6Qv1{Hrx?8%*eO z!E<6^XwRkANW04>?8xfLXD*+tV|Jc^B?B`+)b#rh~=C!q&|K;n=wuNsZ8S2 zWuK3mAGVZwTra}sTdw03TuMFE_@@2+@W%}{GA7+B)vbR14{t5_Hb-mW2!dT%Wx&*t zcYeP^$4nkutkn$`v2py9ucKZisV45wFANDIM08dx2s{YEr`49AG01M%tgi$Ph7H_yPOoUnbt=R zj0Mwv>7ws>c%-!RikNtSH+!qf^SzlI{N@+!m{}+jmV{_WA?;pivH=lYwZM61sih}p zL3ecmhgM8h*@JmPn-AUgkEK7L+VgFW;Q67fs)}4~P0AAwJ0*xB&R3Hq|KzajM+b*z z)@+2**^r*hjxt+{FsEHgO(DHY+cI%1N+Kqo=&eQW(rHW{*LGKjd&{@%XPyvvIcB1p z?$b3Up%K4y$bxx;U`y1TvY5XN@`r#BX0f=oD%>%o*n);B-oN z4!d3s0%@SwT@_j2XC_ln$uU5e83241E*7xV7mML8(1t0s)E6EMkWHh3`R=Hiqx701 zgiId0ITp_+SVOW+Zf-bf_0o3_+-bEAd%B6Vyw-@NANsOa(y`8Xv8bg3j_B`VRt1=6 zCkLL@i^LkoMQWy!t%v(?-^ZqsQJU_^2y@~mx_q9#>?JHx{OR&#ox2?LG)Iaz%VR)z z;q<$C#&34)rG5P(kB&LxQ?>0;lge(#a!I&abYvle*tnfaXxTHFYSt zV^+sPEwA&L_4NA)X$oOnS$KacE!in3K3}%fWufbUc$uc-jMADa#+^6!!F!G(6~`vX z2x!%7?p3HuVifh&=URLVf2xh1%R;k2VJ#5Gbj7$n)`3roT#r_Ho?LhFeWz)LTiE`i zWcUa}ueg!hV@j^%dJ(eYw1sR%ivBKiZn2N^?@OjAY~=I1Ec}k@j%+xjCbiG@W@Thv zl(lfoQ;EkN`>(X?8X>SyDU(;$qzL6fvL8+mJZJC$TPhLrUIZw4Oc4B5Lis5Bf>=l& zz9Eaz2X;*rw_F366Y?!fsFZ0SEMohct#>}R- z1F*v9k)bGc)>S@Rtcctd=5zI;g?^8{SEesi-5F{btZ6^Hs5fQ8egzQfmkhruWY%j? zBC+mba~L)vw;`Y2U_O+NmbM~H{5@9MLD$7)3?ozD(!rQXIjWMsg{XauwOrDyAhk}? zE-=6MN+A~J9Kk25F)i8sjd37p<#b?G+Mc^RDEDHJL4lA0RkHJ9*foSCLR(hM_G`v6 zP8fGjcOcE0L^^vEhgC)q!ris(HvD13hkcf0O=;vY%0B^y0N z*J1P8@4%WLPe34yg9!R43-7TXE)xwKOAy=BthAgi12ysA#JC9_D&D!hd2Qs0^wsV; zKeI`_NmWtEB`g?a7-bgWei5``1#%a%Ej3k~27|4e;gbY9i_|ZbU;&%j^&_rZFWE}O zHr{$v#2xC(g(EbLv7VHQ)rT=KmH2Fk+O_Vei)ch;Vjg9SZHRi5(<$1VAm~&(l@Rw; zK4ARGS5kXL2*u12b7F!Mz5(>`-2c3C8M=GPJO-*Rh~%S3M^om!LMX{`3y8UOcc{_SAM8)KK0P>P)QXJgGy zE+bNB9y&)+yO`TIBZU}F?sWKrIxcnvaplRH;2&-w?ENm`nZ6=)G8V4T#0L{XA!A0h z!v8cboaIIg>WDb*@76B*{*Wf9Slteg3lDTO!sCX zG#BEy&Cz2`>@?S5lMx)c&qF36LyTg%j4?j3ciiirNq#>Z5xzr-=!Fbqc`$lBH%8fI zMS`%db@Xr9Er5ox%Gg4EXgk&MDlFbr>XRew83*_fQZkVQOVSG%K`_KJLcO|}Xm@4l_+uJa z3s|ENi1zOel`~>Kk>#Qvm}JRXpCq@C+i$5G$d-RMH<=nPf7u=oJ`X{kSuUk0(>)_1 z+H~?@;?2~yB$BcFMLJx4t-6yzEHRFM1!NpH;wg322$XgB$b>VfS~CxQP+!{M60fGG zpBK*)t%&y~)pXQM9AZ4cWH2qvCPlS9`b{W)X!tBZv!tv`$KnGYyx3B|i!HUbKI2uU z<;{};}^+CRt-j)G4& zlG(y0Yo~MDbr%<{rT$RAOr{Axk=pDU$7u@@AW9T!o-!OuGt+)G`6c~I;bC*__v8>Y zUZV_ZoLnXA`AaJOB;Qrp6V6c3=pF+`DH2M^_C%fLM}SYzE`1Qm%}F%&N#Kx0q;QsF zBYbti81mqE^Yf%Xrm}oiB=FP9#mTQ9p?c5whft=%YgXR!V2>E?$)NXzgBy1WT<8Y! zDG5jKE(-h?i!L46#NRU)2r#O z-c9r@-sPXZ#!~<8nMN*j+<514L^Y7v92NrENV)6f5SS1DRUS;tHkN!bVFm~vI5xy= ztz0{xBNGq9gVpWHe^4+;fs{S-qV!?Z8SE9w<>&8otW<2Wt{%PgHI$CIm!7O*N%gZy z(KP{oHp#8f^5i3vn|H|s?i*BAx~t=-@i|6xDUIYmTj8_%Hi<8fTBqJ#r{<|{o8c+u zDC}jUA>u_9H@)}C?#`tl6{umz>`=ir9s-FRUgbjqa2yIC=tqm$f+F53ZV}1Y`sLM87=5a` zc_f#}TGGk`L~9g#RC0A!G-Sr5d!*B(>KN${5 z{cZqf;EJE|!Zp z>4ZO+?tFj`Vjbw*N|C`&)O4ug-&SkI4NX}KZfH)G7OH=X5B#N^2-BHgBd1vqxpGrP zX!c-eqO_)qINAU@3M*DK36>W*YPH&$Ii3zmO|B#gU*?C(%$G9`QoZGoe!j~OMj8!1 zaCe8sBR&x+m^vvC`ijR-gTOvxWz!h`BY8#gH}FtuU{EUF{_ddf0Kh2cRc7#8P@f&q zt=i+5XS%N%9VEB11(I%DYLeZ-a*q;#xZ$m8^121{D?)ff8yUBB;~a;WL?eKJXlxjO zzvq|NyAG5!^DctDwA3)*K@)_lauY-bD>wr3p!XrDMwItZj zknt-|-VpDO59i|*0cyf? zvogAWhE76qL(c`3o<~CCwRYsE-kmaPa6fhCX{emJbiu51wN=b#c_6E-{JsP_iCO`+ zt{+k(KirrxbQMq+8AbNweJhxfF!8-W>9Xd(17sTALuGL{BmO*@=@Or4qMWwTXA+6lmdq=ddmIAt{PaH3O#hm?+oVh#ZrCC za-@2^?P1mLdA*n?4zAqF^Pm(b~q-PlqK2fFOd&prp z?3UY-jxpeFY`G;fql9<}JY!zxA#msN5aOY)C>sOH_mD5fJt?L6*Um5fwex2Iuc{*F4GQ(K z+mV5m=NS#Q>t)E-S54QU^d_7A6h6lkr|hvv1SbK-nff%6ZO37pX;FBhiT_<~*p2(()zdqFG+gzb0hM%hHt4dlp9t4UUP>u7IL_0tB{E57GkZkbrVIDjo)HoA7$#+s;^a%Om}3_`x^C|7x~Q zGL+(LjxtuN!f0Z#?y^Yk^~N>?;bNh&;B_o6eP2v>P=2%$T!ugwbcl%n`CzGFF)ihc z`LRf~aZ@&Zcyug#WSSaiakFYdr(Yh7pHy{_L8}7x37d2#112_u50nLCK?U8>N=YkC z;*vcUo1Y8DLgNeZKx`-3GnTj_0!EJqk;{U7tjnOyl`)(iGtfJO2}FHLE2qi~y0hYz z+JKJ`u-2C+mH^9DzPqOfAHs|D;f8nULM@!K&wGAUU$2hSwZq&0Q?4Hd?6Dy9<0 zlst=lHa*%OLEtO{$N$ds#_>7ATCz|Z7$`6$==K^I$l((-IfX2Cz~*w~OELiO3gq;v zvoK#ea3rMj#4Jc*k0hvZ^3V-=Z^)oqOJTSByBGKgt{*y3MH?cMO%k_WU_w?31bsp z+|hW8x}@uRMH_e{XL5Ejw1i|EwV%BYBo*d$v)96c!~J z*d*hWlI}}*Jy<@5U|)KD5*i?;&n_b}?>*t_Fi#p^qM{p>jh;V9c+0GCUO9w+@b?%#iwY~zgx+@T&-UB`KOS% zSLXN+TMH`sjHfD<%?TAlIGNd#+I&YJRoSudwAX39^UxrVA1}V1`9$60o}s@#*{jNR)=iY z7fj_$PGf#usiPe2c?KVu^u5Ubxeo{By7U?ijm=oPR>fpk zejWL6N}rJh=kUYbc_5d~xsH9SZE@POQ$)7gWgQ_jYxNfIo!pl{I*R2bwd(%Z$5At8 zzp9H^q>cTf_9KXo>rMqv@b}9kl7G>4uJL13GZBlbZ=Nxdri6Pm7fqrLLYy?rh8lf_ zCb)#YnJf1NLN>m5ciW)4;Sh=qU7X|_y0N9r3<9j`OvpaF*Y+W?XTZE|T~YfBK?{%N zMcL_8BcH6y9aweF71L4vXM2s1&v>$4JTV!=)>wF|4ktV4={&P@AAA6lnJ27v#lqbh z(Hdg|C3`!P4EGPKed2-(`z+15{mG^@u*8dHL~G@+3=-&iHtN*O%Cf1XGLTjan+++~=b)r1GuR)Fer1{T4b) z{Q)=E&Y7M|n9XgRRnlbzKmXZgSG7*u8EhZhq-stgbCwaAHN`-!#?G!$JXKIlw;UFP zOYbIjSYLNmC?Y#9;qD0Yy4=*`=&hw2M$Y^360AkFd^@F^;W${XnGDlF z@iD9U&-p02)>l39vv{hDBWds~thz2rRfB1H>6=H+$PUYyfx4!ZgRSoS@q}GeD`_b! z_sNFb9$xWp%;K(_SIK-&wbjl{5bv=G>MDHwbv3lEYI+3Ac&1@MiBTCE(2Do)kTbvQ z@Vgmisc`h_bF@cv*-Vk~t9`DeA4)6G(irtga=irQCF~x-R0>?ULp~Mp5xsIlE3>AE z;q;vp!in1w)tD$*Q)0JhGvBU?i!6+2(5{O>&h^^Fx&AZCmC0q^E-~`h&XiyCjU#hJ z!O4qE9%$09)96$8>faHT-i1y^-(bn&k6(@UGUTxK%Zu%TB&5VQJ*Fs6gR2eXR)S~U+|S5$=4GRf$sR?{#T8Oa7^j7QJU5#A_-QLVP7s z(pKIcI>ACer6S7t)J*-iu-f*kH6f=8Z1K=Qs$a`-%XFB^$7H5GjM}QOG?=v5!-4DNLrW>=4{RE*e-BFnjzi*a-_bYa@({=N6iZ z8;~~Y*hC{9%k`B10`p~I`xP?eh%-CB(`5iuTH-$8067=RbR#L@T9WC*f}anY26k>@ zcV}z_7aE(i>uE!)>Z+=iLr$9h2Z)k70YDUyTgdx|JE5lRU|H|H>Gsv?#M6HiFJnhQ zcr&ZxcwSdAHGMfwG5i*Y55~!MkPiyfr8m(d8bDg72AH4|kftfp(v|~azafAN9dhK# z{S6~>ZH^HD={ZcV$-1vJKNbs61h>3GWVevP2M~0mci$a*oP!2%qqq8ZCT4qH;_Bj1Xe0NalYni!F4>**HAwQ5^TbhYFUsm>St^vmDj7eRrO$QDN zq*-NbRMiENMBYwkpq~UujFl}3gnu7kcrC_=73ASn0icY7K}-!3c()Nz=?~X81n$B! zD&|-U;J)9b&m#Q*YG61_c|okn;YSBw7gGIU0;(MkKZGWON-)O*fN3ZB(l76in~4d> zkUR?o=39)e<>ZalRN@aWl0Y*2Ifj@Wc{%uv8{_-jY=Y%K8?&G;4e)#282^U+f{f)+ z!*`iyD&Vq0(FFWZyMqy^v;MFKa` z?AKq1_qLKdu2Iz{DEIDsSK$lRoLk7aT*UcHU~FsESpOS1mO@v2ui{WaI`^{R&Y;ymX(msC#`r}xj(uz;@&rad0vQ;849 z;3tdlf8nhm-W1Cux!hkt)+awd#jU&oHK41A6vibFdkHU}xQg~-E);$rid)${{5?Vm z_ESi`RzKeKO}H@6h~^GgQ7_XTiUOai!?b%$lU0$(X*iG};=r2LAOIBEe37WLVw47>k0;fy~+jpdd#DHk1r~j{d9hl%f?tlk5sCaoa>sj7SbPQX_Uv; zv4SR5zdK5qb`x z{K2C4$)Q7l2#=cb9@>}AIgA7K-R)Gp^Nr>%#n++3rz|S8oeRevvnHulW}fIIUzUPz zA?5d-(xyXj#kc58x9ijh$+}}ZqGm1=PR&$-JPCF^+_P`G$>-TQTl}m8383G7OC;uc zV~8eSXiTG~VE9G;m1^W^xTMR~_Nb4h-MC~4KTu*<_v@Pf!xsOXH7B%;PN-W?C!E1v zedat1zZ4evU=-%j`4Qu7!}^g+$o5kekB%a%35fyksXE+~xUufhxh#JIM*h84-zw*V zBRR*Lg8in83e#H%zG=Rt#Zglk>ovuh!YGm>VS$Tv^pjmetj$vJs0h7t>pD&AGmQcj z;|gkSHpr<*Lsy6WxS}O4DU9?Jt=A;w4K3fgiOChFF6AZhDYhn731d^I$@1PQl~}#W z5c|n=OT;b2=(PBta%)n~Tj1j@1osxQr>3Vk6eCh!<#0G85V5Fc9=}=RWKVQSR=kL_ zToiTsctp6l!8JId+q29&q1C4QkMbn7(#1snm2G6HT#H`ECVsBf{p`r# z*u%f_?Q{}LKIqS7F~^T`ik)cYvC|4)2Sl^0YVsYX;VFQ;ys^|G7ah&^8`$66)OxRUdr9d>L8iYk}%73M8uq~KanvG~TZ)s4u+)g%V# zUw@r?TF!oOezJnGCv5IgwUb$Ls@s1HSsrNWSQ>}t)ZbLF4+dUl7vDmtkPm1~JfZxJ zbdK!XrklboaT^3{wLV|MC!#$=erJKp{IVf%jks2@jzD;z21vNO3k{aG$5Vj&6LkgK zdcmArdmzD}GEBb6J;C&7snpZ6*;dy?WTB21_lg=Gy;CO;6-xKl^IEv z&g+3o9TS_D$^6Str@jef`+_TSmMUZgQ;zrQM*#%Z-rBT=ReWuof7;L%uvWXVYku;5 zXFbC+pBFi)moF3H<+KAJBhKF#Q#82b%t=;l*&pUHN~;51e}1#&C*>nA`M!oM@; zFcYH>`+2P*Y*?&+6E|8_=8=GdY+v$ZDzM%7XBSb%2zwh4?PlFS^0fpxh@GF3OjBU8<#%zXp7VO_}*DCgLP=s%Ca;T8^&9H$a~*f>43z` zW7C^{GH|jD^O02GLLN*;iF$qncia=9n<`oRUi_UVU-w416C16B2pY=ss7A%dqjd$` zV|ihbgoQL&fg-y*gu{D)*B}Wgev$B9o{MnHKoVF|AlF^ia+TY5x+d~Ow*LbR;8c+3lAPsv zUF=k1$YSwCC*`}^;QvCLh)xZPVS|&Ce-Y03W2}R-8~_4xp8!^gwQf1HERg63_Wosb z8jIP2w3|LW3~-H;0Nv!S5jCJjJXE;z%c6RQ0;%L4{>6L$wj}6OI0S?Y;7kI}x8Lfq zTVA7hbAl4R#U)7Rxefr@IQMjWAk7~W71-S%G}VyMNy3 zR5(*wso<_5S2c_g`&-lfBn!U+tBpwllzn6qi)tzYC?$;cuX_h*>{01}+m8Kz`1m2m7C8b)@VWE|P6%v7>B$vhDudmM^n?@+R5sgTOvvyAM`F%QrC*7y7U zUBBnLp64IeIfrvQhjV}K*XOm~pf0u!0;vVBGYyD5I(AY-h7l1I0U9z|K4n5-V!HZ_ zPC8pMS5_O1x~r8LAhB;IvO!p}8&XAjFBH&5Y;6vVhrv-ZA(MPD2i&Th5h)|hk*c`w z;EdReQ_zV>TS-w*Uty%arRkHyl02!vP1yu45I1#EHL6*7PA-;?A04hGqb2VP@T}Ur z@YY!#1@vS>O9F~Vc{DWcHLff*O*Mrwy%ql7Tk2>TC$7?Cp#d_W%c9q!l#kS6%ur-^1nVfMk~#vVQ(hf=t+15U zsH}2W#gmV-{uK-~UxvG+tsdFepD7&yNamZ}>M;!@-VMEW@BRqH#30xzuI^Kng+@do zLQ$)D`=>PIwv={vj4mH148db+Q~VCnL1}X_%#3=1>_ui`OX;$ekRV zt^HzIvV9lkO7E=#A(LL{HRAu3>mPeZZbM8RxHLIJj`?pwVE-xcUsb(v&eSOP7JqCBxZy9&0B(fFg`!Gw@`A>(q*G3KwDaO~|8S-I3%sLZ zXupx)Q|$IYF*dMROQh6ZddjqRpGY6B9q2p*0cpZ*yXHiKb${)b{+z^tcZ+jv*xA}` zQvfdKX(si~5|WemiGGZXoxr@`Cz6#MA)bth*eA;GKGg$XEp;|Koacg9o+!=Meq!C} z(h2F8+pD}LviqkPC|VK2`WBr}A`jM4d+S<#1a7Qe2|>rbF0+uJ^F)9mIi&9)iXyJC z7)xYJQ8ZnncudrW*2GV5!QH#ibN=1kaH)3n{+4y6U5gUG%?zn8J=T`mN3Vq43*r00 z(s1IHUw@LKx{r%adE-!QLoZEl?hd5~Z-;Pw`<`-D(P_yz+zIk|!3M(hi#bj|uTKqf z4wg#QSFE3)#+(0aa}CE;r~d}8XAJf{qrCc9iS#ZPAF3}5Gb%2WLP#`GwNe|@kMCA; zu;H>>=f$xPMSm!rwLLV^zY5^i9h=>51n-V^>oVhFtHCRfs*Tr&)M{qRk}CMK@& z);ES3k{u3zcds~XlPzlaR?f3ts>~uBr1-zY8ZB|>y5Mhz;t7KLMA5_2RY6*3-wEtF z2!Gxuq8zIInDMGmx>si}-DuR!89Q6Ux5?jvZ)GK%DBswPJG;5-wla3%kzjCK^Ot(c zT^Aes3$E!d*ZLE$tE(g{ur3y;Nq&HG12o>VbVv8#r&rqsr$WXdQITnhry9Y+-fu9Lc8`owYiFNBh}6|mf0j4KtBWy zd?{zhYwn!L|LVZDkjirL4+RdTRoo}A!>ZfTcd;WC#nK>TL4^{L5WQEXNuzQ(FV&>iSc=Txr1Ok5E|PWT z?#vh)rl*4*j^e??0sBO$nx2wh&dhz2EgB)%zZcm0WVug7Z&97~ig1QE^8gD~>2$!k z*>#o*MF`iEZlEhLH^s_)-@7`ptM%Oz&S@2E80qe=mG=r?CB^$$9vf&nr{q?fQf_sT zq<5C8*+z{G6BuT!P%ibwA$&H#jPDi~&!&r1HE+{#>R%ROm+0ez>f>$y z(1CydQ+hXpe4pq_bkAJW^n(K7I;R#vGJEsu9Jd)t)rdT?SM}QxcW7p9;*DcHR@u(I zFYaRvyi(o0Iw1ZzQe!E#kUn&VL4-!O4plj7yVv&+yY;2LZ%sN?9Q`WSeBcN-xqXWv zH-ocX$LY(zHwEnJ!c;=u84bB~PWZkbole%5=bm+k3kW@DCRJ*(`?nM%nf z(lM*!_r8`QtgyNzICb$9EX!4wLN=j~;stNFS5WQfKVBYuEYMK=1VV6X32#;$3C{E zM{{!7DlOi0V`t-ZIp!x!%7IhlX6AXmCmU5RsF_VUr4BfY3*xhI6p?D0i^<}RY7W#x zr@n$*$tJEwjnqW*4LGR-a(gT&({ZX3V6P!*aAV1#MBmjJ0UQHXo0XNcXaItFXL%09 z)d2cGk%2uI`ThWe-Fj89AHY?~TnZgw!G?B6?}ICcaI zwn~}5xt}Z2*3=68PQ-OBu+Vy&jlwe z2a{}1Be15+xlyPwu~4Ar{6}oQ3;EKi9)s4a`$T1V|4TqUE&mti^f%yEXIIB9zlx#) zjFy$$Rkv$gTgK?-_hM>)?Gsh)6V+$WC6@kPR)nl50?UkzoS!W58cUGpokxIw`N%d}V+Kg({J}G!S z9mJrMB53%DiA-k0IB=Eu-rbj#S6hZF;#S>1jFr4{nI*Ifl?)t8+0Ay)`IMn|^l&=w zM;p26tjqW)yu0$w>~orqJweoXR&9y4so7^I63!BX3Fi1vC2C#1_q6!}Qe&Y^dFCou zY9?-s_Q2_9RWDX4*|>YM+vX`;`IYK!s@+kZ9JT-I%jSh68y914t@H}-lUkWceR(9{ z48?V#sF&j37|Dhv#^y)7pVG&^>yJy`x%0L6;g{(A_xPn(fL_9f2^Te1-!G>X zr}mS!D{3h%o*9c6w3fyP6r3Hf#sr+Z!=+~VL6tQ}g#q@z#`$4W$@7BzGLsjj8a!~G zIpRtS(!_IbUZVCgu9@!s#cOh4aVkzW*j4IbYF1 z*=HZzHa;hMTQDYOAdo?$_Ot>U%m_YHttDMZl5l6PA3gj=S}4l1kzWlZ!<5`#OyhT! zcBNxD8h4ake7>C1W=x;)Yw=q#6^ys}m;R)Ps$&%jONX^)T5MCIMA5%P*!GDGf>C>p zy6};SFSx%zw)ro^WQd+(h*Q3ie~mbWg>2BB&F6QoZQ%tcv0~KqXi86Fw`soSwm@a_ z{Ej99w_1Mgw9@Ae>-Z|c#H*V$dM4YUh8*Z!4b*+hu6gqMysd&luk+6hB2V(-DZ7dZ z^}Fh;mU05cY<`|8RZ0E_@Go!g=TU6(h!R*S3044mM)P0`aJC`=NAm6y4IASV{amZo zQ)qJX$Q8HQM`dFh=Dq7fDT6!unfID*EGquIn2ZI2vM2S;$1?I?8PDl+H0U`V#gg!g zM-*27Oy~`j`uw#kZ0y;XRzZ>Nd*!ql{sJRMPjMGUi=J@&l@Tf%sfDpCoxL|_jvv?l zl`XL`lC75C)LSs3k3mtlSLhX`ReOFzv02kwIUrQu|e z{14nEzMp%H_xcapwcM`l(?tzo$)2}tw=HjEq2^vP%=ZHZ;Xa_Q1n$|qX%`-uW$)P~ zvlt*mjw;nEVJFUpEwXee%)#~kwO=K6gK~C{STLTI@Ekj%wa1xKy52TC;xkukH7ea& z3#EzQlCfb81p@DFcXJBY;1l=`J_8pDR3$$g2YT=russ}pd(waTnWxS=YInw)^ zQE2>vrDA|$;D`y)bt#$!)&=hJ=CXux<_Rv_KQxfhQJ=1Jsx)v2e$2{%E3xu^QgW)o z{cYYLDPzE0dsMHsX`5HaUi_n~y?9UW5&Vv3CFafjqy^P#=pxhCSJ|_JPcA?&vp-ae zROFMAu#+<6H*eV>Dv?Ta+jZRSA6z1s;!~>86h%Y&n;+LZrj~va;HD!FD|?D z7i}#LlvX%_T7i=k zhpW^oh>LL5zy&2t*wUgAddc$Ldc-MQ$G=$wgnsv9eozs=z#n| z#$^IblPgoyVFZ`g( zjDjMmhkwGUh6E9y#aIC%flrRd77i=}`8Gd@77e!1fcghwUw05#A#i7;9tFy<kb z48>zk`mGg|xnJgBBzrcg#C+fbR`LU4X(bKxycNWnOdbFVAY!J2P)r<2a}0R`Ah-zG zUpfhJR4d-*V1~-9f9g~P@6F5mP6{;g2uAyx`C0GxZGS_SI&pyRh@p36C0~{hp;2?$n|!nR_uiq3*?R>%?9L3M#rd@Ul_pl4X@)AZ0}xD zeDL9q?2CZX%`xeR`T@6FCD+|f#Ql!;lg7V-Cd_2n3d2;r?R26c-+D? zzf&mlF_}JIR}YW!GKGN-w^`Kv+DDZ`Eo`Nh%8nL~Y2rEy+}?eS?7JB?VO_{(CwY@^ z@f(}>PmjlghgDSWzxgRf?5AWi88&V3>gstN;lwzD*Ckzl5})XPl&aKvlSy6dpc}~f zr7JYTleJ{8tK0p`s|Wf_<*g?ft`mi`nOx2AaXeYtS0Ddu=6on==e0C0J|1QT@n@st z39gg#eskYF>aGO6$@;Aw`{=%iORTU1D^s3!Ag_2CZA9m#bjq}Xa`sz11NT&tcC!`i z(iTWB_(-+7e_Cb4eOYr!6Op8^T#GwG!9w4W>i;pM!R3;?hz;L>-5}@3f^&R2L3yuM z#lE`4ADYPY_BvU5PB&^?v)`Mw!_DG$phkL*W*>P|Ecb(`f@_(EuBJ(w(U=&lLV;Fc z*drCw!KMeXC-6~yCo{EvHYA_D$8E8yQ^`k^SW&Vm z)U45bRp8b|M<%H!9r#zk=o<1BUvW)w(^z7p@wqvK2ZamtVzjo#sqB6i`d ztG2{jh`UQn=a20Z<-Nr2YPRC7@vcONQm&b*Y?cC&%U+LbvuMb>G^c&R$=$PLxaq-{ zKn8kUP9M9zCeB=W zyJp16pSn!nhCKgvxjK`B^M z2iC@ylVn3*r7B+CUtUyhtsNkz+N755*fI6r>+N!vn$wv=MHND4lrgK|}!;nywK%vU^g-F$D28>El-5}Ak z%Qe@iK4}+~I9H8|<)6V1;_uZKN9qfes^pRD^OZ^uoj-Xl573f45s$8_Sazl`vHM}w zyl*&Ehu)&aZ}OC zZt{wY#e@0$htFH$^uyDm6V-aU-#Uy&r=&-wGo}IG<@5~vGEKD2ANtn5)BPr{=MZ^% z)SH#%;Y685jNfyT`&mwy3y0pN4yub#^Zxv(%k9KX&Fz5pk{%n_yHs!r@gStm71)nq zWkhz34e9H5Z-U$j%E+y411J}Ug07~1Bh!Xol_PlIK~{9sr1MJ_mt8DBSO#Msz^;ki znS6WNW}oQn<4XRrJt*xe$Un&O&FvE<*@ji~`?2^rG_@N5USol>0x+VbsV$hHVQhMs zqx|630ow+KIo62OiD@)txf>F=GK>}_qliW3D*>hi{}fVV*U=1gAaXEDAiQ2s7an|N z=7%^z1R6*B6PjP9Y|P{#9S8t$q`?h@(Wf{B$ifjEsM$fiEO%9RbyIUd3}(nk6=>CB z=EYtGc2Q~ZdYlXrchm>Y4=6phYZ*%#{<66RyM=JEREU)X4LU7E?|@?qZ5M@QMmr zoQSdsykbp1OI|jyVNqlOk0vAoGXiqrc~(<+lF5Qbx?$YQBHk;GXuIp>ZRHh!*c$F} zQnCRunXay?gB|*QD84FlX3B(dE@HP1_>N&TU9KjzwD2hb=k(wp)|9KWa}BU z>BLfyz0NpXItyQ8GBMcjC7`bkIVI&7TTuOWN~*XA`D+@bx)|;2$mEMUXi{?ORpey? zV0Q=kwmhcrFg&LD(SVC5{j`Vk6=WY|L0cO4Uv{p^_EZp{MBYi|G$8)Jb_a4_)Vny^>h2zni zaLg+-v$8aba9byji{~X7WM`&jSTuec78z?c>Od-onKBU(MGxt1xB0oX9mj*BeLiF6Zy9Z;U8*5ytnlikOwONYqo=4 z?}KK$Q5_I&Y7495osA@FYVY&}M@l1iV_c0SYJmx?0Vc5afC#igh(KjxGF6>d8HclP zXpIE{57^IrearebGz>wg-3bI-;3!uGlaeu%oam~lPb-40T3lS}5G3T{{p^OYnwSVN z7;6(vGoj=lXspSmyuv7WZ@c6A7Q<9s_9uibz2p%hR2z|gg)^uy-)nt25C-(dQ5pC2@bb~zF1WJm)u!b&BAWUe|QVULG= zbl?;Cy~RycodP)`sI%}%bCCJ71MMO`JeTsOp+ze;opBUMZ@KTy2?xyB7@$CJ?|Q+HAqv9!mvsQq3^s3&5)W>S~>6}{QL@~de8+7 zXYM~4SRNSkhJj)V4`^BFh6EM{I#a;Yi2@r2r~&`vt=6ENZ%}p66=HahwSwEOt;ik3 zEAroWkUJnkAZ`w%#0wMzP#gZ<3x&fd{`Xe^0fN%E`s-N%j2tW{VjweAbCr8W_i4cf zKS0@`A5MGl$L1YZb_DGMDI9||D^*q2i)!Q_qMuFi8kqmNG>7#xoU1zXHIN|=gaRDc zDWIQ2zciz%rimXG5zq;ARY1xB535YJ$Fu~jESk5BJdZ2Ws5z_+$4_p+PF3l+^6Efc zq*iHyuvV$_X|EJtcAV=Pvd48!!>Rff7N91OJDx@QPZyI(-P>VrOJ6MlOA?C_UpCuT zyDMy=-PE`=O_6l@#7#$CiX<@fLX8?!6so}%Zyn}|#1B7T3kRVDqpA~;QI0f;AomJk z6D-uIwz8gb2JFMen%#ebk#*q+)Q|vyPYfR{?$!cbRqf73Em**WDH2btBPXn8QmPW4 za`dKA-bGksvW+$CItvdL7Bt+01*N@n`Y!xXt-7 zex99!+$K^qiYMOM!CEI+eDUqm3RQy#e$XM2ot|~~kXKPs>Yd*p7+A3*2ZDtKsl$pq z!^^}UhWop0`Xav(+!3L$tRV}@dMdak;1qg?lY(XBwjTu6Kx+66%UKb;cmoRGO)Xw{ zDuyhD0{P*I$=k5z_^BWKh$j4mcIQ7gwFz)kX40jcOBJRO%jv{Wo3#N359F!ICzz1e z@$x{r*wk`xne$~>{y~AQ2O$>rx_4;wlXsXh3Ci~}_pT%&`(ZcIz19Jj%1!zlBk(1- z_*e1sMr`^KaC1>jTNCTs^;On??^?p$Ij{Hm1Jjm!rGy#&LYjfUZY)wK6%@Wg{sigr383!uBoD_S_dupe zma836@}rYNN5h)5N)3OFR;fyO-@cr#Zm`gXBmdoc9O=+k-q;Z(VmIVplcl`CwXq^N z!a62bCipAvHMG*4UK^fl4yLo?Qid=pBIuD_eUMCr zB;s|Rh$erYLrQSTYx4sw36}h6`PXFWFy{t-hlV%c0bpfp!&4{ri9EASPOym8cruL0 z>jc>E6H$bX_LMB+ia_iP=KT^d5`g4bA52y52_QLE0dvnpl+Pl0Z=d-6ejn*EMVCzy z%lGAEW7yt)o$Z)T^4auC?6$wZ zH3x9jHwENEml3QB2o{0_zx>3cj23i1)HDH@v5ieXD^Li10!U{bveuWo0Ws!+V*@2q zlnhpvU$NhtBDFedqQ3tCo7NDT^4q}@0%OiFuX)mdW)q=~EYPJuZmW|a>)3%e%&na) z8)BufPHCOxv5g(Lo)d_+%k7TTQd&)Jh9Du&;6^*fk0~bzM+pm21<1TNv^bG;X<@b}B$Ka8Dt~DC7VoNzlOMDh@v8N?^TD#gMIK0Bxqk%mqu4s`(8= z@ec7V^>WX(aGV%vy2C(K#|>DxdKI~s08u?yNhhW=x45g~=Srt)aOo_dB-(UXr*!W? zA%7^W3$SeABATLUW?^-CS0H9?!4Bv~@kGYqs)}}>umo6%0B_wmSjIHl4;D4Ex@lOO zbTE&o3lX&9)^SMF*sI8w`7aR5zM=V26?we-+FwZJx81ffd)u$guN_%V0FVJTkYQKx zI|FOd0kT1ss=tRx%3(hEZCas7pN{Xr;gPlVUxyfS()`b_34r$mt0RI9;pBJg;r42z zEN!@Clemq+ZXTP>Ih>1=tZm+0I7P7ke=I$h{;oAQVXfIGI@c4r@|TA@v+l97v5I6% zp`_#AaH%weOJe}{0q8^kqhaw_t}(K%!dv^V@rM{$t<{0wFsrMXdB_vY<RwPHD0OZd(gW%ghC7ZMqzPdJ z(+8i|yKo^Bce+h1&sN=JDH)z0U*330slGyqW!G1pmU=-Od;8U$?&CtcG{cd{xgIMtuR(=%@j?a^g zUt)$ob(bz*2v19-&gQIj3v^epeBXBRd^WAq6}*zCy}k8uXJ+|_Y=`G?X#}RHX;0sb z7IS(%?TK#f6LC2^_DCpEFX$x07fT6WN|i&rT3s*uJy*9Oa^MP~jJZXOte#BD6^h8>%Ls%|t{p*nc&9cH&Vd!Gk#Yhomf8Cm&=iYPHc> z9B(EMK9jK|$Y&GpaE+n_f~h?DSKzuBpTuGxOVqv39%E8AnMSQ|}BCi7LDLFyC-?5 zVU5ll^AFYc9w~Y@>6uGjcyc=X&VYlk_OG}pd}3PyV^U|IbI@xMv6CxD50O-z`4Xb# z<}Uw3^Sp!M+r@DUZk)wJ%)L^jV{jQX;I_`Wg&iqdDv*|}?JO9WVImXvIx(;lw$?BkFjez8 zvs5@NY4BPRk+Rwq){Ax3&Ue}}LvpL^PhmQSZfF+$di6DmS$L^G{e)H0@;w1NpPB@b z(Y%_^>Qd;cpLl^zfsfWM#pqY8ezh0PACpSpmi+0N?W4UK!lMa6LepO3PE5}xCgde7j8h2ut?c(08Wt2YDh=0)u$TdlwQ;P<%miSWz1wQZ*a zOwQ~@=~*mJlDgfplXZVJPZre8yfEDpM%9Ap5;(`Q$M@Q2jlvgS^< zfn6-{U+8qEYjtXN-S0^vY8iZEwAH3lrT;;vFqF?iOYgAw{cUf8Qz*)|dGH+p%o(UA+%>a>9u!fxR@iAB zfJp>6nnCLzbW%)1;mQAXn;Ln))!c|ro)Vsc%tu6g?(I#?q98RQC0U5bNV!GFQg9XQ zS}8nWQ3nJC#;wI$wRw~=l;vbBsq!ODu(1sA%{MH%WvH3(^(+r%osj?rDbPr~3ch%G zI5PAXO+d593&cleV&Fc_j(FU26u=0U$CiVb+7VNHB<%EHKZd@mb>K6DogbPK+KqRh zz*(1H6B>^?+h{bP@kl=l_TO146L{FK!Vxi4>6-c_5O)B&tJQZ=jHSBbh|&s6K&~6b zYYy0LPRi(@nbm&H%YoOe&4<^9MnMsJH(JwtF@}G_K%tFIoJl&qFxZZkscspN6-hfR zp0xq;rW+5xY(T;^MXX$H0u!C&M)JkX=#C`UM-cQ`Q`ux4dYCM&}PNeY-1 zc}kcZ2l^~H$`D^#b`s(^QwK-e8el&rs9#OyL7Zyh9oOeEN5Pj*whCehq#{SakIx3l zPQj!c;NZm7jz=#+s1vq5ti@cs%|P6F=WFA4XG4FD^l*;=jg(F%ZSBxd+TFkaP$>Uu zE$r${Z^$@3AE-u#9qxkb6{+c-dc+w6;R0;d~!M25GN)4dmzoZdUxkWi2a;o@y<_4}4s zNRIW)b|(F|V`C^fl&hj+nmXOs*_C`3+zuz#Vm3ma@`xm#7LE!dHV}HP((&57 z_LiUhv5n%iFrT&8jjq-)`p$}Gncmh0dfWpx?3}+@ZQ`wqUH#{;dg}=;>N!ZbqXJ&y zOQU}_(LZje`|NPxG;O;CC2p1I38%%{$f)wC>miV2I`c=OA@8O`2wl6?=u*|Q%N^hO zM#sm@%pKB;(yys{7iyHLUO3cV)sswMF_m3J>1Od~{P}b$ zEw%4|+ehXydYb8mRlXM+;49kVtAB$I8>W++%9$PLp5?G9zFu7R`=ai0uXoyF>)F@N zUlXgA2{iqiHu1mcoL&WfvfDB7)mM}?Di=U86`C|Br#mLyyD-hG3KL6pu{Ah@H~lq&*mGDu$3%Id9LDgjSjNUjj0VqNmpxKg z)P74RNpJFYrzhfWm-lPAYmM*C^0}@0Y*u~=rZaTvnRl^YNR(o-oi%*!PCJw~NJzN+ zXS_^bxXYi;yV&y#-}>OmU0FUnqkNzd&90+ZW+N}dAP+6Ti~Ly*k-WU zek8;uv^8gUQ2(>UcLrXmkGDP@@!s%jsTpy(OXXZhoNX!TS3n zb`M^9Dd^aZ*AL4*M~8oWyYuB*U@7}Jak~FF-$z5DwLj5?Xchk}a)!kh@U!Bxv!I*2 z-!aTu-C2KPj&Ib7d{8SPxm z5X+~?FE-PbZ+yOFMK)M;xno&EYZyRdi=>-9qJORQrGbF>Y+1y zMJtgZErFxQ_KIf*EVjO^lZ>Li8!B09kCbw_mhMbIJDI0d>>0-Wn_`N|xIXr** zgM|X7*>cDQOsYz$esSQt@^VYy&IHFIF^{BY_l?mRxfDZr>9tyGTTiyR5lml5mD#+y z=w5CT$_7QMYBlrH;+OayDl&Tn61zngYiDT+cIjrm6GB!?=ONBBYP2_$^L^m$;yej# z7QZ~MT|BF8G-sbsYxSmf0is4WofqJ9I;WioYiI28WwF@xj$tp}qPiL=F0j4|k$ppU zUUSKo`P9kVD?4V6JLV>&3s@5r^Ypg)8!s4t99X3cEj zw>^ywxf^!TQcd`{EB-bkMsFo7zrOrS3^`7LEl+Q6ai55#sUQ0Q^e_O;&As(lz&HCu zPaP`uiDL8Y7v~%q-Usg#>_z>3a3e9#zFGtfhSH9R+o}}YRvw4IUANA?>d6O;gkoyR47Ik}Mzpbj7*jLZ}e z$S^SGfOmFvYg_Q-&es$LuM%*_Hr&97h`POC2fsXmTEI_^9Bdeo)hrQjeG^p`MEG7o z^#aT%^W8wghCuADAVtsN$V})zW{!dZ2#E{wR1Yx9nw(@=2b}d81GaQ!#~rhB4G_hE zok$kbk{pozCTTTJWt0}x0<#2beA9$b?7{%b2!n0$BcFG672iFF3 z8NdsFOS4bA^OhzCiS@n6F8qss3=nb%F}z2rCQQg^!3>8apuCf>X>ygrJBJEon6Fru z!*8fLB2eh91N2ah0TAXuYl*9IYxrpb@pMBKUY@IZ6AkzG<}iEtLACBR9tBOPO5Fk3 z5$0mS+rP1%tN==6Efh*jnb~kNlNxJ!)HKQY--HHKN~lJMs;iAu5}TAriiANrq7Eow zzP_c-d=V0S?IOc|%%mpQP7~I^mbnYIEWH<2?8?uo&q-a-(9gaORm+;cIO~FQ^Mq;S zY7MvL6}h1?fo$af3;nKUV^;=Rqcp>NHC;m(C+t_|H9`LdCeS;uP3G0ss+rlS`nzMmC-EwiJz zu!d$YA*X)3$!xA3uHw(+Hg9P5@El>0wyLRq#a$-eq?sQxH;!*xD+|w(P~E1PeEiy7W(FUVob{yp&MLm7;)K=@_d%tqNE{=yx!g6YjneY4=6`4gQK7dCg6Ijz#Z-72fG zmxiOmO%>Dfr<9wCEs?*Cb+hZ+hWo9i2FF+TYxIWwq>g+K;^&c~LwVgY~RrcWPX zwzzG$zQWJ0*~yu9_D3w6%GXCZt%mmcG2=Fe8Ykqwq#sSivXM=a+c4}C+4{Te6WLiO z4p-{iqJ=$`aP4j-T8@r%za<>4P-hZflzOUal~~rVF7bRMgi*@m-P}tz-`m!D{J>(f z`p%odI-Fm@kf)KjJM^>Gxumkw+Imd{l(PkOfD4YpYi zd40JdS&k|OHO#xk6O13gFhL7{DN%mu-(7d4ZzcBdORKtfg=EhNxrn^$FTuNd6WlAX zUN0t%Y@gLseAy?8r-hb{u%4Scr=fM>7i_)P4zBd#JMPXs@`Q^7qp(A$o%%hsX_h9xwkZfm%6Q89yKmr)|}1zrqcH%9lPdSgumZ;CZm5O zWH?>wLaDMx-va07p|rUX)~Q%=A-K-#pyoV3c{?X&)8^)qx>3*GJ{PGciSDy+Xnjw} z3323bdEWfd@x%hDBM;?8$2U)PO1>&6Y?YEMhpQ&zdz`+o&mEyB#VQHJ@akot#?aA{@;b$=s^Q1y2E^rGg-dE|Ts3^ofcsalG5W5+_PgH7|P+X}Rk#n9e zR=|F*-LbbihyDAH+r}r%=Lrh(M!oM1$afexZ3j@bU}VAK=Z&pr-C{h+gx7sVLo&#p zxF6EcE0bEK7*;9EZ-y%#C!yiSzWKFFF6o*d~B$8M7F=9l*{do`F3oNvMAgJ*l`w2uDDTE0jo37jDM8>O3c zd41$r;FS?=>Gqe_wS{2|J-j@FoT5<94B|UT*-%T=j9k(?JVKz`Gli-Okd7vFiGu6_ z7kK~))u82jfY7|i0|*H8e<)0C<5FIQH#V11?%KA%Z9;lb+k|2T1|u0^Ggv2-7H4mP zLk8p`z#2gKghR3)8K(rk7bqT3L|U7ma`d1T3Xp>a3f_I4cT%aI;3-E=m zf)@M+jtGhv9T{zI`l_tJWYSwsfEsS-q@Mu@&dJ>oa*aLm zSxrW)vd7aLkkKdTLyE~gZnAr_U?mDr;CA0uTM(X=KbpuRIemuPeG7a5?IOw5&;>oU zAsD7Br5PRwj6v!!bd$3T_p1mLF&DEji&itP^-Afr5sCs7Nnpo~V>)va%OkH$ ze2u34Il+DULc5h+H+&rMZ-m9{mgKFG|mfd25i%@jNs< z#X)KF@kQ!7=y<9RJ{YnzUCd0{4xI&bW;}3p2Eqe6f8 zJeu>yGSDwaM!D}Tc529DVkD-oLzR3w${C*3nG-{fyFwmvk~ z3?S+kd2l#jLlVx3t5ZPk4{0@p&ZTalnGys1BS> z0;o4e{g#cBe~mS^9=j`h{d0S;G5C-0C;%&sEMyU(roH%_mg`YIrzJr!pTo6Z6)`}G zokGYDpnNEhqGhnJn;mo~=vw)!Ub-!-amk^o{hLV?bN*0<>Cc-d>}zgGw9^cW9u`@p zezK7C>y=aKz5B27Lf6@!SGwHw7A4O+wLta5_suhhV|$Ol3_jDxeE#_bH+7LPmKF|5 zty1N#a*4*huFk{`HPnNP-QWBz5C0xvzk2F*XzxUJ?2sz@3}?~JJa&vE$rYb)6{RM* z5I2U?zHJp#8!KPwLIvs!d=D|iQn*f$-d(76v=NKEf}6S~H0fXRQ1<@L4Z|VxLmUrJ zIK>Jt-wmZy87QPO^pAPSM#JY%84W>`$Y}3B3RA?c+4JJBbU)>A^ti zH*@{)CF8VYONC_x$2VV`n2wC8d!FrZFDb@^tlzpqIusv&q@FxNp{SAi>Esd*o)8wu zt5qQVT;W%scEi&Z9vigEhEJ}M4f*Wu2JBRP;hxnMhI^3!rgqwJoN44(z zSVU(^V?ax9zRNm->Tc5D;Ej{|%Ts>o)~!I<6o-|~Z(FAJb=_HbqyJm1MB=7-OW77r z*P%69m)*`5ykyuy9(vuPgRqt}=ZgxFu19^>7nnoE-FYjTAs1*cd6iOGfs!Y$$<>(D zb#vA(=7ywbbOlB(jFQ9ecP>%y7%YRf zucxY2Gw1S2zWX7ubLzbPk2AvsRvYAU^qIp|#oYiQx}HIN={3YWoAL+otkQ(VCAWQ| z>DlOIB<)z7Bz>~N)-_Qd(dS}XrxFuBu>yJVV{Z^UySBKFU@6=^ zuZ9RhR~EK2PkIECWmIaNFQa7k%+dbnWygm5w76f2{9^qLgO3S9Mfj6Ht+Y=Luzty3 zeHF;b9!Nb9y;7&Xy4?NyqK})Sx&b?DL*S7-zA*;-*6ZA)k_M8}w|;(|IOZOyam_ZA zpL2k>qBZrWlr+Vj?n?Y!=vMO=hj$GlTxSY2@?&|uL~XnX5w~sntb8KZvK_)64Jc)1 zvn6(lf9oY%u@$NP5c8<-wA%S%=J=oe`uND?_s5;PU^K*$FCnw!Q~6OpzkW5F`+eBu z_77KLsZJKUv+g_Ok&=l_PSYy~z(m5`m0`V!e`N6hnM|V|C|U)XerT2|Kd$lUH~(+R zLk6VQ4rHocbr%NR&gl2e|8nU$hY&L_QNK~yV%L5x#0}9d=k6--AH93^^I(Sb`(KZ1 zOotv5K7ojEa^0f3=AA&~hg^=-&kxMD*sdPFA1Ou}@pM!r=ao8kCJbi<*rg+V6+mOS zUhQW|osnYNqj@rI^v?%qLc7PztqkXXo%;6p5|6IjavXo=nyb3?sqEdTj;Oo!baSU% z9ACC2G8qq(-c`&i3>j4^c70_Kw!`2XHgExUEF*++eH&_A8Xt*M)?xCOLcO)`hp&r; z0Dob-H<%AyxO4LjbJ(qO*?0Dd?%0NvwzcEQLT1*u0tvAE-y2^p0HwnT8cQN$>n zk#h0@yrD&^4qhLKX}JjAGGthb^v&0lX(tOyIKvXbqdgh~#uf0p{Al$}F)Lk3TcOqu zVWIpcnDCV?3)&o$N%aAAAl|w-6=)B-tAk@p6jR^`vY%Ao!hpI1O$Dy5YPNsCuMFX| z(WtW3lyNS!4iF=#4PZT&rkjPvx6YNF>JW{kcDqiQ(H z+I%~z%^3ug{eyI zQpYBXFLKu7>f^bMg{fqzl0i-(~owT0L)p-7fedzQB@L*X7k-@^Tv!#V8+IZ1(Vr0Vu;Tv zaRKjuejQ|sxpaz)>=L&h4-;3N*R~EyOqIHm&3h7a#7*^ZFlVzb%MlAzMkizNkrA^o zQ&1;KWDzNGkwX{NQ|ct)#iV1MX(7`TPcO7*9l{*3u+Y5j+Sc||iT55Cnn^;5;Zz9M zsVp>+wSMLNSPx?z775^_;qK&93}~~ zDPLA4o&Xp1NU?Tiow(&RHRpID_w(e%w6ka0#M-!9k~+g9ue}Yej%&wgO|)+6ycU(^ z3;$i3eXicXB*Ln^ft~+rct{{BI%=>8Ma*e;%k8V~+xXL8K3KY0yM|u1s{Sgwdm-UG zKOXx|GSzrd)%BR67_K2hX ziQfeH>$rd2&mY}>HMS)|wmCmt{OH~$zgAWswTH%uqI{2yfTL_yoe!Ust?a~GOO7YL zvw2K2#p=~$*m68-@#bx2oNX;Jd@yQfz7Jxx?)3iQ3pVavjJ$mD@qmesyxRSv4-0W9 zA?q%=E}6L50;#CE={8o=*2h;jl8Bz*6*w!oM{}+|l0It{FkT#I{8Z1W^5}D|YCg;x{^=?UCekseSs3d^Fye{s3Yew9+i#Gg9**0111?;oc!!xkbhmVOe!heEeLv6r{C@vjuf2Bi%r$f7J?ES`r#TpX zpN>oXm9WO6$<>n8J)4+~qTS+=mH6%1M~c~8@6VK4G?Hqp_Hul2*Y6;2R#!M=iLFzy zM*$Dap6zX}omu2&4R^>a5`)cjMd&y6fe- zCnY0Xv1h4d{vDLuw)?HW8bca9%Cn@&I8rJbrB5)4Zn z&C7C6KTj26*i`aZ)4g9q@T2VVGskBi;k{mxk{@JDUscdD$T`@Qp82imCQvW^e2K#E zDg>F}o<3-d7iyZ55Ny^#n@PJEbAGXXqK(zQzIMDuUs+*2|NG62NIfh4!9+OmIwPkV z;W~|A5B-e}-g-tm8eDMdP^`ms++5lp6aGDPAVY+RtW>gnk#K@v{$j-m=ZT0O>&dD& zXL*pj9KO#HI}xdXJUT z`e;2lLaieXo;P+9C?;+bQ8;TZtu}^PeW_y6S-aio(R1yGX-I7Ois|d=CL_^b#UpT} zsB&ZWmT=tEhFxFQN`Ng|Ab9oUcUa=Hybwu9|*#4Zhu|yL;wg@^$#p?OqN=fj8H?Z?>38 zzIFW=wa=*U(-*h6F|>tj-)qLg>_(?2e%Y8-#EVo`A*g5<5Qez@5&19Y|pM z<0OftMk={Dw|N`$&Ra690$3sCpLn}%&n`Va#g2Igp!RF(_smZ*!vCp|)85263Hw<2(7LU{}kr$H5acNEWO|c5M_mp?p*C zBU#g)Z^g^lji#u)pcNYO4O|uN-RWc(_g*RZUh_|G2aqhDqCn z4U@mDL`;m-oJ6GOe2XH^QcaOljRw_{QgT*DBS40z|0D)5{ZG$G<>`=FZoSTMB~to6 zm>lM!A5t9gs8PvaocM6hz7L(&Ijiz*aR@@RejRq+Wlu^UyPe(?qPWFSDgfNNdiWOa zrjenTWmw=XBOl3C`c*c8;;qj+@vI@5iqRzCSZ!@dhdy#Ub+s$-ZWKu(6ACq|;_8Dj zsz)fw8*w*$WJ_ADgEktPv9Ykhy`hI;Z~2#GhmXP(<<&(j4tvLwa1iq)AWmvkx~n8Q zjDPQmG*%#q5Cy1c{WpegvLO;v_?cvfI$v1*NA;6&mXxPJ^y`QK|13&F`InSfIjm8* zy9~w&tBZ|37_{FleXqp^B@wz^+_k0jOwb}sf@e_{$62x>hq$G0sqYQ@9`A5?;GVT%&lbUVaU6lk*lX=+|U}TbxV$`o%`*Q(xRz7ZQat*56 z&T+UI<^x}HF$IRi;{8>hA zM5L=x<55PPy~3|{1nbkhQEw6#qF)?x56=BLOO7?~=_H-2oLVLR#Y99BWd!^z9wS8*PERZor%AD%M!y>wzVs3U zRZXR!0F#Y{6%ImkQqcAIU8M+>y_jRHgmv@}=(9Bn1AzJ|y_?AD1@N&>2Mm=MWm|hx zaUlLqcY4qJ1qAvIk*C?8qr6~!oSuyi_IQ+%&-0DHh;aNW-|h6r=*pDmc3w(qljS}c z{}41N>zs{Z&%Jfe#~>?b&+HGVhR?}dBnI*0_sWvj!mSpb-0H7zn7YI7u;7y4<&Fd) zCe?kX*&m*-0Xe!@x`%x>ZTMrgA}G3y9O@msF|a>w+0lt*w9vp@WtHa~@+Y}Mo42e4 zu7<571bdkBO>SkZAlLC@dVR;`$C(aOX(kCFuj?fH7A8Y8k1(;Q%EnHeM|M233Q#?3 zg`3pb9P|=bE)%bh)K-~m@QM_VMBwIYFf2*C+uD(*$9v@98u7pa|9RoK@6WLU*4Xo4 z>*F}_NbPvM(x-q9@_+Hd5r^R4U#y_cl_~;jBnY#CCdj0Af8A z$FQtK5#z+_2L4)4$5UT{actlK&yx#ZykK~6jX1zxssF-W!rE3(18;U7fumeejX2`T zz+Uw5v06~ITIj=vFT3y``8YXC7&L?#))q5yoH!S1F+gwGBuEy#4k*&3!g-GtQKG|4^?z^PM!qXpbJij3yh$>xf>g;RAq4@lz3FIE=3&ic-vRo zN)KRr3*mTzLU;z=WWaL*0VQtoR7b)~ikRLvM^=Wh>PGUIix;rV84Jhtm_l@RHvzrU__ zqLCTa{kAgv1eKYR<)_?V`;)5P15F?z%(Ah+(R;Mqzg8!fRZGk$E0b0vfh33%6Soz( zCEHv?(=?=lgU~v#<58&O)W64boM1C5oFZV7HdVB=Xz(#Dv?d42@lxFNgP`79o^Of* zzNa%{Ys;szOS)re_bUDf^p8^2I0t(K>=I!sx^J@h)t9lG`I_kJF&rE{vrwxM;{Kkv z)HTe((YXTqj4p=|RTuvjUCQ@Eruv8bY<8 z2wpo~KV(-~wm6@=!t7Gz2PGcZ;=njYz4d*n12QbYp$jTNa-&%lEss-S) zY?O%M;XDFIJv{)(+)cZTrV4m|ONIz6e@3dmOEv^7*(gB}Q7sjKl|`fk4&WBTGO)-w z9pTYHhEKvs4_5YNHO?+WTX3cJ?DLsvx$k=eRjiMv9ZuHpwGG-AuVC(Q`m>)HnSS6e zHgfEi23fW?Jm)*{|6?bf;pQ%lG69tCb8~@1xfSRypmS$$fP&XpFD z201TuetFglH{(%fKmOYNEZV5_0pn(}ewO zy7zdarD&slt~bZi`TfnAqxkpPyE1Jmi@CK0QPqA0srO_dc(*f`th`ZSE8|E8@4$_F zx4Xjw@HGNe)xiVsR}jr^iP@J_w8fiP796UjdQ>k{M%U4hQqNs*?*C}bb^eR_ z{+svmXv9aN^N%@r-FlFWQ~+yA%LPcSNh$AIt*3W~!aOb_+@IW8$O$3BOT5GcV0ol0?CBzHn<7M^O1);z{hPzs#JUNEd>{hV!eO z>JE{|hmI(wH-W>~*$br3G>$LHM^wz@3g7$5mog*bGbx0p;~g@;ZB?JXkc!>cNFKy- zj2u=!+DV)EOIYZ#}XCyxs@OrIk1QPO+#O>X(asi6MK*dcB}%#lSu#?jrX0sKW{GS7+^2ZGVzNa)ZR%r zQ&7Ek)OIRWd&~5a4Bc*SB2WxpBxAnYVdyg7`(TpCDMDXNS8HEV30l&J`hU_Af9;iZ z#dcap1nTNz90J9n8c>1a4IlaCuTsSlQyWqLLtwrnS+eqN@@t4x2Aog0@8yoMy85kk z=A>FIjO)@CyWQ7OJZ_ri9i`ab8|<^X8{{t5YxG*a%8lzeeEsxNF%gitv@F8!SEXW0 z^&!6FQIwVr?4NJiBt(B3-P_DI$mko-7qo$L{Y+GT{5^?r5vEOjQd;T8@I|%W&H3E; zq#jOZKx_&3iq5!A(>iBlR^Gi$i#kFy+h z&otmVNndcssC$w1# zvu1k>ABIQP|C%Zw)OSh`yo~0*R_CP-b-ZplfS$l?~NZS7rOKP=A7&>NQ58wfu}NA=~4qk}cvI5XM{UM;dK-0QC}ofIN&ix8r$ z%8We`ku_IkXWQ@V()}^HqwWjtn_0`$(`*J4}MItC!RIB`>%53CFoCK=gd+ zP;;+bRON*Ov)vlz@;iD7qRPLHm`FDB#7GgrBg8qvir#5^Ci25pqp#|jyg!%VdtD@= z?h&??RO*QjLCGc6ol%QhF<5$+;#QLgvtYL9V;0MC%I|*YiPJ)(?AVz)E+D&3$=7J{ zuHPDrLp&pUR`v6(v-+pcYu|?Vh@FA`=(wS+eAL=-YS?qm(mznO4VAV$PWxKXf!gB& zN!R&1MlK~NXNV)mi9Ix_w3-f;oJO*f4^(fmS|Nv{Zy@XHzNgEO#g!g#R!e&ZS=Tc> zUH)*C2>@$xecNN4+Q|=Rep)dxY!~xrg%mKJ`B{ZJfEAp(K#qlZB8z_kSG zPvYn~0PZXv29Vl2@U0W*;h+epu|mps>>|%3$P;`l?5uG1tJAki30&gM7LW6^KjA!$ zT5|T;G$3yQAZA#9Q?#a3@~pG7{%@A7mI~Jp->T1`#d^@~HPws0UG2^q&rm7(!DTVp&V3_{X13)wRuA7DL$>-0$PQdrd0`mXl zze~;`TCth`_CF!|PFePZ3M7JvIFcvdp}P0O(ekN8oV!4&!T$3y@9+)_3cj=Ty$v|sic zW2I$g^P^};=b0O4@xUl)jJ<8Sgrt?jwyHx^57GC%oPzMzhBl*h@v6w$j2AO(#2-tO zjF81?93)0qpe#ZuJmX9)aJTz1JD3v|FNwek-F3tVkf9R})(NXC<408>-yO~*ie_RT zLCgy?c;hG8Gsf@oj#Qs~T|HWxEV13)p7UDib_l#xEWsg4u*3U@zwDaamrTPF`8ClI zZmKCR8wxiSbDGkU-tKHpaZBC_@B8ub2^o%kj+sgU&CFCr$x_V$Elw$=gmfwoSQ1I| zb5O(l$}w3U{pK1$n4H%<()*5A{5)xH{-8U5^AqBvsWIB1gfE`0?oNiD-VwX0hNq(FSYIcQl zqw9{kF}JOixmjbP+sOF}k)_bL;DqbZ@9x|UG~Z+yF59jjjV4yCLrw?l@zaOpddNQE zQ@Cr+GhpL0QqOvNd*8cbj&I@4y5mU6vp<@weJOe#`$mli+WxC3M2?Fxej+V?Krn9= z&HF%E);X_zi17N@CBNFvHAU3;)r`@#&^PZbXE08}TXLI(-hSKEQ2L5RJ{k^u?&X)+ zR$T8deQ1N#bRbwNlO~MQX}&G&bKwl#X?D-7`qq}J%hbs%SP}_ddw+-r`+C0{dyz~+ zP0-@ZQ`n*|`)j0+=2ZR{CYIZ?WVL;fnPNXP3f6g;n?p&3pCrpFP@3=rF&9Vmlt)Hl z9(Y{w6I*^`G;jFGR$)0_Yd>#Z>81QSU9jJ~scKzC>e##dESKh>`)r>srOj{zz^DX{ zyN5_$HlR$VWvCG{E$t{1g&8jkN8l!b)vCbN$i0~{Vk&JcWhPopb0lBRTg|)e zr;s0Yda~0H!))*GT(aQMA+73mVQ0CjNQu}jqM)vD z(--a6XIPuvSyRa%l)4hZ6x0w?xSdkC{dF@5CL)U^tAlx|V9CNCG9mePQz~$RP2DpV z0SFrqYK9!~vN@=)QVmAup=Ja7g~HyRz!=Gcx48S#j{)Wq3;GDfOhP#B15M&hrjKAG z;^=4%Y43YmI;8N)#-`5BD?9Q?Gkt_AQ7%X~0hMORZbpNSlB2L2DOaFyGa6)}gcZjz z#k~oqBx*-fwOeF_WF$O*kTNCdGLodiSj8~weZ&{1$RpJUy@6sFX<&bZVg^o3 zBLRhl;mS`e+)JwO%OQOl5i`~gLt1m5#*gBICNK*S-TRh|B;Uj^`qF|$DMi465!+$d zeQCi899bO{{66k+%;cjM2$NzvjJvNS*rYlGg-84gCtKS}X?xgT#&$cepm+vK%mqacU=@xv<`AzV60W z-~a2mr#Oe$?{qg2%O`0k9aLfdmyzkYF33>Aj0w{N^`gQ9&*$dyvX>@;4#tk{Jag>v zi_L;$tT+lxKL5n`S0VC?zLWru-)MG|}$)NmaA1+spPdbgcQQFJ17 zAy6UC)w#o|C-KavqkH=NCxU1X8Fa^V3{Q0}J9{HUH4$4rUwX>?OEwvj zX?1&%VV)#*dAT&xC5bojt9W`(mnHI%Bi9_or;1OTV)Bdc4if8K%rOx9W!W1K2_t+T zhe^caUi}JSYx3ZzcFlqjwH`ux%h-mFGoG96k$=qm@V?C?HF$}pXj=DB?m+KLt3jKa zr7d|yYRl5f0iMU$$o|GT(e&K=sH>&{9%`GJmEf_PH7)q85g$yB@C?S?6t|@a=psjm zF<>^x%Yjq}ypf7gACMOZLyAM-=7F@pV`X|m$U0Nvl!fDoMZhEXejqSZBveiGISH=) z2tVF>Bsj$Vdy>_>w(|Q>dD=|I{tk?-N?d6F)xhZ`yDEnU*6~+-9{LZHf4@RKr4W|# z4trjpROwx6NecImL}& z*0Aka#CWitFjvJpTX``lPGcB-4WzNuw*)d@V3e=*L1|v(vDOEdpTf4KL$-2szIc`a ztcOWPex9;m#t<5lR>jtGC;KEmzdQ-=O(j-5^u7&P9gfhbmnbiI-a-3pwrsBX4zQn= zicX`Z0f)_1IkFxo7^zXLpX#MNp9a3Ho(8WOWQcECh5#EI5I^I>49TTp-`r>Oi1~@~w3A?MwrX zIP>aDgb#w-AZ@=-I@*-SLD$d6RM&<;^J_mnhEKFDQKz)V;$s5!e^fz78bL=O7af6# z?+J~b2-m;$iaL$ksI5gG0IC|x&MH$bh?v};sk0N(%MkX>OJqK4{BUk|XHD}(2z~bP~X4fED2~PKjdku|1X+r{?eQeXcqhb(X6$E z3W_r}{143+ga4OiD6p7cv0wdtHoYyoZT)R~TcrYZK4g93D7^uWBco)#cANu~V6?$K zf*eKJli)k2?U1t9vA#bt{xt@W<0RX&&;Q$N&nDmfM#IrChAqfh;o#XTa6R8+^h9eZ z_>^|$U#-*XWr)#Q|C%b<6aQu&4&2J1oz01ALp1~w1A~(-4Pp0kz_U6OIE_ggmHuoH(l1Rs|!15Vt zJ{VF3PP{0QwTZ8LvVNAkMOkqw3qDIt&m(mA;?P$s0j}KB_V{~`H zz2!X4&T_qfnXvIp;M1pcfxyc;VUb!VNkiq>qzbD&G>?D-GxxP|dhQu%%Nw$0%xSia zj8M4~DuR<7J&PZOxjy#od^eu{u#OqdE_fSgV-)o%I79+ZC z=}@!Zp z2iA1C=dWoC7Dr&7 zrZ9>Rh9LBza-(>Zv89*AVc3%tW>nryp1L~dej4fR^4-~Ry@6iw2y_a4{p{n+WxBXd z<7bYa?S$or$$;y~S2Q7uPYXVll`s1BxW6<=J!an}`(^Vs4*RR2dlDilnz-BZnbmrO zQtR|;_Pd>)R`0xe7`^X{F)c;6I4pg(;;QQQ<=%q5AU#Im+fcDr%l%aH02(g+cGtxC zaZqEc`WFjZ8ESNV>X19Y!R0(gQW^Wy0=>0L08$sC)d8@Tau0Jel2QkvxIqX96SxJi zq=KVVi30AM;+Q^!3#l*3&jMA}FvR5!ty?%tCO)26iE>FwUBwYZ5CY=ATT-+DQ@6B- zWB?p(jmo2xAFWP+rGhf7ycJkV6`U~W5tEYzN*6WXncMR5kS^sWjAM7q`5b|!KI*k^ zeyzZ1wBIo5V3Ta6OC$94-GY<#XlOdeT=?l#%0rLnZkOyEwK9@s3Zby;HkXrQE|D%x zS1wxU6(zX`g?>9EjCdf>N^*$I-|FxIGMn=7@%sWeb?PMU>yB1N&0JAB&Nk_#2|qR` z3w8!nNMBF#Pg0bMM6|0DeOPmcaQ`8HmzytE_Fl{Ji*8OH(^Dk?CLH%N-}cy836Qka zWmU$#`Fq?zQ)L{2BgPwGig1DbO(g9RZ$eU<0QLI7PT&}Mc-%d^0>4dU3O(W0xeskL zF70;-A0{V#GQ61^2_$_$WO}-I!l+}AxGOA~9mf(EGZ!hnc5Am{FdrNJ@4 zeTp64A!_|>@DR8ckq63;!4&VO|B5a8l2I2rmWx&YI!Yk_Myc?acFgb}OTH7UG*FZL!ZU!1V(R;Wmm6A@nW9!*7e5 zl-+DS(o!)iZ?OxoBY($$5$2u)hYX)iRWfpp*41Bk?a&wH8IQA0L~DMUx;lgL`CB1? zyB9dO1X$=sbm4{C&Ti#K4I;sLBCRpSI{3^-eLsFWUEsC{24WHUL8VDoUoez;2=fHZ;nAocN%2w#7gVQx!tLzu4h#p za>>eZ%$&HHof`B-4(hNUij<{tW5#(_^tgD^dJ3iKKF|A5CP+oRdT8o2%j%fGY-X-) zDOmn=x@&sXKy+0*n?I*1r#W%*b)DdosZ^J;g8{8sC04KdCQ-|3)%f)H<9%-0nhswH zhxy@;kL_>IfBw@W+P?GFh@1Jj+SS%5|EjE-isxln?9-bhD}Ad%bb;=fN>;=Aw$=R) zi66Ghbyr`Bnyq(_u5%V<#AXi{mF+F$+9^8Zc@K-N&M9OXq}2@9RBT!=`brE9Oa}5k zqGgkIWqv>HewC=#m-Y`Va`3YAp}p=SDoO|D25@Sy>fW9k9nni$P`$2<>Qa$BjUO;1 z1b5w^RF)>(m0kkxCb&JQz}* zG%XoCtU$v042Wb=$(e7}wo)lR&+e!tX=%Ruz^XK-S3{=?+K)eo8Ir9-tQfQ zI_!e9Y1u}60(h(f0Yjp=8HCmiHGI*I5-2;6I0JPAC&Fhf1HlkoPryI^wy6um7VA6$ zVT@FtXIFrPL9{uTRuKw1^EZJhm=Xwg%rADX1KLMorIDZ1y}Y50yP0p#N(IhJ0h7Jx zCQh&coRS_gsRMT8cmkWC_5zX=3pUWq8`QAucVq)AZ!;h*0W=U!f&Nzw)Ef}QlOz!A z7c)pJn*Ey%g~|i{1UBIKFEh{vSuCi{OB8xY`^H2v6iAf#C`2svpX0VBE_NJdt1DOh zarEe@DzWG4@gBA>?)*)0S0o;bEvSQR;y}XFGl|g&2?^2G zdqe1cUBPtUzBH+zScQhgWr)SkW6-c$Xh3V!67}r?v4DKHFqF*+Z}R0~U1@ti7jas? zI&I6sQ04D_F_)mJpA$d1S8Nt@ZgR6Rbtspo^iib$L%{C1RiIc(r^;sa3Grf=bt5;$T5gkch|`|zmwOKiMAeS&tY|EqRew9Sz-x!%P~dpYUMo->z^DSf z!tTN?0Bgo6XAKU*;QJt{()ldZWM$5wlEM4EO?M)2bw z88jc&2lH6MB4%S+#vZ5UJ1w>T_s%WC>orW`e? zEBupkn+je{_|kqH94tjC^(7U@@k^G&^4d=C#vrvwge67X3^eRwNlA-gz-ke(2(z4E z3MRGE=vHo`5_-F)JIdrKU>)K)^KDkxxPUdFdy`h=ZXvXRMYLfzclF9F&DZ2=K3=fj#3F0tb=K1c`~JEM}|+C zRhV5-jg;_Df`K~{0>{0z-WJq&3PAC|htR(lIF^*)*5NnB&4gR>R!MpnL3?@cO$&W0-Thvp*$Vy*Em^ zD&ooHUu}99w27JQsJzWKP%JAsB1!oks~iBLj*T{anU%Sv*6w7F!a*pN9d}b&x{UUF zV2nI7@n&Tr+x~RV=Wq;DoCcJ^kL*)dSc`*HeVv{bTsmb6{=B(Oot|#Uay98xzu}mC zzo7-SMYO_OK0A=X{*`jSZW0A~1p8J2YR)73HsXfDSv;;-Fc!*sLVS~bE5f$^)|{xf zB~0Dh+{o(w)DPCzdhw6-A0hqa>A${^--Uae7hAu&1r#u<)b&tk7UC@xBJ?`5ljkqn zH9_j2mkM|8T+hu~8{u4H?n|Q7w$>3JuuS>^Z*PRBpMpy)Y=EUX?&dCOG4eOZMX^2P zA!2Q_O;22xwLNe5kj(x-Km3S1Fr6mJAlT)(nY@qnGL}{Ui(!EjigAvkKd-2gC7OUu zBm2FQM!uGUX|Vn0H~OVQeZ;@pT}W71Pv>2|d(2%;?(%bXzR8g>aVhC|7ye?N$!T8RqHM#i+?}sOLXXDl zISrJ|k0^h^Y6D5al2F0^Mt=dXkgMEppDH6FS~)AIvLsV8!)mw)EM&M)dfb94o4_>t2E@|qezGg>4PkRWI53@B_#o+Iyfb(kOr4Of=bE?QhIxkYfnpLqfAAAni7s=n>ev-+=gp$(hd1;pAuiuV*TaIu4*4XV+t?1=C zbsCd)x#9X%#-B9n*Jd5d$M;}mx;kY~bSeL;&rdWC*yGK}T z5jbBWhK^K+jkXkpP2P?2NIXvpwqDBYAQRf9? z$%3&|f#m%k&i+x3U2M6`q$|8Qfe%W3=rL{L#LEd(7bV;#6Vo$+Xvfe{U zDc3fSqN#=G4zAKPr_+8{c8o9Yht3J#1WFr3E7}rEKj67@Qjix%qN?QNMuJ+}W8i7Z@-r6SN3cz5l=E=K9$Gq5tqBYtP97#qN)MAFRR-tUsGErx zegZ@@2pYJR2LOR1FLHX(od4g;NoBV_JFPC=V)Yd_Y#UuR7|L9mr2X9L`~q@Dc~xZ5M))6s z#Q5vDtK_tDQaC?yEJI&-?*d|JL5fK=ZK&sFU0=#RzJc|2GYa*x)cKV?oRWHNTGDn& z11gmQN14a~13ZmH;hwD>Fuj@NCTpw)qc_~v%2{jP2p_xV`wGaNxfJPkk5JEs4MVRY z#Gbnid}|84%VwQs&MuHmI{6)vDmM2HS!iVG7MwHi-gF$C?=v zU-o6g%{yJs7Dm5ZiFjcw05KjTJ)uwuRIsXTztryXo~kK&tKp}KaIAiG`BbY&>S(ub zu}1Lo^G{@k!+k?1e#id0ZH|BFzWDS69RSDVjyD*qzXV78LqO(rTXaF;^I0yNMO_>` zo_p;E9rL{2zV_W(jowCI$Xi=Z@fJ;mCpYSHPe_XYD3uN6uTYZ9nI06e*u{*9Nk76_ zp5zE^V`ZeQOFXyz7Ii~5R=cEqxlg{{^9wMJLw#PFl#)!Gc|!+XS25z!XX8tnLC9-O zsF0Rhi5=rp++#{-H)5?%+E#@t1+u;`Ta`a0dK_|yqR4&DpEkRX%+eC!wD6ZE3#2F2 zV4--PqL3anFaIO}Sia%lSL~%*+9LvaQkVicjih9;OmeDfYu@fY?#0%z5TC zPo$+hvlfxm*(|owGAe;IJehELI$G*f{`6kNWuDA9eE9y{^U3zwu|WgNb{F+0l`(ec z+1bsx8ir9@TYOOD2sQG)u57QPo)fFy+P6mA96V1VC!%~Zl*nFaC*~y&`y>46w`sM=c(@{@?e0i$-@C8uu+u6q|V{VBwW|>*$1%>SMYpJ>UY;XdmIHM#2(Md1;)3 zj=p(ZqTs@9@m<5KW3+U84D81IbZg_?VLQxZKk~JgC^xiU+#!COt7CILZ+2YkMOd2y zD}rM*za@p)M;KhRt64L ziA*X2OiqD%X?tIbqvO_|hHk~qAMR+~qGE2hFZ*~*FFVhu*NY{1CwgLv=`f8X>Q(XY zogq`fBTI2jdk8<(6Eb(M{;CSsV$!9hzLm>#gz^u~|xbG$#$Juf9^v;|)5_2|bYaBq6ZFUMh6Bd7gN!LbN|gD?VG8a8JGL z#|*q*EZQtGJDmOCdiw;N;1q${@N;)ZeJl6FJ%_PSM^!%9W%}rcL?#n68xt=|TMerf z%PDG8d;i$8X=pozcWs!S%WsOiJl;*)4X)TaIO)=^ve{25yGKVKPU?qcvlLMx3+D)A zqH^WY{NNb7!b*{n{>T*d^Zi)BVcyI4dvV4hp|=O4F8?{O8V!EU%J2nybbVXwrSpF8 zBbyWcb6??TM*<@o3uGmVgN`&%kojGn-Up}Mnbc<8C=#L%T`a2xVo-_g9iGhlPP=8& zVP#CD#e72v?_0Md&P}GQP2g#^@ZJzsuBhv@)EVZoil4;7swq6E zv)s~;!&+w-beF>OM;Wy(vN$X~Jx3**e2Js}hdf9x62%NBML|G@$-jHTAT)UlL*M{7*yGMTY>Si|ym z_1a0+A*qH=sIicVu1)WKTf$d3zi##>bbfl0=vNQtiRRgl2VUndEpgw!aGC~1;;DM& z5WsgS^4fQDju#SQbOZI_XXn7Fp9&iJ(r{%+?KE`M#_kRb?aN=f+H-uXO!i%M)r8JD zL*J6Hf2)tpU2ii*vjXXd_Q%4@1T&bG92e7U1zn6v;-8G1BeuR}6TP*(nkRb-O6fG@ zn$R=KWh>sdIMK?hc+C7f6!Xs`|2`mYpf}8kSH74_t5ocr^N$;t3gvkHfF&NRlxI^%m2~A!L)O zrwFw94ld8YYp)Q$D4F@He~(gCj9m{YJ36g-Xz5k+boe?^sQd;|d+yh-&!Cg#`Rb3A zi$Whumh1J=c5FCm7Tph;8EUAhaS8vB1Nk5^y2P20-?mou%RT5uM+J|i$ijnb>ECI; z9taZ1m_HXd_im?6ugKwt<9*&-DQz2W+;bwy%UMJTghO36T}IgS$mZyh-lJ33dbks)+;X>~V+-)hw)H1{5! zx#R^{a?cmDNV_~L&!1;G|2j{vSSr`a^bkFz_CeB#(*X)`nRb%N3hg9c7B#cwzI z%D>Fqmd(yE8NKh`_q!(Vu}E}{E&G<@+54vBxVHDUs7oBHRa}EYl{>*Mm0oS^P()R=|e(m7hlq^ce(UY2EQ|4zRUI)t=t@3iLY ze6mubJ!Z&WrI*Q*oV?0Py;wtQy=}X@ee@#bwHXTw6}MMmNU>L z5=;aaVf@gZ7#G9-C*OGX6B?W5%Bl?*lm=-$A1C7DE0;BgJ7%+0592EQC@D+kZrv+C zcCWO;&qQ^T`7IJ*Ei_IX=D$i;6LY8B&U2Ve%V;IcQW?686l))Ah^O_G9_pA3ohi+- zN#|^ntfft|&~*7F`GW6~kON)(30F|WJpHvj+Z$ibPE|tD$sWNiw7)jP;gcm1Y>#QG z3o1Qxw}r3f^iaiHudb0NypX;^g}qEHOyNalQ8FfR?+=YvqsD=8O)Wcf;im9yQ*XBW zMBj0J<#}hYJc0J{--W^q+N{ZaZCh0AB{Q7^ZB`1VZ>ej8ul1FE`_`VImiP39=f|pV zEVK0A856>O+fQ_LFmLW~NHzykB4&U!XaYxr<>6YpQS0wL$t6%Vj6s7tCnrqEI1ekvYfCsx{$ipX-4GRug^+chUl0}yf^B`UskoN zt`UC>q%QaKps#ik%P8}-@vM$m?#(PI;?sZib-nUE0q0}i%NAid(jUhSV@IYDI(i?| zVb`@iqUOA%?aIGu!e~_ zB5gd(tm}9B9dAo))*-DWLX75tFJv3458## zeX4``T_#twB;Br3?tXn{P9m*?OcC+X6#Cq_{q;#`n6QoIE3mx zoU62YCNCgpL$$>-W5C{ANb%>KXIsld{W_I@31MPsX?_t}j=D5S*T_mohtc@coqcr@ zo*7GA%EL_fJ6vWxj)y}OV~}KNJWSIN-rLsCvMMLqHhE;Zn-!Pe&==ugfllZ6OjfpU zr)b2vKBG_9I~D$pDCNxM#TCjaPWOoMmx0`s5dw|D-XmV-6E3o?ol{2MbTs0^hYA@A zE0YB6_MgEIJ)nUSs5ngIB;40^Vlh2VPnPQ}xXCe*Ho>KO5KciZ?cg$bY;bBVtWyxf z`6xj@wy;v&v!3gVM>o=k@u^iK@r!dNjoBm<7g*8I+R#t*_{R~-X0k-)C(PtaN)H&> zH%KPe*h=pTeYhj+Eg8J8chfEY1M?mtUs>q8okh~E$bFSqod}-MUtBRVi;k?M3;{M) z*?qE_;UCr2wR^^xHnt6>CF``@aSugQQmIX9mVUe}Gyhd7>yO#5{j?JN!6w6))ze2p5?tnMZXD$}E=B?IcK^UxWpri z1EF-FoWvnUo3L^zWiN!A?K zu~T*$j&@|CY;=)^t8JIYIigWKt?3oDiZT#*Tp-ibSb2rrmLs2O3&|1RjUK zYGP!_uo>;;x|Y+v!^Eih0pe?3QE|JEo$Vk)SJVgRogSy^4^H|8JVITN{Q0CVjWtzf zCev}4%CP5C+qI8nH&~^o6Unc1n54>WBp-yNht{3EhSeUlIIS?c#l$WTEaG=Jy9|C~ zt19_5^>r5Klz)3yQo_&mGu$e}vBQgI)~v3=Bes~CCOd4rA-+8n>}9zT-}@z!e(Koe zxE+G=YjVqTWm0X|Ri+$>&TK(K&v;jOQg+=ZybuqCb;oH^r(3J631vXn9`evjhv_xN z-QrqI60*G=&SoZR(no+f1IUQ?tx6en=TjZD65`dsCvLmdjMIIKO|lOCni8GgBl8rIVr7mU7O)boF`2PU@nS_DyG_L_IlrTrbTCC$(8(0=q3>mg09nJ@M z<&<#*5I`V-deRF?-@I_;`R^GbBYM_kYg2YvJa_nlP zFC+SEGMj@c2OR+l%Y=dguBjw#Ps)oT6oo~Uf|n8c2=g9dny!=(M1`D0Cm_A<)1%1e z#5U?lK0|)Lt9qWXE+oFOB{cB-#e#oFeMD@Q*|J?17_By;89_-7D`dE#^+6TBX}dMq zYHj;_ZOU))jV&Ai@zTP>?kI$Kg>nD6xfhXXLbGv+$JzuylAR z1Nz5-r&XlqH>?A{hkwuW16%=#TTOwiY4aO_AGK0Ti(0re+J2LZJ-}1aA7 lOZGi zmE?SDXR#eNq{xoUwpSiQVQsiJQcwN+>jFH7t&bw#aCEJuo-w4W>waSY06WO|)5+^| z9=8rW$HaJ~&AA;Gb8WWMX=$VcAp?q1JAk862u~%Dl=-OaKS`*TA(X+-;Xf+Rlc+8l z`x(#?8{Or>clbBkbK`9J5;i^Hkz4d*_Rd`7G`UOssSYX6)OuC=)E#c~PP~T=L>czN zfsYSvCTNY@<*B7`64L7b03ncEKlW(8P`EwPC6(>JE<(dY^mR3`X{MM@1{y<2@ zrD*}HC2DP?7hPIVgpJCSb^unp-satY3t=(ho{E&T{7e3(ll}3dMYZ{>OpDTzz9)9# zp#0Nk%>G&ZRkHBg%9j&qX_@wZKJN68U8-Un7|*@(a)SDx-Yrj^-?q&__}E{V?ot=r zv=pd!)R%2#dR~)lI0jB>GFo>T%0ssp5M`te$#ZEJG2LBAoyH=65yOUjVnsrQM0E!? zmyNdzAI2cHbIY&gZCT(rc5OU=87@H|LOVe6QToSZ4yvja_V2o=DN`ArAsLcR;09!z zfD%;Cb7{1>%L_ZoZ<8+#>3*KdOF1m|1c~=99MNF6sO)5@cNf>yEVg^k#(os_lwz2* zj|#Cv#&OuHVhk{N^HEcdtw(jb;?GsMwGyQ$^(58?2ZY4I?JmXl=Wcgfljd#W?D{}{ z@wuFwW zS@1~ee$KVcc=xtO8DPwF3**>j`fC|2)tB0RFS6T=!(uuaWd*f;VY7-Om(~KIKG#}} zY|JvdU4t3OpEx$3iE!JY(dejrfq1tSU_-{V3Hv2FhSjzh0tuf z&*ckoQNjo?d-*8s`#H2>_B)09GE%Lv?Qz#7^?}VUB|Z%wtfd`*Pk!WAA7nn=8l5)k zx3OCWKWet6N3xGDDy6=p_;!otkPhA9AOYuH6YW=z-LgFh$l_VpTkpg%t_x`cbuE@% zO5E6ZkdRfd{F?Ka6sQFyK%|Yq9mH2}^mozim9;G@-d$4p41p=g(lt}kfPLgB)*UtVQ&$^ zvZ4xyA~)l(>$P|6Yg*9wn{#vCC6A-iX4(hA1Nr;Say>TKmb({fns&g7ZUMk&k=_A2 zxAK@;n;!C|<*;>3+ze00^T9LNO}zY5Qn;{BbZ~ZY-&LOh`L(oZwwXd&X|$mT8zm_l z06xk+sP;9-J>}VGI4w}n5m2Q71syRtBm+ms){N9EF4UCTZwCs)Z09D-R@igo#+~fg zfZ6VTd;LUq{6c!TzT~IIrnj}Lbo>-Volf8f5>7x3#E;igo00}eR5B<-k54`UvN6ui~>!*1h!g=^|3A) z7Z|KGotu*>qyi9D4#fy6DB?LC&rY7kboJ|QVGl3PZwbkA$09rtF--7_ZBFCnU@&*= zQL!Mb?&AB@uL`iaDL)bkNgg2VYo*zHb-fw7WyP2k9}wbXG>Ofv868C8aGZoR3B{dr zW_FD-%Ds`eZ=NUQJkIYYam;5U%kIqBv0D_|t-I=6D9WFnGL18HP+7PRF_s(=M&90%c?c5e(m}NHMd`yyD&_tLS0OBG!@QV8*WwOd@;+Gj5G(Un)!^(!k%9pV6 zp|J9<0d6EvrR+SYY&@wDkw>*Eg$PB*&W904Lu2JhC`GWU(j25;^>ZW2);mZT4YfG* z9}ugQke8+?dZixj*Y-*tbvMg0SU6TMcgyaBc(zzh&T&2~;GNQe>ZNf=^;JbQ*A}>A z*nU5cUe7zc!tO(VS?X0fLd&iL_AOxp(Xm?gAW^reaP3cEc;@myWCox9MOL?NqgXd) zeZ$+I?5Q&1DjYjRRh`ymwx>^;$WoL~-&vC_iGAOFi0`{hNZ2}&xLEv^kE}mk3z*(* zsU-KxD(><^t6{`d2UT`X-e=yX4H<>5iE~n(5N-{7tj)Jb-{5!E2gmI16%E1m#XEIon&{_%Hcod&7d=ITwT9jttxeghq z@|-U~vD}qtxl8)UwcI$ezSMYz-%MqNdMnJSb5b)-DYjXSqyq$n?QunF6#C%|pvPp0e+1f+102mlD(CvbX$<|q{O@2K7_nqqfV zBOisA*yh~gl`yE$?o7DqxSfFsO1C{W?x`_J>NeV>t3~$j!B9cxmRwRX;o@3TdkP$R zbRIn^&Iiu1XU%5b+Eni+9dPk!lnvJ6ynz0H3Tr_o2B;(rReJLajOyb!Hd{(4P(GB| z2g;>)YbuutMc(M|33~#T*#n61N3G*r=r|PLuO)4A+*7)g-8LlV?ea}*(;z{u|=|Cc~t}6=F z3xOqKns!>%p3Xd)#yaA?H2}jbTW*?xWDi2Sh4M^JQrHU}PD)1D-?dQg&N`O1K(ha}U z^;Dwb6MD4~U9g_?Qwy|$euYg9v~At`sXr!uZ|bLuxJ@*IgsRGu-jCHys>Y7~AF8JY z+7H$JR1K_nrk*cjdT&w`88$S2N9v~&W=KCmrh?d?$rNJPpUI%!o+#>qhoaN5`WFIr zBDYC^eHUj?i&i&Z|G>q;&}1nw?I8SC{%Aa++36h5Qz6uX>^xf zj~%r?E~|k1OywFH52{*B zmk05Isg#?WuD+-icGCOl=;Md+O#c8Fj`Qd$z0syFSxuhxsFFjFn$Ir}UC^%Qmu_qb z@LNyhBd};M&v#8)G2+LF%45c9EjFIyr~dsWF#=#HCAs$Too$eoj~ia5T2pdk!6NMe zArBAZIT=l>)Us;Is=HOKmk9V=o6V7xIeF8|lQ~Vb3Hna3*5&p`m|S%}jlA}&F5f;4 z-rpAQYLJ)VPhq)nok`p+ml76(_oRxue!*6iD}loS{g5!978Z_Wu2E1Wu5~lt=dZSW z!g_@s$I6Pb;aNg~8_=OhmUyI*wF*@c9#n{q2&hn^+ivPaN~`^&^seEnnprm+vMH~Y zA;@ayZcB;@ORqP`7K)G2=7lH&-M=0BZ(eR)EVrv2&C4b3%|738X=SCvlNo4uFUyG= zpSykhH?Lg%Dg&4-J*>K9!qlRsqhwNte@-!2LQmXQcCSI`cT6knN^i0>!`Tmb&Im1G zaT*+s9vKrnmL}ENOuTTc9S0%DQU|hEH+wN@?xX1&GIfsqr_-j}E-_UB*bHti@nP~y zhHG(1`P34fi+hJSj!6f)x~~+-tT}q#$+3&PYn`rCb`s2}>*R$I%aTuuR1ZRx0HAsh zDo4phT*{9v_uqcI`R`Sg-%oCl@~hTsrI$Fi9EEN&X2f{}+V6WKa+z%>svYnW8`)#W zkm*?4M{75vmlZB9;+;Y=B5=frYldV;-X1N_5Vu0q?D}n|OSIk_`^U)m9mdI{sD>4( z7W}Gn_|4YYBOS|NH`-3H&QLD|e+?aD;gZ4daUI>t2|X%ZT;;{ROK0_UL?4FY4ul=V zNFy*+_C2)*CY+HG>qJJ>x-EgK>FJpaGbxoZC8W68{*qKT2DNA3Y`sz)nQtB)?=V03 z)B5XhX$PUFGNv4kV}eXFn_@!}nM{bzrN-7bN|JgSqP?R1r(@{!)^RLi)f(%PyuxI> zWhq|o0i}Hcw5MXVr~*QF01b&fK&zfjwQ-j1!qn?=oB+7;93dr>bFKzSu7x) zsz=78uWtS-+kWl$uEB|t#lJ(1GZR~P7A7)@4NC`R18Yc9R8NW6{Snu|*Oa9}1DWnm zTJ+~aaqF%tqd0AX_|lo57S!2&rFXPeRIr2U0>D0^xBij(KeF~k8aH-y!ye*Wgk%Vb znB|Z$6Cza|gMD*!_fo3q08IQmehOC>*4r-o+_zOOS^f3F(H@9Ta!2c^&Zp|`52RLx zuGx*n#5pLbFyO~WX)02Oe*|teNZf9v5Rt!r{VHg_gMI7HPiky}<&bN(Qj3F&E!rd| z*AcaMG{a~~01pmIQj~nju1BHWA7@GSk)v?p7{=l;sS*GKhG&M8y=m=Re@W6(=mYXNt&LxZS@!zI$vN#7THE%MRTsVu9(X+LoUr0xxA^#1@d zy4>W=!Sj}wiORBXD!t4zoixBQ2v$4fc`93trcJ_2O|9tmc*|D?WKCB805sE%9tlzP zQTRbMzR&Ylu{dz;_B(rbT%!8wqS&o!Q33aW-ETTaR7Yt)8;kG&By~`wVK@{lPRBA0 z&Ps2$OY4f2klbZ73A#vHc(0NVK*&$PhQ5kW0PozL3s%#%qAo7+VRlVuI7_z|Oc)K^ zV9T=12gDUgsO%J?Mx=?31HAhNd&1h=!dSUnBA_H8MX4?E+lfk(w&QM_bm_NFwEWwA zX~8AXq&nYL-a*-DK8N|Lt4f!kceu=4CA{{VO-YRsOR6q8j5t&ZS#@1Xpp`3REcIu8 zD)@FH-S7kuK>&gd;DgiahTKmPSJ<2gEOGtIU zjSeXzU`K+y(tK&?Hwf1RtFbCejh&onV`a8{i2i=+!;n>akhl#fh)gSKE)U;T{1`r7 zf1-+Qdn!(mujlhAU4*xct!=IoHA7 z*>HG+)2G@ESF3eytxme4rL6K%J|xzzb^(%U(@#s&Hyyg;!3r;8vgZ^kGJDCl$XOvH z`a`>*_a#ZDF`T-?mqvN}ER~m{E3=ypLuHw8ocxEGSHy$s)hEE3(YCT}I&QCS>}<<8 zf;l)z!hB0l*>QzlrD9wd@dw}Bcl-E&w_A@o$hXW~35?2Y?ZI0suX;~-{S}G7p{2AN zanvEjaY0bzq7K13^*uc*M?nM~5&~sRraY_aOgIe#94r+}aCAur_p#!Ad#P4V&NHvV zkk=XD^KsI5FQA@_1ohj@cve(fjk)xtFy=6{fwEGSj)Ux^++iwO5bG-Jr7(Fg?%zcD zA2gpD!RugkMyB$HTC zuMw95MCr~q4KPxrO;f80L~xP1l%*blI}P{ps#f=?m?ol6W_Ej*TI~1)XA=Fo%OXfK zGFMV}M41uM$-xW}yKI#1;Q>n=b`@`D?8?`#c1H_!AYwd&K=qt=4L6b+2@%)1Z4fgO zbdw$=?gQ}GB`J#*PP<8F?RSjyZ8ePQU$@)dNo~xP#2wQnN>5b61a8|$tE2_5;n*7v z#I-qZ_oaO)PPq4`#ak!r8vr(_EX`qE&I|5gT5Xx%Qd&?%rQ6eNhBq4n+R7+9bst3s z(w7xF@pFpIR+_@F_|3Ku3yXg9EC4p{9g`sUp7jd9b#Gd$11jO7#+;hx32zx_mzNx| z92vmKG&Be))MHGxUgN0)UmYNB6YY|$l|DJnidZ&h}!KLbxLiPgJR=F@My!(lP6 z7P+(9jq?4uCw|BUs2_Q2a;8_eUag*r8)Qt=M`Xl$cB1P3U2}-a3z86t?b%=<9wkYP z5<-8B;i!G5KlVDppOGrdal{*$5es>85fyUfI8q5A4?2}8Yd!!gsi`(yplw4^+&;Wy z=CYfMNIzt_!4vwD3=sqZn{!x}#YkrYTqJV?Pj4cmGrguX-^H_g1e5vFpQ9t~pK55XEiTMf-@jT`yNxC7A3ap2{R> z(UY@mHV@HLo2f-gS5wnbYqwtTd+I%*@?3r^He0&hdUcvGF0UDhBY;iCzA7kFs|f~Q z1doMVq@F6?{zRDWM-v*9%Ht`Z`d>?cv|1yOa4Wlx-Fa`e-D=Lzt821p^>!4)n*5yS z97hvQYz4SlLrfDOhjv^fVJVawQt8;Mx(Lt#86Ek_&pq-p8p?v5KhN(VjJsOzfwg@o znA9dM4Q=8(O`Lp>?8Qg&EKfr;;#^;T!WER|cOA{P-ziRBZqt?dN%xf~Hi5TSSH%hQ z@U64C{za%yF4v=pj*D^@T9=s3WVr>RK9XRxcK63e<@9AIysNsTm}UWw;OLV5yh)vQ zxa_Ny=HTydK6;0XuAbzN2Gg<=;3~PiuVml!x0aLs?U78M*T4X1QadQ@Bl8zE-fdP z!LhP{>DIZ*UX^Nm8?vlYbmt{Z^eng73uCG!K#|a-6p_=s9jm=_;qsPuhGLuUiP=L} zc?bjz*tf%5+x?xYn*9}Mhn)%I+N)#UIT2lDhfl8d6`d+3n_e6Xnmt@Cm;gqj5OFgk z&Td(Cwk0dtu67~_$nTS{xQ#O8xxd*<+1B-^GGuBFdmS-va-%9N3wI#3!ex7NTZC={ ziU0tRPZaOD-0V6;pDIrwSU0mYmB%$OrA5W{*WQu2$g8jq(Cg}k)&lGo*Hskcy4=)=W(PvV3_EY z1znZG+bxqGb-mBXeT95TK1%A7-U$^U+|^~7Vc4cYC5zMxu-9Z@|KF`lY|>GLGCPjZKKElrv( zM_FSe{ln{i&Y1gv4*e1VavO4KoHe}nc<}dg+^v3Ibcf_ejUCx?q%^|}Ax^yOs|anh z0l89?dL#kfO-6FvNyD(aTzjS3?1#+d2JO)xks?*a!}L)Zw4K~1r%~{M)Q~DD#w~W# zsSVxrf%tK)4LBWzhDVl{BcVX>cNKyzUA0Wtmp2l|$1hZ&a3dl#YM^IsiPWLFSKhh(Fm1WzFe(I*Kj9PgvBG#m6j1zyY2RB%+-V4mX*1ACTNV zr+F(1TDHAg51L<3{g8i}wgmWAuHc|9HnoD2v+GZXi)OY!sQ7PG>N~Q|?}<}1W`8@B z69)y2QPmbV+&ePMGrXUtZ{zlU^;7O%RbS(MqZ;3zvQT!DWyhrsr@RSG9-bdWRP^#6 zS8BPn9rf)$p-cY&#Kje`>>t*5@f5yZbU6w}=gNluyH<83JSFlJISNMS(uXfPB@ma$ zP~1{E3O&UpBtl-}&W8T9jl~*4T7nbRN+1F2M4}a2t@Fjr>m~+*rL^K@wpZ^dZSzf_6O!O1vuzO|^3DW7(e8I4oK&SyV`_TQ6*an_bAQprFqq{R<;?9ok>Q zqx1>xtwq-4o3ZP>F`@?h+1y;w$?T&ape$3+YsOX0Cu(2H);yL-j$!z%w z)a97eEY}viVb>GJHSP=07O%)IB!sMbrCe40jcoY}ORlujYIW6wApn&q_W@L?RedM6 zr85o$5P6Lh&R}pYc7;K7vh{YqU{-vAAwMZ0wLrZKTl`P!aI+zi>@*kZg zaZ_M2<8H7!CmK*wYj5CE2<|B(s6L{-8}?V!o6e=_Bx{C8A#!fEiH_Q2%3sox*`+FN z#UB+EEg?t5jr?ncalIbZJhd%5V8E{uBfOHd%Up8QUI9fVW#w#+AcMIC_Z_#c_|dEh z8b_zr`~uS@n~m8@Tah_2}P8*PA}#!IT4<>cQDT1hfEg zPXVBVsf}S^^m>(cRxcoiN15FU#GiBbRi-RE!e4`c5C_9zwGD53;;C)D0L};&}calDh!e%&mO# zCS2j$w%rXMdwc9pL=U}3^;XZ8{Y`4D%J-#8yC3#jyjt{W9TWO`n%QnV>1w(NXAge= z0FoLfJ}F19jSZ-%Qv;Ks4UG+tl?of5I(8(GhTfD2^AtBUe5r9F5_tMh?8)+_avmav z>7~Sy6J@_jF2obvPi`o0OO6oEhB)cK#WdnH zPn6+x`L8WU$z%cc0nk;;dw16urUHrY3^1o&@7JvnkLgisMnftK$ZliGdNCj?ncYbB z2l=XLup_3#8f9ia-O%K$aCpgKn85f_ebiQO0Y}22>k57qgYMpxJ=@Z=xKQF~>q;<% zf$rXvJ=f4ye`o1Mz){weVG9S{eFx~F?xV>IrBI_1AR)h@{S;u)P@@td zC@5C0(0$d}c9YOYbF^fCN?%d>4&t_3^sQych?f{wG0_EH==uEB{S)^q`D$8c6tj=K zeq+T(?4YM++ubjljkUzJFb$Ls(0qPM$MPQWPTTHG&Pvh2Ovq=W%rEI6{{T4hHIzo( z4Pjg@$(T_gmkSda{8Q;2N9L$~WS=SpK=FnC2lKn#>_HG@DmU9lQ*XL|rqFEo)VHBd z%BDF9$!#d5#$z_vTcHXaQl;`rcO5_~+PwHIw0Gpv4=$vXf=Tl==>Gt;Mwu^KH2KR1 zZ64c_*qt{!d3{?j@d+tfId~yy>A%%bPW>x|F8VdAHVJLQyy?~UBacXCD{eU#W)}0S z2x%eh`#*_90r73rSGj#1Uf59Cokljct|N*_1pU%Dh=G=dc<0A$+Ew3ijtOjK%a4q% zwDuR&`|eeZ-!LqObJI>T7?E#%wK-d$0KXZChtA}O?THEOCmHsK8 z^Ofj6`tu%^^%b_&ciIT1V1u~G65+UlWP&x^x;>YTwu}{hVV6C0?*qwYPr65f5ZOZB zkncvAY;Lvx0FZi`uC=4(S8#VM!ax_i!eo46)Wc`sj=5^$>!b3N+;IBJyN3~5{{WZa z+Z}V{=1j+yes*K(JGgx|@cmT`MoyfsTVNZL0T%oQ(zyh;xk$R)L2U{k__q}fcXxK) zwNGZ-zLj3QHzoq(&OK2a9PBMS;P@#n9v<8i>t*M!kdxr8*iax^phdJ=nhipL!*L@v zJCMt12tfYTBAucIDgj9#q>aHH#8K7}aVAcx=xhv-t#zCb?ad#PImI3OfZ~!){#3?U zm*aRfk=%zXyEg(0Y7M1I1#Ud)`YkfrcT=t?^aw~K)o}|M%5{QO!QA#d+-nV7gt(kl zE^wW51puX2Cs2NEvNk^I+3Wk_k9BBT+I6&@TB*TL??lOwlSkSRqZp_lPU?0kxL?){Qx?cBoD04s&RM37FAh;}a7aI|vOl z$ZftzN)nxdQaFx+wO1R0Y`TepYBMA^12DI@h~3CP3S<;^_V)??s2*!`{3=CkwbU;* zcT+~59N!00OB&ZV5#CEpKmY(q5GF~eL+M;&d7Omsf=Dsrg!mO}?rAFTquh@1wIrW9 zDP3iiISlK}s7^NuyO3$H>trgaPZlKrboxjeNwDzesqM>PFj< z)yXj%?(RL+KG=@~`YHgRxBa1-ARaKVCHS8Au4BWy_o~0Moerq(41cwyzZC_1!$&di zpMsz7TKit+ZP{LO`2K_>R>O%;hTnB-EX(OVWP#3ez5O@aVN}?A4)FJ>h|Dc^X@O&z z^adhdSbe!LT1vb@xbW6mXr8Kic5 zIIgJlOLqp-(j&*av>!6F;*-*>g7ZW7=EwFzn3QeO<2?$0xyX$nwKV%L@LO)6jfz&4 zbW%v~2c=q}dkpF>8;+k1?Vas8$1$5u@i0VBqbC~-JKx?n`^Mt3c>)0h5TF#24&gxe z5PU)Nq9=N)O_u)vq-WlA*?)vMkR?yzE=x(31b3BM-CDY&bw28|x8x~3)iSVZJ`0m$ ztIF*eL?4qFL(H&5fpQecjb7IQV;+5_9(p*@*jTNYN>5~!$V4VYocux&M&k1xC zwCl8-56RpPxZOvxS8>i@=bjThX!Hd-R0tsm>4J)5U;$`P8Q@(fk8FurrM1)+Xo2 zWT)MnPt3Uxfw1fgdv-|!rxg4ocY{&^kXYM5Lvxx5NZ14+TLI)Y6^}C{h8hXC$t!(s zAt^i*0(Vf{?ni*8KWJY%qYb>%4Ly+AXm2^zc#|akqE2Z=3L&Q-YIU6F^X0)%dKsfS zLyyh5WS1;?Q>WWhGA1aJsh4}qqE6eMcNrTKw|_u7)X@udmlE?X6gY#)Djnd~w@6!B{`#S~ zs^>YRv`LU4##5l~Rcs3=(P(2p1Ot~fWcSx8`&N54WSLd@-A~h~2IVSKXieVM*1_ei z3F0I&57Q+e1tY(32;XumsBL!vae&mJ$C`%D0rgZn$E9TN<5;^FZQb;8^j+Gg-)Hmto_Tvg4yW0M$dpzBB~N>Lg0gR{upV4=a;ty_)L(AQTcL#bL8rxu`v z1#DDE=y$5CM%p&dm2mYF{-rVt-MzGx1I33PM3zU{!bj?^+iCRn^KaQyXH?d;rKGnr ziLmLDkpMdcVS)~GFtP2nEh{R`IEf};=?5)5CT2$^g^b|Z5vZt@b;L3EvCG6I_IDWe zr=PN1?e6z*kjpAaN%A~O72rs%>A|%w&Hn(By45~gY5Fm8n_CwoeVw~qli#e?*E7?* zMqe&H`H7P3Su6zcgMYS=iEO9%HSh4g$?<8xQhYW&Ye@r9?)aSO_};R_iEDmCfrvRA zKvQSAEc8lLRn{Bl_%{y{eu|HEa%S%0@wlxj8j=3)pcNbk=wb&D{ZJe?wgw}o)iQ~S zqYvpeS`U0^d*pMRlvgXZYqSiG2771I+9rta46TdFLG(oz&uH!5jq zStwtcR7GruwJw#lk3W%I+Q}_QWW35FTrAHtr{y)cTt?jhv|b7vN=k;^{lcwFt;XGB zw@bWUrn3eF1{Ee$m(|6#T1eeXQSt-y^xM9uCvEr0x2Julq+-oAW9_~1+->H{WD9X; zviW65QoAucqh)X@-?2P*=T`Q8OHHv)4fPN(c34{C()S$2KGq?g;s6X89a|AsZCgi8 z7fj|s69ap)j~+e+SQNA0|f-0Df&(?p>fU6 za7e2df|JESf;zqDb>FjeIME|-${YGp-PRjiXi9HC)4abQjUkeUmom}vgclo9ejuk}ml9Ax zJMsgkeKywGyBXCqHnlfu*0>iQ1+Dek;U*3aAcE3CT9p`1BP?*xk(Y5g5OR?{&_F82 z=s+PsK!8C>+z@(#2=4@S-i1+ko}*!Ub0rr026Jk^X}j(OM_W^p>rYH;|-Gn`=uhN0~M{ z1BodqQo--^s=aO~qy)5i_x>LhE*R}-R2}&a%YDf&5?1PpkMj?@o1w8u;!gW?-){c^ zS{s4hYoHJW1xYx>hhjX5OsIvlc@4jktRC5+9L#ZOX_U9xt;ZA+gd=UqQjb6fi9ITv z$@4odDyZ=$`^3wn{lp7Q7W-MV59T-ZQRd>vK=ZAf9WT7+4mWhw_mHc4N>>*Vi7igT zZ*QWhj`a7p2jl=5Pk>i_MR#LO%x%Tf@H03uE_WujME8;!G%lD0)ay7nvY3{mVtTD~LR-D+v|A$pY$D>;M%pPGcL(Jc759XXy0vK2e$IHS*Rq?2ove8s zK@`K}o#D_o85t_iaIP@mE*;V3iaf?^K-Jp)id@>T>Z?&L&AOyD7bc3@{z;gn9g>%K z>k;K2sI$PIqOS_x$JI?N#h!i)%(5)2!W>&GiET>m5*4xN659z`0QB$LnvJ7)1GmQ) zki)p;FD5faQsD3 zWS^j;*XCU%1oQiAP zi!jWMY<;Vp&6lI;+;Gh`F9yDiUt#+naZD+G> zN+Cpmu&tA~Q&R5G@4wyMu>Sz=RBJG8$Qb?lY*cQNSLhAqVr zWu{EKI^{-F3jtwWta84a5#9mxsfCADG|wt?vao0N32zcdVehGF;pkg)ipS`!W|}&+ zVVDz5xKbF#VZL~)Zl*J6AJ1Fc_7{g`s4V2TjhgNi)Aw8!+*kI*Za+;n@!K(#T}0D! zAe`FZK+Zi^2|5q9mWa454-N)nQ6A8K3zI#ywHEDj%^1@t(pJ@)(-n!5Swg-UQk_pU zmF@I`5L3h+o2Z`p=IzegcvaD_ns;nf%5I?PPAeriJa^HahTePE(ftsmYe!?zqigm+#gGoitFZt~I&}n+G+YkUbw}O3)pwk@ zM(ENt~SB!Ep=7MyZT zw&7^T^-wAXo89KPEcoPmqF;$?nBw^}0_5sIE;O#alVM$oR!e7>!&O(arYD?Y>VHuh z!DL+HT%#CKTSdW`vO{HHt>iZL+|sRrLF6~<-YVg|XSjsBT&pgiV%Zg*4CYG`n!4GY zRCS>EcIg+h^RAGxlXkshv)PLo7|RHAgMf@oXfoHdl5r6v$dC)Cdce5a zv#&84NP!cfCs2545s`r%2-|H&X}GK~h8kfZrrK=*32h^wN>Vx`4r}psYSp5A z}bIg`}SC(nj^<`$opT z%(yfwjP(WxkaQwo_k)-+3=l%;t?M%4o;H7x;rZ_wD*2|vXRI!j2*eegYiEV)-g9x!dq2@=QB z8|;R?R(w9r`qy2ot}5E>ryf+PsVQ2VBkGRyp*{oocCD;P@u0FJ#b`Ex*KYDpnLa1h zpXcjY?YQckIk>b0~%cD1)9-fSw4}RNsx|Wq5Y+JNsF$z;^ zaZ6KhoJc0vK~V{B;%P%*t-I>T!?P?iG=4>?t4XzA;G`;jisu;&&Wt}Rk8`b`2U{vZ z9wMt<5!7vC$~2B4=vQ$`t}|#+uW@x!-)dB6SkhaJRHP0@GiiHd za0d&A7y+wVgV|1-Uu@HafN++$>%hU!%pVz760H_FRAR3NaAdbFZG?^wj;5m;PF)B36YCu@yh%h!{;79ZxkB2e_^z0 zb#G%FWsTE41a9t7(62$bfg@A%_^Fr3%=aSWaZlMPRASIZ{BHLlzx4h80F+cL>!tvN z7an6RVIYCKxQ)#tfBKpuui8?Dls}MMBaog)x3SM}h}(X?fw zMKw-0?E#`L6(u^Yj)IS+&O(v5_Q@0-m$inr&;J1HKjBBDTOaq0{o(UdA91+@@ZX>N z*lhm*H4k&V4(sm!0Blr$mZJ@8ZjQcC8r5t}_Jeftx-^}~-bH0Mt6Eg(mXih_i+vG-EOkWdv8WxsRd97{mQj`hmwG}OU{&A~H z8#m~%<3&NXl(8L_lkm8mxVDkq_f>hUZ?xuyW)6ul*Cy#18=(>r$abOVizS89dZ!hGl-~6GVK56eM4eYPV z3Kcx}c`C?Kq_F7;f=5IX-sk@Sa=CljC)&#-#d5jYeWqe8SFJ1edyUohL(BJ#z9yMM zz7+7^VxkEu+yFM~bECF9Lu=iOZJO4STiV6y;h(fKkt9HZW(dqkhW@n+T3uzqI^`PA zC41r;n)X#XRy`vfHe8z8o>S~|r3A@oJ>9EB4l)=%4!5}T;(AukBb{Pz8#wj1r!^w$qsJ*Bb?isx^OtZtjFzANEzuXgrP4)J7JJ!D0BumJs2$ps{LR=(TlrN+j%rz`-w zJRp-Xt^#1XBofjwfPUBo%B`l;W!*PsU~kBGOldr34=ogTQru0IyAGfyariNFL71W5 zZL+qHC+F-eUvOMBTgxz}I?-KMi8$cEO}NUpo}RTTqdb$wi(7rxskA?Z#@jE=-3H z#QMTJM+(nT!)}08nP9P5aa$ZbcEJKX3vIcO(+o16C~YIW`jU4fo}~3AtNBY-p=&f) zaO5pEjXt+3TkfOx%cS%aHkA6e7%cDJJ8xRZw7jQMu@svY^yTgc?GftZ{bv@mG-S~1 zq~dd}Rix&(v}Nwda-CziNuBZB1j^G69kR=B!to%K+U+pefQQK4v@+TPKuGRV$Qz#` zYdarGBSUrwZ7Fgq>utgZRrsYx?MW1cTVA&w-p9ot4~{C;^KNbAeyY8EX^3|wS9;`> zsijP7vw4>W%Wc$k8;?})*T-tdd(CFJgEN^s=XpK(^6^%{4I+EL$#1!ORt@R(wE;~s zc6v&LcE60SoBhLcS$u_SlzsC0l>YZ_Ymxq{zJv59`D(QiF=9Fl{{TKJM+~8CSY^1( zHyN#h8bCsRz&58TAQRjTHtdM(4d0N{f5uo8+(pF@0(y^g=UUe<_IA$SD7LfY-s?Z8 zP(|anJ3_VGr5_FvhzIXGRGnQ&lHYn=yt20> z$&jVERD40-;rj-_cvp4NUV~h8mW!^PR8yu50~m=g3CIzUk(~G}ZKbwZv)(C&;&!|v z*u7~xq?D*^M|eH-2Is)nAp0BY-dB^{=4ob4l24S(Xeys-eZ_^m+FMW#Fjk;cQL=)Q zx#_VT#BJ4IAi`f+5+=y=?If#X_YJF`+q)L0NAF!_a^OgU2pWT-&OCS-S8VSaeMDBK zGWe~7vSTneenU-<&sZPJSgXHTka+x}($AB7tp1>AnzC8hJGX3)xbadsAJTWM#38oE z&9;OOB%}}ItejMb6XZT>*J)tBvSRQ8aK{<5QscfLnuwmxfR*%u#i%;pfaT>1JIqx@vR^}Vyqj47A|p>8b}}$ z$tq8J72rihp2e>1uZt*Y- ziB5$b6$(j06krl|1AnrB1w;Tl>Z_A>ldM-aBie2*TlC(`C<~S0sU3;PB{r_HSaWg4 zA{I7r(06e|f_iREyIW&&ONqk9>UeMii;j&O`2a|Ytx-+@%$*14aNd3}6{tZ6VhA)U zu&~~)T3dL7gyr`)ZWyiI-pdxrWIPsIT8SyP%yYP_yfX1SI0pqDKyPP|Qc6?^AnZZ# zq`S6ttA==*?Aqa>9|ANW<_<*^s)~bw8jc^G$9gIhC~hjOsZgWdiiIShVqSvWv`j() zDlDs@{eP;U0b_3(vbuPHe%grM@xOPF+Tbh{%6GFvC!tD+Ct!QF=xa*C;jETBdaYiZ z<`$E1d#Xc=i>g?VNF3<*6j752{6DMZ~U;EC~U)=uA?vNy|%&HZMNeSj{edzh#z>~l->Ty=>S}rNj?ia zPwTEXtEm$UTM**5JcVQ2B8l+chEj<XEs#kSY7W=@+ZV)-61&4S&0NU+YOYPWTwuk+r4{twsAm&G}?%5kK*~O z^Nn1kSlbppV+;^?gE9#2%a@9U%oPD|9K02KXt&4qgVUUT`H_dM6XKw$6QL`G#@lyf zmSioZ{XiW;xghjfPRbin;paEY7*3gLJXo+V?~cyZbcWw&Z^#)RO-%m)10)Z|M^WFY zQVNsYtU^h^HDf8k?s5;w`AQR)S{_$KhlxuQ+CBjGfv11z3Gu1UXNXvH%x-2al0ihM zQh-ndl&PmucSEhLd`-BMu?tDuR;|U&DmBF#t($j4nHsP=KFsR|TIQUI2h}h{6zRR} z#M9du9&yLNP93t_~meOzAHL zDc6yO5z*hZZ{OP%mC_? z(%lML$Qz@l6SrQS``15vF?&_tH7g@;PIV4ERo*1Dw=I_#jdns(ke*6q#>YUuAgL-& z!0{lB>!f2dDHqF*@tgrn$57n_cqmpL-L<~imv6S(EdjxzBh|w>VbBKakOBTm zD{q2j7kxm(Y}uq)@Yol4h<(OPQ@aoS7n(hL(CSu`_Ez;#FNF2IvIgD|$Mc%5uv_*& zg=d+z3nu>nYL9G{B^h0rYn;kjOH&CD66H2}fp3VkY#G{Jkb865YFkDjBF*a$b1^snT9 z79s@?lkQ~o$Yg(F!(;iYLgue&D|>dDtA`F<-|6gAzvx|IyRNz{!d9m>YZBSBvbfzF zR>yt5sWl61@7?OqaqegT0IG4TKx*;l_^C*?&O36*XZ^`+e>DSYeDBKray$7gBmV$~ zp^Ns0(6-bNb!Qw>ls5qnt$UB`Qfag88KL|;M)evC>EDmcMU>{p*NasD0OZ}Obhm&1 z09pS43QtJ3AMXe651N{)&QE>b=0}I_jlY_c@*@OnsrMy)vX$1Mr`iii)}=PZ_U9o< z8|=nEaC33#vps0WyXpGUm9rZ#c`NQySbfn$%fGRs`8#S4_YWVi?~-3gw)^AEDTl}~ zkJ5H(kF2Ht0G6Ou%SFCMyJWGHmsZ>yLe7nhh3*vWxl8l%NUXox&lg%&W8ylAAfSLk zFxkMLQOP2r7}lvrb5-+utZh8h8D8;kvBgU)QI)x~9e7jxKzj*6cZ0vS} zDOWFJhhr(r?X!;}R5feRE?@*}@G9X>IcMKv5UX?bomu{R!n4_Ja$+s_;I^`4Iuc1e z5*9!m{*@j&wQ~4W#)#^R#H(UHL}eR)Z1}7TE~Mosx1p%|5vVT>*08lE14&lvC#NYD zNLgQCGC<5~_KrMUrt7HU%iMlSdMp-1?X|8qPc-jJ7JE7d!q*qC^q^`3dezL1!D@Q@ zd(n)B*=>xDawXg^R=CZyKRBuW^WX4zZJtakvRgeccvh0VjZIpK`G? z4DS=gE-A+CnE1$Ymz(VwvD;d2wn97|lH$GytdAEQBhV<-b8yhB=A&I)&o7rNEKH$G zVdfc$ZA+FUb|k5`WHhiBaXX%`ua6yv0k9Rg#H^XtBL#NLMu9?(!>)V7q1@6ti-$b`8GLis63LK4z=g{2Bf zS1rIj-D_`U(5}n6cAGAv1KvYR%}Q=06Pz$T*>A!moePLCDcn7dr0TaIz|I3)2blvt zd3OS&xYx9{lG~h>V$W|D?0+JH{{Xtq2zbwg^1%FK5?e`0X!sHXP_Ok=ijPD$?Ulr_Cx)LtaXt2GSCM5(jV{1$0-j{;!Mz<9)}nGd$wYM@08z zGRmH2((qV8kmye3wp0`nwE?gpDm^zf)O4fh;+3tN?{;(CUf3mvFmwkF@OOYqiO?B| z&Dq;|)hdcLzyR;z-@C^yE4qHohuK>D^miphX-FiP@1fE^G#!uV2YSqpCN#**&4neI zF{CyfW%gDU+o&UOq^NEb516fG`t0NwvYCUjr8N=h3sLGAZ7r!qYW*Dtjd=AOeP|6C zh1I$+$yCOR5_F>&du_euBIS2&ziRu7->31aW=`ErBS-NFCr9vL7V{1qdJ5OD8xp31 zyN`F$@Tt5Ea@_&MzIndtdZkWnvQL;Eb4tJavtQxzR4mq z79>S`-KPX@z51ce&iqzCR{i3$q1_PsrW~e>X^9(_ly?dqXg^q1VOIV1k1`Z#?FOWy zekXJ-CuJb{r1%=<-CR{Ct@GYKVQ2@JWlIOD73(MoZ#DS_*{ zm|ov)U)juY>!|L1H)r~>Q{KQh=AgJEuQMU7p}K9c zxbC9kgYm0KJP>&A$yHxvZ2J37=YJ(0 zYSnSL$@iQwiJ3-{W7kx>6C9axNOtD)Jc8E&P7U6M%YYWxb$nb}4->Ee)qd8R;*CbU z+wn|Z=Op8>qc<*FE;hCn^!1avA8kM=DWC#)9)U!jy$xNk^%lhJW?|ttR#AD3!)RtY zZPrD`h}%gUB`I=8W`K|e!)@!I{iE>Q-r;vvs9Z3&ZBS%1%t>vD?M!u*pb(c{?1sn+ z8x8k8M^oKiz0qb-e%Yr}XfQR*1XIM|9f-zmY0cFgnJ)kmb)L>1TWJ@IM>d0`7{vg@ zayyzhPM$MJ9;0K_c^d04VeLn|KQ8g5_epAc-4+qL+Y77d7mCuCngsVCg{5G30uJ5v z$()n6{FAqy_0ay#I$2|la)XlVJ{I@LIY2hdefi*`DyH%TCG_qTkm^B8N?*XWB}2cB zcRf0#X^b z7M~}9jMT9u^S_5?&ozFkh_7Gm)(lz8y{iGvu8?hwa(;4cMhsLp+61Lsf4-oa=HYKibrFYFND&&4IUV{f*0=p&*UsB8 zY|D$9cA}G<9OeRC-~<;2`mJaq*8AmmcOH<+ZmEa}2SLD2r1Idmd8RFju^HKOmvowfl9E#uqx;%FDE@K%LbBdri7Y!7Qw_eu?Ww0)bpdE` z3L7aZN$8}INFevrHd}hNgMA)xQ#m=aBlbhFG!d-91mr<%g6h^xu5sw?$A64Lj~^qY zYVI?wHpO#JONwT(`YA2l8Gh9Il6;(wR>|qNUK<*9jQ;J8fHKVw$!$qa!BXtavw4m^ zcVPCweW>2C+Ijs9eYi8;dPbW*;BF+hR95CN;*QB-yVW^fI zG03nTOq-?lo4n1a^mJf$>#sW9jYFRc=Vhb+MQ- zqQHCoEyd#~Jq9w9_2j%qDQ=DS*lf@vpjUlmofX0(UnCq=%kCoRjD@9m_1*Zbf>eKb zB}|d|ya20BxY>3SNse)-2JqU9X`UknILk(sOyuxoMS|wPXn}@#w4Z!=gWR+5EC(0U z=jA1Fc z2k~8CwzRfwdY5R)V-QEWdm7JKGYt1PshEhhf}4eMiz9orJ}LOBlOvWPWfLYEfj88dB?*qa$?3QN(Q zOU;mcLXb$Td(EG9v&>x6b9fR62f9XT-756!kE?J!##Kztmg%f%+gmXhS8KTVmvFnO zcLlT8s=2;a0rW@$rxu$`dqW9nletLPaY#ZF=#&nLA7G|zEx~FZ7*29t{{TEl*+G6p zcx_uP&0M{|xVSxCSGm#mi6`5j;1o1yF@jpqJCgZP^7W?qQSud?Ds_n3kIS$4{^W1_ z zK{qm`Tyc5r_Lf_ySJNfEkGi;bKRAKJpQMA|T}2D~eW6C4&$?^|j>io1^`Mr!aU?pK z%38?AGhafb2Rgkonb-Wcjv6>|Ix9dca1Q+x0)Yo`LGB>pdy}%*M~k+(B1U-_kTo+N&oNwvExf%N z;~Lp9xmM{YElP^#V84OZkHnMYdZk_j)mx2&bzxPt0K~vSCwK(qhyq|8&IQbO*>jYv zy{B2Fx*Ys_kHEnjz)RAX+qYqu%9NG+vg|gv;ngc}(#&KlkDw&}n$ha6pxW*dq~OWD zzizTH=!ucsAcZ+DONtwdJOm?kxhcR$XiyJi+3G@-(lv@_*9?yUvT1gczZz~*^@Cv(43w$(S%-6zKMwZ@|&Cve8< zB}(J*FTF}tYur$`*_TysFQ=^p;9&KLpXm&O8b0iTPEW#GGpkMSxTe}veOS!y{ZYrC zl+K3-4#=~se0zRd1mYN6c2OQ4A8&La=@HTZLMOa~vQe-fx!OB^T9kJc6}{>9pGMY+ zP@2H(D24bX9rZBW2Eqf2nB!_#PTK{Pd_0F;fUC?AwO5bBMaNt&kyfrDSVS_F52~SX zb7NWiH*DNF^+<7J0}o}SfF;e7thn)WiE-X^Q(Rh80icO8Gd;)1au5_-O{B|@W|fEC zWG(xKB`De)ac6Zs7y!7BALB5j4gAn*Ufa&K)t6R};;4#=Wl*Kzx`SoNWZpzMB$0YhRa z+1pB`%~7)5RE9i&-Qlhz48f3E@3bQz1BPINSGR38`bcPy4Dlnsh~39dYgwgO#;YR^ z$F5Ma+;(rvF&(~`7u!Pq70GzKqNx#<6s0J*71bL8cJ38mr!duPNZTOMxwk5~@S!g}y6ziI?Yi=7zb#FQi^EnT^55ZD8q3v5?z^!65 zEK2?OO#c8@t#_ynK@6j)?VM5EEgOOWY@N@D@2WO0wzjdeJV})g8j~A-u|2^}r)~R# zfqaMC*0g@sYM!ljzI*X=fQ<-6<-XmAVmR^i<jpinc*3vNhF1z)M*|ldd#eguS4iA! zq4bcUxk|Ph>_Gs7LtWeL^jA1QMB1E`=cUs1}&{Z&8&F;0JwyrRP2)D^OjppxdGX;2>_52zR72= z#Nx<8d-w3IEsG1eW|v0tU0r%K$@b@iu(6g2RkG5*s^WqEjDPxS#{{3Ck>6WQF~e*) z)ru|dAH2@H-!1VZ+-~#S?WW7*$`q6-eaUSg?3U0103F-)sV%}1-%=(*T3eXN*$O?& zPl^@je^qhLyrp9{)`Ws28iyxy4g0M+edC!V0$8<0CsvA;bo;x!c%Ns81yg>~zRvRX zb;!APD>6G7EG9kG)}=3lQ`iKQB(JXr5=yowp$B8Oz}GrJ_C?ou%Y6@H5a9+}TEd^X zvGd9UaEADkx|Q5F**opI72c31H#wrTrA@lo;_FKKO{>WN05S2boyhU6-IMf#wssYu zu%(5~ZkT~3qktfZ942{X3YFHww7jd=Izc+Y*Vt4$M%=^^rq*kA4kR~iwX|S49$I)x zjVTA`+K`{B@L5q%^#pDRH8kXEzRWSp-aUMKR5bPvyxi8>QGT+Zgr@x&17M{1CSz&* zTK>5B72rg(8OiK?Im9hBPAdZEOq6saeuAd2R;i#pBkVq2 zhoP$0b_IFDCcl5vIngZ6NN~L_-0NyKTZ)f*on;+iynlvdaVM&fYS82;_XexlZ5q~+ z8fB+eoWUHy%e#ThL4u!FgDU2A8Xf8dZq&P8Mh-W4wm*(#EUBm0WuBy!ynwO0u^vys zDX-3K?mn_bSSs6*_(y-0Li5}c9m#ADM7qF3@mf%jmWe4-4uUp%JBitG-5->KJ$I<| zT$?)6l`1w!i;iKAusIpcvK4%{H|HUg04B5t@67<<9aMwgS}wJrv?+&JoGz2tC%e>f z*OOdlKpgiNPzsNr=&hLIJ|p5XljC=Iw<~P-FOeFR72vkl$_Hpu)44#k)hA*I>H(`Om!(zOO?5hU z&3J+sWF0{jYKT5cOL83Qg{LY10JW*_d3=Ae9{$|_0G5nOlm6Lv{;<;=y(n@Olwn$x ztjV$|@S8+9lO0R0q^U~_JEcCJr_Q1z$dkyaD}QskijmKMw{U-5BUo}+*gb}RX+47U zco2%x&{o~6yttvg{5dIUA7M4YxgM=*MpJTK&gYlh*-=*}@YkMgdmQC%FaAle?1dyIatr%16E1puZhwi-~p{ zOXI(Y-90v`W0kd@ zE-UN&JEL=jt2?z95Tc+A&fMhs}c~##{v6Cw|x;=M@ z%eIzCgKkj*J7`Hx$Q^@dQQN&v6W>!kXpts%GYDR11?G{>C~M zH}hDjTn1QG$-@nlwG!CMAVq9_6eIz-A7BYj-Bsvc-C9^<61<;O=n{{a60 zGNg41b!$cu1D5mhAH=G?X3||KwqO#ZT6EVJfhqfysxn_<*n}(xXR?J2(l^+pL?1$G zWh+56QBs^*bkfS%N|c25LDu#gp6)9fRz9_bJ&M`kr7J%7mgDUFCu6t2Hm|?vRPt2F ztvcf0lG9+RN8@l0AGVUF*4hoQqk`e`T~kN9NTvq<^fuaotm$KPZsEnsx@SVPg*SI% zw}iHanvUC>HFBDQR8)7|rSBnJHLe&e{XEqig25&u%|&uslP%6HsX^$DsHwFDsFJOL z;&&wOlf8NqJ1WO4PhD^Ko5K;U(IKJ7r5(x}Z5)cpBg^%jg+ML1O}kkA>o(Oa3U$@H zn?aimnnMdu$rjMANm(IDDU$iw#|S?PN)g~|s_36euQnxWmzVB+N+qGB7ZPz8BMu`H z1eaz28ksEzGimJFSBz~c05$SXLw7j#e5N=K-DkRFR|wesk`uEoH#Y=WvD;4SamDli z4=%k=a2u0en@nogVZxZjG6~QHvejl{J?>o6lNk|OWhhy5pTa`8oq_P%Zo5=8S#*cl z;@LAVi-p;-WT6EMmm*hfEkKXOwicy0N$%p3H}UCEn3>)amd=Zi>SmzhY)4`7Hra@< z;kh^-??kESjuw}s0k;N5?X~&J>NL7Xwd(ZD)30wu?YE>%5HUT@F`#ZClnwAETy$x# zVQSry9LKm9yTAd=nAh98c}CtVm0c|YMUNcFt{OIpX?dJQ`CKm3A%v1vz*y})$Ck=W zHLNXZ9S8&>r&@cJVoho#)Z%v-YQ}RrTX(2ki+zeB+#rO;nH^1R3r==MI?H4NHa#i# zwR$APE={h_sW+r{iJ=-yD$LRr;Z2%$t<0YO?7R+;ZuUx{2~<-LDZ;nQ=($ z-}ktzDN1o&n!2nIw52Dh*b2y`+PhS`*7yoo!E0r~vf^4>jjt_po?IGTPz*#!Eg+0G zoLg-bD4bCQ17*w8mjV}-lA#+!5S?$9mKIq zmPIf`kr^z^n) z(-zHAgK%fIve@++?(DS}R-0xn>qsR308Nk^PTOtVYF!32TYN<07T9s2DKh&VqcDbc zV0?Us05<8c-nOngKGsQb$LB!&>_WDkF4QamX-09zjlas{8k%b4Bbo4qt@1o_&AjcUmUo@*8Phot%EnJ5=|P zq07#r{#m?fnr;j{Sk|!lv8^fIi%9yyd&xfXRXM4i%zAl~U^ja%8H!sic>}0J&@L{i z$6XILu0gdt>XfjBjnaDfgW+5kkzv_xBXE&w$}yXrvRqzWt8d0w01ov90mVt%uON2X zy;F}iB(+FHNo>5Rj`k7*P6ZZR*d7Z2{3_UkvDoZIBI|8yAhb@$W+6?M}29E}b_uyHG}m$;+cckVX(X%y791elM;J$qKvkJ2g4beJg!SsNYp z*zdm849GR3Nij>QvE+J@kHEMkTo)b5V66)BUl2l1p(3E#{hMU?28P@+40`I~WZV-T z+SAJXY&E>1mAHlZfD{J(#Dn2pdV#k6`>2wlPUS@Rl6%SZHR*j1=o>miMfX=VOIRFQ z17b-45=`g>jG)eWjr{i1+a|^0l{U#DbNm;p*!=iYoS6Nxba{}aDH9_<9n`qfs95S! zq>>2TC%U&4!`{O=rPAN1bFdXiTJ2YBY>9|qtKnixB`W5D;mIU|2Y$fUaPBrxON%5C)VC2jZU?)0;~+$DTMV}{%=PJ%;~FCsmvGg(rf<15!N+1nnv|WF z4lzg%ID_2c+XHZTg(q@qXJye#Dowstb#!+se$)^x@LXXrEY{GCxF#*Vv)QMP!(}*D zuO0WP)vnoXvRfiqZ17oyZ-B~U#f-{$w##W71f+l5les->NtYdrt}cacv!Qng~+EtuY|f@4f)i=O=Nnn3>%dSx%8hV-5C;2!R{65o}poZz@z7ZT(2{UfYvzB_IH|5}sgnJb@<8!82HS#f?`s zVm$(MISxR&TVFpt0%K=ovcJ@%tGP*1R6FZGCC4mzg@$%BlH1Wb*>P{Okiw4Jj_{W%3=yS9G4tJlWn_xQ|xSHMD-*s z1M;dXt&fG^S=1E)649faIJ9W&@`jS|K@MX`AT2wo6IHllUEp`{@ymfK$e<3@hiAOm z@+)IEyn5{l>28oWYFsDM^DFxll1BcW^_f_X4r7a4AdSl4%t%Mdw0Q;M(Pfs+%KdeR zZMWN*T!-Y#eJwPCuYo(ML=Defhkb7M+pT%~?U|ZgaxG-IVz0q_XPB=o1!}f-6Pr_h#iHYrNruEA&}x^cWb&9e$>4d6gtu*%mL3|g6(Wm zSM;+vo^4o$pxExSy6qW>KRT_u_@zJ8I0?&0R;ozv9YLez*QFr{4WTWkxd0@6)OF{0 zxGmJ6Xz4U5WeHWre%cxvl0A{VtT@fR+4k8H8%621Q_09~GM*vwp7PR=5)`5IN_zA= z*JXuy)p@ULa$cLii$+?LBQ3bw@wWJ#lv64~67Jt)DRkFK(LJqkvUas9v?H`bT{>YT zPnNzM7Jk#VW!1Bq{n?K3r^$KIh-ub$p5588;#jr9bhn5b_6tKT1NGc-KqUzSsU-Jz zZ(e}nKFpd?V8m?JE5;=Pb=CwlGcM~LZ$*Af=W^o9jI9bIl88?IzzVlqSlvxBN?8YF zAaqDb=#jUe0}XlyD(w^G|_>ACSfKzN@@mAhLWPomc!+aAWeH~y5dB9*HB1W zPtx5#uB^7=!Q<;G85+Zz0BfcRG5Xu}Q5IHjs^R2*e+7|_N^nCDTW#^~eYeEqm@}Gv zP7~L!5`6osXuH}You^waQAqFYBPfCQjp`>qLNsFH@}#|{76|XG@-M)Ss7~+vc+NtAmQ)WC-F%xZHy}XA4 z*1}JA`+X!4w#UMuw>>n}d5|UA5v|fLaA!wyEAjFiVidr(+(}B3R?lLsib?C#)j^GF z2Bu=rBx&i!aLktFgf}YPb$#SbgsXHoXg!?il0GJsmm5kHlq820)NRn3*rBu8w5>%e zdaZk@lOwbthcTPlz%aWObF0AM4OiDT^)Xaw0o~e;;&l<^r}@rHgG|)RoXqtsPsg!4 zGHfxe#yOVd*6(ux$CzP(xp~CKLn+xK;q8ee4x${qPbRQiZRc7o*M#XB9&t@rMBy#_ z5;-T`{#jZR&QxS|*(p+z&p_ueHw@`MH=5!MH>sEX7QbFE`5Q~SOuX9fFxK+|NDBy$ z6~!e{$7jJRNZm({#Y6xFlAU9;!(}esU}`0imk@p{t{nV^NIeKEj+YWgcsumlzO^cv zmJSxyBdu#dX(7SH5C|riVR2)Ni<}-tEaC%XIB!8?5rn zGdt3JqXD$Yj^x{wh1M-@FrfcS}o#YPI#4+QfBk0qxx~2;@Zmb%MC(*@I3W)O}sGMw4!N`z@!O z>$8bljU_CEDJVg41u9CGvXB5!J96*_yHscvjjr(QH*iK>Z84j5J*~=e&isfyOXsI| zO43|xmaPPu&TWL^6Rg;%X}FnPortR_5z~CBEACExogI*+jM)8saksPAn!dXOt7!EzV{w zY~7tk)|sPJ0{N38andJu##DU!J?NhoxM_uixbtdHCs6C1b~_)5XU3wsDWn=he9{ay z?{w1)Qe4?qRuufFtu3v`9O{)Ul>$lJZ&y?o=jP?SkLIpzyH@?y6rWxNecw;9X4ZgF z$u?NM-`aOlZ}tmy_vI{N?trUiQ*U|%l>Tu+Nk4BDZ}y~Cqem~-8{Pp5E&0w@%z4+b z>&E@&Tt#+8XZYw1xIGs|)%DHQlWO&jC58@X)qIh^bA!P=+BhPe3UsQL;TiJYGCpEF zijgN5#3Lh;F)V$gqjheZL}9kwcj$_S&3Zh^3U!-oCR-=w+iDw00C;b*dYZ5$H`~T} zgr2T8q>a4DHNu|Jn&Xv1%BE@NBXf~xjcM77jF{(x@5mq(HW%K6BXBxwN#Ap~O1sfr zm2=Vyqe7Fj0!can$Ib`gB}25XDcI8waLbnRx}^?Eh_sf}kDp7}3-)*tfH6rwt9N!-_-j@<#}c0i*y5>Nr;zS zSyG)_Q6n~%nm`t_NvxdFRJ?;b+rWe$%=%tY2b?^t? ze2C3O?3!_L$ZnCc?L@kxAiG9aX%TCW+1qW0{{Ucyt;a9@q3%u|@yEKXsF5SWN`ojo zmU%ehk0rQ#)T7jzc%A#r5hAm0_JwQ0F`R}#41gQ}5CG%=6P~3{N`onSy75A*FKD=U zz3WV7VwnpvDCAT{OfyW*%Ys-PzuwEsPiTno zV#Y^lCTx|Rn~uUe*5c1VJO04_&gw>?hFr^Muw$_qrSoFL2}gf(j(k>n z7oG1~x_uA@TG^!8IlD@sATY~gBIgJ&ISh-A;R8|>cOP60xXwm=f%)zq0Jy&6?0K({ zZUPH9&ANIBAG)I5S?o94uH|Ja8y=lUhfV9K{hRavXsx1r>)hD$@;llP)Wxt0n2#MK zksSn&j1qTRh}e(@+YYxaVcci?no!d%_R^m`S!jJ@=6sSoO>XU;p6z~+TKWxaEoq6u z2*nxZNjXk=M?94)U5jgDQ?6u_x=hD*O1QaVH~lr7soE~vFUOdrzb^}r=Ll{S)lWij z^oHR*do%M(J_$W*F}&Luzv+Hsak3KazT&j;SP6 z>fZ!IO5J&Vw5QJ}oj+LqNb*SV6DQzTy&~Bs9c##&%v+YmE?6$l)O|y+;ki$u7P~tZ(Hb()r(iWRzuly~|N(Uhs z;mJf4roMFk^Dx-2P8j4Y%&?IrH@1x))9TsWH7nvZcsaUQ;I^XJIzcXtu6zWx3fd z2Z>8zwGejZK^qW9P3zCf=pSMea2mxlMlO4yu*vtpH{wS&`#xXU_kH!~9WUs1#G$gP z-cg_!>O&C1aLjIZ8S|LWRf)3pt>f-nNzWuW#(Bpv`HoAg6_jfY!>^cpquht8!QHkW3f-SDyZ#jY;vsx`|5FL{DVY-|o^h0e8mbVWYm&O9mdN;**Q!COg|`C4wC}UAVpbTC znS$CIY5u_mOQVP-#~s^}l%u&Pe)Hu{XpNrVD-^e7DVY7vZbJc^o$lu+Zr0;1*1#>S z4uQ6k3KD_QJ9(07OLw%o)6K_9xed6*T949Ce`KTJq4i1c9V*rC)T+n-04AD*wUP|v z3%8ii0T9sw0g?lYlyq&HN2FmA?Kuwr0D0qVwpXI1lqJ>{xvT4IB zP)Ob&2<01?QsD-_Ie z%XHmC)47a$yt^yzmoGNPJPcP)Sxuv0QiTxm3PD1Zuobn&Vzmoga;S}O#jHxi0X^sU zM7_`Hs~5-Cv-Y0axTSQ$TwKf)j5wT*29R22TH&a3NIYZ`4A%CP-AurPF3fkFj^a5L zROuF^#+LCyoV#TD^&DW$EnkwosQfd#&Q-MZTBy5M#*|heOm3>9UW47{NUMX z1dYfgCJm|@n)BLgebY@wW-XUKfdQ~sZ=^fB3U#m~LSJ%%fZa{>QcmMzwQ218YkTdZ zZ+o++5qk|rMr`d4*Z@Ik%18h-=Y@@PTH4j>)T2T4Yv~6Iw;Z(3e+k!#Ad}M0eKmcw z%kQ%phh<59rdVMG$6a+{2yFwYQj~fm4>QuCdG@YYu*vAMH5N_c{V0(V(FHu^NF%(2 z{X`EJ7sUh2_f<)eWY=7p{N1lSwXv4o8{+(I2Gb;_)o$@a_gN>Sq;xwk;QWTw-(2p^ z==Ig>MOvMf5j&)TyaGUuFL7%I_e=wRz4Iv&(V6xR{d432Je1=h)Y(~umg4RiD%)nm zT4`+D^Lz6kz%7tcw&6!}Z+lT2r<%TNbJL%7X|zKgcMWeLnTzAo8pW<{#%jrQzcw1Z zOmHM4iB8=8&tb`+u!5eTCyvIYc&-YJf^=-dQLeG_JJbRUC=D^$T5N<4f-Gb7mIqGV z&D4IVM|Dk|Qm5Y%l@g}lC$w27eco8<`UhXWu3YUadpZ?1b)$O*F>4tRMq~#$(g4Im zSOFol^TcDPZu+ln#Y33W$IJoCGo-t4%Y_vXVl@p1aMD6|^FAlK9Thw^IWrDU5K6^5VS~$0cTNB(5NLnYVUabThmHIfdxmYORa}mSsmPv zpi+L_x2QsNlS?GdAy}(e-R&eO$+1ax*r!i|hhU_kTlpy_th&?WjX`=k68g535AJlN za$3)p!>-=G6{TaeZfJc~DvoGBbdl#8jv%V_s8fGw%?G_`)>)M)D3TX$xukp!B=m54 z9_5ci*0Niwg{@P#3!t_>%5AvxM?JWzTpevjm+oyJj{Iq5}Pg(!r2Aa{PM zofL(Ff}TNN2_Ad+RX3Ams)c@+hvC-rU+=M3#pDNgL$?Fmsc((K4&S{ecMjddzP5HX z2i-W+V3GvRfHrfIasqdgy;Zw-qyah~f5j(1kA*p467#nkj}j|^Q6(j6Zc`7RqwcFm z&UUB4DBph&MN(RgCo^0~QgY~1L#7P>0FkCLhT~tIj+B56%d#ph#|cp?2}$I33gAsz zvmjgWbR#&+Wk||$msTQMf?MEKAYb+o&DRfZYO} z*G`HAkyoV1jL(hOxUPx;#+*UliSJ6%M}OKo>XgE;so4GlV7+Pi7T0c8C`-E8ZY>!W zJIPdZm8rxhk&f!OJGg!UfaL7h?f(lvD%Vwezcldi8Qiey=hq6Qe_vnC=vK+J|X;;^ml-$2|pqf1;EWF6VJ z*mWP}Hy=dp5^jZo# zsb}mPZR=cn?RkUB#dDZfc+Nb#J^80)G?g9F)a}wGY&;6~ZNv7O>3S1kXSMIB?CbN{ z1k`BaKscNGKs5I-#;ZST*Q;wq_2}#Bo>}C4GxJeLy`^=<$szW0=`j$UxJQc>hS)$J z0UQrcw4LhPz_rf(t`FkccaYmz`F1Wt8tmlA&G2GWNR7Dh=8gUbJ4Ch!9|;G3n^!DV ziR|-uw`2Opj$bb~nD(o57anq4xe6(14HlN2l%Nj~RHTj1P4=&6+x;5ZgJ;#RZEV)n zrq?-ztpMT;G3tpjcNvq-a#mYH;MZ#^R3)w;88RYs*G1VGlna%<`)<|D7TT$Ia>AIr z+M7EY#3UzjfIr(!d_|>rjnI3=PqF*`n<=tF$nojunQpi=9**)x+>_!6JyM`SN|Jhl z2tC!Nx6ZfRt}v%#w>VczbcqG@KLSD|Ar2I5p~f(G19H8vNltp)M`zH zNll*($+TKrDM#cRRH0^4SOmzj%4y$raD?}%wH30mI+BEw)m6N*lY;66-wSYXvt{U& zf3csc%;25w5eJto(;*|(-zsZ9gD%r!uxgE>Gmp&0BP}-u86Oqh(skrHgxyMCfl?K~U0+@oy4(2kSWH@ht2KBts zo6K3-b%!_2VfQiXdbp{jCw-$May&dLVO7S5zL|Pyr|Uz()HRE=l&mLUpcTL?Zw|;>wA-mMn z%3D$%l)tCuM3P~V?xFft8dBw6WFi3k`(IYS%*8pIRLC}M!FhP)z zMbjrj4o86*et3+E!?WHkSq-Kg_Q!B$b-LinWJQw7w?nO~xR2x`ybwX^D>ADq%t;zK zb(fh~A-et*ad}d5D@=418(YfyS170*&7Sw#AJq~!iA}7j})J1!@NsCunzry{_QlP`PI)g`XPdjw9ep3z)`zDUwzS0C`{!=JF_C-|$ zieQb$;H3LXlGN-S>}xo~GTbgqD?Re%khvYns$olwA;rRca+MVXCB>)$g7P+VijMn` zDs_x%lxde3xVCX@PBD?nAGum_@azkw-RgrYV3&WXD~qY_DLwRBs=mTTpUvgON6zg$ zk1f6xrS{M1Tqenr+T>a;R@jo_O4!JUDZX*tQ0`GrdKNpv&_aRg4Rn?7DBapy zw)_*>Ee{^EVaLFL2<)7yr8z1dwm9lOP0reI`z;p$D)j!3uyl*zN3+63%X5kN+X~KX~ z;|`Cc)6J*_wBS7o76BEHce7t?-noYzGxmm@ff}^(IT$!ghHBj_hP>f9JBQ1U!+aH^ z)*K!ENYgBX42HJRGJIgDYH;k4A+@E(!9D<~0VMu)d98T2TrumMi!J^O*4uCzQK(** zTcANsSED!Bj83=NX1xixvXC5Y`(ytAx+4W3^eODzQ-5n-VykUN(iLdc)!kRdEVkT= z#R0F~knb^3IdNlxK55R-6*|Z}1U!@5HP9Z>9R`-#xwq+F*&^lG9RO1rwe3BoL$WP% zgSW^efFQtC?J3mRmY-H6PiXH4LzZ#`a)rKDvS9ZuRh^Pf&Ftx86|;+Nsi>_xF;B{s zvyz={EhSs=N?K=^cKn4);A%5+V>XKpEva{Y&=*g*<4TIzQo5g=lw0xfsMdsDEV3|& zP;4{yyEWR~Z*kJdL#;gXrNueL2aBIxgn$6UdsmzN_7)ekH%$jjqc{d7Dy8Yl%bq@V_xK4?sxKzbPzh2lc&bT0N z@-44^E=oR5tnS+>@lh)7L8v_~_32yEp-GI8=Mr%^GzSn#4|Bu-+0c`q5tY9u5nMzV0+4Q*beH|&Yz z_EYYinL|kc?jb{L{w699cpnplZn957Vy@Au(k>k1PKW~oSQ^JaAPbHFFozM^aMbo$ zA*8Z4&}o(diRV&efy+H(aPd0l9McT|#jVrb&$A3Jz>D1E1jV(m*StiuvEC9C=}~?2 zzsB;u#DF{PuHFVa6gL7RF$0H-QQvsr4iX^zbW6vvXkHW%03Am zbeLB4GUBGpbU!h{vX{5Qf%zyN1Nm#6zuGrDQm0>f@b2OUYw$t0}`7WD1g zf{2bM^ZD>eVjP)L6LGw`+Q?7hnkwXm2d4e%^FN}pCfnfMpvkmcnGVCA!;vI5ezQ&_ zp=0zY)AggyGN!P6I`fyh*J^E17V|B5y&(K7v^>h(QW6KPQKZ_p6`4+#tVj(5EXl|Z z6*I8YVRN+Jsf{qf*QuuG3sPEjPZ>o8_4nYy5rTHC_d9At8)A+TeJ8t?1X0H&EcBq~eZ-MOn zdu}dSbxdn^NsJrC-fODeCix5Fn!i;z)8k+}%RmmMrh081IeiScsk+P*N=*dQ{0;Dw#{bMeOlEa*2H~W2s>mH?C#JY5Ho{XIn+ zZl?Vuw-MF^VwVq2j=i?veTm=n)wi{^A>U=W9}PUzO(nT+DeEjnwjQ4AU5e(bwCqPf z+I%{GQSTa*XC*ot@XJs|^;b!Gv@x=Wr7%=uskS#c zxF(-v*^%l@5a#DNXEj2N2y^x$q7GWB<43MG-;}e<4W{85YzX-huNL>? zJcn9xCB>9UN|2=VBed;@+g43cx6B4kF|H9E(;Le;TiIqmDni;~3vSzI&xZOGq{)rb zaHe11lVu775;y7$x;Csij!S0EtTr^_GTvBfJ=qTz>~ofS0NkyCVQ2U^!cUTwPTF<` z_M>jBjy!E^u>HJ-7aTuoT-OtTx-%{fO}DSrG}Eji2@|`B&vry>9Hm&4D2Gs$0t!|F zl0n=k9^wyi9-@x9z*d43q#e5Y(rDA&ODHN8mta<{O2DuBcY1kju_@KI+vppvJApyQ zT}bibt&ZL!aa<3mngOaBWjo!k_B2U~;v_pBd?%9Q546HLy61RO0#WfOC=G|5dOaNW zH_5E`2@JIO`$p+M%~cM*+_AQ^On$+WO;aeiVtG7vSY?MFlJto!M~=!HWwfbkQn{>+ zhjK~o1lOZiafQ@jbK4{D#>`E;z5vN=d5U4ZLdpt7+AeOb?TA>r9(F2zKQ63x(2ajkFN8GSA{; z0^$|2oGV!TfO=HB5YpT-&Dmu(_GZb*<+hg|$Zj%~F6QEh*n;x@ihKS91gFxoHs!&+ zynW8usWm~uTTTo{Kogrwu`=&`WKWC^1{NsDwO2ajxNkKjgBVg2F zyvYbAwx!Zx` z$Z6xlQwbSPMSIjc{OvZtZmz%KolTG6|)sGIa!^UyA35RZgf`(fPDp4fv zrxv5tZYYwaEcH<&n%MS@2U5bFLR?x@q@_y$l&9$)z5{W&@ZYUcy1HxHE$x96o1S{O z{pWMwG-$|4#OF&&nFp+?n~$@Q{N{WB*W}qHmk!70*fX01oNFb9WI~h0 zd!a=RuvS*eDaqIFPOG;WKN0S-O z&P*vNJGUW+JJ9M6P)Ddcf<Jtd-5+Ib4Mw5Ti|sgm-3aYM}r@cT_&t5&@y3OB%2WC(F5)!bPB09qqP znE=ePK>`-Fj~M97PVvu})5jur6^mugu;N74s@eV_CwLFJ<6p`^EL&Yip zispZ5PiIU1a+kGt6A;lYsA_uc5%~eNNoAy|LP8XN5Tv#?DI{_U1a;|GhGnFi3TkWMj z={bc-T3ASa2_-J5g(w2Twas$q zv?=w}9LuK20%g2?^D)2cd~Jhv9qn?15S=wfZka?+H?Ds2wzmci!Y`!^7v zkT&>+Se8>5D0H)U8*wWW@klHx@21nJ8uTNL-v(E+s`aFVu=p+b`Ltj3P) zxi7TC3?R7c&a5G}&^y$n9fCmfCu#<0O9OhWH9q}gfvDIE85QakvejWt&n%xNTj*(~ z!wiVg_bTx&$s_^P@jFxJRW8}1i-n?=7O#w@MYnQ$ky{ov2=DBa0CsAJ9aK zDsm`_3wa-v2HtjCoA@~Y00el1sCB`yri46(WD1%a-GlNMiPu`%w@O>tt`T4|>u(6hD%-RDWl8Hin72NGo!K}KLZk@E+@fmc?tl6cJroU$!2V3<;PG` z8~}~H4~RaZoT`RKMMyJMjEsjZ)qBeB+W8Ahu56-0j0MnTYh#d>D-JpMthIv-R}yl!Z2Mr z%d9BA>ArEO7Fg(UQRGBpB$Lh)BVHY2Fq9qKO!63;ZZIJ4=st5#lz zjNM|m)-3(v#TMe)B&9`e+nqbMq^OP7&lLbt6p#T<)i}xUOQZ~xWx9PjJ*OsG0vlY7 z{l@ajdi#v@j{U!Se0<}7oSoB1HymSIpi9GV*6G$;tX7uA+S2lZZWdW-DO(a-Q0SnQ zfVQ7U!mYm1TlBB$cB?`Yf8+21`zRmX~B)9Z4TEKG5>cMe;uHnPe@1q2ce#Q2zR>KJoVU)u~)kV~BM#mLLcr@Zp8c z5J3gGL>$JEBWSjzMmdfmL%0$%8DOo*6VG$^3q*m&u)g?5jHZ0 z?WM-q=*&``zH4&hz3!`H!mM3Dj~gn_u+1)FGXmDj;_q>ahQMVoE#WIeTma$_sS05L zcWi<-t(}FmExIYSTrD5g_D@(eYd{#B0nE9;w6U-lqzjIU}_9Gh~7y#46=cr zGrvKfo%pUde$Fqkv7AEO<<99bVGOdsglsMnIxQ~m>LZSGoK4vD`dB3;&FB-9|#B@>LjI0P)ZV1Pe1_cS{%nY#xO_> z&dF{irAu?MgAIn1{K@ZlH64U*w^n@y!^DBspi*u5_NuLMa->Mc@#XY5)CqiMN=N)< zHM8?cJ~5-6{1m?m$9$^a8vuod}Rcp0}14O%r&xgN&D{X$& zg-x9&8Pe-?T#oA%&v%`1dD4qDrG8Q*Cvx+nev#Sav*J_VP~8u}+PcWUXW0ydT(T3C zUK#mWXem+(9u2KgSR|quQAr9?cW{6KS4y~Xj8fBJX5Vjx1}iK=C~hoh%q5u7QMmY8 zY;;fC-h0WYj)SPbr$%vKGczE+$>qy^!mg5do&Nxe1!{}xcKbfFY#W4PV@r>$;6urt zU7u6{IN~D`t<<^o*lQWcnA{>gx$ET`DU8U`t@c@sMQApF!-VbBsO)-^-cPM%Lr(~C z(fKT(f7um_e>J!d2Yp(!2-1NB!r;!Qa_$@c|(2y`PL8r5YPHIECh zOv&;|qAk7(LY!DnC9Op4hTBN$w-%xjln$hwt0wh&v}1O8xdr~>#0zb?l}ME-^>IT@ zj`C0H1H6&|>S_my*{<4&e21y|&AIqcAgH~GHhLsJ4!dg3wwWRzBKZ!y7qDZl6 zY{#_RTxChR#;!kgyw3BZMrKSk(bcFy>goE&*f^igXD%c?hT`|j{rRm@)M!RE-7TpW z8!DB_76_{yO1u>@!MA9?s6GVj0VaM*M@_(`k-u81GDfafy;r>B*9`kFv|6F#mg|f- zRx6TW_O7`h=3U)Evb7RYj~xs@*m}1{6X!-F&}Bo zh?Bj%b55gB)so=^7>@vcDI{%GuD@KOuSl~CTV=(*^d{n|DP>9nwSAOqPj}a=GOAB# z8>A~Hn&mTYPA#}dn`CxLLOH9k6IfFCr^M{p1J`PkU^A@Rmf#6E*%A8f9~8^iVYh#A z_Y}QTxUrgKcAYC0=}WdpeT<7F=FmxQ9CwGhGB-a0pOBH)qJkP;-St)bS?J}C-E5ht z@kxztb@jP2J0$?bxIBIr*-D57r+0*Il1MmYopZ-BoG`83!7%$(juVkJ|mW~CM+L`MuVp<@ zurqGB+-75D)pMT)3s)HTBKs*a))LdCB^QdTwI|6qkl6-^qXEeAc1n&7bB<z@U%*L5O5O^N&u(J%Z&4Xj| za$EC`2=U5WZI)5Z`A@9zYC9+$R_fKVQjzDsa3-Ppe`dN{CSA2NIEu=%8?(uXBR0Nm zTH|h#+y+mM#PW4x>92nPDR^zZexu|OHCrc!{A*-hUxD^{3F4+2SlHa3NBd#a{w*h~ zO`ioQ*5&)nj~z7|GwrhMzPPm6IGTV-oxKC0!kWR@S8~4{J7%b_&%8}#A>)!c1;?8i2ONcsmqr#-)LN;Ml5gWz8w*$_nQscRrtua2&}rw#{NMdr|n4uRBu=3 z$GYmKTXctWZsQhp+4(W0wZ8|MS>3fHkO@y6fIY`(m;zaH}Q16S1|1+5&_d2t*j5^(CaBaZ&YGRUcAolf8;?Dc9?r z>oS!g_N~@cC98M`;Q2|$%1gj_sSK#6f9bW>wwW)g^C^jKc2BK_%c&nktptuq^dN&? z4bwY2O_h0DW>_A}PBJ(b0S)FD(qnioa4WOfHEP{bWyNhsf!rrQ6YdH(W&Z$Qth@ec z*#7{=CDgj%CFflhyLz^e;(0jdgAM*y+I$5;qfJ~*cFR*8uGUM~t&I$~;7`0u+@C?- zYj2Te);s}hSya^9_EU8Q*tT0^VcGLl`DG{JWs%+5xKe;T&q~0%wy9p?n`t)_jXH2U zskTYn8Z{*4#~@YePirZ-xWL5k;yHJfDncOH?dw|IYBV^FsmESU57+1VU%dRR^bVzYZ2r=#G+nj_fkW}H>9}ei`denz1xyroX z8??HowI)PeKeS2O%V+$D?$Suv zxE-4YEuzXhX2DD-$~%>8N_%HFaE|UHrF4BBvZt~3jkVQh%||`3M8{}>*%C1WFka#r z6M$CTLi(w?p;DV0*#OUg->i2Dc9VftI8>~;t9{~4jwRkDl~Wo{Nj;{~BP?hsfTaLc z@vb*eQ0EZfK_w{Zy~|Kbu*mf z*KvCp-Wu3eO>RkL$jV;?ULpll4(e&c^cB;&MzoOa7W|!r>OPfSg^uNLB+F&? z54ufx9}3u?7;I4bPU$YAX94PY3e?&cl=j6DT6(san36F@LZOSiR`v1bPp8m{#~kFW?{x*2V@VQlA|Ht<14s*!(mls z3zkO6vQ{w+irVHskt7$Kwn=m;GMi6Q1B z)GX>EKm?Ki9Z5bFk2c{N0#n;5N^yM~X#;hv`VZ&st#qWR*1hi`ct?%D!3_%Ek?;y5 zs?X+*lro8Y6Oi_v;lJFJxD1o(xIb`FV#(&XlZeu&w|y^p+cpE*jN&6r?oRJ?|ZD3eIf?!yXds|{k`>wr8O#2QzRZ1_9hy97Nv=+jd@xw z#im?a2yH7#JQwjDkLK^>Yc~0Pvf~!17u<^L8ryD)0eVb@@j`d*1I0=`!6R}{a5V_Qfo=J$^M0qLWF-un8)i3pLBGQS#fQP1P1M z5>W=wPhFuhqJA;lU2}I1-8lDFvDwtaTXA%7w1EJ6&&zN=s6TnxZ)kK7s|xOt%0f>5 z1HX@O&x~rE?Q?xm1KrKX3&Y$UT_dr*U7J6WKiDj&c%Pn7KT@-AR=gigafan4D;7L* z*4mQ#V<7I5qwuS4s&&<@@$0&lr4F?&P2S$PkKA3F+bl*xos#3~;226q>QjXM5C9~T zRrQ+RCs(daXE&xDkBmSnUM+8VB`TEe#0#tFK0m-9{S^FXsT)aeS=D9L2UTrM<{POb z7!q@H&V8deM34hU6xxgo$-SzM-gtZf;5ExR&1>+i8vA~{J)PyvrWMRL*xN0Sd`8a= z!M_u?LnXnnbzfJa2S935f3q`hwOmk_lK`; zz<(_%*#5Ctc^}T(GN*hPvdCb4raOv1%}G~iSGl|Gz-WK~&E{e#@aBNpNfS4I2sSoxm?PE!4MVV0@@NtKevfEhm z2d{`CvGm9NZ7~n)1-txR6(y<{Rcc$`)T>3st`Bq&V2sZ2tWslYP-|W!K=&#$EyXW* z>+$F37aPsW6|f(ZGrKEbUM$EzR%EB_{a;7(LWXf?$=ypg?(prylN2#hk+%D^5 z)*Ix^2{t=Sxj36wD~+aHFso(6IWfez!>Juw+$0`tkEOX%R0*!HWbGo!e zIRJAL5D8{_;Rcx`OB!YCF6!OX9M@4h`_6}+em$;dMQd2@aan2EbDm!*YwZK&1h%y8 z(1ne`9$pm+_J+Z&`iZ7f-i_K!m1I(G>^B+R)Z$tnJcN$BDNS<&r+7_8u?wZs5Rz?} z1Q+d-n{VE3dUJh{5{01kGHnuir-9sbPVM({N-ERD@_aWtFPm&ZNPGtEWA-nz8p3sJU6 zfXsvkINFLHTS-13H_2!R!-%Z?7+pt`HzlY5pDc_}HEA#Nb3`SB>*e+ue) zMbZtSv@NUJy1qJ5L;;1P7B&i)jzcqtNq|buv9X~-qbdeNrSq9F2MlY^*tWSok>WQu z+PJxjS>W83`cj*Po3yt|=6k+23VghGrlVr|p%ynRM^PorjIgGSzbKB))si};O{u^e zd;`>MRIjkq=HoUryL(b%x)zq@Fizd+JiyqF@OsoQGQHidSzG#Fg53-$mlQVj^6J>% z}*Yc_4<<^+xMG!nq3@+uNWhyn-7f^@9^y(Gp?6#74<@%hBiTv1VZpWl^mUJ7slpTS7?=yJlh83P~vh9g<1ozho8Q zF1(LbaJ!AYy9Kv9yXl4AKFz~!?Pm8z(XiPqEvX3XoIn5#kg!LAqscH_I^&e!nnIf! z3dXH=tG(6uB$7CW(;1}|clN!b>u@-F^GR2IZm}!1!rgFIkLo+=yGm|4owD2}w3i#c zKtSpi6bekHhRJn1d_!f9^S1WT+7@@zX;y6>(?hBdAQwxEO-D7tqh3g0bK4mL47djo zN`(rvUgj5MVY8lDP7pz912_kW&=?A(yHC{Hv?zA0+fyLGy+W9amN{~Ez{0IpIgJGt zlN#X4kQq~emekXuauz`3Du03HI76Q<) zq$nRRS}jV_-4C#0_bbib&i+3dR?IlvfF=Mk6!N3$WHI3+uvnWm)MAR`Ki zhFEUDCd82<+!uZ@6sR8j^JNVMvaqnrs8SB)BqdGn-&X$s;m)Eko>R0Y`mj6Ay8CXD zKH#OHNAy)oh%pZ9f=kXZIA&GnPx4E&^J{Uw&Qk5p%XxC4yvaO}rLdr+gZtx2*wyuI zhl}2=(C=1Ym*CtZw=OH~I-ne7#DyV4W8w(?b(_*>+P3zkb8g(+;OXlmI2qkhqqNc- z3}#D!oGQl8Z9_;fR5<}2uAjPeGBx4FN7bs1MF8D&qf#LQ-l1Yll#jHq>c5hqcz&*6 z_N_tGX_uKdn`$msJ8O)Ib{VN!LM_c9LBN)k*`2}ID{aQabTw}LjKhBu6KAx1ILTK} z(@?Ati-L6o~ekA{{Zc4aetJCyEPxdi7qRv zsQ&=iTv7OvKN6$3_(|9~q1s~kOqTSM8f{A)gVy;D#!(&w6e&AWSEHD#Z7rC#?AG4f zh*N7sEWTopq!F=6LyoB=5J=l?y3`hb$@b*abyqr@y=Im$PXZ5GNOOM6u;rK_w7I4A zdiTK%0mDNl{5l=teXv!W@k6oJ60|KYG)hWHP*O^I5Jz|*?hRwvSI*A~UTh=zDu#C* zy=g_-9jVPp)H^N#BsB`zBuHWOS!9wzW5^D+u%;X&aC4wSz6Eq3)XoK)P_6M>Wvy_U zj>r@iu2`H$(w&JLa%>=<(@9@qQ>9Egox@8Vn@Md<^^kC};5caEIdLF@MqWA2hf^{& z&A$>d;C?a|*IJ2d6tvDs*uu*(_qW^)x41OlfBhv`3r`oEPvogX_0PP`WB>X$<4&7>gVL6sO`^*7<&Pw$5D_M~OV72Pd5H+1iJ2~(*tW6W!?l+bY-@3HR( z%pU{mPzsl?tS9~~nBndnc8vMk!7ClrCB=;!hn6&THMg>c4OmFPrGTc@6 z%p1710#hbD>wU^9!_7AP3;3HPprd}NP+8cJYgeS#!J8)|)z(nrQFjrs+Y4?WrESAt zZnLrK3gkHH`x{S{Xi}q5mhHQ$Fw~$6Amni$=#MB4CA_I99QK!*O)}T#;5~0RgU!oX zn`o@*w@Dh8q>p@&eP&ayw+brK!XH-u02{H?eiofs-;-(umwT20JcGJ&Gv8cX5|(dg&r9@^89DWd$!wm+lTW%yOTo zqT`7QQTRf4SA}F+U%%qACt=vvxnx?Yep)U$P1V4oZnG&#*}mA0y_-n*7>tw2dvZGk z6I|h8)eN^Q&#}1|{O#*(Xc4ABiI7N!sfdRWP4IE@t^hjr%0Lnq)ufd6gK$afd^+D~ z)2lKpRwx79mVN7T_iXg1RyQ(K{{Z_VpLtm%j_%+EZ*9$(b_KN?YOM~aT5=PG(NWF- zm>rhLC4<&<7eH$R=-J$QZAa8;hR=xXftNGH=q58J8^bX;xTngt>wNh)EA8T0UE2FI zWlNRCAQu)#M5Jy*Z6{=)cMlUSazbH*S**;ZZ=pMEeuF9erJp}e^IFS8lcb_UaKcj| z_msI40Nn^rfJd3{+se1_;YWhcV;v`Ac`ZclrK`r;q zpj%+0oA0@~+jFo=K~?b=Rzi$^E9I#$VKzG` zvSlql?8jePRD=$VC89c#O<35@Cy-}1=o*8mHva%~Ts3x>?lQFnqS95+N|p6onnC_6 zAn{Mdx~jRj>N;9yC(Ld4)@_$O?qcHGuCIJZvtw5E82u%E#x*xl4e<$XZThF5fd{UqT6Xp zpq5c8Ktqpz5gk-VuG?0s!+Wr2?!~%UvI}kQI3>q@!b)ChIso`vR^39BPhG(7uQ#&1 zw)dW7bF&PNTIAd=ZWord9m8oqMYMiO5;g!1ohnD7ndRqBbuP($v$*L*iETrj6}|SC z8O4ODwIqEb*L3+P4Xe7T^ipkhbE?y>O~7d}0}RKiH;>lezy*vtOe)t6b3U$od;b6r zYV=A=@@*^m6{#ojqz~6Yn6FwUKvbCuceiZAb;S+7A@L*m#^$_v_+L*B^PhPe z{o|gzS5iABR^>hPYEn?1PAnxpYC8&MT()6Fc3XrLw34DX@A4G|nL16y;{3Nc6WpEt z6ptV(J6lIw$(R8;r9j0$Lfdc>+lg2m{8SF#qLo$C4?PvY^jB2U)6^9I0N<@nZ8rgP zf{7#my~-Q|xF~;@zMXYu>tTo)2&Ys(B@EW3{GE*1^1u%GzD;SmsDZf*nF2fCcV{76 z0I@($zx|b9xQ@-mH~9)F{RX2tmtZ+goaH#;2JT#J(-~{OaOi1DY>m8mr8`zu5r0ls zg=6_T2tO-YqD@xEe~E1*sC~-hKI)#K>o#>aZ)4mKbAwAqn0ZWZX5qt#2g*VCDBi7+ zX_tcLgaDfsR~3m1@{2hOi00?ZlZ_v^RQ6OxB}abUJ~J|7zLL}IJlJu!Q`}Ods1Tv( zMMX7w#QsaAnO&L~{nX{D=i>TEUUvPihJ z&4V_vU04xk!$Drqt_269t+wKeOLelO6grYm!&hu}J1!pQF!ihek%YmKqh1Ilv!F0q z=R;cT@d#_(mNW-;Oz+G`5uiR`fteeWjzc=Pn%yBiIppcRqDWkD+(#Rme0cx`!j-Mr65?BrZ(3Z@qE|=JW0nwX2I>U;(%Q3bL=k zOh#@=8!8-@+&+LSioa1c5wRpl*G%3=AbrCeu)ZS-qt3N-wsXMncf#^qg!t4db`{ z8yo%9#$T0Rla|TTojOx0$!>94l`eCM4oAo1bxD-+ezU3{if6rNWgU_Z@!Z?K`tkS( z?X|TDIRPzeo=M;foz8Lqk^*%niZq1oC7^G}kOznXLOr6dnXJ3y2>L;k-=kkJmS4JL z2)3j-77RCA2}<5a@6tGxEes9S*@Y`$LU)qreEddcFCQDlS{4iCvNLk$LlR7(IWK%d z;+IwZ7R%@35B)9A6>(B_ss0m)S+FZ3R!azdKsc!~WyxP@Ds-jwa!c-at;L?C_YQz; z02NSW*aUnlE4MQS=;l9ZxX*ETo%NLR{1?2wNpb_JJr{16t;lI#3S25k*r_CI+g{pj z3s2;hc5r6L#&3xy8%CfKOIw?o(r}kJVX0Q7Dzqy4QO_2mhMFDP2{{6FftzlTc6J(r zSGELw`Uwd5nOE9-)A!W@?Jt$Tf4*5U3_{&!Pdpf)C} zxGc`otue4UX1+(flt|Jfg>PT7rZY&kZ8Oo^f+EMg zT3DSf>jVU%$6;{*H`$Kl$aOz9(mT_#)_#bq647e0;&wPT8!Sd`)~jnMixGsKlG|zM z2Ist<@_Wf7p8BY?X3%tVD7fiXKx2n;G@ORtb4+wcOxYnJGouba4$UuRw4Vl-w}Go# z;T5*7(Jn7C9%XwnzGIMOUsb?6fbb@;cC#5mptQFn(Tq)Y`h@+Wa?EDXKFkpqi>CFf zoc8sv9lZ$qyuLj9p%zn%*mK-wW;c65e%(C)6|%jYX~dpQywb)cQ)Afc3xK& zn*)kUQ{Len+mrgLqttt4(^oYn92^~}3zennMZvp-nCNwfuTB&#s17Y%%!ZN4&OJib z(H&D#Y;el84Yk9flY4lk3kZzC&M_Eajrd&$01206&2L!2+BrtDKMct68`c-I7F%() z!@6LaiSLo}Sr<9;nVB{I*%t=m_G~`VUi!|)?9$r7>OkZ-8`atJoSFNg%^WEm##6wC z$MHvczmmCeh<V(%n3}MUsH-;^ z*Q?g0;Pi2^m(9uXT&eP1+-;X#bi;W$+}{;i;|`y=`{0^mtULs7jt~~7}3ur z>ICfjtkKkt@{#85rz+bj-_wolRr=&iHxtt6!SR=mBm zw6+@=@@F?ZjM(gm0DYF_FJR9=Vc@k%N{w2zJuG+)#CPvJ4xR)RvAX>9n`|!r(obrk z`K^ur0GDcr>S#AiuT3#g)iUyPd61s}0GYQ%aqa%j)ohQvP{A`Ro%1G@iRRFlw&mFL z5M@VYU%BoKt$PXaH|nNV0Np=HwH_+)By_v$`Ugcfis9t<{TUY-dv|yq$|vX|vMuO7 z%HZ{%VUBc+$7!T7!0!dlVFg-y+f{=O_W|T{9zdwvy*9n^sJI;{xq& zmbjDRtwK{S7T!lv4`hwfproFggIgGoLt|~!&q(t2 z-|80DcJ3y&Nm^k0K}lI43x)>Usvc%F$!YyT+L%~?YRt>>TtjJbE0XJrJW`ONR8&ds z0Chfstli(#YZ%&qKAB}O8tu@~4iX{F&9m7c6R0Vas5FvVaJpTX@0|A>j`7^Jn1-X_ zdC7f@cJ_tNiPBxRQ)-JoA@X)JfH?7;4{fv~qb$Q2r6}uP~8Q+f}aDVzx)J*zJ)m*64`{j}jv( zX_k}Zlq3K@ma@L;sr;hbZq18uev!@R$L#ku3}ojyOVk>f)-%g@mj3`B01>i=!D*0` zHp&1%N>UP(Ab0>e00)4o??OqqVA6936SKL6_UV5l?YU{$3T=6r(necyuT!f?Sbpb(X~qLR)#VA zObmV~0h?4tAh*Z6i%q4m{p}z!z$2efN$+0}4walXgI{lodH4(i;Hvh0Pa#gn;G&g9fq_GY-oOa!$m zh8R{#NK!)5proGS4&czL`rmVtX?Q&yXNLa(8$|pnA7`JfPnmtE+HLlh3LQvNPW|AD zj^^0g5hg)Sr%+T2U3qKe2jbci;YTv54lH+}C>;;>#D0pzgCYEch|<>AyTseK2v_t< z_vn2Q(|=P+Tsar9ag!C^J)?p%T@KDR-_l7Vs0vp7N0lt8WG3Yj+Ve8)ZILDzZ0@!I z+@U^w+oXz=5Y}KCk6}MOIYMd7&hO9psO|GD#q`X`nUUodyE8%3q_aLyYu?#CLYhh4 zMM>&90z4{5ie{LOb#FIZJ0!PWVy9r@;mAvcqI-hV^pJkgE6d#f05-mAK4EgqvMEb? zT<6xLB}b%oK|Px&2k50K*laydd92+i&u@BXs1|&(!t;r7sVy@ploAszGS(8~D<4=8 zdT-o7BdD)juk==5Vb+@e0OtgURG3YJ^r*%GczpsR7XNUN7tBt^>?i)o}c3fs!>X+1v6g}ac1deoCnDhD_+fzf!#Re#Ui3btOl;Aa80VQC#729jV|dVDEo2%A ze$27UGjs1)c0F=L*;ZZD{J0jGXzdudL*L?cC{kW+_IuUY^H0UNJq>HR+V7UQm%Am# zJ;L2Rq`wwab%nI^YVJ~0JICt>q3cv;IjHKNm0{WK#%v>XVZ`BCQ_x%riz%6(J8QYH zq0q;5^bRNLspk-ODcYr%ixuNYu2b_o64dp>7@F#(2hTJ#|_dUZR3DXmZ#@A@(y`kBVQ>^L=OgJ-OI&T9}9EOB$Ajbt~ z))f12l%&ay>0)C;=H3It@>D!i=|y9{S+Z+1Jck~+F>$tB8V|*k!nq;T9}(n7aR;a% zf&r}R~0@QraA^4o?YS&MnP%Nw9Z<{P*z}b zVfuc2h}>0ubh_j@P0A~b+9Tuo$%QcFsP@%5ea|4boxS0h?w`iScmPKK02>a3as#O+ zqM3s^Et4smjxi;e_Z-gX^C(+IGJ$k7wKKr?IF%J8MD!bU-lQ6bYO!hsoQw0{CC9V! zpBn0k0z3A10NBd+gVC+KiaVA%C=hFrSE>1g9K$NKGns}hc}?B6ZnuF6+iYn8NJ>#5 zASbkpm80Tyw#ZwFJEw>Q*K@J~~Gj2vel*A{;(?cmIX;PEkrKpBk z?{Qr?9hRJEWPD2T4>r_?8o%Th2?|^2eb0GfBkNAV!nF=8XeiW(IIHfwWlnwVPsjg_%sc!3vEt;25AbyFO$Cqpa zz%kk6nN96$!yI9>{y8_;e8REMK&27@N=lST*d-&pl6n9<%~lspSGeUWdfB7V;!SC{ z6vT|KdK9ZnWrMPl841~U2Z!Xhvta!t_*T8lUoX`+3biG1+q8uRBNMktQlKMl@028` zWldN;dlYQQJr6pOUNan9PwudB)!Fa&o5PSj<%m`rDyAwlM65OlZL$!Vn~)PCpx03%E(>Q!|?Gl9HLr{(YcQkui`zAd72*)7gp!sC6K z-)AoOa#~k$kb~E=Nl4)lk0x0FZQe=iQ^pu#D@l(Pq_omIl98|#1x}~ra?kozp&E-M zcq&5>ppU#gAQAd0Co#E<`>?v9+iOFAl$gfN!kasT0d3)|ZjZ96* z^}(ob$~KKMwe~E!>FuwRWjK(O6&->aLP_c8Q2i!OsJEqfbZ8ovgxVn{%N0#DG1sQa zLuyYT7NtHARq=J%Aa7PFY{w0sQLm`MFQ>e_M%DX)+*EG42D?aUIXjl6yhz}#jEO*5 z-%0xAwEmT*vZhRhZ;K**!ebF)L}_nGedUBU%SjulYEkGC0Nk3Zu2xL;YXjdk=H^@N z5T&NyiouH7QV!)@+)BvU0(zecgj+ESuCq#&BJG~)J?~Bc2#6k(%Le@=4sY)%OKqSx z6$LvW5KD*R=#VKWY+6+*^_8v~DYVOoYv(wCF)@I+!GSx8FwjU>-Ml*s7;zmWk23`2 z$L2z?ELO{&v)!TR83nRa7)80{$;Pb@u%w%#$@&*LXg^SQu*!!|#wc!51)U{c>Vo{$ zrGyi?Tk8lL`jrt?V)SPem%6TYoX+Z1-fmf(^uZ;dlc|&bA0as@jn`3h z_Q=xSyJxu6RG<<=mx#dqn{(fKsRpoVf;GA}ODjCFNQ)~GdJ$9?l`uzjxyW&}9>ge> zsV*$^9X2&!S~TMwZE@~){1*_lhQTbh-Hnh(lFrElpDoWy*W__7mhEBFxh>pSVN+n= z?SXNkA-*H9W$-rt0HOU=HqIM5ol2C3Clbn&+70zs=SQgj0K2UD?`3mv=C#HI7Ve!$ z97h!tyXy86aMp}U?`AjqqY&}?rOd9TU@XHx)Qet4oxU zt5%NcWWYO$?2SYU``>vxed4gM@n^ZN*h_M6v)xj$oPdS$2`Tx=od(0Z{DP-u#T9*D zmp$L(N>^XeOuwzO>C(1}MEzb;;(y*$;MQ-R31~LCbxzmX+&@LUNl_D=>SO5!27+DM z-?-fJJ9J_9@Kc0-il=pNG1RP+EVJO!F#KNH+jol$%bP2{F5t@4xhwZb@2L!z$V5xqBHk-qUYrNSO1%r;1~;&fu2WNo9~u$sANk@2x(LKH1fJ_FK0WVUgBJa2Scs ztA}NyGZ{I|D!uz^2H-UeZ}oJT8Z>Hrz$#0q`IfX;ad|gaZ~ADdkH}&y8-2bE=}ya* z>!38+RO_h&uIf^VAt^$M8w!P4=IS+)(%v(sdc|**Zigk;Zy8hAgDfn8G-%yz>mDxE3SHUz+lJ9ci!VJkuk zQaBP^LQefn#e(TZhTbf;tg6FkV=0KHN4QSCNOIs>N_rF}r-?%>#i>ok(dUK_W|m#u`Ab)>gZ&da^(RB3$4|oX7)osb zD_a9owC3fH$id`a@-0|?;M(I{K2Eax>SP6gl_%zsn9U$}fzPJflfPP|@H~@N^NUs; zrq@etomli4lOtPRzOBSZzCQ^-GZEgvkok|dAlD>^N;;KrAoM0%GH!i%k)ybDw=LFK z2ugC>lr5SOnotEc$mKf(zUqG%q4>7XuYWi>{oAY zR94+(*u;JLdJ|QwHjSRowy5V)ha5E_(Bn)Vz^DU7Hng6xq%aKH0g)^s zO7v>hsL&mdmgSHqTqa2*c1S0V?LoJ0pJx&*iYZpC68; z$B^1MGkc56iJr*r3LOyZW9Tn)+(k?6mk5^Y3|FPQ$FvGkQU>Z#Dkq@v`)i%PyuY>v z)DYQ>m@u)xfMhFJ zCr5}BLyWeU*bXF+R-J>X18_$D4@#sjUd-*`*ZHwBeD*B6ZMMNHA${v?MTM;lyoQ8v z+8u+7TZu>`mc)UzbiS;;F;Oj9s?wHIkK{J1iAv7O5ZrXeaPQtz!Sl;wSb5eK_D$^r zh+@*NjjGvgu)lM=#bqI&<19`><2A7B#ZRRQK7E~by9cDZLuXphx9jlHYbs(GT1YIH zoF)!hOO8vZ*^XU{W%it6z$_NFC*?P2tNqTxUXjGWWxxaKaFUcQT{lS~ zE8J9z4ZttC15ySq<@*oD?rRTRulFUBlVpc)rSWx$(H+WP#s|Z9=QwO(fm{1m4YryT0a8Tgjw z{lzTUgIvZ2#1f?4&^+@{O zUR4LZY1PXgeqLLEO}FFnCcWFt#-HSE7iIXBp&eL|ui;r6Z}ift*y1^yEZ+YBCmpyz zor_;##6g#j%-Ir4K}X|hW8ry{diIU(Htz{NDkHOLwwvy4U5$WemTb&=z-m89vdtud zaEEp7E)?nrKEvF1cO3XkN|iFEW4z-wmdyF;J3lGaZj>L+SN{MCYcegV47=}=-BhV$ zel>l_^g?@*D=n58jM7++(pzl76v>P;Z zpOP(V@F+sM$asGY;P3h=O-`SEwvee3UP=-^lGbYQb-vHalvJz6C5`=1eRIvj4+fl^ zp^BCBcV)acp7XeJ8Sx`9qqwFkkKu2-WX5fXg%_|xu56;WA9Selb=MS1opAW6M4H;j zwtVrKZ*)eIevt#`+MsJ7rS7)J0(&`X-pxoz6WkWM4#mqx@pGWZ7#6lTtd2q zB<>AXxqWj}uBu6Ki6=Qc=U){iHn2AXbQ~33txdS;5newd# zx8XJtN>rn`96Qoamv+XEdy$ub-I-y~r88%kmlNEitKHU4;P+Kf!rHAMWh@`(&qXZD zOzWB#_7hxLNp{w|75fbA6%Fe#UDhtpIo|W_Po!d#0M-Y&E(~|4$W78yN=fdD!S8J+ z>k;`!^$M--Srv!&?7PKAL2T{hVeu{KK$_Pg`M4~W$Hnx*PUSuORCC({QLlJ*8EnS! z>8`_(lD_kDq_m=!6BVSSsR>Z(lC>=$k=KYL#<-8NYi+wU?Y{lqK7Va`UqdZt+oZFVL^?OGbUqE@+;Haw+zN|`&Y?XO|`e9!}->q zD=fVQX&;Sg;6j;D{Gdm!;|Cw2Blwa*UNq;sK|$Def^=l(}6`Qk`uC|6qIa~xT26Y=xe2B zxY}Dqg4Z3Cl(rC-(2$m*v^%~O>=WpbSP#X~8`o^=gwV-XyvnizpnsF#R zWADD;kWvZ=Dg^CaMULaPyB0HVus}<0Lt+CFlC&r(E}<a)E0HU74GXsK#|8hM4tJ}B5@tT%4wbCQmqM(iQ}=6BQ=a~E8zgpenc*VO9u&WtyPW$AC)iL|K>g2nW5(9Lto7bMS@XL_-@!)dZoC6Jg%kHe^H`kge z`5xD5$CJ3|mxyvygpa+(t20>}#eV`Fo0MFoTU>)7x9sy^qnze0W2Ap{Kvk)z4!?T+uTf zpi7Aa0CNPEwb9dmTT7|}Ta(A#daJfjhFHPhCt(4`1>Mq7_Q(Kp5=!GaeZ(do!>SDI9z z<4M#kI(6;1xkf1^m^o#I4l3Ox-i=9RL+4>g+vzaqAJ`}*W2mgXIm>OCo+T3HZ)w=C zM*YP;3;iE~Edys5bbe6iLGDsWQ0QvC-P`Lmn@-oWfV5!)Dr89j54htbPGpw{5_?XP zl_0`x>SrM5z1{rAhr%FaD!n7cAnHAu{{WpB^RW!Iyte3BxWGIm!E~0C>~{jUOol^+ zW3zA+k=?g_Wnh_unjdp>O0{Ba7*OTD{mUG?#ARr=d$W#iyo%iVLdKa=i2G%K~PW^h<=lGuBJAT`q0 zOo%RU9jR1lv{Rwi)5+{49JS62fvkukGngBpTKj^Kw5Ta7NK#ai2v9x55zvF?Dha3K zmZoZ&KPvHQAO8T%YN7gjH52i+^G|0>nkQJ4e|F&aXUY=|(l=94oja-$h99)-S=5`xzAl~Z#inYAuP>dD1g&zDsOYTLefVx?vv1iv8*TTx|S3#AjoR~ zAdD|*>uF^6WOuA?vJmB!-P&!(Ev~D%DJu76pR?){`YUYeRKrE*z-h76t-=tW z(smVCIi}e4!2bZI`BtZ2x^XFyf0WV{_NAW88pCTw^s?IWpPQxe=QQHLW!t=mWGw{! zRVmjLIQecWp|rApuT|K&FlYPK8fU}XtfH-9>a<6B_%!>}*CWj|wYU;3Id)}HlBa}SahRQ;d$nFTLn*-MjCjorn7cAkc_A=N}u-&2|`EJEeJ(F*eQiIK1M`GY4 ztv?Cs0XD6#3bEOh%EzYog{?!mO+jQU^rgN309`5Ud}R3b#~HjP3)Y!#yu_HQV34ni za#BGYNIMj$bx8n@>a}TKX3(VTdd++3xQXpFLC>_%8t%{;deBBkB^^dwJ;CiDIC44i zIgGqARo1dRn2Zc$6z@3>`hBv%QFkZs(#b# z@pV{vT#&n^*53>$E2ujqPAjhdRS3>3 zBV@})z;!=5os>QxMQv;?=K8yDg6tW0yhjak6maa<9WC9jA2fiW9Y5?w3EXz4=M+AO zNITZEtnKL8F~N=MgPd`>&me~eL}4_^Ck$Z%K?FE997R!8@2W(cL;(N^$em{t4E4~+ ziIA|I&9xTmmYg#c()=y!gk!bQ)MOH*v1c1}_m^622<;DpC@NX;0ClTn76+KOBqnQy zdKv{wWyPVTx(4Tp6r~W7dH_kRoOc|s;x=gb_A_daZMMY+vyQ@cPUpQ!C#sfDQl%22 zd(CWnj`huL4W&-In%i1<7~W);b28^gCW2sUTyt6!E!@COS4lnDcR(I~?G#_icHw*LTu z-Nx4Y^hZ(%{p>h4Qj_FRzAGN0vGk`TxMa9YMc_^RyOSJXA@m7A?IhxT1AVebbMN!&05Q#dM?pS7Qe$ovnQi0f`U7SE46o7&8_vS>w{eTIA& z%NG<&km6cqKWG8%*(z4$rviq+k+B=pD@yX1x;b=zN_8UQ@44f$669QnGNA!TloYlT zBYo8zg?7mcR_NSpR6K0^`jeFE{f8e?x3^&s*OT0TB+Ym!m?%D}K#=a=#XeOW)HO!M zGCX%w?yoZz*>-MLKYe~eL1DP=?({Xh2q`E^(6n_2@w<4}OS7&z+cxaGpkLNBxNdMc zmNXbTv^=yy1l1A=&1cuAhN-!xV+oE#Zzskl^>u}E2O2Di0p|M>-)bET9hq-!`SR}L zw_aQAw_2sHwRWuMVV#B@{ncQaXF8zre#GyUu$t%R(H3?QqVjYbHWV!E~&xJK!^o^d_Sg{yUT&6Ocr^dq{VtFlK&?fp87jY`wmfIg^cjlxfF z7iF<6Yp1kc01s`+zJF5jg`58XOOVqj%Vl-X6$o{rJC->U$nUu)mo3d#F(qdj88wJ^L_d1KUy@pe`$-LUYErs}0X$A!$IpLdtrR$RMvy5j-sMaBeaP$aZi zek;udwCiWR3Q6ciG;O+@I*l7!w)K?U*>g$Im%QQ!Q2h1ZzdXYF8*91MDI6pmi0%Ri z;gW;m87wQ6rJL?Ia7625msZ+TNlH|-or`Np>civ33j_J;Z3<%(+AgxAx=zbNhRbaE zD0o*n-8APi(giuF8d~F%>~Dn|ovGxmK|?7~0WP*TRE81g}0P@%C}&vUaBK9ilu`UvqS9|y49ulXZ- zgQ;PU!bY1=vsv0-X%fX9W8&;p-1L=qIQMw^3D^zHi#5*TVm{|-g>8QMlHK_x(PN09 zAC^aZi&5yy3GZ9W*(n{{orPtJPqufVS@x>95a$g5jM~$nY)(3o0TKZemjC%pDm zK7ns>w+#8~OS4=X5%<-jGKX7=VhHSr=xisXG*_DR$nQz3o)3eM(1 zkIa0SLNe3uF@lL9mm4G#$G9mwf&lI`@3Y^tb@Md3TeAG7A{ochJ>`abavYXHAtaX0 z?Vu*>=aNZ&C$ThJxrJ*i5vOoCi)3APr|D1!0Yc zXfB%e&J#IqoQ!vCP}+^Z{wp5+oOi<@lM zEcIz|pzvx@gCr5K+tCZM9O9jXIbkP)#~#U)WH-(XRo* ze|g_ERQ!#rE&eA_vA6s75^+L4&aKEF$y>`G?eT4lrA2=PoXe{#3SF{$JNS2@#dr_X zQu}T+nGzpF$vq;w*{uzN3c3Og{s4ngrrSeEOHCjl2mvWb?gPr5dA8(rco^^4X!ih$ zR(Z^k;!nJlTsM{Lv^S9B*iH{6C{R)C?l%@X1J~)YuL^}}HfyOE<{kJUA55*+Bx3hD z)>W-JC}C~(Z>ec-irDHB+o%CVjz9yj-)g_Qo|~G1VZ_?CUr}s4xT53oR!Yi7<9^yX zj`8*R$e^E9Qrhhcde*h+y4i;aYxQ`N92vR8K~R*HC_R-}MMIp8+r;l5q82k+ub8vN zcO4PB6oNUXHbZZFarK7EQa9<-@2F;t>U`X925SzPrWu@tV)Lfj6y#Q-JQ_o9DQz^b zp9HNfIM_Q4iS8s-^q>?7Qc`}=Rqimu7QJH6EH1}rwRwqa$)klKJ98*hNgID=!({&e z8j{lb&b_UAM%kbM*W~BsTI@UAbQK(Y5>%sAl^E`@?-~u_cyVY%op7-wTxMW;hZOe` zai<*}-fb;kWhq+qr%VzjeiOmlef{efo{hHi*`r$=*EGd2_v6|Pp?92$;GS0d1+kQ_ zY@zvNcQVlNb;6X7Uh0En3~DwaEt<2M-Xll1+vm2;+ZeR&V8XS?`K7$Kj?0lDrKz_I zoe~lh5;p5n+HCt9z}G&LnUB;Mz%M(4L1T_*EJhnSVw{pyuUe5HI2hM02NRgxoNLKS z{{V088y}6%df zvGLxlmu%Mco-0Du2XPOqf;`7w>c)2!u}Dcv(8AJu6at6q2D9j0Y}vZocBTJfk1fI2IWSxATp3jK$Q$DdiSR3tGc7 zl2NcyH%qBn6h1DMJkeag9L_S$LCWk`c4V_MRh}bZYV4f>hyW-S)Oih@TZuh3+#QLn zey-`ZBaWuqstjUQ`O$IJEvxh;TiAF%XmpO*)S*ZtwP9Rd2W|MJ!Q)k2?KjhGwcMi= z#_?z~^4peMHM<(E{8(n$0fytGO?}4xj6u0jZ*QGaxTJTH+A@~R+cwoJQwE<{0CSqw z!$^A^{7uBx<%O@2ga(s1oC?Vu(2|36hL4tRY26G#Nwl!A$dbaBx7i-hm z7T0p?#j53ITXPDO%qAY}-)2+6AoAjT&B?kLww%(y!B;_`@9gi`%oxc~` zn;1E`KyB3oErNLLk+9sFwczPancuO+WEf6Sb(?Hx^F!9GqT-MWI&4R7do`qYrAZ10 zew9aP+gVewpvo28Xy(G6sZT zr(%=wB#*RhSUCngXv?f~@r(2Z=Uc9k*BV51tB6`i>9W3|1OgN~q<}|tRQXmF6Hl>e z`E&Y_pSKblA#JlCD3Z4%JhBkW4JE{{la4J&LPwgn@T^B6>WIZGdL?(sAGqcDY&-hKu3o+rkrd)NdKLny231P-| z-N5Nl8@dQ{1CtJBna|2HSrWeuo11UG+ak%0*tXrBcc$KP*eTrd3wdiV9r>h$Azc99 z4)1-inKy3Z^YJoOn&F7 zp3M&4FSmeV;PzQ24XW80EIkfV5!x3eyzcKg+95%}gmG)DYuzgI>ZQ1Ew=?*Re_}WJX~dlG_vV~#m6!RA_>jMv@n!HyvEoQ zHZdoX&r#YTM6C)vcLW;2pKP(&^#$x}A;+G764Z%_ofVl35STeDKq&TP9nfSUA;hU9 z5aVk6s^IM^vc;~lZ8BWIT*s0e;x%)aJyDRx(Ikj?Ys1_K+p5=ABC&QvfJR_)4&TV` z(2zib2K+6}wAEJ}dkUI$4;Zvb7P!&wGD~hoyuRTKTO&$HKK}r~o@+vq18GSGW5UbP ze3uHx@wV_r?s8$}u$z%_yW8(E%qxtk>k4*6fZDn`lD+t;r7a;TNZ+Mk;qmOZ{N*<* z4nFHRcAXdO*z9PLwq@(8!4sv9y-Q&BDOXZdr%wCUui67BiI-zp9t~o`VcxE|^{xAr z&VA8@u0A^2SyPE2Nj$ynV0_z2AdUGiVbIHSgKAZ7&os+Ru(|I90uF%G9;qNYTm}+e z>;$>(15%{9ZOBmtT4!gbx0$bm2c%ACmOM#ONPILR=(tjO6M2zE=ZRwLWQ$gxo3 z)~HgXBXPW>`@+H-8?DC43iv>*ok6nNaZN?DHKh6F-s=Z8k8iYQQ^m*uIL>>L7UYxg zq4_O#i`gS~(w>|`wyli`Q$v_Y2w0X%JfRw23pNGkSS8aGkA1jA z&h+9-ChFa`S&t_F001PEI)YM0@)!y!Q0{%TC&srn?L4++xYS(67OCe~J59n6of+em zN>6CglepYA5yeAoi11J&!n~%Y#%&h$mo>+&83OGrO{XBX&vI3Q^vfkqz>UL$Sz6>(Ql*9!jtraDE0W9(lw6DPRC zw~!UJ$0WHrw@LVv+ii#UbzM7?#kiX|-sQqppn#x|de=i`ZDU_&Ru>lzZ6ZS&_frE7c!+Oomx*A+)`D6{aEWx$ ztx~fqu(5!Bn;mbO~R0F|v<_f^WR+1nD{ zm1hSz%xMjCn%v2I@Ro^laswVN*!3WcS;EtCc~Q*-08U^N!LkW}bJ8;3GYesiY8SIk zo>|dg!?TsB5Um|=O-G81Qu?zby(2)SAA5knUXa{mA&=2g@WC5_Oo@S|E6mR2@c zolcpk8Zf4JS^{qn2mOQM@X*RrNw{PR=oiGo?9` zXu0H&`$4Y1H>5yan=pb$0^ZW0pGKJd(UKIIy*ldv5CA7I*>d3$L_jS4=D)7iv9 zbMh`co;6@$OGb@Xz3=M|v7>i)jwG&r)QsCxGF4(&oSiVWTg}%di6srWlXl+lm^`K_ zwf1rT1bosIRyl338+ECe%60l%oQ4;H#^iTBr0bLYN$d0)Po-wuRBsyhVT->e@qAW& z%|L_jzcxd0n?8rLY3;oUs{p8<8cWTgLX8SE+ibzvE+SrFxyOr+tqdt?P0MoO~w?=<|0Iv74JOCLxZXUM(1)n`}U-?+gV`1#>eO| z^WM^ut)6ov_@9AQk;OFy#GiTAdxL-SOmg1WhnY%`eM?oRSyUS+lRI5?2)KqoOEH~; z86j%eB_oV;NQmJC0i#lxQEaPHl6_ z;AI+@X!#n&YLb}-6YEN(&ULkJmiQ!|2;>JMqqzfMI`3UOsKVRg&d(~A~GdB3ZgZ806Wx6^NUT2t_(daZm0-XxmzI-Nb+RrXaY zI#^9=jUyBsMYxLuXBdeFOmI5pu$!@M8FK2boGm(Tn4c&iX>1M4$+dSg&#mpWwz1T; zi9<%)2@Z{{**zhDh&1}vL!`YhR0#ezd#_xJA8fLjS=kjc2Bqf`IfY?ipdzzdF z)$Vu=mGNwADARrO{SxS5uH1u;LBn%ibkZkgK&86pM%hQxS@iNAWb_pP&2tM?j@;G%04lhxm}Wp>hT{%vecg%dV>jOOojR-N zj`Bbo)JG=QOd8dWwUNTOru!=9*J~aRGRvEC;!M-AFL6mA0 z#xA1kYMon8Jj*dGi$P?V>Q_eA&_B7O_iY^~n)}{S`U6VCVlvIXm9&Ybz(~`!;x%g; zeWc_!@52RavsCqV{iEyyi9Zj*bP-|aI`cW1_eb8x^!d3#O)ZigOOB-M7J_~mG6!*d zg2O}}%s8RSteh)MuvhLntEH1M$nr6z+$~Osj=HBxi?-wv_h_P29V$szX}>N+nz&3@ zzU(0TvqCZu8cQiaOKCtG1f?UO0Cxf1RemdQZyeS=LAAJDd=psdmU8bE;*C>uF5wKOIeI${{XZ- z+SjC7g?V}Wes1kzSuC;wcn|#@F@4E(y~hVWijqeAZC4!0F|E;Gop6-QNYM~oefQiA zw#o;3mbGjWJxQv!UM67KPCF|~t~Tiz9z%0vNr7p#p$^H4;(E6&)i37bw3GCf=>bV9 z04sf}n8n5J-h&fYlKuA{!=DkRT6;$%L&jcG`9_%IJC$5vEgN=kD5x{B1tceM=4^It z+i|oWO**I7Y!9rR9>F3Dk7Vf5?@;0*nL~DLo@fl)xc%Nl=b+Ds8srO>=TdSDj<;Xq z;F?P|Y&dbdOr4A4(Uz8Ft~$15xQOVrrBX_Ebz2~m?l}S0)X}VO9l2rROk$>C0sT>3tLM+O2DVzyaGbnaJ`ZD%rf)ga)ML>c zN%)y%#Fa~Yd3JG<>EFzzq>-ROk1`F8)g3HbY%!Z{hEhHh1fU_5e`0~XbCiG4tc%^P zQ~v-%2q(es!U5vP1)%=`tgHahzVq@E<`Y*OZc^=Tw{4n(bPbNh$?iJja^U>5wxp|x z>NiQ;Z@*sZsciY?qa2~^CnC}OIzB+2ON#}}mH6i7$o={ouQaybPntr4)Af=&>iDfT zU58_N)tOctrr3$Gd54-ji*H7FeHWQ^J1M6Jg3>|zM@n6twJA}>PV$~MyL`G^%m%r# zIJA+;&CXKd-S3A|bP?SL%oy+X6ytaudm&`pKmZyi&XA%pj_?K8e{i5$2A^k zUlG3KgzvEKqQJFU!;fH?)vE7==a%yZ$#0IQ&M6_*T;m-Bnp|U;DLZZtl}2?%{Qm$< zwDT>sIWwfQXn~8OdQiOrBQj~>s00003r4sFu zt#NG@D2>|F0xOAP6huYf%P2w*6mjY}f#p~iZ9m2@1i5|E+&1K*4<{Ug$1S{0>ThxS zYaZ(3XI#@|8f|+iwqvx{YpaMYWVps=cLt8g09vHWif%7)1;<>7@YLixx#~)p1Sqrb zDM?d6a_tlGCvQE+O4OmJZx5zi##Y=GmC8F#B&52b`{IG{@*jOxpR}Ho+#W>OI$`p3 zQOkAN=06#3tL<&njw8ciS?SYzBo0E+ooc0op5Wd3L+DT3_;77iy$;ElQ>^S<*fRav zjx*q;>uVobqC3yUp;{Y*=Fl=HBstbSl&0Tczbs1 zH0+A*&OLi!7JFX>$?Y9-H)dVjfWu@VM7X_x0QdB*E~HG2V7EicJ)ow?zE77S3CFbh z(z2)y$|?c0xDLvipt6vsTmhu-@SvTk<7B@1mSC8~$N*;dePu)0Y>cBa=N7OD?MQ^* zNo^BWHyqA@c3Y|V2|SW=ES~4>36(&`w3g`=taA~Z&C>IIoe54yeqRz|A~P!6-Z&lJ z1zR>$8&Tu{-vd=~HuT2tr}sP_1iRj6c*rG3P2T9?kv44Qz;z6|`@dDD)ZufpNC*K0 z0aL$cCa&t!xf3!ZvgScMBD=2c<2^&N(J4(325s1fd{QP5h4KnEOa#S9tP2 z)WwzC<(}4%4;gaUj)cvY4{=Idbqpl`084Xf*tfWS3;MQJ;0t?ZRRm1+gfa3Q-~o0g zNdq7lBmst1N|eLswzWgss9T@i%xE)+9r^FgS+>kd-+<}VOh(e`-DcLqZK^`Q871Q2 zQ?eF&%G_|NWcjMAeNl4%0Dy7@&79wOe%PAXtu-j!xOLF_tly^u_ z>IqKuVCueP#vzTi#AshBUoS7F6_(bOaa7qmg&s%hGJrX_zT~8Nky&^S5WF@NxmQb< z%GoXH@izH(DdJ_w+hnB!>M+>&8$jrkjr++pfoFL8I%jCz_WHu^!?P6$Ad=ztT63LC z;5SDW-m6GeaJQwF5HlZVSOb|J&2#=~GML3esZhg=Z2poL}Fila88^nL0Ct-&YJnMyx(HdhQ_A1wl!wzJ-R>vEv2>=HUnUv zgw-*qa$B=qG5fA6>(X^ew%cymW`fam$@aoh7Zzec0jR`$Vb=)Yy^43-gI{Jm?HWeN z@gO#c59wmwJQ?J&YHOX587UGOUzy4+>(`{07qeamqJ}%6sQ17 zhUp5}Z!C$D$kh4qq+V@KW|<>06sB;7(73idW!Bs%E4wv3$4!-wcemMnGFTTw5~PD2xvk<8>J<;cjUd-vWHQyuCrs>H)7e=7mk;( zIK%<~b51VHfer+*ou{{}CpHR@3eCEzZtAqs5DaJBa?EIRIRi1S6s$Vv^UT|^+>M#q zS!l=-mnYBUh3qJYRQwd zrJH`2;8{iP4m*E{xzg;UOmcC_EqOZIH~4m4a& zD9y|uXNrAd;N3A74b5{K4OVJAY?>y!#xMj?J6~K(*cZdA_#_tf-^Z79Kj=9)wVX+ZR5?m+YZcwuK6SLs=3g# zIE1*`!qBi3l>@jS_|ya12mNI;AM0{|!?jYNO@;7}{T%xK6aLp(@ctq2@--Cpfh1_A zW4QkS(dqvH4%K<)$7T(yYDR{oIQ{UqZp_{ngcAMg(a64+@&kL@8RXvvNDI`vkAtWbO(x(<47G;N=Q5GJUVyQx?LaN)d6B=AMICJBdnNO%-kU1@i-c9vNw8Vzgm~uCdDcKG3 zVQs77QaGQ+ZV3c;RdwwC@{^=E!&d0#T=5m=S0UvRmRm%ZP=pm8yCo?AAb0^&*jCG% zuAsw8;=^00Qeimh3R-vSHy?`!$W%Ak%5+1cxV#QQr_#)m*{Ez(J?-wPKU?js7R5c4 zSTisxQp@%$Yxrf7u6tK3Coapq?9o^eEl6eGe3a>xFKxS3j*0X@?nwQ09?Niidpn+} z84O1h}hhh&)~ zG`Q$ÐYkD3koX^_OW{<&QP!k{kv^Wg2N5w<|RpOpFeQJXA;U!!FYW2)d-TUa`=4 zvzNq=vg4<~T%(|T-~6Gm!1t&otXR@xO^nRgQdyH4LIbS6%YkjWgWRO3ZWIrgrxLcE zYny6IjkpNhY*M72@#IIPS2A?=`-4e(^`lP3j>FCurs8v4D7H&c`bvzkxrFij(b*{d zB#!#DU2U|od{V2&N^U&I`?Jh;IEDa8VCyJmCC&Da@egl)ipHT)oHJ0YS)!!duhj73bknYRtH{t7%ZutkTyF$Jlv-GX!KtVB@9rRCeWI1cosUG*u%u&j3ng6tfA5|Ft| z?WW9q!t}Y$__+JZ_?%bU(02d@Z^A$UAZ!u>Kpnsi;5{h#ir=`sFQ?Q{ZVG$`rZg@; zX!$gKmbOZxPK-Mn%)5^c;k)0&l-9?}q4^ZYC0tg)C$f%sB|aPYpSGr(A4-N~5gdth zbwwwG=d?laJNJ*c(X*P~w9c`TTIAtcy4YD%waZ1k$a%ITt7WCNZMg%dUk&_rtPQ;@ zYmLKlP0bGk{M>(yXTyEE5BvF6t#}liPY?1{S(}6{Re0@k2^1}vZRzaReq9Yt6U^mWdo>8tr`Hsd=AB>n21<|`EPuKwoy zR958_u2k^C)Z328VStc!Lc-J6ko&jazL?5O5-c~A%wVd%9Ztb0Fo8l z3P$}6agXgWtDh>W$@^O5Mru?LP$3e4PjT%&hTgokuSnu3&Tq7i!~HrbDrrel50M0P ztvV!35~sBh2i_l%+3x2=GTH!7C2#A#!^XOfjCAX0^m^6SEMs25n8hOrCp^K^Qyg?T ztnH(*s9#hSi6EV0f8wh>0MfgTkz$Y9Ww^`PJ@M2_xOQsh%oHU&TW^=vDFl|l zt9k?mU^CgaRJEw`am9Tn72-`d6*k*zU7x?k}g9FTyg72;0k^>|h!?8^?d$97%E zEwV;shL-b-UBRD)w2joQg10*X;478dH@ACg?e{eSbKYdY)aU>l0LTs;){VUyR+NP{ zc)hUtAw^{w7@|>d~mirc*cN>E(zQH`+a^WE!?!#>)bz7?eNlHg~K3mkj$)`={ zqIL6Ft^KY=F$hd}4Zfs)55)(YO~r8p6urx3IPy;Ae-F`4)d171-%PW;V9NDhE4o{( z!qgzfu*bUGnQ1C)GOeZ*DO(qKled3n7Dq)@sdd$@B;7I?)jUo&7*?O#gL-jl^vQl!%6wDw_*I9gjdNtcjhm;)x9dA6p7KEiQ2YopT~c zJhUD=#6$DEZuex0hx4}Xc`dTo+P+N8?Fj7h8yMT5l7`3bmVYT}1QGPmd+BFc=IQnx z~cj5k*dwk7tp0WrjT zc=8^%0~sr`OmxoaY{D!#PIB`P-R+ik7_q{R=V=zu+Q2J$3hr`KrA147!+r_a1HGca zjdwdZrA4|$_!hKuSRT{u<(h5LZY|wCookfzNoq>DE!$J+ zM(ppgAZ^*7tb`h$wXOJN?)BL~uxAY3Q57;KThWfbsc==d!lk5a6qEEx8>IB>S({^I zZCk3&xTwb&E+QKk1n66s!bv@#T-Q51PH}RvX?1N)f|w(A{d{=xAP_eJ3eR?Yn2fm^ z?$SV76og4^eA>weV4?L->Zo1v$9Tq^hpM<#2W8G`3oPUohI$*+6b_3(?)SDv<+JtE zyJx&?7}ggq5nPImD%2Sg3o9ikvLy*#!1ejZ2HtxbVh(ecLbtOb)1$fbi7X{+L+OOu zWbM^xQIWb&j@wpEwT<1OCCzA@i(KZ0xE(>42%lj9f(Qg!salm?3FJUB5109jjLM8- zkZl^AH3q?CyEa(KWm#`F>zipL#B=2IDXA&n=&cVjozu{Q6q8a08O!x^5U}2&Z{cU$nzf^E{dckjsmYNw-@jUZN^& zWIqw-6yg&!6sbr{D_CD>2SoiNwN2+`8i|JIqc2XCHx}8OE+wila`Habz5+vB+z^Qh%wZ%ce6r%ch5T?V5 zC_4fwTW-Q}+@k*gsn?v!)~h|%$tDE0XTrFXD%P~xTUyI9hQy znlOuv$T4w!*Wkm4Yi)87v21O=$YETzA=MNBM(V5fLi=qu7<(G<(APPSX(Q`cWZ`iI zrGdmYB)P8|It&%Mn?nQ=B#;O&dn9iIFi6jV92EOPq)pO11_kd?ab2w$Gg0np$l|Bo zL_Sq5SM3UHbhvTg)Y1AI&B}wR^>MFX& zq3gt4e%AsR(}-*_Y>dlkbr%QJ5*kPWKye4Bc<%459k)rf#}%DE_kiSBO|3FoTaIJ# zt#d`LOq18N330>j%RVY^WSBi*1}2Aa*x;WfKWi3O*tdr9Giq&Q~XGmZDP8t$-81IGqO zf$an4L~mAghg$Th*+S5rNwO55=yNhbis=59w7w8`9tXy#d2t=RMjmYa2Jm2VPw19=d68nq@XYxwH+h+3i9XG6X(ci5 zQ2PWQgMXtTph8@0!~nGPCunSJTJoD~w(M@(?H|SV%RzE((Ah$_1a(Y>k^%7>cU5bT z=@z->xGpt5!9MWp5fPW|{{SpxEFqWY{SZzrQ-782?C|7zwAk#m! zwsO(`09D#ZyJ}LhRPCnSGa*3V$93GokM@;`{M5E-huwjp88FB?5Cu}=4(`vjT>ew2 za}ei!PvB{pHsT zmckgi!EfSO>(jM~vERi^dDSe&6{2V%jJHsw*jA!Z+0MY#Y$Ja0&O%)u_NqOo+Fzp4 zFf^wEzRArpNw-!l)|httlx6nadqtkflmqyrtaa{Ehm~q<{*GF;{*O|%LfU|`dY3do z%o%wC7~mdr&>hOPeW6gdtZN%B&({R^KpVk@bK;r4F>JxFI+c`PV#Sr7T~%@{A=JGs z5PCBTIv7DKQ-y>lWC6b?O<`(vlJ%BgH(bM0T$y2M4WYb86o{_T)~*UtebI7)KcXeA zU;xPY8%mYlK_RbaZ4SV!#jRv!-3UJ}I%eCJ)2=QhrIogMAuc)bIN}qyanz zOYym*P%=2$w{&g6VYaEpaEJ;?=Ostsa_FQVtmjlSztL}Xcdnwkt!-npDps;2ieyWO z0E44wuuG#R;V&|9wa_3C_tRxc)Ee5++8Gafj+|r;Bu;S!qJ|XgK2D_Ma@m`G%R(ew zAL4j4(sxTRSwZTS+1W|s;=XDJb`#>nKR^;UO<0)0nGH*9GRv=1Ux=%OJV8 zUIXg3JqPPGiL|b2SzkKpi-^SzZzh(EAVz_$ARYswyhCO<4liuIiC}swNVy) zuH&bAu_@g`l_KwNgNm^kSJ*b?AyC+Idx}f9CK0~{6(prjvLr*z(32RX(VT5?7_mzDVjntw5s)qIK?V3uGtyZ(ng=fyprQ8u{wka&c zf)oeMMR<*bo`ksKhh*}Uq^TetR9UVMp2UNOW#a1=W~AKj@fx*+EkX^ghNHg!0LjsZ zid^jYI_dbFQn%=k1x};ttRd>JbT7jXaSSH zAQqi)yItRJ0!}i_eMELhh?XV(; zkji@ZeTr^6ftg6%xlwG$3M!0|-5E}Vt)^N|!6@eEQ?WW7WVUElIbpiuOtpV=FtjNx ziRys-r!Mo~Z4dS{BoV>#q^T~ExKl|So8!b)Z*J7M7V9kiF3NHnT(xBvh^W0}?aOh+ zxe%!xziPE7ci1epmXNX%wzpDNpR9UoO_Iy%BI{?;a~fcfT4d-&M$F=-&~dh&h2R1m z01W!KCX4#I?ga6l3@3w*h1@4h%IVD-fQ)-jqh1$hL|dL;A$PRfT2CK$WyGa1BZ2i$ z%v3Os)8|$M3vpp;n=xVOu8o$z1Sj$l4OgQ{taQJ2yuTlC=AQQwGErRwY*{h; z#-B(VO2jU%K}P=oNp|-~ZwY^AWakn5Ls|B(^p{<-8+rIy%5lgyhWZY&A-br~f7o%8 zNW6iH>pnkxKkdP`0&adT*9}6e95_ACj%Vd7m9Y z8=o%Omj3fwRS%_IzOL(eAJXl}YwDkR8R>-#C z{q29+no6WYUCZ5~|ZM(A&iEF9G+|{{V`I zdK5n6RVKgYc3eH&F65_^D*pg!$s$jSXklshsWF{O;=Qmv8%YnPA!{K!k=C?!9V(Ys zevL>;dtTV)SU{dZ&`Holm0t1Uoi4G)03i7hKFLSJ(4>MFIOPF1=eN4nJdSNO>p4oJ6b|?5s0)SB#+2Dph>4ax~gr3r4wAAdw{YGRqhsh%UOy z)Qit$qd(FypX}-1-J+3Um=-sJ$B>&Ocb%I(LCb~GYo+=32R|+MmGLXlBV?zz0b84$ zvfp~P%(C61rbHWrcOpt@UCU*}l&wFYn#oeyQi_^bEvw=|^GWIt=-P{M;6-!QE^Ah+ z*VJ(@)=AHomhKAsX|avK(UOAb<;vo5sW?skLY}?g^38^0t3MQmP>`sSeM6DUXMf5{ zs(LEA%6Q>=r;q0dP;S|kAsD6UTkMob-AlJVuk6c=6XfDWZnNy~@I9zjt4*<2jH39p zF=ex%TubgJ>ALE_dXC=vxZ&;{2I?SIo#nIdu31{yztap+pa(pBngIpbO1zL?k zMwx9x>gC=ycak7;G81|3YUPP=@ho=K#q%x5Qnsy@msPV}m(g-mqCXcE0G>qkQTP+N zHB$Q`v1`Rz$*^}V@fo>0G5d^LgH3jSE8E$U3U7yOQPnImpWdT3HwbKcsP3XoQGmk)Y@N|#Q3P@n93LnLqv&(7o705 zwCjq3yDlCx0me89Xg)A!E#Lu^lXYq?K61@I$?=o|T)J82Mr&iS?Mq56{{Y(ws8y#3 zv1%48WYuhH?JE(y5>?6;IVN7SC>A(S4VjzX!(IE1(GhV=gTeU*O721#k!xF_Ca-2Oc&|28=8&>mXqS=a41M zdT)4pTZj>^ftK1%mr|r0gL#vr9(=u%0b129ggL=$bv`P(It&lr1VOh}fcU+g2g>ot6653R? zl$9t}-zRZ4o8^<)?@R6IpBEN+3yBHiyArOcZ7V~QHt1u-5yZ@4xPd#FCTVP?ezgR^wN7Qso0na?D#%v1u5^{^MzS6bTI|%Cy{FaHRv?9Neu{6suPuyTBX>ImTPE8}$$~1#Y5a7Clwcd{I{^ zPPX!`FfJC`v=;(pTjhx8+C+B;a)AU9+@ZQu2(42ypUX=0V^@%fc{+P3_N$~Xnqu+p z>kDpdGJh8#wE$dWy*rXON$CsFTci46h2a9=;>H!2#A&8ob!ADiUdoi(nNp9@k0nSY zJM=@lr@3OUETER73c0Vq64gw99Mex{9L~#+#$0%4v&d~qTb-nt zavv!|+;PQqb@vL6~huLG}nBBd&P$P0uTLhA&sFf|nto2&dNh(^7 z0TO={Kj&S!mw=L>uIGo;EKm$M{cOks*Ffzyj)N35eZja`< z_IeX<$gddiVTh|&G}$e!DIV!=K^^Wm=xt7@eI=c{kSfK;bpF=^OyXgA<+3hGZ@DtL z_gRZ*EpE-1^U@zy{Zg)LJX}vu5}zuKYRpgDHzK^^r3iA>VX@E2Zk?govgu{!ZEPr> zgX8UF?0+G;&%-rh`e z*9P*w&ZmIE560~9wwNsfn8Z(J14$Y7fobhHs8}g7yRl{>2J-CtS#Qke!#x739EF%@ zP4Y%V7dMr5orhaqb_9Fmw3wL|Hsi@E49WU!$c(a*rb%AJ1t0+7O+Afj&G+SJFzimy z-V=`J@_tpd+_GjAV8D>#iI&_24V#kN$^q7b$`T5k>9GQ=>;C|=%&dk2Z&!@wMbkBg z@@HDE5@Vqj%WR}5Eg^^lcF@v_LRZU@mXdcn?NB^UBrQgVdiHV5Ae$!3lRnc5Ne_IB zaX=0^4IH-)veSwZ7(L61@{{!r+mv>NhS9fmYJgRHW2|ryg`+%eWDH@h?e>}s8jLtt zTGLI`E6}JI*=RnfolgRHNXsMlPG(A+>NcO*upKnZa`j4`kmhr(O14MHFAgQ?eXPYM zGii=C?Bln)!(=p&Ht{|6aNF&1>~`pOIEmuLvNJGR9!P1lwx86IT*NGQD}}EP$FQ55 z_DjRrYG7Y*+l$UhvctIBT3p8z*gL%)g%;Xz0kv@`bq9}1wC`H<&IM_iGgmN+t}{L} z4m!^R@0Qz}z!H{Y%s|;HTDAc%1SoEM_O5Sf?AyzVbnWT8t*Bh%83$qPwV*P6P+IuG ziGUf97KOFV8g6skFt!JD=1iVBjbO^JJ*FT=&NVLv#jKXAOSfw@_=auHdnLvrDq5DP zP+tilOKs(_rKvr5r)2@xt=)oWtY8Aiu$DD|s5P;fhq z*5e;C!X~k6%`C}NF(ka7-1Fmax}Zw~s+m#xD*8>UWhcZH+Ap=MX>VZXc4^{kUz}p> zxy1g1i?~ZbX^&VrCCSi_UO+x}dMRyt7H5|lL3TWtU(2uRo=AZ!vn004c}7tJ)@!<)Mn=JT^H zSp~cl+i%v#15@FX^+NlcY@YU<3PJMmsZQh0v97mh)(ceY{Kcg)?Qve5={^W)va|Y= zS=QBS(W)PANPdDj?&c1J9FB!psK!09Q$<8+zM#K~Wm%Q;Rvg`n#qkz4GYIhmHm6l? zZ{5RBBo!(miqx~oe>9~P07k;P198PGK?+Ke6qN!9QcrONbRhYPf#{`xz|ov;+j@%A zuU&1%<8op4!qDr}R;ezfX*-1t-rHl~1z;Q-EYkNq{{TqJZdmeH+RDQx$ti8L@4A(3 zkiGrgUFWe`Egs-eTl-L2?F-6{VGMgt5xI^G?6x;BkO$~{$(LvAR!zHEe_|_aR5@2D7%^C)?jXg+T?FS)b@MHhf}v z%&Qh|6(|R^`qF&KKFNfhKDDlF7w_+{y)h#pq3ETaNA&?)K+}i!rS0d$*0Rb&$d^0) zwJE}%;-}#s$ZI)<8s~U<{t~Blpe*nX^{up%CHc2Q)IRTI(f*c>gY=!vWL@O0CAIfj z>gs@Lq2{$6hw|_DQ?VkV6h>k<^jSbh+DCc)G=SlzPaM3nKLl#zV40@vSF{&TD@&;oPzO&U|ooLGp-80T@%0l zjDf|)C10Ak1%!T@*=X8@*{sJlNaS*CvU`jxZuqfy!)IgS4`8xB+G_}HyuVO0yy5Mf zgu{r#ZEEl=Mcs1NpW{hzr+?{H)CYPUli@S` zAP*HSgL!eZHstq|>{$vuoTYcRvU;}fkq4(Q8r@W83cbO#E?Pq4DP|n>C~gwq=!bUl zQg+|;)_GId{{Yic-nMuX@h&&{$o@*ftwp2T>{;x?%L3-an9h{MB(9754pjIN-mk4yfU*25q@J4Hf

#Bb4J&RB1J$PoG!e*eq?@ldtBbARxLz!=p9h?b`!R|qMu_SZQl}v(9x{}8 zm3Gb$+aMm(sN!ksrm4T1>8QiVlM?UbwH z+@4kGSGy{n%cJQVN{Mt#0MbJv6uHEIx_7QA{h2KQ3_$bLZe4Qh6de=3b2?-{NNJ0H zMDC1?Z`u1Q)7eq=S~t37Ig2tJopG{0zR{^+N!dzbLi(G6cTo?h1gMgLlmZQ0P;%=Y zZ;3_8@!3myE3(>3(&`Iu$9XOQ<1aRTCfz~YDJQ7)1R94<)=Yy2w@b(K`vMi4wAx#- z2raAJa(C9Ww#kfo_G{*}msnBV1Cbl2twC@sBQ&w#((`rY=rc zp1!3@=+u;wLr;EA$X?@Zxr?edJ64~i?MJjcIjyGevgVmFmO+Cc*X}OD{DCy|>Asz_ zx~q1QAeRl`QC|xr@&PM^2R}_ z8m*Bl4VQDpt#?ZWozps1yo;o^8+J+?b_*_aVJjs-y71%@kV1YTRwCqLKG2w3ky(b} zw7B?8l}S%yKG!{?zZ9eCGbN>U3OzhV=`|9;?ib8UUmnGEBJ%eIn z9avh@dN&{?Wh-nQlu}flhha-(P;HG1p3nMAT4jy^!$X+q4IqaR1h^km0C15mY`0G} zpHDxlCsD+LHQlaJuN?uGVc1QN3b)9CX|t3@Opem%$7-i#NOxhhhuv1|kWW+b9fyD& z)f@JP?X4L6ZArmoA-tYEb7zi*n=4J$ER}X?gr>*SP<3f>ugO|Zfi-Mgk#Lx35z)A#adCy3r9KIW5IN2zFufH`NV?$v8>`(wI$g4%K|Fn)Ghr_(LX zi=B}2T-a*};{3QlU3mur%h3Ny(v`r~t z#g@qn9h;$t3P=Sd3fy!xvXeeGmE~*9AUVSY%O<5av;a~pcXg1IP21`+@#2TP66Y27 z6IIu=kF)j@5^U{Pi;XhmsR2T5wQe}ecUey@Eg&+s`nM#lXi`803)w+HZ_2i;O{Mwt6QMC2fNF2MI$L;Z~R_J)#Je0Q+m{=nBEBTTg&#}IKn97NXn^Gnx; z=HsJp%GtY`$otVpHxAXSmC8n*;&v--t0#{L%bhacs28W&vW4y02f3)U9&@h-_fgmt z5C+DxM?~#hVV8{~QelG#;^1oH1NLS}01`kpf0Zf$og>R!`3}q>JRHp*>SA$6EekB1?H#Em1(#MxSLmCfT8$Y|Sxo@aM zl0ex`Lv>sGtb?}X9ot#&*q!T~@A^Znm~I;)J@MRvHLgX%BL)qRA&TTDpOG=3R z)o!iA4^TaKtki#u5tw7+s27X|laawOvxJG93apMsCo?oM&C)G8UbmQ*ivG#GH$e zv^{Kn*6x=3%99FRwcy|4-)+J;p1l+|N{3KLt!3w2^o8k)wXaev??-6-^LFh?-Q8VTYdcG|66LA3x3WaxQ!G|xD7OMc$&I+={fP>32yAQ`>F}?S=iWMXs$Gk{ z>swyB<8!^EtS;g4WhJDpZN3WE6^NRWe!224|4OPjnMK+T7QhrJluWlY7W+5a3&3Beat)V~Im8C(TM% zLH2i_4eOb;@ZEJA9EqtIwl5~bj$-e$wclla@FKgIZAjX7}GZJ%wJBEDsEIZx-p7f-6)}JAnjA>?7 zg+P)P#j^#)?#;B@C@OwZoRtNpgK7Qo=atK0-d3`F=&}6cJIFFChP6)8#-v#-P$b$d zPK>4-OhRF&RqyhG+(-@w7TQuA8x9R@JL(*B9H%+fTYi6e(QA{j;^M8(#^9n{VJGV=6aH-QSYACayHpcrZV~Oei07jp!_MVkpUGu!P_%BPm=(eG@q=2L}%TSzUt<*mz zM(ZSak>goc9cx@}P{x*OEJxGCD7lY^LqPck+&qZ#9V?Z&u)Vx+cxzm9fY&s&pa1{> zIOI2L&PkJ`%G7BHk~_hZuDNgg!kA4l?JceyxT)Kmq@CSTw{Q)&D^k2R8|*5a)@?t! z*lr+~hjXbtM<5B<$=94Z%{u9Ci-~8K zJdrT@#j}17KHSL&S3>H^1U8i@{Uvt+&>F7pdWVl?P|96&f%9GTd;!CL2Tn05Fn5PW+BckYe<&3xB#hM zU0c;*r-)8*o}N5;07>2>HDP61#QuGb%DrK?b}=jT^mnm`hnQTx5ighxzFI{ zvEDuuE54V7VHeBYxyZP@8>Rt@TuNP^a+I>Ht0W{1+;!#dsEw<`ecagmA+c0$7j?pL z{CdZ!D!(^<$)lyn_j?4(JI$r81uS;f;u`GHF|;(2RI|y@R1{LG>2d87qS7FuTpdcX zIOFO0w&Xarlohv7=Op|EKUf~4x~*-En{%=m=({G}&SoMf1B@1!k_hdc?R$m@&YKwE zNNuIXJEwJ*PXNHmMu&!Z4j^aui3?MZ;h(i*>|{DIWLs*F5H)?rp^~9CMuF^OPN$QmX z4#ZW5XVv{2j+HO=+{E0L@6SH!uB{-G;kP0-1He^1d(X6^Nxh(8=6KcG+d9RS*`*@- zyJf|v*tR-kAx272g&rYCWjKM<{7VCU_o=gK-`>->w5>(vv#B@-7MXlQp~cWi7)}G3 zf?1rjHd}*2ld08f#sJ_*1ZBEJZqGg2_|Hn*E$EtrDJ~XdGI=ucM^r2M!SNMC`%P%g z=UA-JE?DbLr!B^i;yuM~1SWh}2qZS6(~wZ)o%$WhPTeZ+V=2XDouXw{NB7%q&g8H* zh$~9!-j(eqQg&eeeAk-rG&fl8S9I8;X_i8Xg+vhiH8R>jI~6I_Y=DvHzSZ7Z3#9)5 zwC!+#;-4Mrq}KbBjp*iGYL4n( zjA4W`Db2>|4>j-u+@%i#zSR3v>+LqKm7Kpd*LQn^@ zj0q?6h}aM3se$QOdq^}^?C{UMUAHF&YBiZ@_CK5=EB7HL71rEJt+$(dy@dD+^J&rb zPm5~Z3hpAKO=o!~p1jqoSQ}~Ua@Lk&W$+j%hUq6{qH`mEbaeC_#_cHhr@E}FL(H`y zmm|K~9BJeh+-+$JO3-=~s2+qLBTl#45lma%Hytscm~($o*~c(JY1Fo0gnb^@Sxm#d zhrDm^*PJTdd48-fm%C_tfB*_ioT>^rdGtt86u+0Xoz7jBl;e$TM%}(#I*%Wr9Mt!- zC%Gr|P%V+*N8MXy_J-?feszEU099A2fBeUP)BY7-n})kMeheo8jydh~nH0BvAM9g} zwA`q=-*IQLl?P2*PUoVYxVyKJ1SLCFhs61cK~rPtIDz)4P%pzwvWo|UkG;hmK{~vj zFMw52FnwilNmH3N0UT_3Ar_g=_4F!rHMM6M)>c{UBkCR>2|RnF;;ye{dHiyE{q;Rm zn2GvQm>+A2{{SsCyN$6O7jcZAwbay)+P76Qz|a1+AA3q@wx-?Z_m9m}8nKRQV;KFV z%f70_mfG@r8*Q}4F2e!0l`R1-@-C>JD%o65L)>a=_6w;Fy611M*L3{hKe@5%dtN<9 zRI8<6>qXAk?lomYHwG4@<@N$XRu4fn8`SQ7;v*<|$i&EVnFvBst%={fox6vPU8{R_ z=u}~hlIQ;bx~IXPp_hM}iL`0;&ysW>Y25j$TnB_Jh7sU!-0D~+XE zyIDb!1W9c+=cfjyx}CdNOWns;ABkFhJ<_XX!%uN(vU6Pg85TG$7MaRqGSWFPgT)9y z0Vx~o0PY)B%2tD6uO(|m&n$7}fEKA4MCGN;zb|J7{{U#ME}rYIl)%8TdqVPeBu$58 zfWz!Vw@Q+DIp;E{NPXBy;-|wNLh7I zuvB2gbB8oU@yJ4DBVuVbaFYXpIxS4i9@8Z)Q6yrr(QzbmTiFEn5yTDoZeRGf1EBAx zt(rBX?6AnZX~ncEM$V!`?mp@qC#d99;o`2n#-EX!UlnJ1J-b75$JY5DKX7UsQnv}% zD3>1GA1;)%WO~Gi>tUT1LZTD^5p~I;g`?1hQnE+gy(6maV$3GwjQ89$9|@`KKJ*6K zgFRpU1pffqwyo%njkED1O0hAqE9SdvXKxZoSBH-E2*$MYPc<(ki;>$fOmg3V*d-S& z>tfc+M*jdU1cgXafVZ-CQa95~m#iADo7N9ux8nBvdff9&US4YFDqB(& z^qsaL9^9~a(dNxiw=-EkhyrbXZeO%e==7oPGfSU$zQy!92V~J z8A~W}NE}MSl1TBVSD|mWE55y2h3yV7X*K7u_FC*sXb>O==Kw(t$`Ox!n*$oyG4_ZV z?;;5%b&qJyZHopOmt1b>)g0>FK3hr_i*=2m&5I;eCt?yn*~x@`H+(*VdJW50`sx6Kz2vUATu%YxJ*ZI75GmEMUNC*mZZd)h~hTT?; zm8Dlyt#NP$1`q&iNN8|0z!)l2A`9e5#7<vETL3ciyM$`!?y7nI9~>V2^y3$IgY^+Y$!b?n+x6 zS{rwO5w%%1iy|#>(A9=}P$H=*C`1@6$4?&d(ExeX8hcA+?BO=wB8#wi@4Tjt4M3@wkGieK*(Jjs7I4mz*Oe$>K1Trr(p=F}0-=_>GTh z&igdz@hMjm*P?1%y-U6)9A$_aF`cX~I$1-~*oh4Ul=qI&?e^P$3RX|1_bUwd^6fXt zO@ie;N98E)wY{$7cIad%Hp{*Up z%c=OLG~SS9knt!Oo>1$MtoI2src6bV(n8pW+O~wKDJvYwM;mFS6{F>{H$dJNzmH1tmkM zAQ4S=-J@$#iFF_t=CI@(NG+DN&BXF0q)5v=!7xfuWa@#yILIV!jPoGFbU%*eU*L1~ zzc#i=YDS=9)~ltObC!FXEn|z2`*K>?1(hxEf*nCasZdA+FVeZH=F3iGI;A=`S131o zb<0dy(QT2Vv3isqYu+R9*;j9zhjnc`p&G}JzZeJ+$>-K3Y5c%F^IG|!wlvc41oPIuBN*~4B}+Wl5@i7 z-xC8k-w`2PakQcwgeg*WcTwL;R9jL%2on?}j)f&HHl4Qatar3GPn>YMKIwD1D4xG7 z4#J0SP1UH!=f*9O}ATvs${J?(IiK!(I?BtQUf zc0dhE+AOufjldD-GsArH)kX5{FVJfiCq7PDhv95mkbsw_q4yCbvQNSqaFw~*6Wo-N zN$@pY$ANZk*%7SM6q4SVg{-){ZJHAq448w7Xxxy(Ly2Rd<=BAghQ(lYtA`lCr<=n$ zuAx9yLz1)B8%?r?ZFX>eq()SFufV>rR(v>)l|M`OaIoW6HXBy^g)Txt2hZUf?^lN3 zeTeEy&2utb=j787G~~!|`|vXZv)Kg0=t8l_7&!s2>qbO!_U8(y!L!>wnMZ+ZkB}na zrgtUwc>VRGxU@L6bb~Ev=vpM~+sCLs1D$p?WIuGZNtbYDbmvst<)YafijuUXjnm{u zN}x5*Ho^42CX))-rtR1J(;cnu(GDNJS}rU&HUi-pMJeX7zRokVfOR|cr?kZuyt%7h zH*(bc-y2oJ5?!-fepF~hmlKkLQ3hva&Ge&Uo-0s!Jb0*d-mTs27Tns6uHfbei6o5O zzZ3zP%1ei3!?th|L5`K_r%>96ByOEIMrTe(Fme(XL|Lw@Q;kQN8e6V*fepHdt3A4R z`de+#`P2tRvTI$VNU`QFZW!L?nUR!UEwq&OQy)*(Q1TS(Ue359t~M_dvRkr{v9Y*c zraA%N#Uao>?W!T8nn9*_9C}l8F&jGWuy0p*Yj#z=xe8ltPIV|FwBNZrN1slC$I+UgD&2g=ayMz!pog=*EU)-cK&VTw|bGNIyI78qsN)47TZPU*#7{& zS?;jrH0+yfpf(FiTT+UUN5bQ0qS8nsxQu)^+J73nz)TC(mhkpV6$%pL*)0-fvmq&0 zA9{{LHw-N7K~~&JDLvIrw=Bk8>)J|7Gf|9Vx>=b`zfOwem@w{5H4T*^Jv&E=oGNXt z{<4xc?j35N#<9$cA=E4GRg`H~J(Jwv#Z98>o6XWQFq3d!7l>uH8(KqcHd}2e1gsS( ztwl$rucp~f8G+%sMcVshxW&rs4X$oy@`_UY$K|C3NLrmp*-_je1~>rNNdN#rtTt|! zj6+T_ycWv*XLSoRkp0d{d~t3x$JN_PnEw6EG0iFb;)c*W>tfB+_HSE4#@sZxhGQ;x zBt|DVIj#{+tpMb64r|HK3bjGF!urpc&Hx#10toeq@`5?4X4$7X4fim}G=k2}g2t%I zH%o`fw~Ld_rYrk$we|sMN|2Q+;#Uw-3PC+;{{Y2whgNarwIYACRmxFepvoFuhAYi9 z(C$K1@hSiTx7aHJ<%`-hw-v>83mXy=fICVaiQ*QKz=IN~J3womO2tZCCH9OB9#k&)9#(8%zWR_7_XS@KLTQr(&RZ>w_kRf-|VZs}fF|L=j&lw32^9QoP0dxTh zTx}H`+)%@u@ge~Od}+*_hZTnc`;F;7f0|hF+X0awadFE=JC57b1G3&sX$~nV-{>H7 z=IhJo2IP`X#&3$?bL~#tvy9er7ktX&5yqa9%1DiEjl_`-7+26~KUrsGI7cOP*a#j4 zHYT6tI+LC1mC7{ht5Fo727*qpEVll=M#}I!YXg~hhhtUJS3H6f!2(>Cptv7u z0Z=5QAxlD}4}~gM!MPjM{{R$;Ytii4>z+#@bUP z5_cspQ`7BiTlH5=t?aghmMz!E(w3X-+hvqQWu!FPQ_%=dX|Nkqds|tVs~0DEFZu6xw9BH}DwpSVk{Q%~Yr9ONcP)~ZP{cA6}n<3b@G~8TCnEJY6ybfTUDusafmLf;BgaDM2V6bQ0j%D#WK=QUAr}o zppi$VIdzW{_F#*t&6XsrT4mj2p@GqJ-F?M-sz-*;xR%04n5j;g*rjQ%6=cUJ_LAku zgeA%->ZfjQp3+JC6yncb;1VgDO6Ik-dz!QfYuN6lp5hx!=LAF+fY5ZDZgI?|x2D>R zrq|{O#_j~WN$({0KntF-O(=n(x8>Y&oF*1aidwwZ{%8XYq^03F&?UhrJHPyHnLg6?GGK}l>AkU zF3G~ZP*+|nCztc!LX);x5`lL=)jakQSDq82_GF_fXOSn89}K`QB1J%aIU zOaw=MLK%4~QkGm+!Nn+&R1b|{U%Kka&ZTihq>Q7hYfgHZU}-bBgP7%+yGN?51DNxk z{6zc7_bS6wt{Sy`!zILfMdUYfwZ9(mZJlYzB1~K9y({W zDiz5iy>O+V@OXY<;^6Kntp~GqEe$$Y!`UR=B0N{5YfBPBITvH%E>&rHE%--3jCm^xR8BEQB2wcVcOMx&NSZ?$!;-)&MpMW z`vA6b0P4^PEhWUQ19?%Rz9HI!20)G*;QV!v0-C9}Tk5vfkF_LLw$_mvWZQ1ziWl!K z56oYG=ukhVsauwcMY`$DE>Ha~+{OP)$sb#5#X z?>P)MGCe%@ioM(zY5xFml-=sd3S+QVqxh)}@MY%I6aXn%2g<2CV_Wq`tqYLqW-~2t zgSa>}JhbWr#OH*#W~CQ(F=2f&95+bQl*IUHz7?%}ZA!I1w!aw183_I|2v_qOrgQB) zIGy5VqO*0(?ig&e2{)U4wx1S0Vv!KHL%QcU0u`V#>PSpJ}UPohqv$Nz?X&OU79+Jg~?kCw4$GB<9lv)mB$H zFdRda!RHf~5t;Xlb}?Bxj6`i1%=7q|`CiXvQ}UUvMss8FBHtZk8A|9I8-YZ5pIUKnRAo+3Qlr1)d*mIpkw%)G!R;=m-F3wCf#g7*9k{S=hM3pwBt+G~9 z9c*vY$E8^}eHppmZPTpMHKS0Tk8z6ZHzUZI5yqTLh$$&pG50Au^&VZ-p!QWwh8CR6 zfos3RoZ_-8so66S$S1m;AmmKN0NTCl!JBRtOk02NDVMlqFTT62zHm92T?H=Tl(WJX80 zDGWZzEV$ZvM<-yW$QzP&+jCNu^yfKfsHb1^HE*~9*cQcs<@>;`ZUuhFuqnCrE0W)B zmRR;|rbNi5;Vrny_i4?dQh4)F-{V`OVdYraf3-G@;+ChbG4gMhUI9po#j-oDB2(aQ z8cY0pAbYp?j==A=KH{F$y}o;FC}V4kwkWZ~3%gCv0UoItlirbzwxdKE?}7v#(HV@6 z{vl|!YZS^^#ZDu2)@+L*xaAHe9iqyMEx9r#NkWpett$uxlr*HJ#l5%)3EzG82MbTE zxjL*jEnvts(Yr13TGwpJCEaXLTucYzI9psqIO2e9prOnqJ9Fk1K_;Fo zy)r_w+xtmn+x}SUVMMmX-*m)*3=;8WJ>_cRTvx=PdI1!=;JId-<_XI+hcw5Fx7hNo zzbkx)@}FgMjD(dj9hb@@w;|@Z_G=9&lF&%&xjiMl7j-V#wJTB&nTE2P+~PBPLmI~h zwi?qe7|CEb2!@RMj~K8uBJq}M2Bg%IRejm$Aq{x(5BK; z!Gyeq)}hF5w-Tiu{*^zJ&UcUdIc1k^NysPNGAX$A#$=H%vroBpvDB8Lq&AeK6eURt z*mYS~hSfM_EW*TX*xPL_*(_QjGFoJQFSB6)l^*1Q#!Edr~nwb&BZB?2g+e zW;*A(G~!lVoei{!1F%bA_UT6Fbgj*{;zd-=XZh|gk!8B9hwo!M<#<$t-cDs$M6o&FadMojnL+X-9_()p4=`DBc zhSH(8AzNEI>YHt!cXY-%&S5nGYZ^`&sL>cfJ#b+zof^kHdd&)@dTuoZDrA5N!zw z)m8dxk#DwSaY5;0)GN~)c{Je1V-)xz|W;rDw=t;It)oO}fKK;#-g3ukAs zw5{5~xPPCvRe7(-M4c^{XK}Q+i@n~;ur)jI2NLbZ< zGM6a9#9e*Iae2}d<-9?Vw4z91!@H5tpMlN*sROg*5;m#Eu>kuRHM6|U&n%#)@{-b> zc|&ipztvOOG2+gS^hxX`_|ltOFSMb!akYXLl01q@6=KV2)!A0G19M}X^5*VyNMi`^ zAUFau5@3ZLTXx*leB?j?9t27EkYhrqT@cDJ9FI+rqB+TGJmtA_wmWT{t_@FYOLUPE zN6?T_xY(fb3P>P#RQ~`^FSnZ&;zp5YQ(TjJw8!_GMaki^uXdn#rPKcav^CMUljp^K z%BuTBu=(EdwX~_&++W;Uw0>Ly0!jAK$?Vj>zt z`Hjjth+J4p8=?bB9nvHX`5yE&$oHsJ6J50@3yo*7;21fFaem8xwz%|YYwWGC8H5(_ zj`Xk#Osk7s5DJyDPfhDntsh@ z&$qs(Lm7K0;&H>IwWZE`j;=7=n%DuLMM7~BmCbsTT=;_M8S9yV2Mu#4IiAjum!{e< zGmA>|?OBs9;U_V1H}^4+vem94mdH$WIM~@zOKgV1L#K**?49>DIM6u`O3@4yxU!|O zWMV4!{I-glSJ~ADtT^gVy)>ePg!YN}KIj|=O1b; zQsW`}R-<6Zkc`gVeYaWdsf>}zYPh)KP_E<2999y0%2u>>3hB}Nf$=Ep2UGS{VU+Tf zl4(qGeks|6yR!WDk8YIePwCKY zSj+KDD%oQ18s>(?=?5lYb?zufp*bpvsz8h z8)VOqAuSX)d$-9}-t-blA?DsHY0jtUwogJUxZ1Z26h+meS{xYAz*@%^u!fgUWRPhW z^>L6P?RWzVipRF5OU){zktCeR1m_#`4vbykjp0|hj)~@YR+ePBU87W3iEIq9a(2N2 z!Xw*UTXNcQE{0UDheJ1d^_$d7gZh3W$CrEIn&&d z?QW57iyG%XECv~T+-<963Xs@oE5=Ri|RaLa~KGg z)5D$z4B{lU*+$?9vinxhE_D*&4F&-@=4UA$K#yv#HAfK6qOokxF#Ts$>n_StoLn<&V_60B41os{*G!hrA0KTtc@}l!*4E_O zpJgE`jQUCm<~H0bs8~FKAn#SKGp4$QlI9mI`$MyAJuKtTA}kx5R)t&O-6X!sT#~$@`bJn3+&4T3+uqjXuwC7hRGAXdJ z=Ap^=0L#W?gfyU_i0M}l*>IhR?x`L#?LDEHMY)TUS<^PQ-#j+EM|LVu=m4pr>XX?O zjrxGUUg~!vyV>)tOVdjpJ9mM2hZ0T4D3as}4asZ>vHKP`QVAc9k~(+v^saW)?MoZR zy7GS5Ra1C(BiqNU()u|Cl^Dr`TI+)|lW~WImvD8^_T(vB+*zDUvXTw5L zkyZ-(|()H|0egKa4m_tAuwN zi|tCaHmxSy0OoeF3=KbI?f@B{;*)6GwYJVI$mJQHA#9oUUB#o@?O&k1q~!50?<$-HlkCp@b9R6t3*vUXWX^>Fw_{ZS_DPQt=VMtTPCO^kcXN{ zD~TmQ9h*C$XQCD1TirhPmD6iZo!Tw=c4XY_hS(jlxgOgc*^gXbjHNcxnGK|+W#-b_ z)7P&f?~n?E$~LKM7ksJ=2Sl z?q^i;8M$??+Zri$!;akYDN@}jAe2j=&1F5Rfdwol;XvD#>gX<8RC5y97CHJzX*|D- zM(6dDR0}q}aKlTC>DHJk7A+|%+Z4ZK@t#v}1Ia0X<6M-&Rk7WbsYv{uH73Jy{an=e z(j2qqI0ic=0>f7;XUKDyL3`%|9lNol|83@4`KDQ2_3k?q?{t+==iV8{^H97Jjw z9e}$$q+~M0LZ8yA7hz>NJjlzwCOzkHDqWT9Wu_&L+OSnZ~R*{!eYqv&?$olVfR;xU%fYE=9aFri+o@Wi9gvAw)K` zsb30{)b*+XLJ*5Y0{JC3xP(f{{U(1Y8o+19PD=0B{y~58nm|t z&f9FR=K<5(%!Z_-0Hl`Va|7~_Ht#iiW`7~x+*8@&lkm8X*)40NXC(H3ZlHkx11==A zIYDvhl$h%HgpI;T_-FCRM8d(wwSL>CS|@9TX~MI4^6XYNP`N6{UX2i39UTgse}?H) zheuR}Z@*3Jd#7&d#v_DTaQjY*Ee>1a?IvaOCW#n7DP*npN*NBUa&>DxM*9(49H$_) zYOYPERx1q8f3jRv_;E35JEc%!?e3^h@Z~AQbnq;wcvYC}sO;{k5?Vp;Je&i1ue5Ke z(xrvg!yQ*Tql3%I1Q#?;63oHt3=yFUN)%k@NU7Oq4G;$aPN3zdkyWDD*J)QC&O`cf zV{Yn#mk^d`T@NG2->1>ZPyRd%Op<%bDn_8dy?c?*a`tin(rBrNQQB`j`Q zXr72^0J!IJLGK37H<#~i8+t91yQ^tAXl3A#BPl25isF?3UiX~0;v?7y9>uK7u>2bq zv8MB%W;w;?8lEU(Gr1$>$xnBd;xNf=E+_zbC;)_w$si5t1%v2}O>4b3md3;_*rp)L zmYDCd6(#q^`zSQ?#Y&R#?o^e0(`RtfR8m>b*?CSNtED<&7s~caY!7J z)9oal!Z_Fw6(@ClCzPY`i}m|^uw3`cD`-gx334JpOKv0$fJhw>KLFLZjRCBPO-{<3aw)pvgppHTA|^PTeDem z%SXx4+GHLHxoCR>eZ0 zoX#Ksl4tC=K{Ew~+2#zF3~efJ49GGb-wk8L9|H?zkl5jCK6Q1pT$_<27r@ZCh!LeJ zg-FOW=+)Q%Y@K z2WM>-8-4QUtTua;He^S0Bi>YZ+vd!sAGbzXLehKwj^K`|Jwc#cQ?~1DYId#K))o<# zSb*yrz`VGZH1C!>)di*pRKr=hJdz0K1mQi=k?|VmAk46P)O+vkk*6JmY^kjcy}!2UJr%l7NHCX$Yi^~(i9Q6Mb(MwBhjF(_S7AEUaF23fToeby z%k7nNLy7O`q>>G6?fG227>^x{w(BP0r|vq4k#7@R=QhCVzJzIn{7=U{&AtIrM!{f@ zinO^y({gQ8!ZjO71Xy=##fI=&a^(d~BOr?zOeqaIPRm0}O4M6;jzeUfih5L56l>FT zIOG`dPOy*$7CG2lbp^2^drXb=%Sd&1u#ATBiJIy_k>5SXzCxK;^9%M7b&aQ*nLpm- z%L;EC$yShtuN+56@Ax$}BdH%n&JP4b| zig>xurDZO;*TrJ6^*>N-+Kr@>?2zorP3Gf0jDAImHzJux6 zJ>r;ccbI!1cACg@oW{gvOC5wr?3XYWNMeVFRsgzK21kK808Y9{opS)^1nKil57k_m zna!VQD}&KzGNxwRov}xc9$Q3wO)uTZR>~WKciyPxuxyR3NVjR0L2btJtA&O+y`DC?g>`T+E!D-0uS`GMM^`6?jgqk@2lGfq?cCop4+0) zPbRdrfsp3{1CLf1OG81$sU4W@VImvL3U6>U!NsYPPD6KUb0#ClbgH^tn=r#QVHp+^ zrFJV7;`ae2)dmIDE4Ih|%_H8JbuS^kgN>;RNk-14O_c(n$W_m1_L*YkCViserTO$* zm7}vw$|Puw%n_8KxFfT3lG{I3z><^aze>HxoC~+sk(;R>uBwi>z<& zEw*;}nSLtWC|7YwPl`=#HE!Wy$>n27)4P19^#)@}or_P3(pvX;3fWFSET|N&2}#*a ztPr&j5wg0ASB|B%UrLtHwiucm%wf*#;DQ|I7a671mYi9~ZQ~H^mQfD24s{Bd7%>Am z+yoBRDa*KJsKU+tr}6mAeh%V^xY}|lYC~(4eX~i9&6SmcOt;%oIWJ%jW`H(Yw%qIu zZuI9-G&>owBEgJjR@q8%Hb4v zOf3FF+_~K)PMV^t_$2=9isC@+8A^6YWNcXgB&WpAt{4rv_>Ru;X|Xzmbtzl~$lvd9 z8pywP%CrW+Q+rvF#2(X+gR*XI*9LI}liDR)q3Jb#yAUTxf+Lo?@ypnwdb5yQuQ=U? z9025zGcd3>yFm_x0WmHgqE7uAslC?jr&Or%nwJ?XT9YC|qbwGOkQ;p?`l^`Jy&BTk z_lzplZ^SJO&bsB0qe*roB*xl5V*86p?21ob1G}$J3YUMO#|UXWEO(5y&{VC!fso8V z-}tI=N&Qt7YFj0iv;nH{4krmlM{#F3-&_{{ScH6}|M8zWP*`vZ*LP3RydIOjN78 zht52q@a&P+5308dCN~c6YlWzJW^Zh>hEsfnI^f#u?QZ7Ci!w`Xhg?#Y6Z@H!6|ZfT z#XFkc`#)xQcB$Xry}d1W{L;$6TPVCn5g4~b=zuM;P*l?mr=m&f?{?>7TK4-0Z0kd| z0CTYD;2^eOgW4UJ84&kmz(bfG;tMK1^U^AdT!il2d1P}LXf@0d2C-T8%hK%2V(p8~ z{i{;`!c?m~7jo7u{5l1ECB(+xr)kAfQjY=OR<)B!G&1*(+ofZeRs=0iFoO?0 zAP$NywzR2g^C~>I-+ijo_nWd8+BsllN(X*TNI^UK^#ZGV?g4qQ-K6FkO=eB5<0*Zu z%EK)PRi5y#6%d}WWFH5c8x!=>d+MRv)i#I}n*O1~Fk1L)kLkgjNAKo1hKX{RSvKFa zTa0g!nC|7vUS~w?Z3)xnTn^i>_sd$h=q(vE)(73m<<>4;q*0TN-5QIldXC|d0!t`} z?e!B8AeLA2YD=x1(tD3l)5f>B#-ibwNR@AU7?Wzq?a3b~9-SO%Uscp@ zl0AA>CSj1;ahzgA*l^*@$uAGJYqP*D6gH{S4+fFc4~09GtzVLb8l_I_Ym27Rs`r=3 zfarA2hD?k*@o5=@8L3@P*Q+jQ5z8;h<*cq@!h@F$VAFf_Oy%(Ot2}E&YxLVhSxm;d zrXo+fCBJ|zEQgTKYNN?3SR3@K^HJT`D_lE%4---GS$90i$E{J8oPFqWVz3$}OwMS1 zNlIEl@8k5AkUEi7o-u}4a$I9guvm$T%(*yDPaRTObF)h~HfFQNSyGQeA7`HZ`+e1$ zr_Nrzps%y;MM{F1FVL@i^AFsj$)$iR2Lk?`unjvAkw@$;cJAw?Ne$PO;mu zta}>5f#Qa;14!Am%0BdNAuAn!nnMs$Urd+mh zJ8LqTx8$*$EunIxx`+9`mcq)46gYvn;p9bKaB&+Qt_*u+q9f2@LuC=;MvVH<+btw) zlq3Vt0Z^Q7&eWY;)Q%FS?;hQc!$=YqN{Ejs&L@8V0LK2aTyr+%8e987DnAHQ5PngK+qnSo5_?^(MfxaM3+MjtWqfI~p8W zt%8*&eyu;hhnI~(wKv)ZWE+HQ1Wacuz-&`imL}y_3}wYgN|t)F6`hC<9mwq!$b;O2 z-%@+-c7~sO5Wu4^B)=9Ka$5+H8@JlsByuyK0E8X3A*62U+qieu^A6JqQf^J>nbuh= zCCOVVL@AE2lth5vcF?0Mbg~a$oIAKjQbzkz=nbE-}99lf=M|Vnlm0;zFqLeEUo!891GzP@?IUIP=OA-Jdnavb>F8>|B0Bsopbc z@F156`XRAbCOeX8Ev_xU62S7zS`Df>_m#H}?#SkM_<1O=lGdelQj|)MiK!Mxk3_oL zp8O_}8tWOh$y0X=YZ3w5z;j9Q^7E?NSF-Mx$Y|Z8+FQottv$P@+JvE`9lof(Dc_!JZ-iVu z&Fx6VV9pOU2Q2wUDkHuY6&`zSNgQ@0ADr!bymzHvF&w)GyiC;$F2?NPaan&P#hPew znBV(mAR}bVN=L+=iFgf<#j&bhW{0_J)X28DE#fzHUo2*1B;Ra)<;=#C8&C$`ruZzS zl`W<9Tmvpwv)M;gl7|FByHj&xchDoxhK2Ev}@T})2b@axDFK> zF#^_r{D5%n)u!VwX)@7?uF!%dK+3gT+%IHdCJdajBxu(S4)X+SlC>^5_LcqwGpLfD zwoI~332rilxwl`G`8y^nY?Sjs!x;oq=zXi3Ozgs7hX8v=B<0 z8z^tqlG@sa$GBjpRMTxk4mDc#y8$p-$Z2kE)$$9p0gc6osQ2A4;%ISbk%WRIZ!$sG zLA#lVY3(%AeJIKWqbR^JnbXMc24os)&h4I^!W_EOR} z-9#F%R+iAcA6I$x?VLfi9Nb}}Hw;)iLOXnYeQ}j5A3~AsQtTZipv3OVayz`ZDP7Y= zbaCkA>n@x?X}HP-QrUh(DNg5N5y8M!-_(jInlGjcKH&ovoQsobQ%*eANpVAIPW@CA zAL=~?W=+X;nnZHM?GRtPc6$cxIYcFu9}UB)LD+c!D2||BeoTnDIvJ>N-DLqyKW~W+ zSVFeyM`Zr$Nj>9gs4H!Nz|nF}d#(e#;--DTelp;+nKa|}2=}#R$IvV)I75lN=50ty zLIbR2bCJY(Cv}~LMRl`Ft=4TN%5G6}Z29F!gq8LZ@^|Dgzn*Mtw$=zx>%ZCsN*mXV zZYK!&1}mLf?-Cx}@1m=^OTyxvJW(Z&=AN=Lp0nJj5p4n*na{($H|80L&@ey&;~ri;QbFq%^kO zF160-9pHs+&<<=KpUYa9cqU%$`6wFOoG~d3tG4Z!w>`NXz;7IGk~TFbkF5Gla(PLC zsQByWe&pO*1pUQ8)06LzZWH0$EEg!UJ2a^(akYh{`6Q9P;Pn-f%blr8Y1~tcj&7#h zvi&A+R*gGnXHC=G&_VJTGPo6B!ZK_QNG(%b?p0f zwlkwRyH8wWp<$U@rA`2UqTZ)dzr6lVtVdhQuKyqeOG;_ zac4D~D;L(84E8cL2MU^y2NNR{c?2uO?uk7Q*;=o_eQRZ9NXazrP~2>XEpyofc**gee(b7@K2&+i|af-sGC zg34HpZqp|am2^rZ+*uoa=vL;GF1q&L9@hlZ`2%yyvdN`2)5C6Yk6NAupHh!pjQ#a4KC(&aUzU<0`NZ>O`aalR|BgM}fNLEhPQnmfikZQf&Ii zB`1bArPj^*eC(@)(m%CA0joQS^rGYGOgm0JN-Q2PKg9PBVqZt%@_dK51zHRzR%C74 zXIM?Yf2Hm?dY(UVen<0D$1 z+_@>Su-~EF{;F^CrPH!&S^ognPv3%lseKxl)SupW%~86)rw!V1t6bZLpl3H?=1*l| zO~|_;v~0O(NhocW&;r2fl@6&rMM_hlI(CTCZW#@#>2Y`m9OFMAp8o&@jrK|1;0g2T zSlYQPT&$L9i!T*3me`ccbTqXV+f2ljxUGkN-=I|X_jP7mNfBc)ElPcTm_F{Ljf%SW z`fpmbZ}x*M=(Uv`(p>q%OLFXG*?_AyjRRHX%?BY9pB^d|ZqYq3va=rO&X;3Sxw3^x zOSa@Z>Q3A9N}SxD{-D%`?O?>A;g_j49B#)IA0$mETw7}m5&NBfDL^3px_2Sed~XwL zw7$RYAPl`|iR_7-y3!NpLDjLWu*e z$w4aa+pR%LmekrVZCb^}vO7+9XI&-6C&fL^wxA1G1n>htA)1-#)zV^g(+sy;ol>Q* z9k!^RM~f-=6}iIRUVr_*4MQE033;H7)CJ{ zQYl#e`1u4IpX$sg6fRo3&ewDSErMH`i-#pfQqUhSXt}I8BYmBc>${PGXxwrMVh~46J#tkce>c^%UZ256rIYNQ``_=aBdKy zc%*K#zQ7!N#yO1PQ{mZkuL-uw%dW^$E%KbTg^1g19GNBef|c2}o}hB%;`T`ICa0LD z12-GA8*3K{ZapE@OSn&)Elx?FEq{!V?+^zg+!MaZNJ>XRS1Hih+d7jjY6ACtfaqvB z=I1lCNE!i;v_@cnL5a_=aN|rs8{>NtFeK-QN(isGmPVNKyHddX4AbQhjB9QxV66NaYFef=XZVE zqSk!*NmBTeAG3?hwH4iGVNr2q`MzR?CL@8BQj)v`-;WAwYj`-eQK?E8cljeJlRb9n5#YDRWm?&bWryhdIt_2^?c61G(GBYOl0%OOoxGPDstBM3m%NRn9_J@TDb& zRwFbzp6)E4*ICzm+gEJXN%z^hQ)gwU5gwGjIci;6LJ(E(r7`{^)1Vzm*i>^%PO#$o zd1J@mX`PB!xyDIb(sHiFPA$ROsYL=hib+$1kWWLn@Tbw)@26o_3nk8A{+65&F&AbY z21f#G>$TOJu0Wqv+T;fA5&=&$u*|G@@@|&0h)RHjvh#&8gYpK={$Wtx?xTb9#79+V zY(r~`OU(xqrH%ykN=Vy&C;4hOChe_r7+h&OM~f}Bjfng*ApN8FFGE;H^R0)nZcd!z z37Kylvu-OYaF?6gWhdc_u{(4hL}d3;ys#M_qBtSJW{0Wc!T?szka)VRsp!{40i1k)u-5q2}@`oWe@JK zo~bJy>+tQl^Y5wcZ7yg4<~@ygG9slgK^kc7dFrF7R-5DyNc~rQly3eqJuuZQZeCS- zzUCGcS#J@e$&A9ByF!c=6>cg@Ti8fLd{TL!c7!OBlgD#!*&D>NTTFZPis04W-7P$r zmdv#}+Q*Mw`Vry|#CU;K#z%100jQWA#!e54u_>1>(W51tl0%Izw1^b0(PAj=4CR|I(K_3IKgj9t-qq0)#YC`utNfoDY zXzis=-kYA*RxE%+*@effcmTDhB696C9P%yHiuCG>cT{i$v>gaCJ1yRQ#xmczX6AZp zVAmY8AjGEghhoHF2n@-LNK$XILR?d$^1B8@Nhv-qsCV~4F*RPLWs1b}#KL1DXHcvu zxQ1N`4z&i*>V(9Tw#aZY7t6R3cdfQ1sTz-_b}88APln^rZxNq<=`-K%6PF_H{FiyD z=>G2=#@;GY!U7VmN#Fqh4eFrO3of8#dbXVNC&8dwWlXq3jVjM2=bvG;CL;>kOO1iE zD@%w%$Uh2{pstBkb$cGd>E^YKdW{qXGu68jTyYEpxv=RXq3yeHI7E!6Htw}B8(EDd z<=vqKWu8YZTcGh)%*@viY1-6OLO zAxa#LZ8Ul9LSn-zZV!gx-zYUDY~*pS7ul)0yEF~YzAeU;&1dN$VDKp;he~aoPMNE? zHPZ-VO)l_qI5OI9%^t?WgllAmIIW2alK5?wlG~u8WRet=tB4^$mm0+NTU25{s+Mcr zvkS?ai0z^4O`JT#^Hj^bI~ruSJ`@sCzR7L2LOVc0h^+eUZ98{c-cw~9+R|QeNdRCk z>c}o@o0cdIW0{CXBBx<4s47x%exyK%;~1t0JGCIj;H5bpvt~F{HsR4k92127VXJTr0nBv%6O@2SUp05xTtJLO0kT)%ctP8w19kGlsEke z2Dy8x_BJ&-x^`?YY0SOP9vB5uJaIT48^XBi)pJ;e31Px5+Fg?APNLi_kS)Qb zBIAj;wYc|erK&=E6{xhXYb7c{1wM5&%9*}P)lE8WNQmFNCiyI>2vdkb_e8XwTlw~c z4(}S5DkaQa*$cXqEpgU6Mi~j>0*Axbe*XYE*xEfNt#xN>ZC(Rf(%=|NaFF-3K>)-x zxp177S{pf(>r|-CNrNI}5DA|0b5z_7V3(&-n1<4GT*DSqfwUsyjNBT%+S9(n=a$HB zYeVytmq77dIi~uzq^#RASn@N-miLjEUFN}c_|LRb)9xW?DjlE0$d&GeY_5l;UFv}U z00k;eL3C152_(wwz#e@&g>!YAPwu+2m*@8^8z+~H+26g&OPBT@l$Y-bb~8aSjBZFu zIuzrbDJc z5z9C@wC_1?+!V6$?FWu#7Zl-mR;1&&`gjFhvWYWVOL5Snw`Ffjga@Q6uf|6Phf}(? z6*a^1oesn7&10CJb&EF{wU-nVdu8Y3JhSn<^2&;xb!V!UuBYtjT5AQu-DX|CPcr-p z(}~^Vq%~=FBrzt(a!Pt}Wz{5>lt)s_+bk2fQoHI_j9(MV9%?*{7aeVcDIWB}%br>5 zq(xz_j9N7lBE+p&Gms@@i|yt+FWyLD>tt0S-*b_`{NrxP>_S4f2~a8>HwI&+xXwbC zuG)c?8=HB$1PXgO3QS*&w9k} zmzlQG)6fgD?C5Swos{O9;tHdZvX!MHno@+QZk@pBhV&~#BikC1)}Psf2xSWo#9Qrl zgO5c5hX;Q9{q?i9jke6TZp>{*If7e=FgTMqoZ0JJ5O9Ln`yh;tbuBu}iiY5{0&|HY zbOY*{-=8u!EzQ=$@2*L-U2#U$8uZHJt(C0omXv~rdZ(ZqNj-WEx2kVUqh*?@kG-c+ zGGBGZ@kiBWzTTltQlH2Wf;kvJhN%y@ZrPAj4|PUMXD1%;_R_OUeh{8L} zgosiec}ZUw&7n>uFMF$_!*P0R$GUs&KBf^{&`0e5J2QnTLp ztmgIxMh~nEY1tW1nA0t7jEf>(oW`@^*t-nfFVma%dj>lZhS+T_Ha0G}q4xUM?7y_Y&1 z*VK48h91F&f+9#hwkHDY3~Q^@s@DhLGTgw(hK)1NUH%XmY&PF* z(dPIiTzVxx4^6=}k*YR5O`C^Zthsh^gio}pGNejh;Jn6NjF$FNA8ky?QA?><3P&B) z%WbMdeCFSJBQ6`&5Z0CfQz!re0jGt;9nL2h$;L=6Z&D2&wahrPGuhsFhe+_#QO+T$ zI+dtef2g)3k`j!vWZzTqjcTZ6I!ZQI21miId5-ld{orw?^!PU;)H8 zA;y|`gWkC7+rF=)Sc{hK)V`-K3ql-i^>pB*>{5_C2Z8h6xn^s+wQ|$^$2!Y&)=l>3 zewiIs>-~~rVat2A$5xo{`bo?PaYUjhX(;>>Sh&d}dC6cN$?eTTk z*Bfv`9YNx>_HR7fh#Y|+CM1`+AHJ|Lhz>U$7e~qMT?Dc> z{8|dRboC_ee(I=lFK7&6*CJy#d_yz0$8&cU(1jjAG62|qA?0-OJ9vuRS-q&VmQyT4 zjNW6(v?Uu1Qfl@iVf7HNwyde_2bVn1vx=SOkW=U<))ukvMhT2E8wwzj}&A3RkXEGG(lG|O5B{UAnJqN_J zq;)%*t@STd^ut-8Zw~^<<6c^6Ohvj*_c%j_{TYylocZQCYu9~5YTOGnKFkno$xZ4n|c>|Xf%S;43k_z5+rj{i#(e6_AbsGS#TXHkW zShlBr)yvbXDbv{Stqip`0;60ePMhycC`d$vI1+@Z#~w)}s2;8z#?@*+<^8?VvHF;mnB6MuW$X;}h%!KZfv4?c9`7aT?Bx(+ zy4n<{qsw6-^`vwpCHB?JZsM)ae)VA#WDS1(Z+s8RO4}#&ZYz^DQU;*kZ+d;MGIHqf z=1R4yTXJl6M`ODIw$|H@GPF4Ai>pc=-c(1kLag2dW5X;&#^0TEx$g)k0S+z#x#Dv0q)hSIlfw&Go$Sjk% zM)iAW?7fR#v3=EdwyVZM0Bgw5w7JeKYzPi;>i*|sK!%0Q+ZY8l0@2?iz5tWRcDU!Q zpucD=7EIDG>#UraEr0`78(g`oc|`T%<8AOx-<+T-t(kVqFIu~M)V(#*a>RUI8rsM$ z`6J9jOsNZCyCKCeR=&Nz^Rij7n>xAMQzEmy3~ZpOUN6k z2}>zcP6ZLLS_whled?=LQW?;j(`;CCshLJc7JSL_5stjvcJ3DyaVb;ogc6j88*gwA zL=C!BdnUutT>h%n%`PIB))GMvJ)&@mV1Nd*k#k5n1na8(Xc(#k67I}y(_BVp+(A*_ zXZ<*bsW!X4pgn=@6>zSyAC-%Ip>Sn$8RWKt-cx;%O50b$Qa~qk{3IH?>>kVbg_bmi z9nQ4^K$Mb~5XM6ECQ=W0Lrru@^xmuc!gYext2ov33c;MmB*?OxQro;uikR1CAt}9t zu^hRhK^XPJ)wm`x$m*GCBvtUbNY}wG=1oTo=rkYqIWhH4P zDhHvf8-DX@+&j|kZd%F@4I~G&mY-C~V8lUlOc9jiVC0!&O4^v}s#7K6Mu$E5PD3N{ z(LvB$ujKr`m@n#yJWLagXg*!?31^q z)tP;?YrdT-_3i~pX={sVVkQX$VsmM_FyWDjSfg&h=P{JaV91w1(||4-&gF}fE6t^+f8fx8pgb(ThB2SF0 zqqMgpt;7iFM?Z}u?xzQL6IV8Igz0V-WkYx-l;c+%w)paGwm~A_@0&?jKR)x1bK=`c z3UNf9yg=_YHNo`G9tn)wtTt(AY5QwQ2$0(;Ky~g#ZaU_`6&1Ktqu@6H4TUX^)7g72 zlP?`amq`rum~@9Yg8I$EJyF@r;)pFtTHfa6#*ltn^gY61rg!VYbya?#=Xma(a#=0UburW$VjB_^ z9Gxj@?AVTgJJ#aLQa;jrDx%4S#>Q`&Ot=zSgLTU+IR%}xrAle0WL(-*_7F!vlswv; z1E$G*`BcvLeu0o?_l-ly5g=i@T)74dX=O@P&hy0fvJj28SY-Hox{&9*3{vMc&7*;`0GB!4oTmxIa}6p=bzLqljBo~!0R_@yfEn}4ii1bSZ$LxQ z=Pt4v$}?A7x@Q*{u95EW_u~3cN6`f#5ZND+98jtDtXW}WRy<1~x5$$kCMlCzN*YU_ z1ZZxk!dg%^_&`JHA7W^$e5UvDPV^S_Soy)T{`ZxE2#3R z(n$#f zcaIvL*g<9z;iwVruej28A6J&?{YJ7jyQ(s_7it?ybkx#rFZz|6P}ed>guD2RdlVZ` zpvZGK((7vPGTbiYlBNzp9{^(6Ao3kQDQWPj&&jtgI}Cg}AbKw%P{jTD$=N04+y(QRaoKTM}4z z5Baq@aD?hj0rd&v{{U-OIK0c4R<~tYN^VZ6i=I&q!Fc#mKce8U^}azJ!;Fxo$?|%q zcO65VyWERr9XcG#j1{(=mhn9yyH-BmAQaQC@AHxma$~pGateqY4ud??e5U=AX9&Mv zpkw%!QEs<8bDq;d`O92r>Q7-ECv>#hR2x#>Q091qqp!p?dM$5%P%QZ5%k8t6xH!{gHkcAUK>+32b|B`4 zgAEO7d4Yl140T#nO%{m6Tt}abh{jbu-;?Ed8{^3{VWem$oxme%&wM+sfKAdFAK z{23k2Y?2C^SI#%F?;UChfU_VH@8UT1 ztyZtu4i#dNh(&1^EH5Uw1VW7_GlASv;Ol`-%#0F}uE00itBOeM)}TkiqAJ0`uuR7t z&*4vIb-wkFW0=GCdnD90nDU-savD-&%4lqm;u~*dy4d2ew)-9T4Le&aI+s*j8`ib$ z0EifHkU)`<#O8w1Nf7KM%yfelO4VCU;&6~peEV_-F|3IIjP1XZu0VtMMJu7-qb-l- zs2-48vXXp39(4-S zgsL`as!){D(3NEjO=ysKtq)scZc?oj zvKaDB_eAAO#h40DwJe8-294HJeUtZrx_3s1~dJ$wVvw(o)oB zM%_9MoLH&t-6i)RL)eh)q^*5EssUGDyb7sxhU+@nmt?ny5ac@|)1|i-qN$`PlP+Y` zw%QUv>aTzuhh2vCZ(_0a>k4f-xvv|((@O`n$}6`DY{YjF>@)CLx<`jv^Gz5^vS%hq zhn=}{yH8*`QXOh+mll(^YgtF$rn61 zN|!D>DLtji(C-g=jGexc(YI{a{G=!krCNJGY8#9C&1^&5=Q4Bzk^@iZy{7}S84yW^ zs`c%ON*QI;s8)J~dxyD(^Ww9Wuhx4nc>fwuN0L zhtQ-nyQz8T@Y22d9>uWh+jaPs-=$cyx1`E%bzogVu4}}cCkbs%$uMBzXr}DFZ1jSe zR-=oW8Tuw#Uak;f)#-vDQaz}A_sV^m!xI7%}!n_G1 zrC0WkXB=}k)oi(W9!qp_7`iR65hlkDEs0Lbb)`P?Pr5dqiAtIq@5|!qNCK?n>1G!R z#5av++K*x+goN92S&uf6{7E6y?djc6ttGJPv$*W+vmMl%qi*E4nfE6ey<+hthteG^ zGV6=x%YkZXmdOL4>98IvYqTyILbZA~BA!iPE~kOOlOwd&z0rk^Z>(Z_ky71;qZs;S z+MJyu$dEN4=(l+vvaTh5ahKcAbGFwVAt+|ydvfO$dv^yJc#%+j)0ty{65lBG{lB{I@kce7Kda1{2cC0jO-m8o5JtKTEk9eKgz+U=7pcpg)5 zyS>=Ng$v)H$WlT0-*YcA+>k1$gKpL<+?`pSnPAhhTea5ItZCbgwoF3R zy*}*Y-IPY%Q)~BfTxWh8ozkvcmD08PKXJ5ZxL3ZoYv{CO3)%~$;4oYRfD&e6NW$4O z6|b2=!vz{-Jnw^)3Eh;aM+t$w%J{V;# zCuJDrfa9eh#S|UB3bNySMCrRT6xL&sryNs>1uWk!((UVId;J9$+W|z5yQGopRhCDn z&}3lPhHNqX{vH0oD%*Q)QZcu~Qjl6pGmx1Lh4fnvJ+VbvN8vxR}6832R$}EU^cMw+tDO zBs`OJJdWkHCpDk z>u$7PVoTCS8;xRM2v(j8A~uTLUj4K&sfO`%ctmSZUJfoj(JDVS!>&En+S=&W63`%*tOQo+IA)Edoz!Ij-UEB2Wmj_;oVt#Qp&?=RTZdQ z2h={XFagpqwA98UF#)B&a0b9uE$dgeZU?Z2?&IQc$F_LpuJYbO?)%HPus<=iI7jIq zRdsg@$#vTn#Zi#1OZb(xc0oA|Aqg>LK9oj?6}J3TQn!==>$hJTt}oi3a<;t!)rRZt zZf;UtQIy_OipFWHWMi(BPpu#&YCD{hICOm>? z6Ph58yd;nvEsd9rvS(KInK^+Q&ZD>;{5!L>Gfi8TyQ5epl12GvA&|Al65*jQwH0Hd ziHa5QKI(qFhYw)aiBh&jNc3Mr?U+o*Z<%9=yXCApUk@q5^DxxvdbrY-IXdy`l}K3S z(_{oEVNeY|)}1KQ8*jRwhC^p>Nly;&|dRHEaNcVN@+e!MZAt4DK1f9(GAGDU7 zTrP30G3}YWbgi<({k9`JZaeOix)7jtJCDAr?JGU0Z$Z{uI@#@{M`_?osu9GtVY-0P ztjWxfpKn&)qs42AfVgNh4fEg6%RsD*f-k!|-QksS2zjMRPvr!wqW;mjA7QP9(s_*! zwTeRBe1_wJ^_H&V_IUl(Pvkz-noO8dU@e@$yyIjgD#d0a;_n|#DeAt-(oeb}QKCi&QpW1sy!v~(fKQ!sgQJ^MsTC{Gb3N%ltJTNM096qym&J*tOMAOpIBhmPmC*Ed`7rt>>)reO%VN0VyO`(5HbGXb^o zaSzN(kM_#nq4#iwMr@9QV~^zxV{xWe0>|ppvaohg>8{CbQe-2xRMaQ#yhS! zw4nS)Vc<9Ks?AV!UfXHGE|>Ft}7i^y*n*6qeb*Yk;8!^gbHVb(HD4>a+gELXxAAyG zBbQ(jB)1kJTAS??%q7PqB?Vm(x`SkpHv+C~{hL9LxY{`9+|nEMj5u&KxR6K$ zVmZ170g1uU*SLFwE=V99Kn71_=O#1HaMSH%B4KXQi#Dv~h@T?kH!8N{E}=Io_3hr! zUG6P_eou(fHf4y6o{p)s?zI5lA_A)>`yDz(sW@%w&mQ$Z7;Ik><~bdp`_?kJ?XE+S zw>;d85`eJ4+^1!5>G0IQ46*9GTzUnE9L;bWHL~9owxKZ>6q52pej|dCU6P(#_mX;& zNj+;vpJJMw8#BOe_dQyOWv1&oJbAD&7FaO3z;Klmms|@6haZbzId9)oY3&Za@Y_|m zX`&fXp@*!~tA2S?!qRn?`lGVRZ)D2rd=!O7FybYop3mMNr6hOjsn0cqkiz}7^@`V+ z=~d3ix%j1#7nUxrx7x(Gt<{#|MR=rx#Gpw~Ud3&48+eMJTCP^DW5%x7_2sh}))nqK z1~+kxg7g=A!caA}WxMV7UrYZL)=+ zas?-S%VhFSbI7A^m~bF2q{lpc2W4H5TuxW!+hq_K1-V z8;H+Xi5>?~RDVz-Wi}ZWJq*nw|DU640NABSea=z0fLrC9c#U*f>jOKs;NvEHO{x87X5+!kZFzM>4&A?<-l zA2lEVm3(Fi6DAiA4ugXV(Vy9ib;c zG642YQ!sVhRP$5uoT?r22Bct^uub|iA0LPbE0kPeGm=^EEx?^~dQZbL4`h|W4Hp&3wh)CW32(V23MZ+lMe9TK9vy;Z z(q02%YCLG_kf~C`?*`JJi;?8D6W!ghJ{wgNkZIH#zHg^mnHNee82%k}$RXNVv^M}H z^~Nev{q}1?B`6M(q@}VqN6InJPB(=Lo9CKgEoQWb8wGETv6B^DpZEmmNit|>8t))YM-vCEVk6rv~8ImdnphCFmwgrV+@1?~SF4+aj zA&qIbIp)c1CCM+K^-5VFB_^_t;XcHCPG)qM5Azml8%Rxx7xb}h?hffo?5wuEj^kP zx1)u2pAVrO+wD<3H*WhAB-FTxY8t`I*DMzT+Y1jvj-sS&JGTUQ^#-P~=|yX8opWhG zG_c`u4<*t`Xfj9>GAEAx>6Nx~YFDRKuxcQi=K7t-*R>4&d$GM zyT_8sQT!s>L2=f{p20wM>sgi^Vb60bVY*?sHWzYdaE>U>QtQZBA4P397_ABiyen<0 z&G*~eN0O|lQBP1=mN)%iRZ*)Jj5Buc6Y%L*eD>z(iDy-b<=f{XK_3YB#X*NM?7;?>hnyaie01DRH!%0G8x4!Q7o3^3TZOEnK`SsfAskWvN$=EvtvRz6of?XoBJG5Z>iHiQMf;ylR|m7Zb-ccE_X>W;rT4?USupYCi&LDy8xnRTS5HlixV2@qqZw2X=7%uhEd(C0x2(;K9;n1HyqA(e zEsW<9%7&##DJfS?)3saKwx{ZT zYR#fX8HD3ki!feju zIX%@jdwV1#ys0iEt!KHm(g{|rlv6(ycn#x4(APRDYTZt z;*|V5bR7*=O;6T!+NVuUonhrKI1|?^f07g01#t=YPU1~%V`Li9XlhJXG}{nTV-oTL za>u@c&fzKj$=74k!@jca>~1SbFJqmE%+#c2Vs&=1d(Ndy3AEQuWPtq2IdilGo~7hhk>BW0T0V%8nxBPFdYMB|%M2jI@M;5Adxw zx7$H0USYgz6(#c;she&ePVSZlM&k9}>}SwWPvsvD?#;cwm2 zRHBrrI|KE+YI?l4S)Oc4i=*~8n|o&)3Cz$w6%E1oqmKeA&DPF+TPt@G$!<1(O-u+5 z#xpM?GV|RX$|`lkD?AC#5324F!;n&GSFC>H8BlIg7?BxcWid65ZK!z^IFgh1imxZ> zevZt$17gQx{HD0v09(vsP2Ttb!Jd@4hf zWBQqQvdgmPnq_UunUGMl+_7u_0J}|y@((8DM`B`8*|^$(Qc7GWako%SYOzeEtK=497@@$;7q#XKfRiIbo~ot2 zHa?i7F>f2-#$RX=yS+xRzi10}qC0rLxT}<%4UMOA(~?Ok1FC@fZ&-G6!l0*x)|364tm{)-=B%jeUiMSAFXlfaER8S#a19 z)|~Famq2w^HJR-?*-E|HX5H%|~ zwJG+b62x1ZPRB;lT^l8*npjVEtIbjV&u}W?RHK0UktdJhnAzp3>S++0~m;qnzPVoMP=r!&*mbM-O1& zqIJEvxM6ER7+t1Y*}@j=>8^6Zlwj*+)zEvBbH^=`w)@iCW|%l$uSW;5&2TG;9maMd z>~cq`Vn^}{u+cgbpNp2#zW)I7)4+ud(m@_z);IFS*vZXj{{U4n?Fyz7F&!?s^y}DME)a?S-L-*9Gj$#zCT4*{lsxS@ly&d+2h!K_)tpB3Hl_Yn zpIU Date: Wed, 21 Aug 2019 18:18:46 +0300 Subject: [PATCH 018/133] Refactoring --- pom.xml | 2 +- .../home}/components/contact.component.html | 0 .../home}/components/contact.component.ts | 2 +- .../components/details-panel.component.html | 0 .../components/details-panel.component.scss | 2 +- .../components/details-panel.component.ts | 0 .../entity/add-entity-dialog.component.html | 0 .../entity/add-entity-dialog.component.scss | 0 .../entity/add-entity-dialog.component.ts | 33 +++--- .../entity/contact-based.component.ts | 4 +- .../entity/entities-table.component.html | 0 .../entity/entities-table.component.scss | 0 .../entity/entities-table.component.ts | 8 +- .../entity-details-panel.component.html | 9 +- .../entity-details-panel.component.scss | 0 .../entity/entity-details-panel.component.ts | 61 +++++++++- .../entity/entity-table-header.component.ts | 2 +- .../entity/entity-tabs.component.ts | 85 ++++++++++++++ .../components/entity/entity.component.ts | 4 +- .../home/components/home-components.module.ts | 50 +++++++++ .../home/models}/contact.models.ts | 0 .../models/datasource/entity-datasource.ts | 2 +- .../entity/entities-table-config.models.ts | 37 +++--- .../models}/entity/entity-component.models.ts | 4 +- .../modules/home/pages/admin/admin.module.ts | 6 +- .../home/pages/asset/asset-routing.module.ts | 2 +- .../asset/asset-table-header.component.ts | 2 +- .../home/pages/asset/asset.component.ts | 2 +- .../modules/home/pages/asset/asset.module.ts | 2 + .../asset/assets-table-config.resolver.ts | 4 +- .../pages/customer/customer-routing.module.ts | 2 +- .../home/pages/customer/customer.component.ts | 2 +- .../home/pages/customer/customer.module.ts | 2 + .../customers-table-config.resolver.ts | 4 +- .../dashboard/dashboard-form.component.ts | 4 +- .../dashboard/dashboard-routing.module.ts | 2 +- .../home/pages/dashboard/dashboard.module.ts | 2 + .../dashboards-table-config.resolver.ts | 4 +- .../pages/device/device-routing.module.ts | 2 +- .../device/device-table-header.component.ts | 2 +- .../pages/device/device-tabs.component.html | 29 +++++ .../pages/device/device-tabs.component.ts | 41 +++++++ .../home/pages/device/device.component.ts | 2 +- .../home/pages/device/device.module.ts | 5 + .../device/devices-table-config.resolver.ts | 6 +- .../entity-view/entity-view-routing.module.ts | 2 +- .../entity-view-table-header.component.ts | 2 +- .../entity-view/entity-view.component.ts | 2 +- .../pages/entity-view/entity-view.module.ts | 2 + .../entity-views-table-config.resolver.ts | 4 +- .../rulechain/rulechain-routing.module.ts | 2 +- .../pages/rulechain/rulechain.component.ts | 4 +- .../home/pages/rulechain/rulechain.module.ts | 2 + .../rulechains-table-config.resolver.ts | 4 +- .../pages/tenant/tenant-routing.module.ts | 2 +- .../home/pages/tenant/tenant.component.ts | 2 +- .../home/pages/tenant/tenant.module.ts | 2 + .../tenant/tenants-table-config.resolver.ts | 4 +- .../modules/home/pages/user/user.component.ts | 2 +- .../modules/home/pages/user/user.module.ts | 2 + .../pages/user/users-table-config.resolver.ts | 4 +- .../widget/widget-library-routing.module.ts | 2 +- .../pages/widget/widget-library.module.ts | 2 + .../pages/widget/widgets-bundle.component.ts | 2 +- .../widgets-bundles-table-config.resolver.ts | 4 +- .../entity/entity-select.component.ts | 10 +- ui-ngx/src/app/shared/shared.module.ts | 106 ++++++++---------- ui-ngx/tsconfig.json | 3 +- 68 files changed, 425 insertions(+), 178 deletions(-) rename ui-ngx/src/app/{shared => modules/home}/components/contact.component.html (100%) rename ui-ngx/src/app/{shared => modules/home}/components/contact.component.ts (93%) rename ui-ngx/src/app/{shared => modules/home}/components/details-panel.component.html (100%) rename ui-ngx/src/app/{shared => modules/home}/components/details-panel.component.scss (97%) rename ui-ngx/src/app/{shared => modules/home}/components/details-panel.component.ts (100%) rename ui-ngx/src/app/{shared => modules/home}/components/entity/add-entity-dialog.component.html (100%) rename ui-ngx/src/app/{shared => modules/home}/components/entity/add-entity-dialog.component.scss (100%) rename ui-ngx/src/app/{shared => modules/home}/components/entity/add-entity-dialog.component.ts (76%) rename ui-ngx/src/app/{shared => modules/home}/components/entity/contact-based.component.ts (95%) rename ui-ngx/src/app/{shared => modules/home}/components/entity/entities-table.component.html (100%) rename ui-ngx/src/app/{shared => modules/home}/components/entity/entities-table.component.scss (100%) rename ui-ngx/src/app/{shared => modules/home}/components/entity/entities-table.component.ts (97%) rename ui-ngx/src/app/{shared => modules/home}/components/entity/entity-details-panel.component.html (69%) rename ui-ngx/src/app/{shared => modules/home}/components/entity/entity-details-panel.component.scss (100%) rename ui-ngx/src/app/{shared => modules/home}/components/entity/entity-details-panel.component.ts (68%) rename ui-ngx/src/app/{shared => modules/home}/components/entity/entity-table-header.component.ts (92%) create mode 100644 ui-ngx/src/app/modules/home/components/entity/entity-tabs.component.ts rename ui-ngx/src/app/{shared => modules/home}/components/entity/entity.component.ts (94%) create mode 100644 ui-ngx/src/app/modules/home/components/home-components.module.ts rename ui-ngx/src/app/{shared/components => modules/home/models}/contact.models.ts (100%) rename ui-ngx/src/app/{shared => modules/home}/models/datasource/entity-datasource.ts (97%) rename ui-ngx/src/app/{shared/components => modules/home/models}/entity/entities-table-config.models.ts (82%) rename ui-ngx/src/app/{shared/components => modules/home/models}/entity/entity-component.models.ts (84%) create mode 100644 ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/device/device-tabs.component.ts diff --git a/pom.xml b/pom.xml index 20a2e13531..8301cb782f 100755 --- a/pom.xml +++ b/pom.xml @@ -104,7 +104,6 @@ tools application msa - ui @@ -307,6 +306,7 @@ **/*.proto.js docker/haproxy/** docker/tb-node/** + ui/** JAVADOC_STYLE diff --git a/ui-ngx/src/app/shared/components/contact.component.html b/ui-ngx/src/app/modules/home/components/contact.component.html similarity index 100% rename from ui-ngx/src/app/shared/components/contact.component.html rename to ui-ngx/src/app/modules/home/components/contact.component.html diff --git a/ui-ngx/src/app/shared/components/contact.component.ts b/ui-ngx/src/app/modules/home/components/contact.component.ts similarity index 93% rename from ui-ngx/src/app/shared/components/contact.component.ts rename to ui-ngx/src/app/modules/home/components/contact.component.ts index f16993bfbc..cce7d3f03b 100644 --- a/ui-ngx/src/app/shared/components/contact.component.ts +++ b/ui-ngx/src/app/modules/home/components/contact.component.ts @@ -16,7 +16,7 @@ import { Component, Input } from '@angular/core'; import { FormGroup } from '@angular/forms'; -import { COUNTRIES } from '@shared/components/contact.models'; +import { COUNTRIES } from '@home/models/contact.models'; @Component({ selector: 'tb-contact', diff --git a/ui-ngx/src/app/shared/components/details-panel.component.html b/ui-ngx/src/app/modules/home/components/details-panel.component.html similarity index 100% rename from ui-ngx/src/app/shared/components/details-panel.component.html rename to ui-ngx/src/app/modules/home/components/details-panel.component.html diff --git a/ui-ngx/src/app/shared/components/details-panel.component.scss b/ui-ngx/src/app/modules/home/components/details-panel.component.scss similarity index 97% rename from ui-ngx/src/app/shared/components/details-panel.component.scss rename to ui-ngx/src/app/modules/home/components/details-panel.component.scss index bcbc36cbbd..bc7127b393 100644 --- a/ui-ngx/src/app/shared/components/details-panel.component.scss +++ b/ui-ngx/src/app/modules/home/components/details-panel.component.scss @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@import '../../../scss/constants'; +@import '../../../../scss/constants'; :host { width: 100%; diff --git a/ui-ngx/src/app/shared/components/details-panel.component.ts b/ui-ngx/src/app/modules/home/components/details-panel.component.ts similarity index 100% rename from ui-ngx/src/app/shared/components/details-panel.component.ts rename to ui-ngx/src/app/modules/home/components/details-panel.component.ts diff --git a/ui-ngx/src/app/shared/components/entity/add-entity-dialog.component.html b/ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.html similarity index 100% rename from ui-ngx/src/app/shared/components/entity/add-entity-dialog.component.html rename to ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.html diff --git a/ui-ngx/src/app/shared/components/entity/add-entity-dialog.component.scss b/ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.scss similarity index 100% rename from ui-ngx/src/app/shared/components/entity/add-entity-dialog.component.scss rename to ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.scss diff --git a/ui-ngx/src/app/shared/components/entity/add-entity-dialog.component.ts b/ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.ts similarity index 76% rename from ui-ngx/src/app/shared/components/entity/add-entity-dialog.component.ts rename to ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.ts index a4c0de7ec3..4364e5ff3a 100644 --- a/ui-ngx/src/app/shared/components/entity/add-entity-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.ts @@ -14,26 +14,19 @@ /// limitations under the License. /// -import { - Component, - ComponentFactoryResolver, - Inject, - OnInit, - SkipSelf, - ViewChild -} from '@angular/core'; -import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; -import { PageComponent } from '@shared/components/page.component'; -import { Store } from '@ngrx/store'; -import { AppState } from '@core/core.state'; -import { FormControl, FormGroupDirective, NgForm } from '@angular/forms'; -import { EntityTypeResource, EntityTypeTranslation } from '@shared/models/entity-type.models'; -import { EntityTableConfig } from '@shared/components/entity/entities-table-config.models'; -import { BaseData, HasId } from '@shared/models/base-data'; -import { EntityId } from '@shared/models/id/entity-id'; -import { AddEntityDialogData } from '@shared/components/entity/entity-component.models'; -import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; -import { EntityComponent } from '@shared/components/entity/entity.component'; +import {Component, ComponentFactoryResolver, Inject, OnInit, SkipSelf, ViewChild} from '@angular/core'; +import {ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef} from '@angular/material'; +import {PageComponent} from '@shared/components/page.component'; +import {Store} from '@ngrx/store'; +import {AppState} from '@core/core.state'; +import {FormControl, FormGroupDirective, NgForm} from '@angular/forms'; +import {EntityTypeResource, EntityTypeTranslation} from '@shared/models/entity-type.models'; +import {BaseData, HasId} from '@shared/models/base-data'; +import {EntityId} from '@shared/models/id/entity-id'; +import {TbAnchorComponent} from '@shared/components/tb-anchor.component'; +import {EntityComponent} from './entity.component'; +import {EntityTableConfig} from '@home/models/entity/entities-table-config.models'; +import {AddEntityDialogData} from '@home/models/entity/entity-component.models'; @Component({ selector: 'tb-add-entity-dialog', diff --git a/ui-ngx/src/app/shared/components/entity/contact-based.component.ts b/ui-ngx/src/app/modules/home/components/entity/contact-based.component.ts similarity index 95% rename from ui-ngx/src/app/shared/components/entity/contact-based.component.ts rename to ui-ngx/src/app/modules/home/components/entity/contact-based.component.ts index 9f4a50a73f..845fe5741e 100644 --- a/ui-ngx/src/app/shared/components/entity/contact-based.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/contact-based.component.ts @@ -16,12 +16,12 @@ import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { EntityComponent } from '@shared/components/entity/entity.component'; import { FormBuilder, FormGroup, ValidatorFn, Validators } from '@angular/forms'; import { ContactBased } from '@shared/models/contact-based.model'; import { AfterViewInit } from '@angular/core'; -import { POSTAL_CODE_PATTERNS } from '@shared/components/contact.models'; +import { POSTAL_CODE_PATTERNS } from '@home/models/contact.models'; import { HasId } from '@shared/models/base-data'; +import {EntityComponent} from './entity.component'; export abstract class ContactBasedComponent> extends EntityComponent implements AfterViewInit { diff --git a/ui-ngx/src/app/shared/components/entity/entities-table.component.html b/ui-ngx/src/app/modules/home/components/entity/entities-table.component.html similarity index 100% rename from ui-ngx/src/app/shared/components/entity/entities-table.component.html rename to ui-ngx/src/app/modules/home/components/entity/entities-table.component.html diff --git a/ui-ngx/src/app/shared/components/entity/entities-table.component.scss b/ui-ngx/src/app/modules/home/components/entity/entities-table.component.scss similarity index 100% rename from ui-ngx/src/app/shared/components/entity/entities-table.component.scss rename to ui-ngx/src/app/modules/home/components/entity/entities-table.component.scss diff --git a/ui-ngx/src/app/shared/components/entity/entities-table.component.ts b/ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts similarity index 97% rename from ui-ngx/src/app/shared/components/entity/entities-table.component.ts rename to ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts index e21b048541..9e08976d00 100644 --- a/ui-ngx/src/app/shared/components/entity/entities-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts @@ -29,7 +29,7 @@ import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { PageLink, TimePageLink } from '@shared/models/page/page-link'; import { MatDialog, MatPaginator, MatSort } from '@angular/material'; -import { EntitiesDataSource } from '@shared/models/datasource/entity-datasource'; +import { EntitiesDataSource } from '@home/models/datasource/entity-datasource'; import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators'; import { Direction, SortOrder } from '@shared/models/page/sort-order'; import { forkJoin, fromEvent, merge, Observable } from 'rxjs'; @@ -43,14 +43,14 @@ import { EntityTableConfig, GroupActionDescriptor, HeaderActionDescriptor -} from '@shared/components/entity/entities-table-config.models'; +} from '@home/models/entity/entities-table-config.models'; import { EntityTypeTranslation } from '@shared/models/entity-type.models'; import { DialogService } from '@core/services/dialog.service'; -import { AddEntityDialogComponent } from '@shared/components/entity/add-entity-dialog.component'; +import { AddEntityDialogComponent } from './add-entity-dialog.component'; import { AddEntityDialogData, EntityAction -} from '@shared/components/entity/entity-component.models'; +} from '@home/models/entity/entity-component.models'; import { Timewindow } from '@shared/models/time/time.models'; import {DomSanitizer, SafeHtml} from '@angular/platform-browser'; import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; diff --git a/ui-ngx/src/app/shared/components/entity/entity-details-panel.component.html b/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.html similarity index 69% rename from ui-ngx/src/app/shared/components/entity/entity-details-panel.component.html rename to ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.html index 23e4633fcf..839c8aac75 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-details-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.html @@ -31,13 +31,6 @@ - + diff --git a/ui-ngx/src/app/shared/components/entity/entity-details-panel.component.scss b/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.scss similarity index 100% rename from ui-ngx/src/app/shared/components/entity/entity-details-panel.component.scss rename to ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.scss diff --git a/ui-ngx/src/app/shared/components/entity/entity-details-panel.component.ts b/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts similarity index 68% rename from ui-ngx/src/app/shared/components/entity/entity-details-panel.component.ts rename to ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts index e61daaac2e..b791d0a8f5 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-details-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts @@ -23,12 +23,15 @@ import { OnDestroy, OnInit, Output, - ViewChild + ViewChild, + ViewChildren, + QueryList, + ContentChildren, AfterViewInit } from '@angular/core'; import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { EntityTableConfig } from '@shared/components/entity/entities-table-config.models'; +import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; import { BaseData, HasId } from '@shared/models/base-data'; import { EntityType, @@ -36,11 +39,12 @@ import { EntityTypeTranslation } from '@shared/models/entity-type.models'; import { NgForm } from '@angular/forms'; -import { EntityComponent } from '@shared/components/entity/entity.component'; +import { EntityComponent } from './entity.component'; import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; -import { EntityAction } from '@shared/components/entity/entity-component.models'; +import { EntityAction } from '@home/models/entity/entity-component.models'; import { Subscription } from 'rxjs'; -// import { AuditLogMode } from '@shared/models/audit-log.models'; +import { MatTabGroup, MatTab } from '@angular/material'; +import { EntityTabsComponent } from '@home/components/entity/entity-tabs.component'; @Component({ selector: 'tb-entity-details-panel', @@ -48,7 +52,7 @@ import { Subscription } from 'rxjs'; styleUrls: ['./entity-details-panel.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) -export class EntityDetailsPanelComponent extends PageComponent implements OnInit, OnDestroy { +export class EntityDetailsPanelComponent extends PageComponent implements OnInit, AfterViewInit, OnDestroy { @Input() entitiesTableConfig: EntityTableConfig>; @@ -62,6 +66,7 @@ export class EntityDetailsPanelComponent extends PageComponent implements OnInit entityAction = new EventEmitter>>(); entityComponent: EntityComponent>; + entityTabsComponent: EntityTabsComponent>; detailsForm: NgForm; isEditValue = false; @@ -71,6 +76,12 @@ export class EntityDetailsPanelComponent extends PageComponent implements OnInit @ViewChild('entityDetailsForm', {static: true}) entityDetailsFormAnchor: TbAnchorComponent; + @ViewChild('entityTabs', {static: true}) entityTabsAnchor: TbAnchorComponent; + + @ViewChild(MatTabGroup, {static: true}) matTabGroup: MatTabGroup; + + @ViewChildren(MatTab) inclusiveTabs: QueryList; + translations: EntityTypeTranslation; resources: EntityTypeResource; entity: BaseData; @@ -94,6 +105,9 @@ export class EntityDetailsPanelComponent extends PageComponent implements OnInit set isEdit(val: boolean) { this.isEditValue = val; this.entityComponent.isEdit = val; + if (this.entityTabsComponent) { + this.entityTabsComponent.isEdit = val; + } } get isEdit() { @@ -125,6 +139,19 @@ export class EntityDetailsPanelComponent extends PageComponent implements OnInit this.entityActionSubscription = this.entityComponent.entityAction.subscribe((action) => { this.entityAction.emit(action); }); + this.buildEntityTabsComponent(); + } + + buildEntityTabsComponent() { + if (this.entitiesTableConfig.entityTabsComponent) { + const componentTabsFactory = this.componentFactoryResolver.resolveComponentFactory(this.entitiesTableConfig.entityTabsComponent); + const viewContainerRef = this.entityTabsAnchor.viewContainerRef; + viewContainerRef.clear(); + const componentTabsRef = viewContainerRef.createComponent(componentTabsFactory); + this.entityTabsComponent = componentTabsRef.instance; + this.entityTabsComponent.isEdit = this.isEdit; + this.entityTabsComponent.entitiesTableConfig = this.entitiesTableConfig; + } } reload(): void { @@ -133,6 +160,9 @@ export class EntityDetailsPanelComponent extends PageComponent implements OnInit (entity) => { this.entity = entity; this.entityComponent.entity = entity; + if (this.entityTabsComponent) { + this.entityTabsComponent.entity = entity; + } } ); } @@ -145,6 +175,9 @@ export class EntityDetailsPanelComponent extends PageComponent implements OnInit this.isEdit = isEdit; if (!this.isEdit) { this.entityComponent.entity = this.entity; + if (this.entityTabsComponent) { + this.entityTabsComponent.entity = this.entity; + } } else { this.selectedTab = 0; } @@ -157,6 +190,9 @@ export class EntityDetailsPanelComponent extends PageComponent implements OnInit (entity) => { this.entity = entity; this.entityComponent.entity = entity; + if (this.entityTabsComponent) { + this.entityTabsComponent.entity = entity; + } this.isEdit = false; this.entityUpdated.emit(this.entity); } @@ -164,4 +200,17 @@ export class EntityDetailsPanelComponent extends PageComponent implements OnInit } } + ngAfterViewInit(): void { + if (this.entityTabsComponent) { + this.entityTabsComponent.entityTabsChanged.subscribe( + (entityTabs) => { + if (entityTabs) { + this.matTabGroup._tabs.reset([...this.inclusiveTabs.toArray(), ...entityTabs]); + this.matTabGroup._tabs.notifyOnChanges(); + } + } + ); + } + } + } diff --git a/ui-ngx/src/app/shared/components/entity/entity-table-header.component.ts b/ui-ngx/src/app/modules/home/components/entity/entity-table-header.component.ts similarity index 92% rename from ui-ngx/src/app/shared/components/entity/entity-table-header.component.ts rename to ui-ngx/src/app/modules/home/components/entity/entity-table-header.component.ts index a398567e7f..966bd7d27b 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-table-header.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/entity-table-header.component.ts @@ -19,7 +19,7 @@ import { PageComponent } from '@shared/components/page.component'; import { Input, OnInit } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { EntityTableConfig } from '@shared/components/entity/entities-table-config.models'; +import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; export abstract class EntityTableHeaderComponent> extends PageComponent implements OnInit { diff --git a/ui-ngx/src/app/modules/home/components/entity/entity-tabs.component.ts b/ui-ngx/src/app/modules/home/components/entity/entity-tabs.component.ts new file mode 100644 index 0000000000..ddfe2cf845 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/entity/entity-tabs.component.ts @@ -0,0 +1,85 @@ +/// +/// Copyright © 2016-2019 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 { BaseData, HasId } from '@shared/models/base-data'; +import { PageComponent } from '@shared/components/page.component'; +import { + AfterViewInit, + ContentChildren, + EventEmitter, + Input, + OnInit, + Output, + QueryList, + ViewChildren +} from '@angular/core'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; +import { MatTab } from '@angular/material'; +import { EntityAction } from '@home/models/entity/entity-component.models'; +import { BehaviorSubject } from 'rxjs'; + +export abstract class EntityTabsComponent> extends PageComponent implements OnInit, AfterViewInit { + + entityValue: T; + + @ViewChildren(MatTab) entityTabs: QueryList; + + isEditValue: boolean; + + @Input() + set isEdit(isEdit: boolean) { + this.isEditValue = isEdit; + } + + get isEdit() { + return this.isEditValue; + } + + @Input() + set entity(entity: T) { + this.entityValue = entity; + } + + get entity(): T { + return this.entityValue; + } + + @Input() + entitiesTableConfig: EntityTableConfig; + + private entityTabsSubject = new BehaviorSubject>(null); + + entityTabsChanged = this.entityTabsSubject.asObservable(); + + protected constructor(protected store: Store) { + super(store); + } + + ngOnInit() { + } + + ngAfterViewInit(): void { + this.entityTabsSubject.next(this.entityTabs.toArray()); + this.entityTabs.changes.subscribe( + () => { + this.entityTabsSubject.next(this.entityTabs.toArray()); + } + ); + } + +} diff --git a/ui-ngx/src/app/shared/components/entity/entity.component.ts b/ui-ngx/src/app/modules/home/components/entity/entity.component.ts similarity index 94% rename from ui-ngx/src/app/shared/components/entity/entity.component.ts rename to ui-ngx/src/app/modules/home/components/entity/entity.component.ts index 63624a1dcd..0edf4a48d7 100644 --- a/ui-ngx/src/app/shared/components/entity/entity.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/entity.component.ts @@ -20,8 +20,8 @@ import { PageComponent } from '@shared/components/page.component'; import { EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { EntityAction } from '@shared/components/entity/entity-component.models'; -import { EntityTableConfig } from '@shared/components/entity/entities-table-config.models'; +import { EntityAction } from '@home/models/entity/entity-component.models'; +import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; export abstract class EntityComponent> extends PageComponent implements OnInit { diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts new file mode 100644 index 0000000000..1f47f9ba6c --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -0,0 +1,50 @@ +/// +/// Copyright © 2016-2019 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 {CommonModule} from '@angular/common'; +import {SharedModule} from '@app/shared/shared.module'; +import {AddEntityDialogComponent} from './entity/add-entity-dialog.component'; +import {EntitiesTableComponent} from './entity/entities-table.component'; +import {DetailsPanelComponent} from './details-panel.component'; +import {EntityDetailsPanelComponent} from './entity/entity-details-panel.component'; +import {ContactComponent} from './contact.component'; + +@NgModule({ + entryComponents: [ + AddEntityDialogComponent + ], + declarations: + [ + EntitiesTableComponent, + AddEntityDialogComponent, + DetailsPanelComponent, + EntityDetailsPanelComponent, + ContactComponent, + ], + imports: [ + CommonModule, + SharedModule + ], + exports: [ + EntitiesTableComponent, + AddEntityDialogComponent, + DetailsPanelComponent, + EntityDetailsPanelComponent, + ContactComponent + ] +}) +export class HomeComponentsModule { } diff --git a/ui-ngx/src/app/shared/components/contact.models.ts b/ui-ngx/src/app/modules/home/models/contact.models.ts similarity index 100% rename from ui-ngx/src/app/shared/components/contact.models.ts rename to ui-ngx/src/app/modules/home/models/contact.models.ts diff --git a/ui-ngx/src/app/shared/models/datasource/entity-datasource.ts b/ui-ngx/src/app/modules/home/models/datasource/entity-datasource.ts similarity index 97% rename from ui-ngx/src/app/shared/models/datasource/entity-datasource.ts rename to ui-ngx/src/app/modules/home/models/datasource/entity-datasource.ts index a61677f668..19ece9066d 100644 --- a/ui-ngx/src/app/shared/models/datasource/entity-datasource.ts +++ b/ui-ngx/src/app/modules/home/models/datasource/entity-datasource.ts @@ -22,7 +22,7 @@ import { BaseData, HasId } from '@shared/models/base-data'; import { CollectionViewer, DataSource } from '@angular/cdk/typings/collections'; import { catchError, map, take, tap } from 'rxjs/operators'; import { SelectionModel } from '@angular/cdk/collections'; -import {EntityBooleanFunction} from '@shared/components/entity/entities-table-config.models'; +import {EntityBooleanFunction} from '@home/models/entity/entities-table-config.models'; export type EntitiesFetchFunction, P extends PageLink> = (pageLink: P) => Observable>; diff --git a/ui-ngx/src/app/shared/components/entity/entities-table-config.models.ts b/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts similarity index 82% rename from ui-ngx/src/app/shared/components/entity/entities-table-config.models.ts rename to ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts index 536b5289a1..2065941254 100644 --- a/ui-ngx/src/app/shared/components/entity/entities-table-config.models.ts +++ b/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts @@ -14,26 +14,22 @@ /// limitations under the License. /// -import { BaseData, HasId } from '@shared/models/base-data'; -import { EntityId } from '@shared/models/id/entity-id'; -import { EntitiesFetchFunction } from '@shared/models/datasource/entity-datasource'; -import { Observable, of } from 'rxjs'; -import { emptyPageData } from '@shared/models/page/page-data'; -import { DatePipe } from '@angular/common'; -import { Direction, SortOrder } from '@shared/models/page/sort-order'; -import { - EntityType, - EntityTypeResource, - EntityTypeTranslation -} from '@shared/models/entity-type.models'; -import { EntityComponent } from '@shared/components/entity/entity.component'; -import { Type } from '@angular/core'; -import { EntityAction } from '@shared/components/entity/entity-component.models'; -import { HasUUID } from '@shared/models/id/has-uuid'; -import { PageLink } from '@shared/models/page/page-link'; -import { EntitiesTableComponent } from '@shared/components/entity/entities-table.component'; -import { EntityTableHeaderComponent } from '@shared/components/entity/entity-table-header.component'; -import { ActivatedRoute } from '@angular/router'; +import {BaseData, HasId} from '@shared/models/base-data'; +import {EntitiesFetchFunction} from '@home/models/datasource/entity-datasource'; +import {Observable, of} from 'rxjs'; +import {emptyPageData} from '@shared/models/page/page-data'; +import {DatePipe} from '@angular/common'; +import {Direction, SortOrder} from '@shared/models/page/sort-order'; +import {EntityType, EntityTypeResource, EntityTypeTranslation} from '@shared/models/entity-type.models'; +import {EntityComponent} from '@home/components/entity/entity.component'; +import {Type} from '@angular/core'; +import {EntityAction} from './entity-component.models'; +import {HasUUID} from '@shared/models/id/has-uuid'; +import {PageLink} from '@shared/models/page/page-link'; +import {EntitiesTableComponent} from '@home/components/entity/entities-table.component'; +import {EntityTableHeaderComponent} from '@home/components/entity/entity-table-header.component'; +import {ActivatedRoute} from '@angular/router'; +import { EntityTabsComponent } from '../../components/entity/entity-tabs.component'; export type EntityBooleanFunction> = (entity: T) => boolean; export type EntityStringFunction> = (entity: T) => string; @@ -117,6 +113,7 @@ export class EntityTableConfig, P extends PageLink = P entityTranslations: EntityTypeTranslation; entityResources: EntityTypeResource; entityComponent: Type>; + entityTabsComponent: Type>; addDialogStyle = {}; defaultSortOrder: SortOrder = {property: 'createdTime', direction: Direction.ASC}; columns: Array> = []; diff --git a/ui-ngx/src/app/shared/components/entity/entity-component.models.ts b/ui-ngx/src/app/modules/home/models/entity/entity-component.models.ts similarity index 84% rename from ui-ngx/src/app/shared/components/entity/entity-component.models.ts rename to ui-ngx/src/app/modules/home/models/entity/entity-component.models.ts index a65d44a96c..9c14b38953 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-component.models.ts +++ b/ui-ngx/src/app/modules/home/models/entity/entity-component.models.ts @@ -14,8 +14,8 @@ /// limitations under the License. /// -import { BaseData, HasId } from '@shared/models/base-data'; -import { EntityTableConfig } from '@shared/components/entity/entities-table-config.models'; +import {BaseData, HasId} from '@shared/models/base-data'; +import {EntityTableConfig} from './entities-table-config.models'; export interface AddEntityDialogData> { entitiesTableConfig: EntityTableConfig; diff --git a/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts b/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts index 18dc45c68c..a270d0ccd9 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts @@ -20,8 +20,9 @@ import { CommonModule } from '@angular/common'; import { AdminRoutingModule } from './admin-routing.module'; import { SharedModule } from '@app/shared/shared.module'; import { MailServerComponent } from '@modules/home/pages/admin/mail-server.component'; -import {GeneralSettingsComponent} from "@modules/home/pages/admin/general-settings.component"; -import {SecuritySettingsComponent} from "@modules/home/pages/admin/security-settings.component"; +import {GeneralSettingsComponent} from '@modules/home/pages/admin/general-settings.component'; +import {SecuritySettingsComponent} from '@modules/home/pages/admin/security-settings.component'; +import {HomeComponentsModule} from '@modules/home/components/home-components.module'; @NgModule({ declarations: @@ -33,6 +34,7 @@ import {SecuritySettingsComponent} from "@modules/home/pages/admin/security-sett imports: [ CommonModule, SharedModule, + HomeComponentsModule, AdminRoutingModule ] }) diff --git a/ui-ngx/src/app/modules/home/pages/asset/asset-routing.module.ts b/ui-ngx/src/app/modules/home/pages/asset/asset-routing.module.ts index 8ded96dc19..23914db1dd 100644 --- a/ui-ngx/src/app/modules/home/pages/asset/asset-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/asset/asset-routing.module.ts @@ -17,7 +17,7 @@ import {NgModule} from '@angular/core'; import {RouterModule, Routes} from '@angular/router'; -import {EntitiesTableComponent} from '@shared/components/entity/entities-table.component'; +import {EntitiesTableComponent} from '../../components/entity/entities-table.component'; import {Authority} from '@shared/models/authority.enum'; import {AssetsTableConfigResolver} from './assets-table-config.resolver'; diff --git a/ui-ngx/src/app/modules/home/pages/asset/asset-table-header.component.ts b/ui-ngx/src/app/modules/home/pages/asset/asset-table-header.component.ts index 20b638ef5a..29ccd643f4 100644 --- a/ui-ngx/src/app/modules/home/pages/asset/asset-table-header.component.ts +++ b/ui-ngx/src/app/modules/home/pages/asset/asset-table-header.component.ts @@ -17,7 +17,7 @@ import {Component} from '@angular/core'; import {Store} from '@ngrx/store'; import {AppState} from '@core/core.state'; -import {EntityTableHeaderComponent} from '@shared/components/entity/entity-table-header.component'; +import {EntityTableHeaderComponent} from '../../components/entity/entity-table-header.component'; import {EntityType} from '@shared/models/entity-type.models'; import {AssetInfo} from '@shared/models/asset.models'; diff --git a/ui-ngx/src/app/modules/home/pages/asset/asset.component.ts b/ui-ngx/src/app/modules/home/pages/asset/asset.component.ts index 6265126dbf..b0365273e3 100644 --- a/ui-ngx/src/app/modules/home/pages/asset/asset.component.ts +++ b/ui-ngx/src/app/modules/home/pages/asset/asset.component.ts @@ -17,7 +17,7 @@ import {Component} from '@angular/core'; import {Store} from '@ngrx/store'; import {AppState} from '@core/core.state'; -import {EntityComponent} from '@shared/components/entity/entity.component'; +import {EntityComponent} from '../../components/entity/entity.component'; import {FormBuilder, FormGroup, Validators} from '@angular/forms'; import {EntityType} from '@shared/models/entity-type.models'; import {NULL_UUID} from '@shared/models/id/has-uuid'; diff --git a/ui-ngx/src/app/modules/home/pages/asset/asset.module.ts b/ui-ngx/src/app/modules/home/pages/asset/asset.module.ts index 04c401979e..cbdabf8aea 100644 --- a/ui-ngx/src/app/modules/home/pages/asset/asset.module.ts +++ b/ui-ngx/src/app/modules/home/pages/asset/asset.module.ts @@ -21,6 +21,7 @@ import {HomeDialogsModule} from '../../dialogs/home-dialogs.module'; import {AssetComponent} from './asset.component'; import {AssetTableHeaderComponent} from './asset-table-header.component'; import {AssetRoutingModule} from './asset-routing.module'; +import {HomeComponentsModule} from '@modules/home/components/home-components.module'; @NgModule({ entryComponents: [ @@ -34,6 +35,7 @@ import {AssetRoutingModule} from './asset-routing.module'; imports: [ CommonModule, SharedModule, + HomeComponentsModule, HomeDialogsModule, AssetRoutingModule ] diff --git a/ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts index 016004a493..40599b1d72 100644 --- a/ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts @@ -25,11 +25,11 @@ import { EntityTableConfig, GroupActionDescriptor, HeaderActionDescriptor -} from '@shared/components/entity/entities-table-config.models'; +} from '@home/models/entity/entities-table-config.models'; import {TranslateService} from '@ngx-translate/core'; import {DatePipe} from '@angular/common'; import {EntityType, entityTypeResources, entityTypeTranslations} from '@shared/models/entity-type.models'; -import {EntityAction} from '@shared/components/entity/entity-component.models'; +import {EntityAction} from '@home/models/entity/entity-component.models'; import {forkJoin, Observable, of} from 'rxjs'; import {select, Store} from '@ngrx/store'; import {selectAuthUser} from '@core/auth/auth.selectors'; diff --git a/ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts b/ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts index d405c8cf12..10a0e0f4eb 100644 --- a/ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts @@ -17,7 +17,7 @@ import {NgModule} from '@angular/core'; import {RouterModule, Routes} from '@angular/router'; -import {EntitiesTableComponent} from '@shared/components/entity/entities-table.component'; +import {EntitiesTableComponent} from '../../components/entity/entities-table.component'; import {Authority} from '@shared/models/authority.enum'; import {UsersTableConfigResolver} from '../user/users-table-config.resolver'; import {CustomersTableConfigResolver} from './customers-table-config.resolver'; diff --git a/ui-ngx/src/app/modules/home/pages/customer/customer.component.ts b/ui-ngx/src/app/modules/home/pages/customer/customer.component.ts index 3a7d4b55cb..6ef3349c98 100644 --- a/ui-ngx/src/app/modules/home/pages/customer/customer.component.ts +++ b/ui-ngx/src/app/modules/home/pages/customer/customer.component.ts @@ -19,10 +19,10 @@ import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { Customer } from '@shared/models/customer.model'; -import { ContactBasedComponent } from '@shared/components/entity/contact-based.component'; import {Tenant} from '@app/shared/models/tenant.model'; import {ActionNotificationShow} from '@app/core/notification/notification.actions'; import {TranslateService} from '@ngx-translate/core'; +import {ContactBasedComponent} from '../../components/entity/contact-based.component'; @Component({ selector: 'tb-customer', diff --git a/ui-ngx/src/app/modules/home/pages/customer/customer.module.ts b/ui-ngx/src/app/modules/home/pages/customer/customer.module.ts index c0b5ff743f..3b4b78244e 100644 --- a/ui-ngx/src/app/modules/home/pages/customer/customer.module.ts +++ b/ui-ngx/src/app/modules/home/pages/customer/customer.module.ts @@ -19,6 +19,7 @@ import { CommonModule } from '@angular/common'; import { SharedModule } from '@shared/shared.module'; import {CustomerComponent} from '@modules/home/pages/customer/customer.component'; import {CustomerRoutingModule} from './customer-routing.module'; +import {HomeComponentsModule} from '@modules/home/components/home-components.module'; @NgModule({ entryComponents: [ @@ -30,6 +31,7 @@ import {CustomerRoutingModule} from './customer-routing.module'; imports: [ CommonModule, SharedModule, + HomeComponentsModule, CustomerRoutingModule ] }) diff --git a/ui-ngx/src/app/modules/home/pages/customer/customers-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/customer/customers-table-config.resolver.ts index 7f340b9afe..76477a2c4e 100644 --- a/ui-ngx/src/app/modules/home/pages/customer/customers-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/customer/customers-table-config.resolver.ts @@ -23,7 +23,7 @@ import { DateEntityTableColumn, EntityTableColumn, EntityTableConfig -} from '@shared/components/entity/entities-table-config.models'; +} from '@home/models/entity/entities-table-config.models'; import { TranslateService } from '@ngx-translate/core'; import { DatePipe } from '@angular/common'; import { @@ -31,7 +31,7 @@ import { entityTypeResources, entityTypeTranslations } from '@shared/models/entity-type.models'; -import { EntityAction } from '@shared/components/entity/entity-component.models'; +import { EntityAction } from '@home/models/entity/entity-component.models'; import {Customer} from '@app/shared/models/customer.model'; import {CustomerService} from '@app/core/http/customer.service'; import {CustomerComponent} from '@modules/home/pages/customer/customer.component'; diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-form.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-form.component.ts index 6914a4f0c0..95e1f2f620 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-form.component.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-form.component.ts @@ -17,7 +17,7 @@ import {Component} from '@angular/core'; import {Store} from '@ngrx/store'; import {AppState} from '@core/core.state'; -import {EntityComponent} from '@shared/components/entity/entity.component'; +import {EntityComponent} from '../../components/entity/entity.component'; import {FormBuilder, FormGroup, Validators} from '@angular/forms'; import {ActionNotificationShow} from '@core/notification/notification.actions'; import {TranslateService} from '@ngx-translate/core'; @@ -35,7 +35,7 @@ import {DashboardService} from '@core/http/dashboard.service'; templateUrl: './dashboard-form.component.html', styleUrls: ['./dashboard-form.component.scss'] }) -export class DashboardFormComponent extends EntityComponent { +export class DashboardFormComponent extends EntityComponent { dashboardScope: 'tenant' | 'customer' | 'customer_user'; customerId: string; diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-routing.module.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-routing.module.ts index bda080e3bd..ee52db4849 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-routing.module.ts @@ -17,7 +17,7 @@ import {NgModule} from '@angular/core'; import {RouterModule, Routes} from '@angular/router'; -import {EntitiesTableComponent} from '@shared/components/entity/entities-table.component'; +import {EntitiesTableComponent} from '../../components/entity/entities-table.component'; import {Authority} from '@shared/models/authority.enum'; import {DashboardsTableConfigResolver} from './dashboards-table-config.resolver'; diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard.module.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard.module.ts index 77476a0087..9726fe8ec6 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard.module.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard.module.ts @@ -22,6 +22,7 @@ import {DashboardFormComponent} from '@modules/home/pages/dashboard/dashboard-fo import {ManageDashboardCustomersDialogComponent} from '@modules/home/pages/dashboard/manage-dashboard-customers-dialog.component'; import {DashboardRoutingModule} from './dashboard-routing.module'; import {MakeDashboardPublicDialogComponent} from '@modules/home/pages/dashboard/make-dashboard-public-dialog.component'; +import {HomeComponentsModule} from '@modules/home/components/home-components.module'; @NgModule({ entryComponents: [ @@ -37,6 +38,7 @@ import {MakeDashboardPublicDialogComponent} from '@modules/home/pages/dashboard/ imports: [ CommonModule, SharedModule, + HomeComponentsModule, HomeDialogsModule, DashboardRoutingModule ] diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts index 9cb38ff734..968a5a2563 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts @@ -25,11 +25,11 @@ import { EntityTableConfig, GroupActionDescriptor, HeaderActionDescriptor -} from '@shared/components/entity/entities-table-config.models'; +} from '@home/models/entity/entities-table-config.models'; import {TranslateService} from '@ngx-translate/core'; import {DatePipe} from '@angular/common'; import {EntityType, entityTypeResources, entityTypeTranslations} from '@shared/models/entity-type.models'; -import {EntityAction} from '@shared/components/entity/entity-component.models'; +import {EntityAction} from '@home/models/entity/entity-component.models'; import {forkJoin, Observable, of} from 'rxjs'; import {select, Store} from '@ngrx/store'; import {selectAuthUser} from '@core/auth/auth.selectors'; diff --git a/ui-ngx/src/app/modules/home/pages/device/device-routing.module.ts b/ui-ngx/src/app/modules/home/pages/device/device-routing.module.ts index 78ccb6370e..52e6a43dfd 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/device/device-routing.module.ts @@ -17,7 +17,7 @@ import {NgModule} from '@angular/core'; import {RouterModule, Routes} from '@angular/router'; -import {EntitiesTableComponent} from '@shared/components/entity/entities-table.component'; +import {EntitiesTableComponent} from '../../components/entity/entities-table.component'; import {Authority} from '@shared/models/authority.enum'; import {DevicesTableConfigResolver} from '@modules/home/pages/device/devices-table-config.resolver'; diff --git a/ui-ngx/src/app/modules/home/pages/device/device-table-header.component.ts b/ui-ngx/src/app/modules/home/pages/device/device-table-header.component.ts index 4a89ebc60c..4e7017c62c 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-table-header.component.ts +++ b/ui-ngx/src/app/modules/home/pages/device/device-table-header.component.ts @@ -17,7 +17,7 @@ import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { EntityTableHeaderComponent } from '@shared/components/entity/entity-table-header.component'; +import { EntityTableHeaderComponent } from '../../components/entity/entity-table-header.component'; import {DeviceInfo} from '@app/shared/models/device.models'; import {EntityType} from '@shared/models/entity-type.models'; diff --git a/ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html b/ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html new file mode 100644 index 0000000000..bb8056d55b --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html @@ -0,0 +1,29 @@ + + + Hobotok
{{ test }} + {{ entity | json }} +
+ + Hobotok 2
+ {{ entity | json }} +
+ + Hobotok 2
+ {{ entity | json }} +
diff --git a/ui-ngx/src/app/modules/home/pages/device/device-tabs.component.ts b/ui-ngx/src/app/modules/home/pages/device/device-tabs.component.ts new file mode 100644 index 0000000000..e335218800 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/device/device-tabs.component.ts @@ -0,0 +1,41 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { DeviceInfo } from '@shared/models/device.models'; +import { EntityType } from '@shared/models/entity-type.models'; +import { EntityTabsComponent } from '../../components/entity/entity-tabs.component'; + +@Component({ + selector: 'tb-device-tabs', + templateUrl: './device-tabs.component.html', + styleUrls: [] +}) +export class DeviceTabsComponent extends EntityTabsComponent { + + entityType = EntityType; + + constructor(protected store: Store) { + super(store); + } + + ngOnInit() { + super.ngOnInit(); + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/device/device.component.ts b/ui-ngx/src/app/modules/home/pages/device/device.component.ts index ca86fa33af..721b4bf875 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device.component.ts +++ b/ui-ngx/src/app/modules/home/pages/device/device.component.ts @@ -17,7 +17,7 @@ import { Component, OnInit } from '@angular/core'; import { select, Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { EntityComponent } from '@shared/components/entity/entity.component'; +import { EntityComponent } from '../../components/entity/entity.component'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { User } from '@shared/models/user.model'; import { selectAuth, selectUserDetails } from '@core/auth/auth.selectors'; diff --git a/ui-ngx/src/app/modules/home/pages/device/device.module.ts b/ui-ngx/src/app/modules/home/pages/device/device.module.ts index 2644e2b26a..2e3b659860 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device.module.ts +++ b/ui-ngx/src/app/modules/home/pages/device/device.module.ts @@ -22,21 +22,26 @@ import {DeviceRoutingModule} from './device-routing.module'; import {DeviceTableHeaderComponent} from '@modules/home/pages/device/device-table-header.component'; import {DeviceCredentialsDialogComponent} from '@modules/home/pages/device/device-credentials-dialog.component'; import {HomeDialogsModule} from '../../dialogs/home-dialogs.module'; +import {HomeComponentsModule} from '@modules/home/components/home-components.module'; +import { DeviceTabsComponent } from '@home/pages/device/device-tabs.component'; @NgModule({ entryComponents: [ DeviceComponent, + DeviceTabsComponent, DeviceTableHeaderComponent, DeviceCredentialsDialogComponent ], declarations: [ DeviceComponent, + DeviceTabsComponent, DeviceTableHeaderComponent, DeviceCredentialsDialogComponent ], imports: [ CommonModule, SharedModule, + HomeComponentsModule, HomeDialogsModule, DeviceRoutingModule ] 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 95e6edc67b..b916313ce9 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 @@ -24,11 +24,11 @@ import { EntityTableColumn, EntityTableConfig, GroupActionDescriptor, HeaderActionDescriptor -} from '@shared/components/entity/entities-table-config.models'; +} from '@home/models/entity/entities-table-config.models'; import {TranslateService} from '@ngx-translate/core'; import {DatePipe} from '@angular/common'; import {EntityType, entityTypeResources, entityTypeTranslations} from '@shared/models/entity-type.models'; -import {EntityAction} from '@shared/components/entity/entity-component.models'; +import {EntityAction} from '@home/models/entity/entity-component.models'; import {Device, DeviceCredentials, DeviceInfo} from '@app/shared/models/device.models'; import {DeviceComponent} from '@modules/home/pages/device/device.component'; import {forkJoin, Observable, of} from 'rxjs'; @@ -58,6 +58,7 @@ import { AddEntitiesToCustomerDialogComponent, AddEntitiesToCustomerDialogData } from '../../dialogs/add-entities-to-customer-dialog.component'; +import { DeviceTabsComponent } from '@home/pages/device/device-tabs.component'; @Injectable() export class DevicesTableConfigResolver implements Resolve> { @@ -78,6 +79,7 @@ export class DevicesTableConfigResolver implements Resolve { }; diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index feab000ddc..653edf41ab 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -14,72 +14,67 @@ /// limitations under the License. /// -import { NgModule } from '@angular/core'; -import { CommonModule, DatePipe } from '@angular/common'; -import { FooterComponent } from './components/footer.component'; -import { LogoComponent } from './components/logo.component'; -import { ToastDirective, TbSnackBarComponent } from './components/toast.directive'; -import { BreadcrumbComponent } from '@app/shared/components/breadcrumb.component'; +import {NgModule} from '@angular/core'; +import {CommonModule, DatePipe} from '@angular/common'; +import {FooterComponent} from './components/footer.component'; +import {LogoComponent} from './components/logo.component'; +import {TbSnackBarComponent, ToastDirective} from './components/toast.directive'; +import {BreadcrumbComponent} from '@app/shared/components/breadcrumb.component'; import { + MatAutocompleteModule, MatButtonModule, + MatCardModule, MatCheckboxModule, + MatChipsModule, + MatDatepickerModule, + MatDialogModule, + MatDividerModule, + MatExpansionModule, + MatGridListModule, MatIconModule, - MatCardModule, - MatProgressBarModule, MatInputModule, - MatSnackBarModule, - MatSidenavModule, - MatToolbarModule, MatMenuModule, - MatGridListModule, - MatDialogModule, - MatSelectModule, - MatTooltipModule, - MatTableModule, MatPaginatorModule, - MatSortModule, + MatProgressBarModule, MatProgressSpinnerModule, - MatDividerModule, - MatTabsModule, MatRadioModule, - MatSlideToggleModule, - MatDatepickerModule, + MatSelectModule, + MatSidenavModule, MatSliderModule, - MatExpansionModule, + MatSlideToggleModule, + MatSnackBarModule, + MatSortModule, MatStepperModule, - MatAutocompleteModule, - MatChipsModule + MatTableModule, + MatTabsModule, + MatToolbarModule, + MatTooltipModule } from '@angular/material'; -import { MatDatetimepickerModule, MatNativeDatetimeModule } from '@mat-datetimepicker/core'; -import { FlexLayoutModule } from '@angular/flex-layout'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { RouterModule } from '@angular/router'; -import { ShareModule as ShareButtonsModule } from '@ngx-share/core'; -import { UserMenuComponent } from '@shared/components/user-menu.component'; -import { NospacePipe } from './pipe/nospace.pipe'; -import { TranslateModule } from '@ngx-translate/core'; -import { TbCheckboxComponent } from '@shared/components/tb-checkbox.component'; -import { HelpComponent } from '@shared/components/help.component'; -import { EntitiesTableComponent } from '@shared/components/entity/entities-table.component'; -import { AddEntityDialogComponent } from '@shared/components/entity/add-entity-dialog.component'; -import { DetailsPanelComponent } from '@shared/components/details-panel.component'; -import { EntityDetailsPanelComponent } from '@shared/components/entity/entity-details-panel.component'; -import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; -import { ContactComponent } from '@shared/components/contact.component'; +import {MatDatetimepickerModule, MatNativeDatetimeModule} from '@mat-datetimepicker/core'; +import {FlexLayoutModule} from '@angular/flex-layout'; +import {FormsModule, ReactiveFormsModule} from '@angular/forms'; +import {RouterModule} from '@angular/router'; +import {ShareModule as ShareButtonsModule} from '@ngx-share/core'; +import {UserMenuComponent} from '@shared/components/user-menu.component'; +import {NospacePipe} from './pipe/nospace.pipe'; +import {TranslateModule} from '@ngx-translate/core'; +import {TbCheckboxComponent} from '@shared/components/tb-checkbox.component'; +import {HelpComponent} from '@shared/components/help.component'; +import {TbAnchorComponent} from '@shared/components/tb-anchor.component'; // import { AuditLogDetailsDialogComponent } from '@shared/components/audit-log/audit-log-details-dialog.component'; // import { AuditLogTableComponent } from '@shared/components/audit-log/audit-log-table.component'; -import { MillisecondsToTimeStringPipe } from '@shared/pipe/milliseconds-to-time-string.pipe'; -import { TimewindowComponent } from '@shared/components/time/timewindow.component'; -import { OverlayModule } from '@angular/cdk/overlay'; -import { TimewindowPanelComponent } from '@shared/components/time/timewindow-panel.component'; -import { TimeintervalComponent } from '@shared/components/time/timeinterval.component'; -import { DatetimePeriodComponent } from '@shared/components/time/datetime-period.component'; -import { EnumToArrayPipe } from '@shared/pipe/enum-to-array.pipe'; -import { ClipboardModule } from 'ngx-clipboard'; +import {MillisecondsToTimeStringPipe} from '@shared/pipe/milliseconds-to-time-string.pipe'; +import {TimewindowComponent} from '@shared/components/time/timewindow.component'; +import {OverlayModule} from '@angular/cdk/overlay'; +import {TimewindowPanelComponent} from '@shared/components/time/timewindow-panel.component'; +import {TimeintervalComponent} from '@shared/components/time/timeinterval.component'; +import {DatetimePeriodComponent} from '@shared/components/time/datetime-period.component'; +import {EnumToArrayPipe} from '@shared/pipe/enum-to-array.pipe'; +import {ClipboardModule} from 'ngx-clipboard'; // import { ValueInputComponent } from '@shared/components/value-input.component'; -import { FullscreenDirective } from '@shared/components/fullscreen.directive'; -import { HighlightPipe } from '@shared/pipe/highlight.pipe'; +import {FullscreenDirective} from '@shared/components/fullscreen.directive'; +import {HighlightPipe} from '@shared/pipe/highlight.pipe'; import {DashboardAutocompleteComponent} from '@shared/components/dashboard-autocomplete.component'; import {EntitySubTypeAutocompleteComponent} from '@shared/components/entity/entity-subtype-autocomplete.component'; import {EntitySubTypeSelectComponent} from './components/entity/entity-subtype-select.component'; @@ -102,7 +97,6 @@ import {SocialSharePanelComponent} from './components/socialshare-panel.componen entryComponents: [ TbSnackBarComponent, TbAnchorComponent, - AddEntityDialogComponent, // AuditLogDetailsDialogComponent, TimewindowPanelComponent, ], @@ -117,11 +111,6 @@ import {SocialSharePanelComponent} from './components/socialshare-panel.componen TbSnackBarComponent, BreadcrumbComponent, UserMenuComponent, - EntitiesTableComponent, - AddEntityDialogComponent, - DetailsPanelComponent, - EntityDetailsPanelComponent, - ContactComponent, // AuditLogTableComponent, // AuditLogDetailsDialogComponent, TimewindowComponent, @@ -195,11 +184,6 @@ import {SocialSharePanelComponent} from './components/socialshare-panel.componen TbCheckboxComponent, BreadcrumbComponent, UserMenuComponent, - EntitiesTableComponent, - AddEntityDialogComponent, - DetailsPanelComponent, - EntityDetailsPanelComponent, - ContactComponent, // AuditLogTableComponent, TimewindowComponent, TimewindowPanelComponent, diff --git a/ui-ngx/tsconfig.json b/ui-ngx/tsconfig.json index 0c3e7e411f..8f4576a7bf 100644 --- a/ui-ngx/tsconfig.json +++ b/ui-ngx/tsconfig.json @@ -21,7 +21,8 @@ ], "@core/*": ["src/app/core/*"], "@modules/*": ["src/app/modules/*"], - "@shared/*": ["src/app/shared/*"] + "@shared/*": ["src/app/shared/*"], + "@home/*": ["src/app/modules/home/*"] }, "lib": [ "es2018", From c8ec87f41d65063af262ecdf207f97251a4d25aa Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 22 Aug 2019 10:59:22 +0300 Subject: [PATCH 019/133] Improve audit logs page link --- .../server/controller/AuditLogController.java | 38 ++++---- .../dao/sql/audit/AuditLogRepository.java | 80 ++++++++++++++++- .../server/dao/sql/audit/JpaAuditLogDao.java | 86 +++++++++---------- .../pages/device/device-tabs.component.html | 2 +- 4 files changed, 137 insertions(+), 69 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/AuditLogController.java b/application/src/main/java/org/thingsboard/server/controller/AuditLogController.java index c3ef7f5f17..77e8b43f96 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AuditLogController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AuditLogController.java @@ -44,14 +44,15 @@ public class AuditLogController extends BaseController { @PathVariable("customerId") String strCustomerId, @RequestParam int pageSize, @RequestParam int page, + @RequestParam(required = false) String textSearch, + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder, @RequestParam(required = false) Long startTime, - @RequestParam(required = false) Long endTime, - @RequestParam(required = false, defaultValue = "false") boolean ascOrder) throws ThingsboardException { + @RequestParam(required = false) Long endTime) throws ThingsboardException { try { checkParameter("CustomerId", strCustomerId); TenantId tenantId = getCurrentUser().getTenantId(); - TimePageLink pageLink = createTimePageLink(pageSize, page, "", - "createdTime", ascOrder ? "asc" : "desc", startTime, endTime); + TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); return checkNotNull(auditLogService.findAuditLogsByTenantIdAndCustomerId(tenantId, new CustomerId(UUID.fromString(strCustomerId)), pageLink)); } catch (Exception e) { throw handleException(e); @@ -65,15 +66,15 @@ public class AuditLogController extends BaseController { @PathVariable("userId") String strUserId, @RequestParam int pageSize, @RequestParam int page, + @RequestParam(required = false) String textSearch, + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder, @RequestParam(required = false) Long startTime, - @RequestParam(required = false) Long endTime, - @RequestParam(required = false, defaultValue = "false") boolean ascOrder) throws ThingsboardException { + @RequestParam(required = false) Long endTime) throws ThingsboardException { try { checkParameter("UserId", strUserId); TenantId tenantId = getCurrentUser().getTenantId(); - TimePageLink pageLink = createTimePageLink(pageSize, page, "", - "createdTime", ascOrder ? "asc" : "desc", startTime, endTime); - return checkNotNull(auditLogService.findAuditLogsByTenantIdAndUserId(tenantId, new UserId(UUID.fromString(strUserId)), pageLink)); + TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); return checkNotNull(auditLogService.findAuditLogsByTenantIdAndUserId(tenantId, new UserId(UUID.fromString(strUserId)), pageLink)); } catch (Exception e) { throw handleException(e); } @@ -87,16 +88,16 @@ public class AuditLogController extends BaseController { @PathVariable("entityId") String strEntityId, @RequestParam int pageSize, @RequestParam int page, + @RequestParam(required = false) String textSearch, + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder, @RequestParam(required = false) Long startTime, - @RequestParam(required = false) Long endTime, - @RequestParam(required = false, defaultValue = "false") boolean ascOrder) throws ThingsboardException { + @RequestParam(required = false) Long endTime) throws ThingsboardException { try { checkParameter("EntityId", strEntityId); checkParameter("EntityType", strEntityType); TenantId tenantId = getCurrentUser().getTenantId(); - TimePageLink pageLink = createTimePageLink(pageSize, page, "", - "createdTime", ascOrder ? "asc" : "desc", startTime, endTime); - return checkNotNull(auditLogService.findAuditLogsByTenantIdAndEntityId(tenantId, EntityIdFactory.getByTypeAndId(strEntityType, strEntityId), pageLink)); + TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); return checkNotNull(auditLogService.findAuditLogsByTenantIdAndEntityId(tenantId, EntityIdFactory.getByTypeAndId(strEntityType, strEntityId), pageLink)); } catch (Exception e) { throw handleException(e); } @@ -108,13 +109,14 @@ public class AuditLogController extends BaseController { public PageData getAuditLogs( @RequestParam int pageSize, @RequestParam int page, + @RequestParam(required = false) String textSearch, + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder, @RequestParam(required = false) Long startTime, - @RequestParam(required = false) Long endTime, - @RequestParam(required = false, defaultValue = "false") boolean ascOrder) throws ThingsboardException { + @RequestParam(required = false) Long endTime) throws ThingsboardException { try { TenantId tenantId = getCurrentUser().getTenantId(); - TimePageLink pageLink = createTimePageLink(pageSize, page, "", - "createdTime", ascOrder ? "asc" : "desc", startTime, endTime); + TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); return checkNotNull(auditLogService.findAuditLogsByTenantId(tenantId, pageLink)); } catch (Exception e) { throw handleException(e); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/audit/AuditLogRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/audit/AuditLogRepository.java index 6a4cc9f72e..bc52512e0f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/audit/AuditLogRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/audit/AuditLogRepository.java @@ -15,10 +15,84 @@ */ package org.thingsboard.server.dao.sql.audit; -import org.springframework.data.jpa.repository.JpaSpecificationExecutor; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.data.repository.query.Param; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.dao.model.sql.AuditLogEntity; -public interface AuditLogRepository extends CrudRepository, JpaSpecificationExecutor { +public interface AuditLogRepository extends PagingAndSortingRepository { + + @Query("SELECT a FROM AuditLogEntity a WHERE " + + "a.tenantId = :tenantId " + + "AND (:startId IS NULL OR a.id >= :startId) " + + "AND (:endId IS NULL OR a.id <= :endId) " + + "AND (LOWER(a.entityType) LIKE LOWER(CONCAT(:textSearch, '%'))" + + "OR LOWER(a.entityName) LIKE LOWER(CONCAT(:textSearch, '%'))" + + "OR LOWER(a.userName) LIKE LOWER(CONCAT(:textSearch, '%'))" + + "OR LOWER(a.actionType) LIKE LOWER(CONCAT(:textSearch, '%'))" + + "OR LOWER(a.actionStatus) LIKE LOWER(CONCAT(:textSearch, '%')))" + ) + Page findByTenantId( + @Param("tenantId") String tenantId, + @Param("textSearch") String textSearch, + @Param("startId") String startId, + @Param("endId") String endId, + Pageable pageable); + + @Query("SELECT a FROM AuditLogEntity a WHERE " + + "a.tenantId = :tenantId " + + "AND a.entityType = :entityType AND a.entityId = :entityId " + + "AND (:startId IS NULL OR a.id >= :startId) " + + "AND (:endId IS NULL OR a.id <= :endId) " + + "AND (LOWER(a.entityName) LIKE LOWER(CONCAT(:textSearch, '%'))" + + "OR LOWER(a.userName) LIKE LOWER(CONCAT(:textSearch, '%'))" + + "OR LOWER(a.actionType) LIKE LOWER(CONCAT(:textSearch, '%'))" + + "OR LOWER(a.actionStatus) LIKE LOWER(CONCAT(:textSearch, '%')))" + ) + Page findAuditLogsByTenantIdAndEntityId(@Param("tenantId") String tenantId, + @Param("entityType") EntityType entityType, + @Param("entityId") String entityId, + @Param("textSearch") String textSearch, + @Param("startId") String startId, + @Param("endId") String endId, + Pageable pageable); + + @Query("SELECT a FROM AuditLogEntity a WHERE " + + "a.tenantId = :tenantId " + + "AND a.customerId = :customerId " + + "AND (:startId IS NULL OR a.id >= :startId) " + + "AND (:endId IS NULL OR a.id <= :endId) " + + "AND (LOWER(a.entityType) LIKE LOWER(CONCAT(:textSearch, '%'))" + + "OR LOWER(a.entityName) LIKE LOWER(CONCAT(:textSearch, '%'))" + + "OR LOWER(a.userName) LIKE LOWER(CONCAT(:textSearch, '%'))" + + "OR LOWER(a.actionType) LIKE LOWER(CONCAT(:textSearch, '%'))" + + "OR LOWER(a.actionStatus) LIKE LOWER(CONCAT(:textSearch, '%')))" + ) + Page findAuditLogsByTenantIdAndCustomerId(@Param("tenantId") String tenantId, + @Param("customerId") String customerId, + @Param("textSearch") String textSearch, + @Param("startId") String startId, + @Param("endId") String endId, + Pageable pageable); + + @Query("SELECT a FROM AuditLogEntity a WHERE " + + "a.tenantId = :tenantId " + + "AND a.userId = :userId " + + "AND (:startId IS NULL OR a.id >= :startId) " + + "AND (:endId IS NULL OR a.id <= :endId) " + + "AND (LOWER(a.entityType) LIKE LOWER(CONCAT(:textSearch, '%'))" + + "OR LOWER(a.entityName) LIKE LOWER(CONCAT(:textSearch, '%'))" + + "OR LOWER(a.actionType) LIKE LOWER(CONCAT(:textSearch, '%'))" + + "OR LOWER(a.actionStatus) LIKE LOWER(CONCAT(:textSearch, '%')))" + ) + Page findAuditLogsByTenantIdAndUserId(@Param("tenantId") String tenantId, + @Param("userId") String userId, + @Param("textSearch") String textSearch, + @Param("startId") String startId, + @Param("endId") String endId, + Pageable pageable); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDao.java index 91b0a16dd7..8cb2c1f764 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDao.java @@ -19,13 +19,8 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.data.jpa.domain.Specification; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.UUIDConverter; import org.thingsboard.server.common.data.audit.AuditLog; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; @@ -36,18 +31,16 @@ import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.audit.AuditLogDao; import org.thingsboard.server.dao.model.sql.AuditLogEntity; import org.thingsboard.server.dao.sql.JpaAbstractDao; -import org.thingsboard.server.dao.sql.JpaAbstractSearchTimeDao; import org.thingsboard.server.dao.util.SqlDao; import javax.annotation.PreDestroy; -import javax.persistence.criteria.Predicate; -import java.util.ArrayList; -import java.util.List; +import java.util.Objects; import java.util.UUID; import java.util.concurrent.Executors; -import static org.springframework.data.jpa.domain.Specifications.where; -import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY; +import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID; +import static org.thingsboard.server.dao.DaoUtil.endTimeToId; +import static org.thingsboard.server.dao.DaoUtil.startTimeToId; @Component @SqlDao @@ -83,53 +76,52 @@ public class JpaAuditLogDao extends JpaAbstractDao imp @Override public PageData findAuditLogsByTenantIdAndEntityId(UUID tenantId, EntityId entityId, TimePageLink pageLink) { - return findAuditLogs(tenantId, entityId, null, null, pageLink); + return DaoUtil.toPageData( + auditLogRepository + .findAuditLogsByTenantIdAndEntityId( + fromTimeUUID(tenantId), + entityId.getEntityType(), + fromTimeUUID(entityId.getId()), + Objects.toString(pageLink.getTextSearch(), ""), + startTimeToId(pageLink.getStartTime()), + endTimeToId(pageLink.getEndTime()), + DaoUtil.toPageable(pageLink))); } @Override public PageData findAuditLogsByTenantIdAndCustomerId(UUID tenantId, CustomerId customerId, TimePageLink pageLink) { - return findAuditLogs(tenantId, null, customerId, null, pageLink); + return DaoUtil.toPageData( + auditLogRepository + .findAuditLogsByTenantIdAndCustomerId( + fromTimeUUID(tenantId), + fromTimeUUID(customerId.getId()), + Objects.toString(pageLink.getTextSearch(), ""), + startTimeToId(pageLink.getStartTime()), + endTimeToId(pageLink.getEndTime()), + DaoUtil.toPageable(pageLink))); } @Override public PageData findAuditLogsByTenantIdAndUserId(UUID tenantId, UserId userId, TimePageLink pageLink) { - return findAuditLogs(tenantId, null, null, userId, pageLink); + return DaoUtil.toPageData( + auditLogRepository + .findAuditLogsByTenantIdAndUserId( + fromTimeUUID(tenantId), + fromTimeUUID(userId.getId()), + Objects.toString(pageLink.getTextSearch(), ""), + startTimeToId(pageLink.getStartTime()), + endTimeToId(pageLink.getEndTime()), + DaoUtil.toPageable(pageLink))); } @Override public PageData findAuditLogsByTenantId(UUID tenantId, TimePageLink pageLink) { - return findAuditLogs(tenantId, null, null, null, pageLink); - } - - private PageData findAuditLogs(UUID tenantId, EntityId entityId, CustomerId customerId, UserId userId, TimePageLink pageLink) { - Specification timeSearchSpec = JpaAbstractSearchTimeDao.getTimeSearchPageSpec(pageLink, "id"); - Specification fieldsSpec = getEntityFieldsSpec(tenantId, entityId, customerId, userId); - Pageable pageable = DaoUtil.toPageable(pageLink); - return DaoUtil.toPageData(auditLogRepository.findAll(where(timeSearchSpec).and(fieldsSpec), pageable)); - } - - private Specification getEntityFieldsSpec(UUID tenantId, EntityId entityId, CustomerId customerId, UserId userId) { - return (root, criteriaQuery, criteriaBuilder) -> { - List predicates = new ArrayList<>(); - if (tenantId != null) { - Predicate tenantIdPredicate = criteriaBuilder.equal(root.get("tenantId"), UUIDConverter.fromTimeUUID(tenantId)); - predicates.add(tenantIdPredicate); - } - if (entityId != null) { - Predicate entityTypePredicate = criteriaBuilder.equal(root.get("entityType"), entityId.getEntityType()); - predicates.add(entityTypePredicate); - Predicate entityIdPredicate = criteriaBuilder.equal(root.get("entityId"), UUIDConverter.fromTimeUUID(entityId.getId())); - predicates.add(entityIdPredicate); - } - if (customerId != null) { - Predicate tenantIdPredicate = criteriaBuilder.equal(root.get("customerId"), UUIDConverter.fromTimeUUID(customerId.getId())); - predicates.add(tenantIdPredicate); - } - if (userId != null) { - Predicate tenantIdPredicate = criteriaBuilder.equal(root.get("userId"), UUIDConverter.fromTimeUUID(userId.getId())); - predicates.add(tenantIdPredicate); - } - return criteriaBuilder.and(predicates.toArray(new Predicate[]{})); - }; + return DaoUtil.toPageData( + auditLogRepository.findByTenantId( + fromTimeUUID(tenantId), + Objects.toString(pageLink.getTextSearch(), ""), + startTimeToId(pageLink.getStartTime()), + endTimeToId(pageLink.getEndTime()), + DaoUtil.toPageable(pageLink))); } } diff --git a/ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html b/ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html index bb8056d55b..8a1c1718ba 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html @@ -16,7 +16,7 @@ --> - Hobotok
{{ test }} + Hobotok
{{ entity | json }}
From 9d4b79c91cec7273b82cf4b537c7a4172c6e2601 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 22 Aug 2019 13:34:15 +0300 Subject: [PATCH 020/133] UI: Audit logs. --- ui-ngx/package.json | 2 +- ui-ngx/src/app/core/auth/auth.service.ts | 9 +- ui-ngx/src/app/core/http/audit-log.service.ts | 59 ++++++++ .../audit-log-details-dialog.component.html | 45 ++++++ .../audit-log-details-dialog.component.scss | 25 ++++ .../audit-log-details-dialog.component.ts | 124 ++++++++++++++++ .../audit-log/audit-log-table-config.ts | 138 ++++++++++++++++++ .../audit-log/audit-log-table.component.html | 18 +++ .../audit-log/audit-log-table.component.scss | 22 +++ .../audit-log/audit-log-table.component.ts | 133 +++++++++++++++++ .../entity/entities-table.component.html | 4 +- .../entity/entity-tabs.component.ts | 14 ++ .../home/components/home-components.module.ts | 10 +- .../pages/asset/asset-tabs.component.html | 21 +++ .../home/pages/asset/asset-tabs.component.ts | 38 +++++ .../modules/home/pages/asset/asset.module.ts | 3 + .../asset/assets-table-config.resolver.ts | 2 + .../audit-log/audit-log-routing.module.ts | 41 ++++++ .../home/pages/audit-log/audit-log.module.ts | 31 ++++ .../customer/customer-tabs.component.html | 21 +++ .../pages/customer/customer-tabs.component.ts | 38 +++++ .../home/pages/customer/customer.module.ts | 7 +- .../customers-table-config.resolver.ts | 2 + .../dashboard/dashboard-tabs.component.html | 21 +++ .../dashboard/dashboard-tabs.component.ts | 38 +++++ .../home/pages/dashboard/dashboard.module.ts | 3 + .../dashboards-table-config.resolver.ts | 2 + .../pages/device/device-tabs.component.html | 14 +- .../pages/device/device-tabs.component.ts | 2 - .../entity-view-tabs.component.html | 21 +++ .../entity-view/entity-view-tabs.component.ts | 38 +++++ .../pages/entity-view/entity-view.module.ts | 3 + .../entity-views-table-config.resolver.ts | 2 + .../modules/home/pages/home-pages.module.ts | 4 +- .../rulechain/rulechain-tabs.component.html | 21 +++ .../rulechain/rulechain-tabs.component.ts | 39 +++++ .../home/pages/rulechain/rulechain.module.ts | 7 +- .../rulechains-table-config.resolver.ts | 2 + .../shared/components/user-menu.component.ts | 4 - .../src/app/shared/models/audit-log.models.ts | 102 +++++++++++++ .../src/app/shared/models/id/audit-log-id.ts | 24 +++ ui-ngx/src/app/shared/shared.module.ts | 6 - 42 files changed, 1125 insertions(+), 35 deletions(-) create mode 100644 ui-ngx/src/app/core/http/audit-log.service.ts create mode 100644 ui-ngx/src/app/modules/home/components/audit-log/audit-log-details-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/components/audit-log/audit-log-details-dialog.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/audit-log/audit-log-details-dialog.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/audit-log/audit-log-table-config.ts create mode 100644 ui-ngx/src/app/modules/home/components/audit-log/audit-log-table.component.html create mode 100644 ui-ngx/src/app/modules/home/components/audit-log/audit-log-table.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/audit-log/audit-log-table.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/asset/asset-tabs.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/asset/asset-tabs.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/audit-log/audit-log-routing.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/audit-log/audit-log.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/customer/customer-tabs.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/customer/customer-tabs.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/dashboard/dashboard-tabs.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/dashboard/dashboard-tabs.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/entity-view/entity-view-tabs.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/entity-view/entity-view-tabs.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/rulechain/rulechain-tabs.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/rulechain/rulechain-tabs.component.ts create mode 100644 ui-ngx/src/app/shared/models/audit-log.models.ts create mode 100644 ui-ngx/src/app/shared/models/id/audit-log-id.ts diff --git a/ui-ngx/package.json b/ui-ngx/package.json index 6908849900..b180dcfdc6 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -3,7 +3,7 @@ "version": "3.0.0", "scripts": { "ng": "ng", - "start": "ng serve --host 0.0.0.0 --open", + "start": "ng serve --open", "build": "ng build", "build:prod": "ng build --prod --vendor-chunk", "test": "ng test", diff --git a/ui-ngx/src/app/core/auth/auth.service.ts b/ui-ngx/src/app/core/auth/auth.service.ts index 2df91a6416..1ae48af68c 100644 --- a/ui-ngx/src/app/core/auth/auth.service.ts +++ b/ui-ngx/src/app/core/auth/auth.service.ts @@ -165,7 +165,14 @@ export class AuthService { if (captureLastUrl) { this.redirectUrl = this.router.url; } - this.clearJwtToken(); + this.http.post('/api/auth/logout', null, defaultHttpOptions(true, true)) + .subscribe(() => { + this.clearJwtToken(); + }, + () => { + this.clearJwtToken(); + } + ); } private notifyUserLoaded(isUserLoaded: boolean) { diff --git a/ui-ngx/src/app/core/http/audit-log.service.ts b/ui-ngx/src/app/core/http/audit-log.service.ts new file mode 100644 index 0000000000..5c98d849e8 --- /dev/null +++ b/ui-ngx/src/app/core/http/audit-log.service.ts @@ -0,0 +1,59 @@ +/// +/// Copyright © 2016-2019 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 { Injectable } from '@angular/core'; +import { defaultHttpOptions } from './http-utils'; +import { Observable } from 'rxjs/index'; +import { HttpClient } from '@angular/common/http'; +import { PageLink, TimePageLink } from '@shared/models/page/page-link'; +import { PageData } from '@shared/models/page/page-data'; +import { AuditLog } from '@shared/models/audit-log.models'; +import { EntityId } from '@shared/models/id/entity-id'; + +@Injectable({ + providedIn: 'root' +}) +export class AuditLogService { + + constructor( + private http: HttpClient + ) { } + + public getAuditLogs(pageLink: TimePageLink, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + return this.http.get>(`/api/audit/logs${pageLink.toQuery()}`, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public getAuditLogsByCustomerId(customerId: string, pageLink: TimePageLink, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + return this.http.get>(`/api/audit/logs/customer/${customerId}${pageLink.toQuery()}`, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public getAuditLogsByUserId(userId: string, pageLink: TimePageLink, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + return this.http.get>(`/api/audit/logs/user/${userId}${pageLink.toQuery()}`, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public getAuditLogsByEntityId(entityId: EntityId, pageLink: TimePageLink, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + return this.http.get>(`/api/audit/logs/entity/${entityId.entityType}/${entityId.id}${pageLink.toQuery()}`, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/audit-log/audit-log-details-dialog.component.html b/ui-ngx/src/app/modules/home/components/audit-log/audit-log-details-dialog.component.html new file mode 100644 index 0000000000..a07587a5b6 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/audit-log/audit-log-details-dialog.component.html @@ -0,0 +1,45 @@ + + +

audit-log.audit-log-details

+ + + +
+ +
+
+ + +
+
+
+
+ + +
diff --git a/ui-ngx/src/app/modules/home/components/audit-log/audit-log-details-dialog.component.scss b/ui-ngx/src/app/modules/home/components/audit-log/audit-log-details-dialog.component.scss new file mode 100644 index 0000000000..7aedf2416a --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/audit-log/audit-log-details-dialog.component.scss @@ -0,0 +1,25 @@ +/** + * Copyright © 2016-2019 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-audit-log-action-data, + .tb-audit-log-failure-details { + width: 100%; + min-width: 400px; + height: 100%; + min-height: 50px; + border: 1px solid #c0c0c0; + } +} diff --git a/ui-ngx/src/app/modules/home/components/audit-log/audit-log-details-dialog.component.ts b/ui-ngx/src/app/modules/home/components/audit-log/audit-log-details-dialog.component.ts new file mode 100644 index 0000000000..4980c61b49 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/audit-log/audit-log-details-dialog.component.ts @@ -0,0 +1,124 @@ +/// +/// Copyright © 2016-2019 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, + Inject, + OnInit, + Renderer2, + ViewChild +} from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; +import { ActionStatus, AuditLog } from '@shared/models/audit-log.models'; + +import * as ace from 'ace-builds'; + +export interface AuditLogDetailsDialogData { + auditLog: AuditLog; +} + +@Component({ + selector: 'tb-audit-log-details-dialog', + templateUrl: './audit-log-details-dialog.component.html', + styleUrls: ['./audit-log-details-dialog.component.scss'] +}) +export class AuditLogDetailsDialogComponent extends PageComponent implements OnInit { + + @ViewChild('actionDataEditor', {static: true}) + actionDataEditorElmRef: ElementRef; + private actionDataEditor: ace.Ace.Editor; + + @ViewChild('failureDetailsEditor', {static: true}) + failureDetailsEditorElmRef: ElementRef; + private failureDetailsEditor: ace.Ace.Editor; + + auditLog: AuditLog; + displayFailureDetails: boolean; + actionData: string; + actionFailureDetails: string; + + @ViewChild('entityDetailsForm', {static: true}) entityDetailsFormAnchor: TbAnchorComponent; + + constructor(protected store: Store, + @Inject(MAT_DIALOG_DATA) public data: AuditLogDetailsDialogData, + public dialogRef: MatDialogRef, + private renderer: Renderer2) { + super(store); + } + + ngOnInit(): void { + this.auditLog = this.data.auditLog; + this.displayFailureDetails = this.auditLog.actionStatus === ActionStatus.FAILURE; + this.actionData = this.auditLog.actionData ? JSON.stringify(this.auditLog.actionData, null, 2) : ''; + this.actionFailureDetails = this.auditLog.actionFailureDetails; + + this.actionDataEditor = this.createEditor(this.actionDataEditorElmRef, this.actionData); + if (this.displayFailureDetails) { + this.failureDetailsEditor = this.createEditor(this.failureDetailsEditorElmRef, this.actionFailureDetails); + } + } + + createEditor(editorElementRef: ElementRef, content: string): ace.Ace.Editor { + const editorElement = editorElementRef.nativeElement; + let editorOptions: Partial = { + mode: 'ace/mode/java', + theme: 'ace/theme/github', + showGutter: false, + showPrintMargin: false, + readOnly: true + }; + + const advancedOptions = { + enableSnippets: false, + enableBasicAutocompletion: false, + enableLiveAutocompletion: false + }; + + editorOptions = {...editorOptions, ...advancedOptions}; + const editor = ace.edit(editorElement, editorOptions); + editor.session.setUseWrapMode(false); + editor.setValue(content, -1); + this.updateEditorSize(editorElement, content, editor); + return editor; + } + + updateEditorSize(editorElement: any, content: string, editor: ace.Ace.Editor) { + let newHeight = 200; + let newWidth = 600; + if (content && content.length > 0) { + const lines = content.split('\n'); + newHeight = 16 * lines.length + 16; + let maxLineLength = 0; + lines.forEach((row) => { + const line = row.replace(/\t/g, ' ').replace(/\n/g, ''); + const lineLength = line.length; + maxLineLength = Math.max(maxLineLength, lineLength); + }); + newWidth = 8 * maxLineLength + 16; + } + newHeight = Math.min(400, newHeight); + this.renderer.setStyle(editorElement, 'minHeight', newHeight.toString() + 'px'); + this.renderer.setStyle(editorElement, 'height', newHeight.toString() + 'px'); + this.renderer.setStyle(editorElement, 'width', newWidth.toString() + 'px'); + editor.resize(); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/audit-log/audit-log-table-config.ts b/ui-ngx/src/app/modules/home/components/audit-log/audit-log-table-config.ts new file mode 100644 index 0000000000..26ad0f2edd --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/audit-log/audit-log-table-config.ts @@ -0,0 +1,138 @@ +/// +/// Copyright © 2016-2019 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 { + DateEntityTableColumn, + EntityTableColumn, + EntityTableConfig +} from '@home/models/entity/entities-table-config.models'; +import { + actionStatusTranslations, + actionTypeTranslations, + AuditLog, + AuditLogMode +} from '@shared/models/audit-log.models'; +import { + EntityTypeResource, + EntityTypeTranslation, + entityTypeTranslations +} from '@shared/models/entity-type.models'; +import { AuditLogService } from '@core/http/audit-log.service'; +import { TranslateService } from '@ngx-translate/core'; +import { DatePipe } from '@angular/common'; +import { Direction } from '@shared/models/page/sort-order'; +import { MatDialog } from '@angular/material'; +import { PageLink, TimePageLink } from '@shared/models/page/page-link'; +import { Observable } from 'rxjs'; +import { PageData } from '@shared/models/page/page-data'; +import { EntityId } from '@shared/models/id/entity-id'; +import { UserId } from '@shared/models/id/user-id'; +import { CustomerId } from '@shared/models/id/customer-id'; +import { + AuditLogDetailsDialogComponent, + AuditLogDetailsDialogData +} from '@home/components/audit-log/audit-log-details-dialog.component'; + +export class AuditLogTableConfig extends EntityTableConfig { + + constructor(private auditLogService: AuditLogService, + private translate: TranslateService, + private datePipe: DatePipe, + private dialog: MatDialog, + private auditLogMode: AuditLogMode = AuditLogMode.TENANT, + public entityId: EntityId = null, + public userId: UserId = null, + public customerId: CustomerId = null, + updateOnInit = true) { + super(); + this.loadDataOnInit = updateOnInit; + this.tableTitle = ''; + this.useTimePageLink = true; + this.detailsPanelEnabled = false; + this.selectionEnabled = false; + this.searchEnabled = true; + this.addEnabled = false; + this.entitiesDeleteEnabled = false; + this.actionsColumnTitle = 'audit-log.details'; + this.entityTranslations = { + noEntities: 'audit-log.no-audit-logs-prompt', + search: 'audit-log.search' + }; + this.entityResources = { + } as EntityTypeResource; + + this.entitiesFetchFunction = pageLink => this.fetchAuditLogs(pageLink); + + this.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC}; + + this.columns.push( + new DateEntityTableColumn('createdTime', 'audit-log.timestamp', this.datePipe, '150px')); + + if (this.auditLogMode !== AuditLogMode.ENTITY) { + this.columns.push( + new EntityTableColumn('entityType', 'audit-log.entity-type', '100%', + (entity) => translate.instant(entityTypeTranslations.get(entity.entityId.entityType).type)), + new EntityTableColumn('entityName', 'audit-log.entity-name'), + ); + } + + if (this.auditLogMode !== AuditLogMode.USER) { + this.columns.push( + new EntityTableColumn('userName', 'audit-log.user') + ); + } + + this.columns.push( + new EntityTableColumn('actionType', 'audit-log.type', '100%', + (entity) => translate.instant(actionTypeTranslations.get(entity.actionType))), + new EntityTableColumn('actionStatus', 'audit-log.status', '100%', + (entity) => translate.instant(actionStatusTranslations.get(entity.actionStatus))) + ); + + this.cellActionDescriptors.push( + { + name: this.translate.instant('audit-log.details'), + icon: 'more_horiz', + isEnabled: () => true, + onAction: ($event, entity) => this.showAuditLogDetails(entity) + } + ); + } + + fetchAuditLogs(pageLink: TimePageLink): Observable> { + switch (this.auditLogMode) { + case AuditLogMode.TENANT: + return this.auditLogService.getAuditLogs(pageLink); + case AuditLogMode.ENTITY: + return this.auditLogService.getAuditLogsByEntityId(this.entityId, pageLink); + case AuditLogMode.USER: + return this.auditLogService.getAuditLogsByUserId(this.userId.id, pageLink); + case AuditLogMode.CUSTOMER: + return this.auditLogService.getAuditLogsByCustomerId(this.customerId.id, pageLink); + } + } + + showAuditLogDetails(entity: AuditLog) { + this.dialog.open(AuditLogDetailsDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + auditLog: entity + } + }); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/audit-log/audit-log-table.component.html b/ui-ngx/src/app/modules/home/components/audit-log/audit-log-table.component.html new file mode 100644 index 0000000000..42d385746a --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/audit-log/audit-log-table.component.html @@ -0,0 +1,18 @@ + + diff --git a/ui-ngx/src/app/modules/home/components/audit-log/audit-log-table.component.scss b/ui-ngx/src/app/modules/home/components/audit-log/audit-log-table.component.scss new file mode 100644 index 0000000000..4108c47242 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/audit-log/audit-log-table.component.scss @@ -0,0 +1,22 @@ +/** + * Copyright © 2016-2019 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 ::ng-deep { + tb-entities-table.tb-details-mode { + .mat-drawer-container { + background-color: white; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/audit-log/audit-log-table.component.ts b/ui-ngx/src/app/modules/home/components/audit-log/audit-log-table.component.ts new file mode 100644 index 0000000000..9913395e00 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/audit-log/audit-log-table.component.ts @@ -0,0 +1,133 @@ +/// +/// Copyright © 2016-2019 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, Input, OnInit, ViewChild } from '@angular/core'; +import { AuditLogService } from '@core/http/audit-log.service'; +import { TranslateService } from '@ngx-translate/core'; +import { DatePipe } from '@angular/common'; +import { MatDialog } from '@angular/material'; +import { AuditLogMode } from '@shared/models/audit-log.models'; +import { EntityId } from '@shared/models/id/entity-id'; +import { UserId } from '@shared/models/id/user-id'; +import { CustomerId } from '@shared/models/id/customer-id'; +import { AuditLogTableConfig } from '@home/components/audit-log/audit-log-table-config'; +import { EntitiesTableComponent } from '@home/components/entity/entities-table.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { Authority } from '@shared/models/authority.enum'; +import { getCurrentAuthUser } from '@core/auth/auth.selectors'; + +@Component({ + selector: 'tb-audit-log-table', + templateUrl: './audit-log-table.component.html', + styleUrls: ['./audit-log-table.component.scss'] +}) +export class AuditLogTableComponent implements OnInit { + + @Input() + auditLogMode: AuditLogMode; + + @Input() + detailsMode: boolean; + + activeValue = false; + dirtyValue = false; + entityIdValue: EntityId; + userIdValue: UserId; + customerIdValue: CustomerId; + + @Input() + set active(active: boolean) { + if (this.activeValue !== active) { + this.activeValue = active; + if (this.activeValue && this.dirtyValue) { + this.dirtyValue = false; + this.entitiesTable.updateData(); + } + } + } + + @Input() + set entityId(entityId: EntityId) { + this.entityIdValue = entityId; + if (this.auditLogTableConfig && this.auditLogTableConfig.entityId !== entityId) { + this.auditLogTableConfig.entityId = entityId; + this.entitiesTable.resetSortAndFilter(this.activeValue); + if (!this.activeValue) { + this.dirtyValue = true; + } + } + } + + @Input() + set userId(userId: UserId) { + this.userIdValue = userId; + if (this.auditLogTableConfig && this.auditLogTableConfig.userId !== userId) { + this.auditLogTableConfig.userId = userId; + this.entitiesTable.resetSortAndFilter(this.activeValue); + if (!this.activeValue) { + this.dirtyValue = true; + } + } + } + + @Input() + set customerId(customerId: CustomerId) { + this.customerIdValue = customerId; + if (this.auditLogTableConfig && this.auditLogTableConfig.customerId !== customerId) { + this.auditLogTableConfig.customerId = customerId; + this.entitiesTable.resetSortAndFilter(this.activeValue); + if (!this.activeValue) { + this.dirtyValue = true; + } + } + } + + @ViewChild(EntitiesTableComponent, {static: true}) entitiesTable: EntitiesTableComponent; + + auditLogTableConfig: AuditLogTableConfig; + + constructor(private auditLogService: AuditLogService, + private translate: TranslateService, + private datePipe: DatePipe, + private dialog: MatDialog, + private store: Store) { + } + + ngOnInit() { + let updateOnInit = this.activeValue; + this.dirtyValue = !this.activeValue; + if (!this.auditLogMode) { + const authUser = getCurrentAuthUser(this.store); + if (authUser.authority === Authority.TENANT_ADMIN) { + this.auditLogMode = AuditLogMode.TENANT; + } + updateOnInit = true; + } + this.auditLogTableConfig = new AuditLogTableConfig( + this.auditLogService, + this.translate, + this.datePipe, + this.dialog, + this.auditLogMode, + this.entityIdValue, + this.userIdValue, + this.customerIdValue, + updateOnInit + ); + } + +} 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 268ce79e41..adfe8f00a7 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 @@ -158,10 +158,10 @@ [ngStyle]="cellStyle(entity, column, row, col)"> - + {{ entitiesTableConfig.actionsColumnTitle ? (entitiesTableConfig.actionsColumnTitle | translate) : '' }} - +
- + 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 9e08976d00..f49fa9afca 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 @@ -42,7 +42,8 @@ import { EntityTableColumn, EntityTableConfig, GroupActionDescriptor, - HeaderActionDescriptor + HeaderActionDescriptor, + EntityColumn } from '@home/models/entity/entities-table-config.models'; import { EntityTypeTranslation } from '@shared/models/entity-type.models'; import { DialogService } from '@core/services/dialog.service'; @@ -72,8 +73,10 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn groupActionDescriptors: Array>>; cellActionDescriptors: Array>>; - columns: Array>>; - displayedColumns: string[] = []; + columns: Array>>; + displayedColumns: string[]; + + headerCellStyleCache: Array = []; cellContentCache: Array = []; @@ -143,22 +146,12 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn } ); - this.columns = [...this.entitiesTableConfig.columns]; - const enabledGroupActionDescriptors = this.groupActionDescriptors.filter((descriptor) => descriptor.isEnabled); this.selectionEnabled = this.entitiesTableConfig.selectionEnabled && enabledGroupActionDescriptors.length; - if (this.selectionEnabled) { - this.displayedColumns.push('select'); - } - this.columns.forEach( - (column) => { - this.displayedColumns.push(column.key); - } - ); - this.displayedColumns.push('actions'); + this.columnsUpdated(); const sortOrder: SortOrder = { property: this.entitiesTableConfig.defaultSortOrder.property, direction: this.entitiesTableConfig.defaultSortOrder.direction }; @@ -235,6 +228,7 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn } private dataLoaded() { + this.headerCellStyleCache.length = 0; this.cellContentCache.length = 0; this.cellStyleCache.length = 0; } @@ -365,21 +359,65 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn } } - cellContent(entity: BaseData, column: EntityTableColumn>, row: number, col: number) { - const index = row * this.columns.length + col; - let res = this.cellContentCache[index]; + columnsUpdated(resetData: boolean = false) { + this.columns = [...this.entitiesTableConfig.columns]; + + this.displayedColumns = []; + + if (this.selectionEnabled) { + this.displayedColumns.push('select'); + } + this.columns.forEach( + (column) => { + this.displayedColumns.push(column.key); + } + ); + this.displayedColumns.push('actions'); + this.headerCellStyleCache.length = 0; + this.cellContentCache.length = 0; + this.cellStyleCache.length = 0; + if (resetData) { + this.dataSource.reset(); + } + } + + headerCellStyle(column: EntityColumn>, col: number) { + const index = col; + let res = this.headerCellStyleCache[index]; if (!res) { - res = this.domSanitizer.bypassSecurityTrustHtml(column.cellContentFunction(entity, column.key)); - this.cellContentCache[index] = res; + if (column instanceof EntityTableColumn) { + res = {...column.headerCellStyleFunction(column.key), ...{maxWidth: column.maxWidth}}; + } else { + res = {maxWidth: column.maxWidth}; + } + this.headerCellStyleCache[index] = res; } return res; } - cellStyle(entity: BaseData, column: EntityTableColumn>, row: number, col: number) { + cellContent(entity: BaseData, column: EntityColumn>, row: number, col: number) { + if (column instanceof EntityTableColumn) { + const index = row * this.columns.length + col; + let res = this.cellContentCache[index]; + if (!res) { + res = this.domSanitizer.bypassSecurityTrustHtml(column.cellContentFunction(entity, column.key)); + this.cellContentCache[index] = res; + } + return res; + } else { + return null; + } + } + + cellStyle(entity: BaseData, column: EntityColumn>, row: number, col: number) { const index = row * this.columns.length + col; let res = this.cellStyleCache[index]; if (!res) { - res = {...column.cellStyleFunction(entity, column.key), ...{maxWidth: column.maxWidth}}; + if (column instanceof EntityTableColumn) { + res = {...column.cellStyleFunction(entity, column.key), ...{maxWidth: column.maxWidth}}; + } else { + res = {maxWidth: column.maxWidth}; + } this.cellStyleCache[index] = res; } return res; diff --git a/ui-ngx/src/app/modules/home/components/entity/entity-tabs.component.ts b/ui-ngx/src/app/modules/home/components/entity/entity-tabs.component.ts index a9639e0923..dd8a02c8bc 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entity-tabs.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/entity-tabs.component.ts @@ -37,6 +37,8 @@ import { selectAuthUser, getCurrentAuthUser } from '@core/auth/auth.selectors'; import { AuthUser } from '@shared/models/user.model'; import { EntityType } from '@shared/models/entity-type.models'; import { AuditLogMode } from '@shared/models/audit-log.models'; +import { DebugEventType, EventType } from '@shared/models/event.models'; +import { NULL_UUID } from '@shared/models/id/has-uuid'; export abstract class EntityTabsComponent> extends PageComponent implements OnInit, AfterViewInit { @@ -46,8 +48,14 @@ export abstract class EntityTabsComponent> extends Pag auditLogModes = AuditLogMode; + eventTypes = EventType; + + debugEventTypes = DebugEventType; + authUser: AuthUser; + nullUid = NULL_UUID; + entityValue: T; @ViewChildren(MatTab) entityTabs: QueryList; 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 new file mode 100644 index 0000000000..9e2973a264 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/event/event-table-config.ts @@ -0,0 +1,212 @@ +/// +/// Copyright © 2016-2019 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 { + DateEntityTableColumn, + EntityActionTableColumn, + EntityTableColumn, + EntityTableConfig +} from '@home/models/entity/entities-table-config.models'; +import { DebugEventType, Event, EventType } from '@shared/models/event.models'; +import { TimePageLink } from '@shared/models/page/page-link'; +import { TranslateService } from '@ngx-translate/core'; +import { DatePipe } from '@angular/common'; +import { MatDialog } from '@angular/material/dialog'; +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 { PageData } from '@shared/models/page/page-data'; +import { Direction } from '@shared/models/page/sort-order'; +import { MsgDataType } from '@shared/models/rule-node.models'; +import { DialogService } from '@core/services/dialog.service'; + +export class EventTableConfig extends EntityTableConfig { + + eventTypeValue: EventType | DebugEventType; + + set eventType(eventType: EventType | DebugEventType) { + if (this.eventTypeValue !== eventType) { + this.eventTypeValue = eventType; + this.updateColumns(true); + } + } + + get eventType(): EventType | DebugEventType { + return this.eventTypeValue; + } + + eventTypes: Array; + + constructor(private eventService: EventService, + private dialogService: DialogService, + private translate: TranslateService, + private datePipe: DatePipe, + private dialog: MatDialog, + public entityId: EntityId, + public tenantId: string, + private defaultEventType: EventType | DebugEventType, + private disabledEventTypes: Array = null, + private debugEventTypes: Array = null) { + super(); + this.loadDataOnInit = false; + this.tableTitle = ''; + this.useTimePageLink = true; + this.detailsPanelEnabled = false; + this.selectionEnabled = false; + this.searchEnabled = false; + this.addEnabled = false; + this.entitiesDeleteEnabled = false; + + this.headerComponent = EventTableHeaderComponent; + + this.eventTypes = Object.keys(EventType).map(type => EventType[type]); + + if (disabledEventTypes && disabledEventTypes.length) { + this.eventTypes = this.eventTypes.filter(type => disabledEventTypes.indexOf(type) === -1); + } + + if (debugEventTypes && debugEventTypes.length) { + this.eventTypes = [...this.eventTypes, ...debugEventTypes]; + } + + this.eventTypeValue = defaultEventType; + + this.entityTranslations = { + noEntities: 'event.no-events-prompt' + }; + this.entityResources = { + } as EntityTypeResource; + this.entitiesFetchFunction = pageLink => this.fetchEvents(pageLink); + + this.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC}; + + this.updateColumns(); + } + + fetchEvents(pageLink: TimePageLink): Observable> { + return this.eventService.getEvents(this.entityId, this.eventType, this.tenantId, pageLink); + } + + updateColumns(updateTableColumns: boolean = false): void { + this.columns = []; + this.columns.push( + new DateEntityTableColumn('createdTime', 'event.event-time', this.datePipe, '150px'), + new EntityTableColumn('server', 'event.server', '150px', + (entity) => entity.body.server, entity => ({}), false)); + switch (this.eventType) { + case EventType.ERROR: + this.columns.push( + new EntityTableColumn('method', 'event.method', '100%', + (entity) => entity.body.method, entity => ({}), false), + new EntityActionTableColumn('error', 'event.error', + { + name: this.translate.instant('action.view'), + icon: 'more_horiz', + isEnabled: (entity) => entity.body.error && entity.body.error.length > 0, + onAction: ($event, entity) => this.showContent($event, entity.body.error, 'event.error') + }, + '100px') + ); + break; + case EventType.LC_EVENT: + this.columns.push( + new EntityTableColumn('method', 'event.event', '100%', + (entity) => entity.body.event, entity => ({}), false), + new EntityTableColumn('status', 'event.status', '100%', + (entity) => + this.translate.instant(entity.body.success ? 'event.success' : 'event.failed'), entity => ({}), false), + new EntityActionTableColumn('error', 'event.error', + { + name: this.translate.instant('action.view'), + icon: 'more_horiz', + isEnabled: (entity) => entity.body.error && entity.body.error.length > 0, + onAction: ($event, entity) => this.showContent($event, entity.body.error, 'event.error') + }, + '100px') + ); + break; + case EventType.STATS: + this.columns.push( + new EntityTableColumn('messagesProcessed', 'event.messages-processed', '100%', + (entity) => entity.body.messagesProcessed + '', + entity => ({justifyContent: 'flex-end'}), + false, + key => ({justifyContent: 'flex-end'})), + new EntityTableColumn('errorsOccurred', 'event.errors-occurred', '100%', + (entity) => entity.body.errorsOccurred + '', + entity => ({justifyContent: 'flex-end'}), + false, + key => ({justifyContent: 'flex-end'})) + ); + break; + case DebugEventType.DEBUG_RULE_NODE: + case DebugEventType.DEBUG_RULE_CHAIN: + this.columns.push( + new EntityTableColumn('type', 'event.type', '100%', + (entity) => entity.body.type, entity => ({}), false), + new EntityTableColumn('entity', 'event.entity', '100%', + (entity) => entity.body.entityName, entity => ({}), false), + new EntityTableColumn('msgId', 'event.message-id', '100%', + (entity) => entity.body.msgId, entity => ({}), false), + new EntityTableColumn('msgType', 'event.message-type', '100%', + (entity) => entity.body.msgType, entity => ({}), false), + new EntityTableColumn('relationType', 'event.relation-type', '100%', + (entity) => entity.body.relationType, entity => ({}), false), + new EntityActionTableColumn('data', 'event.data', + { + name: this.translate.instant('action.view'), + icon: 'more_horiz', + isEnabled: (entity) => entity.body.data && entity.body.data.length > 0, + onAction: ($event, entity) => this.showContent($event, entity.body.data, + 'event.data', entity.body.dataType) + }, + '60px'), + new EntityActionTableColumn('metadata', 'event.metadata', + { + name: this.translate.instant('action.view'), + icon: 'more_horiz', + isEnabled: (entity) => entity.body.metadata && entity.body.metadata.length > 0, + onAction: ($event, entity) => this.showContent($event, entity.body.metadata, + 'event.metadata', MsgDataType.JSON) + }, + '60px'), + new EntityActionTableColumn('error', 'event.error', + { + name: this.translate.instant('action.view'), + icon: 'more_horiz', + isEnabled: (entity) => entity.body.error && entity.body.error.length > 0, + onAction: ($event, entity) => this.showContent($event, entity.body.error, + 'event.error') + }, + '60px') + ); + break; + } + if (updateTableColumns) { + this.table.columnsUpdated(true); + } + } + + showContent($event: MouseEvent, content: string, title: string, contentType: MsgDataType = null): void { + if ($event) { + $event.stopPropagation(); + } + // TODO: + this.dialogService.todo(); + } +} diff --git a/ui-ngx/src/app/modules/home/components/event/event-table-header.component.html b/ui-ngx/src/app/modules/home/components/event/event-table-header.component.html new file mode 100644 index 0000000000..f73c30be7d --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/event/event-table-header.component.html @@ -0,0 +1,27 @@ + + + + event.event-type + + + {{ eventTypeTranslationsMap.get(type) | translate }} + + + diff --git a/ui-ngx/src/app/modules/home/components/event/event-table-header.component.scss b/ui-ngx/src/app/modules/home/components/event/event-table-header.component.scss new file mode 100644 index 0000000000..d1a828bd74 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/event/event-table-header.component.scss @@ -0,0 +1,35 @@ +/** + * Copyright © 2016-2019 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 { +/* flex: 1; + display: flex; + justify-content: flex-start; */ + padding-right: 8px; +} + +:host ::ng-deep { + mat-form-field { + font-size: 16px; + + .mat-form-field-wrapper { + padding-bottom: 0; + } + + .mat-form-field-underline { + bottom: 0; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/event/event-table-header.component.ts b/ui-ngx/src/app/modules/home/components/event/event-table-header.component.ts new file mode 100644 index 0000000000..fd2ae23183 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/event/event-table-header.component.ts @@ -0,0 +1,45 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { EntityTableHeaderComponent } from '../../components/entity/entity-table-header.component'; +import { DebugEventType, Event, EventType, eventTypeTranslations } from '@app/shared/models/event.models'; +import { EventTableConfig } from '@home/components/event/event-table-config'; + +@Component({ + selector: 'tb-event-table-header', + templateUrl: './event-table-header.component.html', + styleUrls: ['./event-table-header.component.scss'] +}) +export class EventTableHeaderComponent extends EntityTableHeaderComponent { + + eventTypeTranslationsMap = eventTypeTranslations; + + get eventTableConfig(): EventTableConfig { + return this.entitiesTableConfig as EventTableConfig; + } + + constructor(protected store: Store) { + super(store); + } + + eventTypeChanged(eventType: EventType | DebugEventType) { + this.eventTableConfig.eventType = eventType; + this.eventTableConfig.table.updateData(); + } +} diff --git a/ui-ngx/src/app/modules/home/components/event/event-table.component.html b/ui-ngx/src/app/modules/home/components/event/event-table.component.html new file mode 100644 index 0000000000..79966651a8 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/event/event-table.component.html @@ -0,0 +1,18 @@ + + diff --git a/ui-ngx/src/app/modules/home/components/event/event-table.component.scss b/ui-ngx/src/app/modules/home/components/event/event-table.component.scss new file mode 100644 index 0000000000..0d3cc92455 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/event/event-table.component.scss @@ -0,0 +1,22 @@ +/** + * Copyright © 2016-2019 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 ::ng-deep { + tb-entities-table { + .mat-drawer-container { + background-color: white; + } + } +} 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 new file mode 100644 index 0000000000..c881686a6e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/event/event-table.component.ts @@ -0,0 +1,112 @@ +/// +/// Copyright © 2016-2019 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, Input, OnInit, ViewChild } from '@angular/core'; +import { AuditLogService } from '@core/http/audit-log.service'; +import { TranslateService } from '@ngx-translate/core'; +import { DatePipe } from '@angular/common'; +import { MatDialog } from '@angular/material'; +import { AuditLogMode } from '@shared/models/audit-log.models'; +import { EntityId } from '@shared/models/id/entity-id'; +import { UserId } from '@shared/models/id/user-id'; +import { CustomerId } from '@shared/models/id/customer-id'; +import { AuditLogTableConfig } from '@home/components/audit-log/audit-log-table-config'; +import { EntitiesTableComponent } from '@home/components/entity/entities-table.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { Authority } from '@shared/models/authority.enum'; +import { getCurrentAuthUser } from '@core/auth/auth.selectors'; +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'; + +@Component({ + selector: 'tb-event-table', + templateUrl: './event-table.component.html', + styleUrls: ['./event-table.component.scss'] +}) +export class EventTableComponent implements OnInit { + + @Input() + tenantId: string; + + @Input() + defaultEventType: EventType | DebugEventType; + + @Input() + disabledEventTypes: Array; + + @Input() + debugEventTypes: Array; + + activeValue = false; + dirtyValue = false; + entityIdValue: EntityId; + + @Input() + set active(active: boolean) { + if (this.activeValue !== active) { + this.activeValue = active; + if (this.activeValue && this.dirtyValue) { + this.dirtyValue = false; + this.entitiesTable.updateData(); + } + } + } + + @Input() + set entityId(entityId: EntityId) { + this.entityIdValue = entityId; + if (this.eventTableConfig && this.eventTableConfig.entityId !== entityId) { + this.eventTableConfig.eventType = this.defaultEventType; + this.eventTableConfig.entityId = entityId; + this.entitiesTable.resetSortAndFilter(this.activeValue); + if (!this.activeValue) { + this.dirtyValue = true; + } + } + } + + @ViewChild(EntitiesTableComponent, {static: true}) entitiesTable: EntitiesTableComponent; + + eventTableConfig: EventTableConfig; + + constructor(private eventService: EventService, + private dialogService: DialogService, + private translate: TranslateService, + private datePipe: DatePipe, + private dialog: MatDialog, + private store: Store) { + } + + ngOnInit() { + this.dirtyValue = !this.activeValue; + this.eventTableConfig = new EventTableConfig( + this.eventService, + this.dialogService, + this.translate, + this.datePipe, + this.dialog, + this.entityIdValue, + this.tenantId, + this.defaultEventType, + this.disabledEventTypes, + this.debugEventTypes + ); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index ff81170814..d2e7f1e4b8 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -24,11 +24,14 @@ import {EntityDetailsPanelComponent} from './entity/entity-details-panel.compone import {ContactComponent} from './contact.component'; import { AuditLogDetailsDialogComponent } from './audit-log/audit-log-details-dialog.component'; import { AuditLogTableComponent } from './audit-log/audit-log-table.component'; +import { EventTableHeaderComponent } from '@home/components/event/event-table-header.component'; +import { EventTableComponent } from '@home/components/event/event-table.component'; @NgModule({ entryComponents: [ AddEntityDialogComponent, - AuditLogDetailsDialogComponent + AuditLogDetailsDialogComponent, + EventTableHeaderComponent ], declarations: [ @@ -38,7 +41,9 @@ import { AuditLogTableComponent } from './audit-log/audit-log-table.component'; EntityDetailsPanelComponent, ContactComponent, AuditLogTableComponent, - AuditLogDetailsDialogComponent + AuditLogDetailsDialogComponent, + EventTableHeaderComponent, + EventTableComponent ], imports: [ CommonModule, @@ -50,7 +55,8 @@ import { AuditLogTableComponent } from './audit-log/audit-log-table.component'; DetailsPanelComponent, EntityDetailsPanelComponent, ContactComponent, - AuditLogTableComponent + AuditLogTableComponent, + EventTableComponent ] }) export class HomeComponentsModule { } diff --git a/ui-ngx/src/app/modules/home/models/datasource/entity-datasource.ts b/ui-ngx/src/app/modules/home/models/datasource/entity-datasource.ts index 19ece9066d..d0ab2bec71 100644 --- a/ui-ngx/src/app/modules/home/models/datasource/entity-datasource.ts +++ b/ui-ngx/src/app/modules/home/models/datasource/entity-datasource.ts @@ -50,6 +50,13 @@ export class EntitiesDataSource, P extends PageLink = this.pageDataSubject.complete(); } + reset() { + const pageData = emptyPageData(); + this.entitiesSubject.next(pageData.data); + this.pageDataSubject.next(pageData); + this.dataLoadedFunction(); + } + loadEntities(pageLink: P): Observable> { const result = new ReplaySubject>(); this.fetchFunction(pageLink).pipe( 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 2065941254..18236ca35b 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 @@ -41,6 +41,7 @@ export type EntityActionFunction> = (action: EntityAct export type CreateEntityOperation> = () => Observable; export type CellContentFunction> = (entity: T, key: string) => string; +export type HeaderCellStyleFunction> = (key: string) => object; export type CellStyleFunction> = (entity: T, key: string) => object; export interface CellActionDescriptor> { @@ -67,13 +68,35 @@ export interface HeaderActionDescriptor { onAction: ($event: MouseEvent) => void; } -export class EntityTableColumn> { +export type EntityTableColumnType = 'content' | 'action'; + +export class BaseEntityTableColumn> { + constructor(public type: EntityTableColumnType, + public key: string, + public title: string, + public maxWidth: string = '100%', + public sortable: boolean = true) { + } +} + +export class EntityTableColumn> extends BaseEntityTableColumn { constructor(public key: string, public title: string, public maxWidth: string = '100%', public cellContentFunction: CellContentFunction = (entity, property) => entity[property], public cellStyleFunction: CellStyleFunction = () => ({}), - public sortable: boolean = true) { + public sortable: boolean = true, + public headerCellStyleFunction: HeaderCellStyleFunction = () => ({})) { + super('content', key, title, maxWidth, sortable); + } +} + +export class EntityActionTableColumn> extends BaseEntityTableColumn { + constructor(public key: string, + public title: string, + public actionDescriptor: CellActionDescriptor, + public maxWidth: string = '100%') { + super('action', key, title, maxWidth, false); } } @@ -92,6 +115,8 @@ export class DateEntityTableColumn> extends EntityTabl } } +export type EntityColumn> = EntityTableColumn | EntityActionTableColumn; + export class EntityTableConfig, P extends PageLink = PageLink> { constructor() {} @@ -116,7 +141,7 @@ export class EntityTableConfig, P extends PageLink = P entityTabsComponent: Type>; addDialogStyle = {}; defaultSortOrder: SortOrder = {property: 'createdTime', direction: Direction.ASC}; - columns: Array> = []; + columns: Array> = []; cellActionDescriptors: Array> = []; groupActionDescriptors: Array> = []; headerActionDescriptors: Array = []; diff --git a/ui-ngx/src/app/modules/home/pages/asset/asset-tabs.component.html b/ui-ngx/src/app/modules/home/pages/asset/asset-tabs.component.html index ed78ad1b29..0e7b3001be 100644 --- a/ui-ngx/src/app/modules/home/pages/asset/asset-tabs.component.html +++ b/ui-ngx/src/app/modules/home/pages/asset/asset-tabs.component.html @@ -15,6 +15,11 @@ limitations under the License. --> + + + diff --git a/ui-ngx/src/app/modules/home/pages/customer/customer-tabs.component.html b/ui-ngx/src/app/modules/home/pages/customer/customer-tabs.component.html index 81c1ddbc3d..a6a4106d61 100644 --- a/ui-ngx/src/app/modules/home/pages/customer/customer-tabs.component.html +++ b/ui-ngx/src/app/modules/home/pages/customer/customer-tabs.component.html @@ -15,6 +15,11 @@ limitations under the License. --> + + + diff --git a/ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html b/ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html index ed78ad1b29..1b68496f6f 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html @@ -15,6 +15,11 @@ limitations under the License. --> + + + diff --git a/ui-ngx/src/app/modules/home/pages/entity-view/entity-view-tabs.component.html b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view-tabs.component.html index ed78ad1b29..d1795cdc63 100644 --- a/ui-ngx/src/app/modules/home/pages/entity-view/entity-view-tabs.component.html +++ b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view-tabs.component.html @@ -15,6 +15,11 @@ limitations under the License. --> + + + diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-tabs.component.html b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-tabs.component.html index ed78ad1b29..7941b41e8c 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-tabs.component.html +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-tabs.component.html @@ -15,6 +15,14 @@ limitations under the License. --> + + + diff --git a/ui-ngx/src/app/modules/home/pages/tenant/tenant-tabs.component.html b/ui-ngx/src/app/modules/home/pages/tenant/tenant-tabs.component.html new file mode 100644 index 0000000000..062bf3147d --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/tenant/tenant-tabs.component.html @@ -0,0 +1,22 @@ + + + + diff --git a/ui-ngx/src/app/modules/home/pages/tenant/tenant-tabs.component.ts b/ui-ngx/src/app/modules/home/pages/tenant/tenant-tabs.component.ts new file mode 100644 index 0000000000..0cbfd65d05 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/tenant/tenant-tabs.component.ts @@ -0,0 +1,38 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { EntityTabsComponent } from '../../components/entity/entity-tabs.component'; +import { Tenant } from '@shared/models/tenant.model'; + +@Component({ + selector: 'tb-tenant-tabs', + templateUrl: './tenant-tabs.component.html', + styleUrls: [] +}) +export class TenantTabsComponent extends EntityTabsComponent { + + constructor(protected store: Store) { + super(store); + } + + ngOnInit() { + super.ngOnInit(); + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/tenant/tenant.module.ts b/ui-ngx/src/app/modules/home/pages/tenant/tenant.module.ts index f7cc6876be..eacf54c4b5 100644 --- a/ui-ngx/src/app/modules/home/pages/tenant/tenant.module.ts +++ b/ui-ngx/src/app/modules/home/pages/tenant/tenant.module.ts @@ -20,13 +20,16 @@ import { SharedModule } from '@shared/shared.module'; import {TenantComponent} from '@modules/home/pages/tenant/tenant.component'; import {TenantRoutingModule} from '@modules/home/pages/tenant/tenant-routing.module'; import {HomeComponentsModule} from '@modules/home/components/home-components.module'; +import { TenantTabsComponent } from '@home/pages/tenant/tenant-tabs.component'; @NgModule({ entryComponents: [ - TenantComponent + TenantComponent, + TenantTabsComponent ], declarations: [ - TenantComponent + TenantComponent, + TenantTabsComponent ], imports: [ CommonModule, diff --git a/ui-ngx/src/app/modules/home/pages/tenant/tenants-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/tenant/tenants-table-config.resolver.ts index cfaafcd417..0ccc7e20d9 100644 --- a/ui-ngx/src/app/modules/home/pages/tenant/tenants-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/tenant/tenants-table-config.resolver.ts @@ -35,6 +35,7 @@ import { import { TenantComponent } from '@modules/home/pages/tenant/tenant.component'; import { EntityAction } from '@home/models/entity/entity-component.models'; import { User } from '@shared/models/user.model'; +import { TenantTabsComponent } from '@home/pages/tenant/tenant-tabs.component'; @Injectable() export class TenantsTableConfigResolver implements Resolve> { @@ -48,6 +49,7 @@ export class TenantsTableConfigResolver implements Resolve + + + diff --git a/ui-ngx/src/app/modules/home/pages/user/user-tabs.component.ts b/ui-ngx/src/app/modules/home/pages/user/user-tabs.component.ts new file mode 100644 index 0000000000..d14ddbed3a --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/user/user-tabs.component.ts @@ -0,0 +1,38 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { EntityTabsComponent } from '../../components/entity/entity-tabs.component'; +import { User } from '@app/shared/models/user.model'; + +@Component({ + selector: 'tb-user-tabs', + templateUrl: './user-tabs.component.html', + styleUrls: [] +}) +export class UserTabsComponent extends EntityTabsComponent { + + constructor(protected store: Store) { + super(store); + } + + ngOnInit() { + super.ngOnInit(); + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/user/user.module.ts b/ui-ngx/src/app/modules/home/pages/user/user.module.ts index d9e20921ec..5e198a651c 100644 --- a/ui-ngx/src/app/modules/home/pages/user/user.module.ts +++ b/ui-ngx/src/app/modules/home/pages/user/user.module.ts @@ -22,15 +22,18 @@ import { UserRoutingModule } from '@modules/home/pages/user/user-routing.module' import { AddUserDialogComponent } from '@modules/home/pages/user/add-user-dialog.component'; import { ActivationLinkDialogComponent } from '@modules/home/pages/user/activation-link-dialog.component'; import {HomeComponentsModule} from '@modules/home/components/home-components.module'; +import { UserTabsComponent } from '@home/pages/user/user-tabs.component'; @NgModule({ entryComponents: [ UserComponent, + UserTabsComponent, AddUserDialogComponent, ActivationLinkDialogComponent ], declarations: [ UserComponent, + UserTabsComponent, AddUserDialogComponent, ActivationLinkDialogComponent ], diff --git a/ui-ngx/src/app/modules/home/pages/user/users-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/user/users-table-config.resolver.ts index a85f445182..e075d93a1a 100644 --- a/ui-ngx/src/app/modules/home/pages/user/users-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/user/users-table-config.resolver.ts @@ -57,6 +57,7 @@ import { NULL_UUID } from '@shared/models/id/has-uuid'; import { Customer } from '@shared/models/customer.model'; import {TenantService} from '@app/core/http/tenant.service'; import {TenantId} from '@app/shared/models/id/tenant-id'; +import { UserTabsComponent } from '@home/pages/user/user-tabs.component'; export interface UsersTableRouteData { authority: Authority; @@ -83,6 +84,7 @@ export class UsersTableConfigResolver implements Resolve this.config.entityType = EntityType.USER; this.config.entityComponent = UserComponent; + this.config.entityTabsComponent = UserTabsComponent; this.config.entityTranslations = entityTypeTranslations.get(EntityType.USER); this.config.entityResources = entityTypeResources.get(EntityType.USER); diff --git a/ui-ngx/src/app/shared/models/event.models.ts b/ui-ngx/src/app/shared/models/event.models.ts new file mode 100644 index 0000000000..a8f8927a99 --- /dev/null +++ b/ui-ngx/src/app/shared/models/event.models.ts @@ -0,0 +1,89 @@ +/// +/// Copyright © 2016-2019 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 { ActionStatus, ActionType } from '@shared/models/audit-log.models'; +import { BaseData } from '@shared/models/base-data'; +import { AuditLogId } from '@shared/models/id/audit-log-id'; +import { TenantId } from '@shared/models/id/tenant-id'; +import { CustomerId } from '@shared/models/id/customer-id'; +import { EntityId } from '@shared/models/id/entity-id'; +import { UserId } from '@shared/models/id/user-id'; +import { EventId } from './id/event-id'; +import { MsgDataType } from './rule-node.models'; + +export enum EventType { + ERROR = 'ERROR', + LC_EVENT = 'LC_EVENT', + STATS = 'STATS' +} + +export enum DebugEventType { + DEBUG_RULE_NODE = 'DEBUG_RULE_NODE', + DEBUG_RULE_CHAIN = 'DEBUG_RULE_CHAIN' +} + +export const eventTypeTranslations = new Map( + [ + [EventType.ERROR, 'event.type-error'], + [EventType.LC_EVENT, 'event.type-lc-event'], + [EventType.STATS, 'event.type-stats'], + [DebugEventType.DEBUG_RULE_NODE, 'event.type-debug-rule-node'], + [DebugEventType.DEBUG_RULE_CHAIN, 'event.type-debug-rule-chain'], + ] +); + +export interface BaseEventBody { + server: string; +} + +export interface ErrorEventBody extends BaseEventBody { + method: string; + error: string; +} + +export interface LcEventEventBody extends BaseEventBody { + event: string; + success: boolean; + error: string; +} + +export interface StatsEventBody extends BaseEventBody { + messagesProcessed: number; + errorsOccurred: number; +} + +export interface DebugRuleNodeEventBody extends BaseEventBody { + type: string; + entityId: string; + entityName: string; + msgId: string; + msgType: string; + relationType: string; + dataType: MsgDataType; + data: string; + metadata: string; + error: string; +} + +export type EventBody = ErrorEventBody & LcEventEventBody & StatsEventBody & DebugRuleNodeEventBody; + +export interface Event extends BaseData { + tenantId: TenantId; + entityId: EntityId; + type: string; + uid: string; + body: EventBody; +} diff --git a/ui-ngx/src/app/shared/models/id/event-id.ts b/ui-ngx/src/app/shared/models/id/event-id.ts new file mode 100644 index 0000000000..f7cfe7dd61 --- /dev/null +++ b/ui-ngx/src/app/shared/models/id/event-id.ts @@ -0,0 +1,24 @@ +/// +/// Copyright © 2016-2019 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 { HasUUID } from '@shared/models/id/has-uuid'; + +export class EventId implements HasUUID { + id: string; + constructor(id: string) { + this.id = id; + } +} 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 5448bd21af..360c8099dc 100644 --- a/ui-ngx/src/app/shared/models/rule-node.models.ts +++ b/ui-ngx/src/app/shared/models/rule-node.models.ts @@ -21,6 +21,12 @@ import {CustomerId} from '@shared/models/id/customer-id'; import {RuleChainId} from '@shared/models/id/rule-chain-id'; import {RuleNodeId} from '@shared/models/id/rule-node-id'; +export enum MsgDataType { + JSON = 'JSON', + TEXT = 'TEXT', + BINARY = 'BINARY' +} + export interface RuleNodeConfiguration { todo: Array; // TODO: diff --git a/ui-ngx/src/theme.scss b/ui-ngx/src/theme.scss index 662ae1632a..861b998f35 100644 --- a/ui-ngx/src/theme.scss +++ b/ui-ngx/src/theme.scss @@ -308,7 +308,7 @@ $tb-dark-theme: get-tb-dark-theme( } .mat-cell, .mat-header-cell { - min-width: 80px; + min-width: 40px; &:last-child { padding: 0 12px 0 0; } From 851a3657dbfffa9e220fbf1970c662063e6596a8 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 23 Aug 2019 15:06:42 +0300 Subject: [PATCH 022/133] Entity Relations Table --- .../app/core/http/entity-relation.service.ts | 113 +++++++++ .../home/components/home-components.module.ts | 7 +- .../relation/relation-table.component.html | 168 +++++++++++++ .../relation/relation-table.component.scss | 54 +++++ .../relation/relation-table.component.ts | 225 ++++++++++++++++++ .../models/datasource/relation-datasource.ts | 143 +++++++++++ .../pages/device/device-tabs.component.html | 4 + .../src/app/shared/models/page/page-data.ts | 7 +- .../src/app/shared/models/page/page-link.ts | 40 ++++ .../src/app/shared/models/relation.models.ts | 87 +++++++ .../assets/locale/locale.constant-en_US.json | 5 +- 11 files changed, 846 insertions(+), 7 deletions(-) create mode 100644 ui-ngx/src/app/core/http/entity-relation.service.ts create mode 100644 ui-ngx/src/app/modules/home/components/relation/relation-table.component.html create mode 100644 ui-ngx/src/app/modules/home/components/relation/relation-table.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/relation/relation-table.component.ts create mode 100644 ui-ngx/src/app/modules/home/models/datasource/relation-datasource.ts create mode 100644 ui-ngx/src/app/shared/models/relation.models.ts diff --git a/ui-ngx/src/app/core/http/entity-relation.service.ts b/ui-ngx/src/app/core/http/entity-relation.service.ts new file mode 100644 index 0000000000..b11b67ec4a --- /dev/null +++ b/ui-ngx/src/app/core/http/entity-relation.service.ts @@ -0,0 +1,113 @@ +/// +/// Copyright © 2016-2019 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 { Injectable } from '@angular/core'; +import { defaultHttpOptions } from './http-utils'; +import { Observable } from 'rxjs/index'; +import { HttpClient } from '@angular/common/http'; +import { EntityRelation, EntityRelationInfo, EntityRelationsQuery } from '@shared/models/relation.models'; +import { EntityId } from '@app/shared/models/id/entity-id'; + +@Injectable({ + providedIn: 'root' +}) +export class EntityRelationService { + + constructor( + private http: HttpClient + ) { } + + public saveRelation(relation: EntityRelation, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.post('/api/relation', relation, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public deleteRelation(fromId: EntityId, relationType: string, toId: EntityId, + ignoreErrors: boolean = false, ignoreLoading: boolean = false) { + return this.http.delete(`/api/relation?fromId=${fromId.id}&fromType=${fromId.entityType}` + + `&relationType=${relationType}&toId=${toId.id}&toType=${toId.entityType}`, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public deleteRelations(entityId: EntityId, + ignoreErrors: boolean = false, ignoreLoading: boolean = false) { + return this.http.delete(`/api/relations?entityId=${entityId.id}&entityType=${entityId.entityType}`, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public getRelation(fromId: EntityId, relationType: string, toId: EntityId, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.get(`/api/relation?fromId=${fromId.id}&fromType=${fromId.entityType}` + + `&relationType=${relationType}&toId=${toId.id}&toType=${toId.entityType}`, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public findByFrom(fromId: EntityId, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + return this.http.get>( + `/api/relations?fromId=${fromId.id}&fromType=${fromId.entityType}`, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public findInfoByFrom(fromId: EntityId, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + return this.http.get>( + `/api/relations/info?fromId=${fromId.id}&fromType=${fromId.entityType}`, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public findByFromAndType(fromId: EntityId, relationType: string, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + return this.http.get>( + `/api/relations?fromId=${fromId.id}&fromType=${fromId.entityType}&relationType=${relationType}`, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public findByTo(toId: EntityId, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + return this.http.get>( + `/api/relations?toId=${toId.id}&toType=${toId.entityType}`, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public findInfoByTo(toId: EntityId, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + return this.http.get>( + `/api/relations/info?toId=${toId.id}&toType=${toId.entityType}`, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public findByToAndType(toId: EntityId, relationType: string, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + return this.http.get>( + `/api/relations?toId=${toId.id}&toType=${toId.entityType}&relationType=${relationType}`, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public findByQuery(query: EntityRelationsQuery, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + return this.http.post>( + '/api/relations', query, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public findInfoByQuery(query: EntityRelationsQuery, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + return this.http.post>( + '/api/relations/info', query, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index d2e7f1e4b8..daeb05e3fa 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -26,6 +26,7 @@ import { AuditLogDetailsDialogComponent } from './audit-log/audit-log-details-di import { AuditLogTableComponent } from './audit-log/audit-log-table.component'; import { EventTableHeaderComponent } from '@home/components/event/event-table-header.component'; import { EventTableComponent } from '@home/components/event/event-table.component'; +import { RelationTableComponent } from '@home/components/relation/relation-table.component'; @NgModule({ entryComponents: [ @@ -43,7 +44,8 @@ import { EventTableComponent } from '@home/components/event/event-table.componen AuditLogTableComponent, AuditLogDetailsDialogComponent, EventTableHeaderComponent, - EventTableComponent + EventTableComponent, + RelationTableComponent ], imports: [ CommonModule, @@ -56,7 +58,8 @@ import { EventTableComponent } from '@home/components/event/event-table.componen EntityDetailsPanelComponent, ContactComponent, AuditLogTableComponent, - EventTableComponent + EventTableComponent, + RelationTableComponent ] }) export class HomeComponentsModule { } diff --git a/ui-ngx/src/app/modules/home/components/relation/relation-table.component.html b/ui-ngx/src/app/modules/home/components/relation/relation-table.component.html new file mode 100644 index 0000000000..183f14aa0f --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/relation/relation-table.component.html @@ -0,0 +1,168 @@ + +
+
+ +
+ {{(direction == directions.FROM ? + 'relation.from-relations' : 'relation.to-relations') | translate}} + + relation.direction + + + {{ directionTypeTranslations.get(type) | translate }} + + + + + + + +
+
+ +
+ + +   + + + +
+
+ +
+ + {{ translate.get('relation.selected-relations', {count: dataSource.selection.selected.length}) | async }} + + + +
+
+
+ + + + + + + + + + + + + {{ 'relation.type' | translate }} + + {{ relation.type }} + + + + {{ 'relation.to-entity-type' | translate }} + + {{ relation.toEntityTypeName }} + + + + {{ 'relation.to-entity-name' | translate }} + + {{ relation.toName }} + + + + {{ 'relation.from-entity-type' | translate }} + + {{ relation.fromEntityTypeName }} + + + + {{ 'relation.from-entity-name' | translate }} + + {{ relation.fromName }} + + + + + + +
+ + +
+
+
+ + +
+ {{ 'relation.no-relations-text' }} +
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/relation/relation-table.component.scss b/ui-ngx/src/app/modules/home/components/relation/relation-table.component.scss new file mode 100644 index 0000000000..a2a6b728b3 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/relation/relation-table.component.scss @@ -0,0 +1,54 @@ +/** + * Copyright © 2016-2019 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 { + width: 100%; + height: 100%; + .tb-entity-table { + .tb-entity-table-content { + width: 100%; + height: 100%; + background: #fff; + + .tb-entity-table-title { + padding-right: 20px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .table-container { + overflow: auto; + } + } + } +} + +:host ::ng-deep { + .mat-sort-header-sorted .mat-sort-header-arrow { + opacity: 1 !important; + } + mat-form-field.tb-relation-direction { + font-size: 16px; + + .mat-form-field-wrapper { + padding-bottom: 0; + } + + .mat-form-field-underline { + bottom: 0; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/relation/relation-table.component.ts b/ui-ngx/src/app/modules/home/components/relation/relation-table.component.ts new file mode 100644 index 0000000000..fddf1db15a --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/relation/relation-table.component.ts @@ -0,0 +1,225 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { PageLink } from '@shared/models/page/page-link'; +import { MatPaginator } from '@angular/material/paginator'; +import { MatSort } from '@angular/material/sort'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { TranslateService } from '@ngx-translate/core'; +import { MatDialog } from '@angular/material/dialog'; +import { DialogService } from '@core/services/dialog.service'; +import { EntityRelationService } from '@core/http/entity-relation.service'; +import { Direction, SortOrder } from '@shared/models/page/sort-order'; +import { fromEvent, merge } from 'rxjs'; +import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators'; +import { EntityRelationInfo, EntitySearchDirection, entitySearchDirectionTranslations } from '@shared/models/relation.models'; +import { EntityId } from '@shared/models/id/entity-id'; +import { RelationsDatasource } from '../../models/datasource/relation-datasource'; +import { DebugEventType, EventType } from '@shared/models/event.models'; + +@Component({ + selector: 'tb-relation-table', + templateUrl: './relation-table.component.html', + styleUrls: ['./relation-table.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class RelationTableComponent extends PageComponent implements AfterViewInit, OnInit { + + directions = EntitySearchDirection; + + directionTypes = Object.keys(EntitySearchDirection); + + directionTypeTranslations = entitySearchDirectionTranslations; + + displayedColumns: string[]; + direction: EntitySearchDirection; + pageLink: PageLink; + textSearchMode = false; + dataSource: RelationsDatasource; + + activeValue = false; + dirtyValue = false; + entityIdValue: EntityId; + + viewsInited = false; + + @Input() + set active(active: boolean) { + if (this.activeValue !== active) { + this.activeValue = active; + if (this.activeValue && this.dirtyValue) { + this.dirtyValue = false; + if (this.viewsInited) { + this.updateData(true); + } + } + } + } + + @Input() + set entityId(entityId: EntityId) { + if (this.entityIdValue !== entityId) { + this.entityIdValue = entityId; + if (this.viewsInited) { + this.resetSortAndFilter(this.activeValue); + if (!this.activeValue) { + this.dirtyValue = true; + } + } + } + } + + @ViewChild('searchInput', {static: false}) searchInputField: ElementRef; + + @ViewChild(MatPaginator, {static: false}) paginator: MatPaginator; + @ViewChild(MatSort, {static: false}) sort: MatSort; + + constructor(protected store: Store, + private entityRelationService: EntityRelationService, + public translate: TranslateService, + public dialog: MatDialog, + private dialogService: DialogService) { + super(store); + this.dirtyValue = !this.activeValue; + const sortOrder: SortOrder = { property: 'type', direction: Direction.ASC }; + this.direction = EntitySearchDirection.FROM; + this.pageLink = new PageLink(10, 0, null, sortOrder); + this.dataSource = new RelationsDatasource(this.entityRelationService, this.translate); + this.updateColumns(); + } + + ngOnInit() { + } + + updateColumns() { + if (this.direction === EntitySearchDirection.FROM) { + this.displayedColumns = ['select', 'type', 'toEntityTypeName', 'toName', 'actions']; + } else { + this.displayedColumns = ['select', 'type', 'fromEntityTypeName', 'fromName', 'actions']; + } + } + + directionChanged(direction: EntitySearchDirection) { + this.direction = direction; + this.updateColumns(); + this.updateData(true); + } + + ngAfterViewInit() { + + fromEvent(this.searchInputField.nativeElement, 'keyup') + .pipe( + debounceTime(150), + distinctUntilChanged(), + tap(() => { + this.paginator.pageIndex = 0; + this.updateData(); + }) + ) + .subscribe(); + + this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0); + + merge(this.sort.sortChange, this.paginator.page) + .pipe( + tap(() => this.updateData()) + ) + .subscribe(); + + this.viewsInited = true; + if (this.activeValue && this.entityIdValue) { + this.updateData(true); + } + } + + updateData(reload: boolean = false) { + this.pageLink.page = this.paginator.pageIndex; + this.pageLink.pageSize = this.paginator.pageSize; + this.pageLink.sortOrder.property = this.sort.active; + this.pageLink.sortOrder.direction = Direction[this.sort.direction.toUpperCase()]; + this.dataSource.loadRelations(this.direction, this.entityIdValue, this.pageLink, reload); + } + + enterFilterMode() { + this.textSearchMode = true; + this.pageLink.textSearch = ''; + setTimeout(() => { + this.searchInputField.nativeElement.focus(); + this.searchInputField.nativeElement.setSelectionRange(0, 0); + }, 10); + } + + exitFilterMode() { + this.textSearchMode = false; + this.pageLink.textSearch = null; + this.paginator.pageIndex = 0; + this.updateData(); + } + + resetSortAndFilter(update: boolean = true) { + this.direction = EntitySearchDirection.FROM; + this.updateColumns(); + this.pageLink.textSearch = null; + this.paginator.pageIndex = 0; + const sortable = this.sort.sortables.get('type'); + this.sort.active = sortable.id; + this.sort.direction = 'asc'; + if (update) { + this.updateData(true); + } + } + + reloadRelations() { + this.updateData(true); + } + + addRelation($event: Event) { + this.openRelationDialog($event); + } + + editRelation($event: Event, relation: EntityRelationInfo) { + this.openRelationDialog($event, relation); + } + + deleteRelation($event: Event, relation: EntityRelationInfo) { + if ($event) { + $event.stopPropagation(); + } + + // TODO: + } + + deleteRelations($event: Event) { + if ($event) { + $event.stopPropagation(); + } + if (this.dataSource.selection.selected.length > 0) { + // TODO: + } + } + + openRelationDialog($event: Event, relation: EntityRelationInfo = null) { + if ($event) { + $event.stopPropagation(); + } + // TODO: + } + + +} diff --git a/ui-ngx/src/app/modules/home/models/datasource/relation-datasource.ts b/ui-ngx/src/app/modules/home/models/datasource/relation-datasource.ts new file mode 100644 index 0000000000..12a5c5252d --- /dev/null +++ b/ui-ngx/src/app/modules/home/models/datasource/relation-datasource.ts @@ -0,0 +1,143 @@ +/// +/// Copyright © 2016-2019 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 { CollectionViewer, DataSource } from '@angular/cdk/typings/collections'; +import { EntityRelationInfo, EntitySearchDirection } from '@shared/models/relation.models'; +import { BehaviorSubject, Observable, of, ReplaySubject } from 'rxjs'; +import { emptyPageData, PageData } from '@shared/models/page/page-data'; +import { SelectionModel } from '@angular/cdk/collections'; +import { EntityRelationService } from '@core/http/entity-relation.service'; +import { PageLink } from '@shared/models/page/page-link'; +import { catchError, map, publishReplay, refCount, take, tap } from 'rxjs/operators'; +import { EntityId } from '@app/shared/models/id/entity-id'; +import { TranslateService } from '@ngx-translate/core'; +import { entityTypeTranslations } from '@shared/models/entity-type.models'; + +export class RelationsDatasource implements DataSource { + + private relationsSubject = new BehaviorSubject([]); + private pageDataSubject = new BehaviorSubject>(emptyPageData()); + + public pageData$ = this.pageDataSubject.asObservable(); + + public selection = new SelectionModel(true, []); + + private allRelations: Observable>; + + constructor(private entityRelationService: EntityRelationService, + private translate: TranslateService) {} + + connect(collectionViewer: CollectionViewer): Observable> { + return this.relationsSubject.asObservable(); + } + + disconnect(collectionViewer: CollectionViewer): void { + this.relationsSubject.complete(); + this.pageDataSubject.complete(); + } + + loadRelations(direction: EntitySearchDirection, entityId: EntityId, + pageLink: PageLink, reload: boolean = false): Observable> { + if (reload) { + this.allRelations = null; + } + const result = new ReplaySubject>(); + this.fetchRelations(direction, entityId, pageLink).pipe( + tap(() => { + this.selection.clear(); + }), + catchError(() => of(emptyPageData())), + ).subscribe( + (pageData) => { + this.relationsSubject.next(pageData.data); + this.pageDataSubject.next(pageData); + result.next(pageData); + } + ); + return result; + } + + fetchRelations(direction: EntitySearchDirection, entityId: EntityId, + pageLink: PageLink): Observable> { + return this.getAllRelations(direction, entityId).pipe( + map((data) => pageLink.filterData(data)) + ); + } + + getAllRelations(direction: EntitySearchDirection, entityId: EntityId): Observable> { + if (!this.allRelations) { + let relationsObservable: Observable>; + switch (direction) { + case EntitySearchDirection.FROM: + relationsObservable = this.entityRelationService.findInfoByFrom(entityId); + break; + case EntitySearchDirection.TO: + relationsObservable = this.entityRelationService.findInfoByTo(entityId); + break; + } + this.allRelations = relationsObservable.pipe( + map(relations => { + relations.forEach(relation => { + if (direction === EntitySearchDirection.FROM) { + relation.toEntityTypeName = this.translate.instant(entityTypeTranslations.get(relation.to.entityType).type); + } else { + relation.fromEntityTypeName = this.translate.instant(entityTypeTranslations.get(relation.from.entityType).type); + } + }); + return relations; + }), + publishReplay(1), + refCount() + ); + } + return this.allRelations; + } + + isAllSelected(): Observable { + const numSelected = this.selection.selected.length; + return this.relationsSubject.pipe( + map((relations) => numSelected === relations.length) + ); + } + + isEmpty(): Observable { + return this.relationsSubject.pipe( + map((relations) => !relations.length) + ); + } + + total(): Observable { + return this.pageDataSubject.pipe( + map((pageData) => pageData.totalElements) + ); + } + + masterToggle() { + this.relationsSubject.pipe( + tap((relations) => { + const numSelected = this.selection.selected.length; + if (numSelected === relations.length) { + this.selection.clear(); + } else { + relations.forEach(row => { + this.selection.select(row); + }); + } + }), + take(1) + ).subscribe(); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html b/ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html index 1b68496f6f..cdcc19e08c 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html @@ -20,6 +20,10 @@
+ + + diff --git a/ui-ngx/src/app/shared/models/page/page-data.ts b/ui-ngx/src/app/shared/models/page/page-data.ts index 76d1438d4e..64f6e96a19 100644 --- a/ui-ngx/src/app/shared/models/page/page-data.ts +++ b/ui-ngx/src/app/shared/models/page/page-data.ts @@ -14,16 +14,17 @@ /// limitations under the License. /// -import { BaseData, HasId } from '@shared/models/base-data'; +import { PageLink } from '@shared/models/page/page-link'; +import { Direction, SortOrder } from '@shared/models/page/sort-order'; -export interface PageData> { +export interface PageData { data: Array; totalPages: number; totalElements: number; hasNext: boolean; } -export function emptyPageData>(): PageData { +export function emptyPageData(): PageData { return { data: [], totalPages: 0, diff --git a/ui-ngx/src/app/shared/models/page/page-link.ts b/ui-ngx/src/app/shared/models/page/page-link.ts index e2cf956b2b..4be0264466 100644 --- a/ui-ngx/src/app/shared/models/page/page-link.ts +++ b/ui-ngx/src/app/shared/models/page/page-link.ts @@ -15,6 +15,27 @@ /// import { Direction, SortOrder } from '@shared/models/page/sort-order'; +import { emptyPageData, PageData } from '@shared/models/page/page-data'; + +export type PageLinkSearchFunction = (entity: T, textSearch: string) => boolean; + +const defaultPageLinkSearchFunction: PageLinkSearchFunction = + (entity: any, textSearch: string) => { + if (textSearch === null || !textSearch.length) { + return true; + } + const expected = ('' + textSearch).toLowerCase(); + for (const key of Object.keys(entity)) { + const val = entity[key]; + if (val !== null && val !== Object(val)) { + const actual = ('' + val).toLowerCase(); + if (actual.indexOf(expected) !== -1) { + return true; + } + } + } + return false; + }; export class PageLink { @@ -65,6 +86,25 @@ export class PageLink { return 0; } + public filterData(data: Array, + searchFunction: PageLinkSearchFunction = defaultPageLinkSearchFunction): PageData { + const pageData = emptyPageData(); + pageData.data = [...data]; + if (this.textSearch && this.textSearch.length) { + pageData.data = pageData.data.filter((entity) => searchFunction(entity, this.textSearch)); + } + pageData.totalElements = pageData.data.length; + pageData.totalPages = Math.ceil(pageData.totalElements / this.pageSize); + if (this.sortOrder) { + pageData.data = pageData.data.sort(this.sort); + } + const startIndex = this.pageSize * this.page; + const endIndex = startIndex + this.pageSize; + pageData.data = pageData.data.slice(startIndex, startIndex + this.pageSize); + pageData.hasNext = pageData.totalElements > startIndex + pageData.data.length; + return pageData; + } + } export class TimePageLink extends PageLink { diff --git a/ui-ngx/src/app/shared/models/relation.models.ts b/ui-ngx/src/app/shared/models/relation.models.ts new file mode 100644 index 0000000000..a33e66fd3c --- /dev/null +++ b/ui-ngx/src/app/shared/models/relation.models.ts @@ -0,0 +1,87 @@ +/// +/// Copyright © 2016-2019 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 { EntityId } from '@shared/models/id/entity-id'; +import { EntityType } from '@shared/models/entity-type.models'; +import { ActionStatus } from '@shared/models/audit-log.models'; + +export const CONTAINS_TYPE = 'Contains'; +export const MANAGES_TYPE = 'Manages'; + +export const RelationTypes = [ + CONTAINS_TYPE, + MANAGES_TYPE +]; + +export enum RelationTypeGroup { + COMMON = 'COMMON', + ALARM = 'ALARM', + DASHBOARD = 'DASHBOARD', + RULE_CHAIN = 'RULE_CHAIN', + RULE_NODE = 'RULE_NODE', +} + +export enum EntitySearchDirection { + FROM = 'FROM', + TO = 'TO' +} + +export const entitySearchDirectionTranslations = new Map( + [ + [EntitySearchDirection.FROM, 'relation.search-direction.FROM'], + [EntitySearchDirection.TO, 'relation.search-direction.TO'], + ] +); + +export const directionTypeTranslations = new Map( + [ + [EntitySearchDirection.FROM, 'relation.direction-type.FROM'], + [EntitySearchDirection.TO, 'relation.direction-type.TO'], + ] +); + +export interface EntityTypeFilter { + relationType: string; + entityTypes: Array; +} + +export interface RelationsSearchParameters { + rootId: string; + rootType: EntityType; + direction: EntitySearchDirection; + relationTypeGroup: RelationTypeGroup; + maxLevel: number; +} + +export interface EntityRelationsQuery { + parameters: RelationsSearchParameters; + filters: Array; +} + +export interface EntityRelation { + from: EntityId; + to: EntityId; + type: string; + typeGroup: RelationTypeGroup; + additionalInfo?: any; +} + +export interface EntityRelationInfo extends EntityRelation { + fromName: string; + toEntityTypeName?: string; + toName: string; + fromEntityTypeName?: 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 e1f090b863..c87564f227 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1278,7 +1278,8 @@ "any-relation": "Any relation", "relation-filters": "Relation filters", "additional-info": "Additional info (JSON)", - "invalid-additional-info": "Unable to parse additional info json." + "invalid-additional-info": "Unable to parse additional info json.", + "no-relations-text": "No relations found" }, "rulechain": { "rulechain": "Rule chain", @@ -1749,7 +1750,7 @@ "tr_TR": "Turkish", "fa_IR": "Persian", "uk_UA": "Ukrainian", - "cs_CZ": "Czech" + "cs_CZ": "Czech" } } } From b438cdd255edbf0afd41086cfa2ce71f17f86c24 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 27 Aug 2019 20:07:09 +0300 Subject: [PATCH 023/133] Edit relation dialog --- .../home/components/home-components.module.ts | 7 +- .../relation/relation-dialog.component.html | 68 +++++++ .../relation/relation-dialog.component.scss | 18 ++ .../relation/relation-dialog.component.ts | 134 +++++++++++++ .../relation/relation-table.component.ts | 112 ++++++++++- .../entity/entity-list-select.component.html | 36 ++++ .../entity/entity-list-select.component.scss | 32 ++++ .../entity/entity-list-select.component.ts | 170 +++++++++++++++++ .../entity/entity-list.component.html | 4 +- .../entity/entity-list.component.ts | 57 ++++-- .../json-object-edit.component.html | 35 ++++ .../json-object-edit.component.scss | 55 ++++++ .../components/json-object-edit.component.ts | 180 ++++++++++++++++++ .../relation-type-autocomplete.component.html | 42 ++++ .../relation-type-autocomplete.component.ts | 168 ++++++++++++++++ .../src/app/shared/models/page/page-link.ts | 2 +- ui-ngx/src/app/shared/shared.module.ts | 9 + 17 files changed, 1099 insertions(+), 30 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.ts create mode 100644 ui-ngx/src/app/shared/components/entity/entity-list-select.component.html create mode 100644 ui-ngx/src/app/shared/components/entity/entity-list-select.component.scss create mode 100644 ui-ngx/src/app/shared/components/entity/entity-list-select.component.ts create mode 100644 ui-ngx/src/app/shared/components/json-object-edit.component.html create mode 100644 ui-ngx/src/app/shared/components/json-object-edit.component.scss create mode 100644 ui-ngx/src/app/shared/components/json-object-edit.component.ts create mode 100644 ui-ngx/src/app/shared/components/relation/relation-type-autocomplete.component.html create mode 100644 ui-ngx/src/app/shared/components/relation/relation-type-autocomplete.component.ts diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index daeb05e3fa..3cf870f96d 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -27,12 +27,14 @@ import { AuditLogTableComponent } from './audit-log/audit-log-table.component'; import { EventTableHeaderComponent } from '@home/components/event/event-table-header.component'; import { EventTableComponent } from '@home/components/event/event-table.component'; import { RelationTableComponent } from '@home/components/relation/relation-table.component'; +import { RelationDialogComponent } from './relation/relation-dialog.component'; @NgModule({ entryComponents: [ AddEntityDialogComponent, AuditLogDetailsDialogComponent, - EventTableHeaderComponent + EventTableHeaderComponent, + RelationDialogComponent ], declarations: [ @@ -45,7 +47,8 @@ import { RelationTableComponent } from '@home/components/relation/relation-table AuditLogDetailsDialogComponent, EventTableHeaderComponent, EventTableComponent, - RelationTableComponent + RelationTableComponent, + RelationDialogComponent ], imports: [ CommonModule, diff --git a/ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.html b/ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.html new file mode 100644 index 0000000000..f8b5ff439f --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.html @@ -0,0 +1,68 @@ + +
+ +

{{ (isAdd ? 'relation.add' : 'relation.edit' ) | translate }}

+ + +
+ + +
+
+
+ + + {{(direction === entitySearchDirection.FROM ? + 'relation.to-entity' : 'relation.from-entity') | translate}} + + + + + +
+
+
+ + + +
+
diff --git a/ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.scss b/ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.scss new file mode 100644 index 0000000000..dfbd362f33 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.scss @@ -0,0 +1,18 @@ +/** + * Copyright © 2016-2019 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 { + +} diff --git a/ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.ts b/ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.ts new file mode 100644 index 0000000000..5bd216c10f --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.ts @@ -0,0 +1,134 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Inject, OnInit, SkipSelf } from '@angular/core'; +import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; +import { + CONTAINS_TYPE, + EntityRelation, + EntitySearchDirection, + RelationTypeGroup +} from '@shared/models/relation.models'; +import { EntityRelationService } from '@core/http/entity-relation.service'; +import { EntityId } from '@shared/models/id/entity-id'; +import { Observable, forkJoin } from 'rxjs'; + +export interface RelationDialogData { + isAdd: boolean; + direction: EntitySearchDirection; + relation: EntityRelation; +} + +@Component({ + selector: 'tb-relation-dialog', + templateUrl: './relation-dialog.component.html', + providers: [{provide: ErrorStateMatcher, useExisting: RelationDialogComponent}], + styleUrls: ['./relation-dialog.component.scss'] +}) +export class RelationDialogComponent extends PageComponent implements OnInit, ErrorStateMatcher { + + relationFormGroup: FormGroup; + + isAdd: boolean; + direction: EntitySearchDirection; + entitySearchDirection = EntitySearchDirection; + + submitted = false; + + constructor(protected store: Store, + @Inject(MAT_DIALOG_DATA) public data: RelationDialogData, + private entityRelationService: EntityRelationService, + @SkipSelf() private errorStateMatcher: ErrorStateMatcher, + public dialogRef: MatDialogRef, + public fb: FormBuilder) { + super(store); + this.isAdd = data.isAdd; + this.direction = data.direction; + } + + ngOnInit(): void { + this.relationFormGroup = this.fb.group({ + type: [this.isAdd ? CONTAINS_TYPE : this.data.relation.type, [Validators.required]], + targetEntityIds: [this.isAdd ? null : + [this.direction === EntitySearchDirection.FROM ? this.data.relation.to : this.data.relation.from], + [Validators.required]], + additionalInfo: [this.data.relation.additionalInfo] + }); + if (!this.isAdd) { + this.relationFormGroup.get('type').disable(); + this.relationFormGroup.get('targetEntityIds').disable(); + } + this.relationFormGroup.valueChanges.subscribe( + () => { + this.submitted = false; + } + ); + } + + isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { + const originalErrorState = this.errorStateMatcher.isErrorState(control, form); + const customErrorState = !!(control && control.invalid && this.submitted); + return originalErrorState || customErrorState; + } + + cancel(): void { + this.dialogRef.close(false); + } + + save(): void { + this.submitted = true; + if (this.relationFormGroup.valid) { + const additionalInfo = this.relationFormGroup.get('additionalInfo').value; + if (this.isAdd) { + const tasks: Observable[] = []; + const type: string = this.relationFormGroup.get('type').value; + const entityIds: Array = this.relationFormGroup.get('targetEntityIds').value; + entityIds.forEach(entityId => { + const relation = { + type, + additionalInfo, + typeGroup: RelationTypeGroup.COMMON + } as EntityRelation; + if (this.direction === EntitySearchDirection.FROM) { + relation.from = this.data.relation.from; + relation.to = entityId; + } else { + relation.from = entityId; + relation.to = this.data.relation.to; + } + tasks.push(this.entityRelationService.saveRelation(relation)); + }); + forkJoin(tasks).subscribe( + () => { + this.dialogRef.close(true); + } + ); + } else { + const relation: EntityRelation = {...this.data.relation}; + relation.additionalInfo = additionalInfo; + this.entityRelationService.saveRelation(relation).subscribe( + () => { + this.dialogRef.close(true); + } + ); + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/relation/relation-table.component.ts b/ui-ngx/src/app/modules/home/components/relation/relation-table.component.ts index fddf1db15a..26f7deb804 100644 --- a/ui-ngx/src/app/modules/home/components/relation/relation-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/relation/relation-table.component.ts @@ -26,12 +26,18 @@ import { MatDialog } from '@angular/material/dialog'; import { DialogService } from '@core/services/dialog.service'; import { EntityRelationService } from '@core/http/entity-relation.service'; import { Direction, SortOrder } from '@shared/models/page/sort-order'; -import { fromEvent, merge } from 'rxjs'; +import { forkJoin, fromEvent, merge, Observable } from 'rxjs'; import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators'; -import { EntityRelationInfo, EntitySearchDirection, entitySearchDirectionTranslations } from '@shared/models/relation.models'; +import { + EntityRelation, + EntityRelationInfo, + EntitySearchDirection, + entitySearchDirectionTranslations, + RelationTypeGroup +} from '@shared/models/relation.models'; import { EntityId } from '@shared/models/id/entity-id'; import { RelationsDatasource } from '../../models/datasource/relation-datasource'; -import { DebugEventType, EventType } from '@shared/models/event.models'; +import { RelationDialogComponent, RelationDialogData } from '@home/components/relation/relation-dialog.component'; @Component({ selector: 'tb-relation-table', @@ -201,8 +207,35 @@ export class RelationTableComponent extends PageComponent implements AfterViewIn if ($event) { $event.stopPropagation(); } + let title; + let content; + if (this.direction === EntitySearchDirection.FROM) { + title = this.translate.instant('relation.delete-to-relation-title', {entityName: relation.toName}); + content = this.translate.instant('relation.delete-to-relation-text', {entityName: relation.toName}); + } else { + title = this.translate.instant('relation.delete-from-relation-title', {entityName: relation.fromName}); + content = this.translate.instant('relation.delete-from-relation-text', {entityName: relation.fromName}); + } - // TODO: + this.dialogService.confirm( + title, + content, + this.translate.instant('action.no'), + this.translate.instant('action.yes'), + true + ).subscribe((result) => { + if (result) { + this.entityRelationService.deleteRelation( + relation.from, + relation.type, + relation.to + ).subscribe( + () => { + this.reloadRelations(); + } + ); + } + }); } deleteRelations($event: Event) { @@ -210,16 +243,79 @@ export class RelationTableComponent extends PageComponent implements AfterViewIn $event.stopPropagation(); } if (this.dataSource.selection.selected.length > 0) { - // TODO: + let title; + let content; + + if (this.direction === EntitySearchDirection.FROM) { + title = this.translate.instant('relation.delete-to-relations-title', {count: this.dataSource.selection.selected.length}); + content = this.translate.instant('relation.delete-to-relations-text'); + } else { + title = this.translate.instant('relation.delete-from-relations-title', {count: this.dataSource.selection.selected.length}); + content = this.translate.instant('relation.delete-from-relations-text'); + } + + this.dialogService.confirm( + title, + content, + this.translate.instant('action.no'), + this.translate.instant('action.yes'), + true + ).subscribe((result) => { + if (result) { + const tasks: Observable[] = []; + this.dataSource.selection.selected.forEach((relation) => { + tasks.push(this.entityRelationService.deleteRelation( + relation.from, + relation.type, + relation.to + )); + }); + forkJoin(tasks).subscribe( + () => { + this.reloadRelations(); + } + ); + } + }); } } - openRelationDialog($event: Event, relation: EntityRelationInfo = null) { + openRelationDialog($event: Event, relation: EntityRelation = null) { if ($event) { $event.stopPropagation(); } - // TODO: - } + let isAdd = false; + if (!relation) { + isAdd = true; + relation = { + from: null, + to: null, + type: null, + typeGroup: RelationTypeGroup.COMMON + }; + if (this.direction === EntitySearchDirection.FROM) { + relation.from = this.entityIdValue; + } else { + relation.to = this.entityIdValue; + } + } + + this.dialog.open(RelationDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + isAdd, + direction: this.direction, + relation: {...relation} + } + }).afterClosed().subscribe( + (res) => { + if (res) { + this.reloadRelations(); + } + } + ); + } } diff --git a/ui-ngx/src/app/shared/components/entity/entity-list-select.component.html b/ui-ngx/src/app/shared/components/entity/entity-list-select.component.html new file mode 100644 index 0000000000..6a5a117cd3 --- /dev/null +++ b/ui-ngx/src/app/shared/components/entity/entity-list-select.component.html @@ -0,0 +1,36 @@ + +
+ + + + +
diff --git a/ui-ngx/src/app/shared/components/entity/entity-list-select.component.scss b/ui-ngx/src/app/shared/components/entity/entity-list-select.component.scss new file mode 100644 index 0000000000..21da8f6580 --- /dev/null +++ b/ui-ngx/src/app/shared/components/entity/entity-list-select.component.scss @@ -0,0 +1,32 @@ +/** + * Copyright © 2016-2019 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 { +} + +:host ::ng-deep { + tb-entity-list { + &.tb-not-empty { + .mat-form-field-flex { + padding-top: 0; + } + } + .mat-form-field-flex { + .mat-form-field-infix { + border-top: 0; + } + } + } +} diff --git a/ui-ngx/src/app/shared/components/entity/entity-list-select.component.ts b/ui-ngx/src/app/shared/components/entity/entity-list-select.component.ts new file mode 100644 index 0000000000..c10d37d9a8 --- /dev/null +++ b/ui-ngx/src/app/shared/components/entity/entity-list-select.component.ts @@ -0,0 +1,170 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import {AfterViewInit, Component, forwardRef, Input, OnInit} from '@angular/core'; +import {ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR} from '@angular/forms'; +import {Store} from '@ngrx/store'; +import {AppState} from '@core/core.state'; +import {TranslateService} from '@ngx-translate/core'; +import {AliasEntityType, EntityType, entityTypeTranslations} from '@shared/models/entity-type.models'; +import {EntityService} from '@core/http/entity.service'; +import {EntityId} from '@shared/models/id/entity-id'; +import {coerceBooleanProperty} from '@angular/cdk/coercion'; + +interface EntityListSelectModel { + entityType: EntityType | AliasEntityType; + ids: Array; +} + +@Component({ + selector: 'tb-entity-list-select', + templateUrl: './entity-list-select.component.html', + styleUrls: ['./entity-list-select.component.scss'], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => EntityListSelectComponent), + multi: true + }] +}) + +export class EntityListSelectComponent implements ControlValueAccessor, OnInit, AfterViewInit { + + entityListSelectFormGroup: FormGroup; + + modelValue: EntityListSelectModel = {entityType: null, ids: []}; + + @Input() + allowedEntityTypes: Array; + + @Input() + useAliasEntityTypes: boolean; + + private requiredValue: boolean; + get required(): boolean { + return this.requiredValue; + } + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + @Input() + disabled: boolean; + + displayEntityTypeSelect: boolean; + + private defaultEntityType: EntityType | AliasEntityType = null; + + private propagateChange = (v: any) => { }; + + constructor(private store: Store, + private entityService: EntityService, + public translate: TranslateService, + private fb: FormBuilder) { + + const entityTypes = this.entityService.prepareAllowedEntityTypesList(this.allowedEntityTypes, + this.useAliasEntityTypes); + if (entityTypes.length === 1) { + this.displayEntityTypeSelect = false; + this.defaultEntityType = entityTypes[0]; + } else { + this.displayEntityTypeSelect = true; + } + + this.entityListSelectFormGroup = this.fb.group({ + entityType: [this.defaultEntityType], + entityIds: [[]] + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + ngOnInit() { + this.entityListSelectFormGroup.get('entityType').valueChanges.subscribe( + (value) => { + this.updateView(value, this.modelValue.ids); + } + ); + this.entityListSelectFormGroup.get('entityIds').valueChanges.subscribe( + (values) => { + this.updateView(this.modelValue.entityType, values); + } + ); + } + + ngAfterViewInit(): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.entityListSelectFormGroup.disable(); + } else { + this.entityListSelectFormGroup.enable(); + } + } + + writeValue(value: Array | null): void { + if (value != null && value.length > 0) { + const id = value[0]; + this.modelValue = { + entityType: id.entityType, + ids: value.map(val => val.id) + }; + } else { + this.modelValue = { + entityType: this.defaultEntityType, + ids: [] + }; + } + this.entityListSelectFormGroup.get('entityType').patchValue(this.modelValue.entityType, {emitEvent: true}); + this.entityListSelectFormGroup.get('entityIds').patchValue([...this.modelValue.ids], {emitEvent: true}); + } + + updateView(entityType: EntityType | AliasEntityType | null, entityIds: Array | null) { + if (this.modelValue.entityType !== entityType || + !this.compareIds(this.modelValue.ids, entityIds)) { + this.modelValue = { + entityType, + ids: this.modelValue.entityType !== entityType || !entityIds ? [] : [...entityIds] + }; + this.propagateChange(this.toEntityIds(this.modelValue)); + } + } + + compareIds(ids1: Array | null, ids2: Array | null): boolean { + if (ids1 !== null && ids2 !== null) { + return JSON.stringify(ids1) === JSON.stringify(ids2); + } else { + return ids1 === ids2; + } + } + + toEntityIds(modelValue: EntityListSelectModel): Array { + if (modelValue !== null && modelValue.entityType && modelValue.ids && modelValue.ids.length > 0) { + const entityType = modelValue.entityType; + return modelValue.ids.map(id => ({entityType, id})); + } else { + return null; + } + } + +} diff --git a/ui-ngx/src/app/shared/components/entity/entity-list.component.html b/ui-ngx/src/app/shared/components/entity/entity-list.component.html index 435427716e..8e0bae6b53 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-list.component.html +++ b/ui-ngx/src/app/shared/components/entity/entity-list.component.html @@ -16,7 +16,7 @@ --> - + - + {{ 'entity.entity-list-empty' | translate }} diff --git a/ui-ngx/src/app/shared/components/entity/entity-list.component.ts b/ui-ngx/src/app/shared/components/entity/entity-list.component.ts index 8ab9600dd6..d0af0f533d 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-list.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-list.component.ts @@ -14,16 +14,26 @@ /// limitations under the License. /// -import {AfterViewInit, Component, ElementRef, forwardRef, Input, OnInit, SkipSelf, ViewChild} from '@angular/core'; +import { + AfterContentInit, + AfterViewInit, + Component, + ElementRef, + forwardRef, + Input, + OnInit, + SkipSelf, + ViewChild +} from '@angular/core'; import { ControlValueAccessor, FormBuilder, FormControl, FormGroup, FormGroupDirective, - NG_VALUE_ACCESSOR, NgForm + NG_VALUE_ACCESSOR, NgForm, Validators } from '@angular/forms'; -import {Observable} from 'rxjs'; +import {Observable, of} from 'rxjs'; import {map, mergeMap, startWith, tap, share, pairwise, filter} from 'rxjs/operators'; import {Store} from '@ngrx/store'; import {AppState} from '@app/core/core.state'; @@ -34,6 +44,7 @@ import {EntityId} from '@shared/models/id/entity-id'; import {EntityService} from '@core/http/entity.service'; import {ErrorStateMatcher, MatAutocomplete, MatAutocompleteSelectedEvent, MatChipList} from '@angular/material'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { emptyPageData } from '@shared/models/page/page-data'; @Component({ selector: 'tb-entity-list', @@ -69,7 +80,11 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV } @Input() set required(value: boolean) { - this.requiredValue = coerceBooleanProperty(value); + const newVal = coerceBooleanProperty(value); + if (this.requiredValue !== newVal) { + this.requiredValue = newVal; + this.updateValidators(); + } } @Input() @@ -77,7 +92,7 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV @ViewChild('entityInput', {static: false}) entityInput: ElementRef; @ViewChild('entityAutocomplete', {static: false}) matAutocomplete: MatAutocomplete; - @ViewChild('chipList', {static: false}) chipList: MatChipList; + @ViewChild('chipList', {static: true}) chipList: MatChipList; entities: Array> = []; filteredEntities: Observable>>; @@ -91,10 +106,16 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV private entityService: EntityService, private fb: FormBuilder) { this.entityListFormGroup = this.fb.group({ + entities: [this.entities, this.required ? [Validators.required] : []], entity: [null] }); } + updateValidators() { + this.entityListFormGroup.get('entities').setValidators(this.required ? [Validators.required] : []); + this.entityListFormGroup.get('entities').updateValueAndValidity(); + } + registerOnChange(fn: any): void { this.propagateChange = fn; } @@ -120,34 +141,39 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV ); } - ngAfterViewInit(): void {} + ngAfterViewInit(): void { + } setDisabledState(isDisabled: boolean): void { + const emitEvent = this.disabled !== isDisabled; this.disabled = isDisabled; - if (this.disabled) { - this.entityListFormGroup.disable(); + if (isDisabled) { + this.entityListFormGroup.disable({emitEvent}); } else { - this.entityListFormGroup.enable(); + this.entityListFormGroup.enable({emitEvent}); } } writeValue(value: Array | null): void { this.searchText = ''; - if (value != null) { + if (value != null && value.length > 0) { this.modelValue = [...value]; this.entityService.getEntities(this.entityTypeValue, value).subscribe( (entities) => { this.entities = entities; + this.entityListFormGroup.get('entities').setValue(this.entities); } ); } else { this.entities = []; + this.entityListFormGroup.get('entities').setValue(this.entities); this.modelValue = null; } } reset() { this.entities = []; + this.entityListFormGroup.get('entities').setValue(this.entities); this.modelValue = null; this.entityListFormGroup.get('entity').patchValue('', {emitEvent: true}); this.propagateChange(this.modelValue); @@ -160,9 +186,7 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV } this.modelValue.push(entity.id.id); this.entities.push(entity); - if (this.required) { - this.chipList.errorState = false; - } + this.entityListFormGroup.get('entities').setValue(this.entities); } this.propagateChange(this.modelValue); this.clear(); @@ -172,12 +196,10 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV const index = this.entities.indexOf(entity); if (index >= 0) { this.entities.splice(index, 1); + this.entityListFormGroup.get('entities').setValue(this.entities); this.modelValue.splice(index, 1); if (!this.modelValue.length) { this.modelValue = null; - if (this.required) { - this.chipList.errorState = true; - } } this.propagateChange(this.modelValue); this.clear(); @@ -190,7 +212,8 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV fetchEntities(searchText?: string): Observable>> { this.searchText = searchText; - return this.entityService.getEntitiesByNameFilter(this.entityTypeValue, searchText, + return this.disabled ? of([]) : + this.entityService.getEntitiesByNameFilter(this.entityTypeValue, searchText, 50, '', false, true).pipe( map((data) => data ? data : [])); } diff --git a/ui-ngx/src/app/shared/components/json-object-edit.component.html b/ui-ngx/src/app/shared/components/json-object-edit.component.html new file mode 100644 index 0000000000..a723cf1c33 --- /dev/null +++ b/ui-ngx/src/app/shared/components/json-object-edit.component.html @@ -0,0 +1,35 @@ + +
+
+ + + +
+
+
+
+
diff --git a/ui-ngx/src/app/shared/components/json-object-edit.component.scss b/ui-ngx/src/app/shared/components/json-object-edit.component.scss new file mode 100644 index 0000000000..913da86348 --- /dev/null +++ b/ui-ngx/src/app/shared/components/json-object-edit.component.scss @@ -0,0 +1,55 @@ +/** + * Copyright © 2016-2019 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. + */ + +/** + * Copyright © 2016-2019 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 { + position: relative; + + .fill-height { + height: 100%; + } + + .tb-json-object-panel { + height: 100%; + margin-left: 15px; + border: 1px solid #c0c0c0; + + #tb-json-input { + width: 100%; + min-width: 200px; + height: 100%; + + &:not(.fill-height) { + min-height: 200px; + } + } + } + +} diff --git a/ui-ngx/src/app/shared/components/json-object-edit.component.ts b/ui-ngx/src/app/shared/components/json-object-edit.component.ts new file mode 100644 index 0000000000..72f64e1bb7 --- /dev/null +++ b/ui-ngx/src/app/shared/components/json-object-edit.component.ts @@ -0,0 +1,180 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + Attribute, + Component, + ElementRef, + forwardRef, + Input, + OnInit, + ViewChild +} from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormControl, Validator, NG_VALIDATORS } from '@angular/forms'; +import * as ace from 'ace-builds'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; + +@Component({ + selector: 'tb-json-object-edit', + templateUrl: './json-object-edit.component.html', + styleUrls: ['./json-object-edit.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => JsonObjectEditComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => JsonObjectEditComponent), + multi: true, + } + ] +}) +export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Validator { + + @ViewChild('jsonEditor', {static: true}) + jsonEditorElmRef: ElementRef; + + private jsonEditor: ace.Ace.Editor; + + @Input() label: string; + + @Input() disabled: boolean; + + @Input() fillHeight: boolean; + + private requiredValue: boolean; + get required(): boolean { + return this.requiredValue; + } + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + private readonlyValue: boolean; + get readonly(): boolean { + return this.readonlyValue; + } + @Input() + set readonly(value: boolean) { + this.readonlyValue = coerceBooleanProperty(value); + } + + fullscreen = false; + + modelValue: any; + + contentValue: string; + + objectValid: boolean; + + private propagateChange = null; + + constructor() { + } + + ngOnInit(): void { + const editorElement = this.jsonEditorElmRef.nativeElement; + let editorOptions: Partial = { + mode: 'ace/mode/json', + theme: 'ace/theme/github', + showGutter: true, + showPrintMargin: false, + readOnly: this.readonly + }; + + const advancedOptions = { + enableSnippets: true, + enableBasicAutocompletion: true, + enableLiveAutocompletion: true + }; + + editorOptions = {...editorOptions, ...advancedOptions}; + this.jsonEditor = ace.edit(editorElement, editorOptions); + this.jsonEditor.session.setUseWrapMode(false); + this.jsonEditor.setValue(this.contentValue ? this.contentValue : '', -1); + this.jsonEditor.on('change', () => { + this.updateView(); + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + public validate(c: FormControl) { + return (this.objectValid) ? null : { + jsonParseError: { + valid: false, + }, + }; + } + + writeValue(value: any): void { + this.modelValue = value; + this.contentValue = ''; + this.objectValid = false; + try { + if (this.modelValue) { + this.contentValue = JSON.stringify(this.modelValue, undefined, 2); + this.objectValid = true; + } else { + this.objectValid = !this.required; + } + } catch (e) { + // + } + if (this.jsonEditor) { + this.jsonEditor.setValue(this.contentValue ? this.contentValue : '', -1); + } + } + + updateView() { + const editorValue = this.jsonEditor.getValue(); + if (this.contentValue !== editorValue) { + this.contentValue = editorValue; + let data = null; + this.objectValid = false; + if (this.contentValue && this.contentValue.length > 0) { + try { + data = JSON.parse(this.contentValue); + this.objectValid = true; + } catch (ex) {} + } else { + this.objectValid = !this.required; + } + this.propagateChange(data); + } + } + + onFullscreen() { + if (this.jsonEditor) { + setTimeout(() => { + this.jsonEditor.resize(); + }, 0); + } + } + +} diff --git a/ui-ngx/src/app/shared/components/relation/relation-type-autocomplete.component.html b/ui-ngx/src/app/shared/components/relation/relation-type-autocomplete.component.html new file mode 100644 index 0000000000..9e0d80c1c8 --- /dev/null +++ b/ui-ngx/src/app/shared/components/relation/relation-type-autocomplete.component.html @@ -0,0 +1,42 @@ + + + + + + + + + + + {{ 'relation.relation-type-required' | translate }} + + diff --git a/ui-ngx/src/app/shared/components/relation/relation-type-autocomplete.component.ts b/ui-ngx/src/app/shared/components/relation/relation-type-autocomplete.component.ts new file mode 100644 index 0000000000..3c1536b361 --- /dev/null +++ b/ui-ngx/src/app/shared/components/relation/relation-type-autocomplete.component.ts @@ -0,0 +1,168 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import {AfterViewInit, Component, ElementRef, forwardRef, Input, OnInit, ViewChild, OnDestroy} from '@angular/core'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import {Observable, of, throwError, Subscription} from 'rxjs'; +import {PageLink} from '@shared/models/page/page-link'; +import {Direction} from '@shared/models/page/sort-order'; +import {filter, map, mergeMap, publishReplay, refCount, startWith, tap, publish} from 'rxjs/operators'; +import {PageData, emptyPageData} from '@shared/models/page/page-data'; +import {DashboardInfo} from '@app/shared/models/dashboard.models'; +import {DashboardId} from '@app/shared/models/id/dashboard-id'; +import {DashboardService} from '@core/http/dashboard.service'; +import {Store} from '@ngrx/store'; +import {AppState} from '@app/core/core.state'; +import {getCurrentAuthUser} from '@app/core/auth/auth.selectors'; +import {Authority} from '@shared/models/authority.enum'; +import {TranslateService} from '@ngx-translate/core'; +import {DeviceService} from '@core/http/device.service'; +import {EntitySubtype, EntityType} from '@app/shared/models/entity-type.models'; +import {BroadcastService} from '@app/core/services/broadcast.service'; +import {coerceBooleanProperty} from '@angular/cdk/coercion'; +import {AssetService} from '@core/http/asset.service'; +import {EntityViewService} from '@core/http/entity-view.service'; +import { RelationTypes } from '@app/shared/models/relation.models'; + +@Component({ + selector: 'tb-relation-type-autocomplete', + templateUrl: './relation-type-autocomplete.component.html', + styleUrls: [], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => RelationTypeAutocompleteComponent), + multi: true + }] +}) +export class RelationTypeAutocompleteComponent implements ControlValueAccessor, OnInit, AfterViewInit, OnDestroy { + + relationTypeFormGroup: FormGroup; + + modelValue: string | null; + + private requiredValue: boolean; + get required(): boolean { + return this.requiredValue; + } + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + @Input() + disabled: boolean; + + @ViewChild('relationTypeInput', {static: true}) relationTypeInput: ElementRef; + + filteredRelationTypes: Observable>; + + private searchText = ''; + + private dirty = false; + + private propagateChange = (v: any) => { }; + + constructor(private store: Store, + private broadcast: BroadcastService, + public translate: TranslateService, + private fb: FormBuilder) { + this.relationTypeFormGroup = this.fb.group({ + relationType: [null, this.required ? [Validators.required] : []] + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + ngOnInit() { + + this.filteredRelationTypes = this.relationTypeFormGroup.get('relationType').valueChanges + .pipe( + tap(value => { + this.updateView(value); + }), + // startWith(''), + map(value => value ? value : ''), + mergeMap(type => this.fetchRelationTypes(type) ) + ); + } + + ngAfterViewInit(): void { + } + + ngOnDestroy(): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.relationTypeFormGroup.disable({emitEvent: false}); + } else { + this.relationTypeFormGroup.enable({emitEvent: false}); + } + } + + writeValue(value: string | null): void { + this.searchText = ''; + this.modelValue = value; + this.relationTypeFormGroup.get('relationType').patchValue(value, {emitEvent: false}); + this.dirty = true; + } + + onFocus() { + if (this.dirty) { + this.relationTypeFormGroup.get('relationType').updateValueAndValidity({onlySelf: true, emitEvent: true}); + this.dirty = false; + } + } + + updateView(value: string | null) { + if (this.modelValue !== value) { + this.modelValue = value; + this.propagateChange(this.modelValue); + } + } + + displayRelationTypeFn(relationType?: string): string | undefined { + return relationType ? relationType : undefined; + } + + fetchRelationTypes(searchText?: string, strictMatch: boolean = false): Observable> { + this.searchText = searchText; + return of(RelationTypes).pipe( + map(relationTypes => relationTypes.filter( relationType => { + if (strictMatch) { + return searchText ? relationType === searchText : false; + } else { + return searchText ? relationType.toUpperCase().startsWith(searchText.toUpperCase()) : true; + } + })) + ); + } + + clear() { + this.relationTypeFormGroup.get('relationType').patchValue(null, {emitEvent: true}); + setTimeout(() => { + this.relationTypeInput.nativeElement.blur(); + this.relationTypeInput.nativeElement.focus(); + }, 0); + } + +} diff --git a/ui-ngx/src/app/shared/models/page/page-link.ts b/ui-ngx/src/app/shared/models/page/page-link.ts index 4be0264466..e4e35a481e 100644 --- a/ui-ngx/src/app/shared/models/page/page-link.ts +++ b/ui-ngx/src/app/shared/models/page/page-link.ts @@ -96,7 +96,7 @@ export class PageLink { pageData.totalElements = pageData.data.length; pageData.totalPages = Math.ceil(pageData.totalElements / this.pageSize); if (this.sortOrder) { - pageData.data = pageData.data.sort(this.sort); + pageData.data = pageData.data.sort((a, b) => this.sort(a, b)); } const startIndex = this.pageSize * this.page; const endIndex = startIndex + this.pageSize; diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index dd102aa6ed..78b3bad20d 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -83,6 +83,9 @@ import {EntitySelectComponent} from './components/entity/entity-select.component import {DatetimeComponent} from '@shared/components/time/datetime.component'; import {EntityKeysListComponent} from './components/entity/entity-keys-list.component'; import {SocialSharePanelComponent} from './components/socialshare-panel.component'; +import { RelationTypeAutocompleteComponent } from '@shared/components/relation/relation-type-autocomplete.component'; +import { EntityListSelectComponent } from './components/entity/entity-list-select.component'; +import { JsonObjectEditComponent } from './components/json-object-edit.component'; @NgModule({ providers: [ @@ -122,7 +125,10 @@ import {SocialSharePanelComponent} from './components/socialshare-panel.componen EntityTypeSelectComponent, EntitySelectComponent, EntityKeysListComponent, + EntityListSelectComponent, + RelationTypeAutocompleteComponent, SocialSharePanelComponent, + JsonObjectEditComponent, NospacePipe, MillisecondsToTimeStringPipe, EnumToArrayPipe, @@ -192,7 +198,10 @@ import {SocialSharePanelComponent} from './components/socialshare-panel.componen EntityTypeSelectComponent, EntitySelectComponent, EntityKeysListComponent, + EntityListSelectComponent, + RelationTypeAutocompleteComponent, SocialSharePanelComponent, + JsonObjectEditComponent, // ValueInputComponent, MatButtonModule, MatCheckboxModule, From 52ab3d6dea044edaf81b8193e2eeee7685b435fe Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 28 Aug 2019 16:02:27 +0300 Subject: [PATCH 024/133] Relation edit dialog improvements. Minor improvements. --- ui-ngx/src/app/core/guards/auth.guard.ts | 13 +---- .../interceptors/global-http-interceptor.ts | 23 ++++---- .../core/notification/notification.actions.ts | 13 +++-- .../core/notification/notification.effects.ts | 9 ++++ .../core/notification/notification.models.ts | 5 ++ .../core/notification/notification.reducer.ts | 5 +- .../src/app/core/services/dialog.service.ts | 28 +++++++++- .../app/core/services/notification.service.ts | 13 ++++- .../audit-log-details-dialog.component.ts | 9 ++-- .../entity/add-entity-dialog.component.ts | 7 ++- .../relation/relation-dialog.component.html | 7 +-- .../relation/relation-dialog.component.ts | 31 ++++++----- ...d-entities-to-customer-dialog.component.ts | 8 ++- .../assign-to-customer-dialog.component.ts | 8 ++- .../pages/asset/asset-tabs.component.html | 4 ++ .../customer/customer-tabs.component.html | 4 ++ .../make-dashboard-public-dialog.component.ts | 7 ++- ...ge-dashboard-customers-dialog.component.ts | 8 ++- .../device-credentials-dialog.component.ts | 8 ++- .../entity-view-tabs.component.html | 4 ++ .../change-password-dialog.component.ts | 7 ++- .../rulechain/rulechain-tabs.component.html | 4 ++ .../pages/tenant/tenant-tabs.component.html | 4 ++ .../user/activation-link-dialog.component.ts | 7 ++- .../pages/user/add-user-dialog.component.ts | 7 ++- .../app/shared/components/dialog.component.ts | 51 ++++++++++++++++++ .../entity/entity-list.component.html | 1 + .../entity/entity-list.component.ts | 26 +++++++--- .../shared/components/fullscreen.directive.ts | 21 ++++---- .../json-object-edit.component.html | 6 ++- .../json-object-edit.component.scss | 23 ++++---- .../components/json-object-edit.component.ts | 52 ++++++++++++++++++- .../app/shared/components/page.component.ts | 4 +- .../app/shared/components/toast.directive.ts | 15 ++++++ 34 files changed, 343 insertions(+), 99 deletions(-) create mode 100644 ui-ngx/src/app/shared/components/dialog.component.ts diff --git a/ui-ngx/src/app/core/guards/auth.guard.ts b/ui-ngx/src/app/core/guards/auth.guard.ts index 4ba3ca7612..f022d6d44b 100644 --- a/ui-ngx/src/app/core/guards/auth.guard.ts +++ b/ui-ngx/src/app/core/guards/auth.guard.ts @@ -82,18 +82,7 @@ export class AuthGuard implements CanActivate, CanActivateChild { } else { const authority = Authority[authState.authUser.authority]; if (data.auth && data.auth.indexOf(authority) === -1) { - this.dialogService.confirm( - this.translate.instant('access.access-forbidden'), - this.translate.instant('access.access-forbidden-text'), - this.translate.instant('action.cancel'), - this.translate.instant('action.sign-in'), - true - ).subscribe((res) => { - if (res) { - this.authService.logout(); - } - } - ); + this.dialogService.forbidden(); return false; } else { return true; diff --git a/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts b/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts index 3b87d3f6ed..7955604908 100644 --- a/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts +++ b/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts @@ -50,6 +50,8 @@ export class GlobalHttpInterceptor implements HttpInterceptor { '/api/auth/token' ]; + private activeRequests = 0; + constructor(private store: Store, private dialogService: DialogService, private translate: TranslateService, @@ -135,7 +137,7 @@ export class GlobalHttpInterceptor implements HttpInterceptor { } } else if (errorResponse.status === 403) { if (!ignoreErrors) { - this.permissionDenied(); + this.dialogService.forbidden(); } } else if (errorResponse.status === 0 || errorResponse.status === -1) { this.showError('Unable to connect'); @@ -241,7 +243,16 @@ export class GlobalHttpInterceptor implements HttpInterceptor { private updateLoadingState(config: InterceptorConfig, isLoading: boolean) { if (!config.ignoreLoading) { - this.store.dispatch(isLoading ? new ActionLoadStart() : new ActionLoadFinish()); + if (isLoading) { + this.activeRequests++; + } else { + this.activeRequests--; + } + if (this.activeRequests === 1) { + this.store.dispatch(new ActionLoadStart()); + } else if (this.activeRequests === 0) { + this.store.dispatch(new ActionLoadFinish()); + } } } @@ -253,14 +264,6 @@ export class GlobalHttpInterceptor implements HttpInterceptor { } } - private permissionDenied() { - this.dialogService.alert( - this.translate.instant('access.permission-denied'), - this.translate.instant('access.permission-denied-text'), - this.translate.instant('action.close') - ); - } - private showError(error: string, timeout: number = 0) { setTimeout(() => { this.store.dispatch(new ActionNotificationShow({message: error, type: 'error'})); diff --git a/ui-ngx/src/app/core/notification/notification.actions.ts b/ui-ngx/src/app/core/notification/notification.actions.ts index aa1fb865af..5fc3402d21 100644 --- a/ui-ngx/src/app/core/notification/notification.actions.ts +++ b/ui-ngx/src/app/core/notification/notification.actions.ts @@ -15,10 +15,11 @@ /// import { Action } from '@ngrx/store'; -import { NotificationMessage } from '@app/core/notification/notification.models'; +import { NotificationMessage, HideNotification } from '@app/core/notification/notification.models'; export enum NotificationActionTypes { - SHOW_NOTIFICATION = '[Notification] Show' + SHOW_NOTIFICATION = '[Notification] Show', + HIDE_NOTIFICATION = '[Notification] Hide' } export class ActionNotificationShow implements Action { @@ -27,5 +28,11 @@ export class ActionNotificationShow implements Action { constructor(readonly notification: NotificationMessage ) {} } +export class ActionNotificationHide implements Action { + readonly type = NotificationActionTypes.HIDE_NOTIFICATION; + + constructor(readonly hideNotification: HideNotification ) {} +} + export type NotificationActions = - | ActionNotificationShow; + | ActionNotificationShow | ActionNotificationHide; diff --git a/ui-ngx/src/app/core/notification/notification.effects.ts b/ui-ngx/src/app/core/notification/notification.effects.ts index dd687cc8be..8e95c40a38 100644 --- a/ui-ngx/src/app/core/notification/notification.effects.ts +++ b/ui-ngx/src/app/core/notification/notification.effects.ts @@ -44,4 +44,13 @@ export class NotificationEffects { }) ); + @Effect({dispatch: false}) + hideNotification = this.actions$.pipe( + ofType( + NotificationActionTypes.HIDE_NOTIFICATION, + ), + map(({ hideNotification }) => { + this.notificationService.hideNotification(hideNotification); + }) + ); } diff --git a/ui-ngx/src/app/core/notification/notification.models.ts b/ui-ngx/src/app/core/notification/notification.models.ts index f0fc4135c3..26df85b101 100644 --- a/ui-ngx/src/app/core/notification/notification.models.ts +++ b/ui-ngx/src/app/core/notification/notification.models.ts @@ -17,6 +17,7 @@ export interface NotificationState { notification: NotificationMessage; + hideNotification: HideNotification; } export declare type NotificationType = 'info' | 'success' | 'error'; @@ -31,3 +32,7 @@ export class NotificationMessage { horizontalPosition?: NotificationHorizontalPosition; verticalPosition?: NotificationVerticalPosition; } + +export class HideNotification { + target?: string; +} diff --git a/ui-ngx/src/app/core/notification/notification.reducer.ts b/ui-ngx/src/app/core/notification/notification.reducer.ts index 81c0387fa2..b4a97511af 100644 --- a/ui-ngx/src/app/core/notification/notification.reducer.ts +++ b/ui-ngx/src/app/core/notification/notification.reducer.ts @@ -18,7 +18,8 @@ import { NotificationState } from './notification.models'; import { NotificationActions, NotificationActionTypes } from './notification.actions'; export const initialState: NotificationState = { - notification: null + notification: null, + hideNotification: null }; export function notificationReducer( @@ -28,6 +29,8 @@ export function notificationReducer( switch (action.type) { case NotificationActionTypes.SHOW_NOTIFICATION: return { ...state, notification: action.notification }; + case NotificationActionTypes.HIDE_NOTIFICATION: + return { ...state, hideNotification: action.hideNotification }; default: return state; } diff --git a/ui-ngx/src/app/core/services/dialog.service.ts b/ui-ngx/src/app/core/services/dialog.service.ts index ada1baf26c..36fe76d693 100644 --- a/ui-ngx/src/app/core/services/dialog.service.ts +++ b/ui-ngx/src/app/core/services/dialog.service.ts @@ -20,7 +20,8 @@ import { MatDialog, MatDialogConfig } from '@angular/material'; import { ConfirmDialogComponent } from '@core/services/dialog/confirm-dialog.component'; import { TranslateService } from '@ngx-translate/core'; import { AlertDialogComponent } from '@core/services/dialog/alert-dialog.component'; -import {TodoDialogComponent} from "@core/services/dialog/todo-dialog.component"; +import { TodoDialogComponent } from '@core/services/dialog/todo-dialog.component'; +import { AuthService } from '@core/auth/auth.service'; @Injectable( { @@ -31,6 +32,7 @@ export class DialogService { constructor( private translate: TranslateService, + private authService: AuthService, public dialog: MatDialog ) { } @@ -68,6 +70,30 @@ export class DialogService { return dialogRef.afterClosed(); } + private permissionDenied() { + this.alert( + this.translate.instant('access.permission-denied'), + this.translate.instant('access.permission-denied-text'), + this.translate.instant('action.close') + ); + } + + forbidden(): Observable { + const observable = this.confirm( + this.translate.instant('access.access-forbidden'), + this.translate.instant('access.access-forbidden-text'), + this.translate.instant('action.cancel'), + this.translate.instant('action.sign-in'), + true + ); + observable.subscribe((res) => { + if (res) { + this.authService.logout(); + } + }); + return observable; + } + todo(): Observable { const dialogConfig: MatDialogConfig = { disableClose: true, diff --git a/ui-ngx/src/app/core/services/notification.service.ts b/ui-ngx/src/app/core/services/notification.service.ts index fed93cf93a..f63cd8adec 100644 --- a/ui-ngx/src/app/core/services/notification.service.ts +++ b/ui-ngx/src/app/core/services/notification.service.ts @@ -15,7 +15,7 @@ /// import { Injectable } from '@angular/core'; -import { NotificationMessage } from '@app/core/notification/notification.models'; +import { HideNotification, NotificationMessage } from '@app/core/notification/notification.models'; import { BehaviorSubject, Observable, Subject } from 'rxjs'; @@ -28,6 +28,8 @@ export class NotificationService { private notificationSubject: Subject = new Subject(); + private hideNotificationSubject: Subject = new Subject(); + constructor( ) { } @@ -36,8 +38,15 @@ export class NotificationService { this.notificationSubject.next(notification); } + hideNotification(hideNotification: HideNotification) { + this.hideNotificationSubject.next(hideNotification); + } + getNotification(): Observable { - return this.notificationSubject; + return this.notificationSubject.asObservable(); } + getHideNotification(): Observable { + return this.hideNotificationSubject.asObservable(); + } } diff --git a/ui-ngx/src/app/modules/home/components/audit-log/audit-log-details-dialog.component.ts b/ui-ngx/src/app/modules/home/components/audit-log/audit-log-details-dialog.component.ts index 4980c61b49..bdd869ca94 100644 --- a/ui-ngx/src/app/modules/home/components/audit-log/audit-log-details-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/audit-log/audit-log-details-dialog.component.ts @@ -30,6 +30,8 @@ import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; import { ActionStatus, AuditLog } from '@shared/models/audit-log.models'; import * as ace from 'ace-builds'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { Router } from '@angular/router'; export interface AuditLogDetailsDialogData { auditLog: AuditLog; @@ -40,7 +42,7 @@ export interface AuditLogDetailsDialogData { templateUrl: './audit-log-details-dialog.component.html', styleUrls: ['./audit-log-details-dialog.component.scss'] }) -export class AuditLogDetailsDialogComponent extends PageComponent implements OnInit { +export class AuditLogDetailsDialogComponent extends DialogComponent implements OnInit { @ViewChild('actionDataEditor', {static: true}) actionDataEditorElmRef: ElementRef; @@ -58,10 +60,11 @@ export class AuditLogDetailsDialogComponent extends PageComponent implements OnI @ViewChild('entityDetailsForm', {static: true}) entityDetailsFormAnchor: TbAnchorComponent; constructor(protected store: Store, + protected router: Router, @Inject(MAT_DIALOG_DATA) public data: AuditLogDetailsDialogData, public dialogRef: MatDialogRef, private renderer: Renderer2) { - super(store); + super(store, router, dialogRef); } ngOnInit(): void { @@ -114,7 +117,7 @@ export class AuditLogDetailsDialogComponent extends PageComponent implements OnI }); newWidth = 8 * maxLineLength + 16; } - newHeight = Math.min(400, newHeight); + // newHeight = Math.min(400, newHeight); this.renderer.setStyle(editorElement, 'minHeight', newHeight.toString() + 'px'); this.renderer.setStyle(editorElement, 'height', newHeight.toString() + 'px'); this.renderer.setStyle(editorElement, 'width', newWidth.toString() + 'px'); diff --git a/ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.ts b/ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.ts index 4364e5ff3a..902d62da03 100644 --- a/ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.ts @@ -27,6 +27,8 @@ import {TbAnchorComponent} from '@shared/components/tb-anchor.component'; import {EntityComponent} from './entity.component'; import {EntityTableConfig} from '@home/models/entity/entities-table-config.models'; import {AddEntityDialogData} from '@home/models/entity/entity-component.models'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { Router } from '@angular/router'; @Component({ selector: 'tb-add-entity-dialog', @@ -34,7 +36,7 @@ import {AddEntityDialogData} from '@home/models/entity/entity-component.models'; providers: [{provide: ErrorStateMatcher, useExisting: AddEntityDialogComponent}], styleUrls: ['./add-entity-dialog.component.scss'] }) -export class AddEntityDialogComponent extends PageComponent implements OnInit, ErrorStateMatcher { +export class AddEntityDialogComponent extends DialogComponent> implements OnInit, ErrorStateMatcher { entityComponent: EntityComponent>; detailsForm: NgForm; @@ -49,11 +51,12 @@ export class AddEntityDialogComponent extends PageComponent implements OnInit, E @ViewChild('entityDetailsForm', {static: true}) entityDetailsFormAnchor: TbAnchorComponent; constructor(protected store: Store, + protected router: Router, @Inject(MAT_DIALOG_DATA) public data: AddEntityDialogData>, public dialogRef: MatDialogRef>, private componentFactoryResolver: ComponentFactoryResolver, @SkipSelf() private errorStateMatcher: ErrorStateMatcher) { - super(store); + super(store, router, dialogRef); } ngOnInit(): void { diff --git a/ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.html b/ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.html index f8b5ff439f..2a9ba7fec4 100644 --- a/ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.html @@ -41,11 +41,12 @@ required="true"> @@ -54,7 +55,7 @@
-
+
diff --git a/ui-ngx/src/app/shared/components/json-object-edit.component.scss b/ui-ngx/src/app/shared/components/json-object-edit.component.scss index 913da86348..c4590b4375 100644 --- a/ui-ngx/src/app/shared/components/json-object-edit.component.scss +++ b/ui-ngx/src/app/shared/components/json-object-edit.component.scss @@ -35,21 +35,20 @@ .fill-height { height: 100%; } +} - .tb-json-object-panel { - height: 100%; - margin-left: 15px; - border: 1px solid #c0c0c0; +.tb-json-object-panel { + height: 100%; + margin-left: 15px; + border: 1px solid #c0c0c0; - #tb-json-input { - width: 100%; - min-width: 200px; - height: 100%; + #tb-json-input { + width: 100%; + min-width: 200px; + height: 100%; - &:not(.fill-height) { - min-height: 200px; - } + &:not(.fill-height) { + min-height: 200px; } } - } diff --git a/ui-ngx/src/app/shared/components/json-object-edit.component.ts b/ui-ngx/src/app/shared/components/json-object-edit.component.ts index 72f64e1bb7..f0789711ab 100644 --- a/ui-ngx/src/app/shared/components/json-object-edit.component.ts +++ b/ui-ngx/src/app/shared/components/json-object-edit.component.ts @@ -26,6 +26,9 @@ import { import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormControl, Validator, NG_VALIDATORS } from '@angular/forms'; import * as ace from 'ace-builds'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { ActionNotificationHide, ActionNotificationShow } from '@core/notification/notification.actions'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; @Component({ selector: 'tb-json-object-edit', @@ -83,9 +86,14 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va objectValid: boolean; + validationError: string; + + errorShowed = false; + private propagateChange = null; - constructor() { + constructor(public elementRef: ElementRef, + protected store: Store) { } ngOnInit(): void { @@ -109,6 +117,7 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va this.jsonEditor.session.setUseWrapMode(false); this.jsonEditor.setValue(this.contentValue ? this.contentValue : '', -1); this.jsonEditor.on('change', () => { + this.cleanupJsonErrors(); this.updateView(); }); } @@ -132,6 +141,33 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va }; } + validateOnSubmit(): void { + if (!this.readonly) { + this.cleanupJsonErrors(); + if (!this.objectValid) { + this.store.dispatch(new ActionNotificationShow( + { + message: this.validationError, + type: 'error', + target: 'jsonObjectEditor', + verticalPosition: 'bottom', + horizontalPosition: 'left' + })); + this.errorShowed = true; + } + } + } + + cleanupJsonErrors(): void { + if (this.errorShowed) { + this.store.dispatch(new ActionNotificationHide( + { + target: 'jsonObjectEditor' + })); + this.errorShowed = false; + } + } + writeValue(value: any): void { this.modelValue = value; this.contentValue = ''; @@ -142,6 +178,7 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va this.objectValid = true; } else { this.objectValid = !this.required; + this.validationError = 'Json object is required.'; } } catch (e) { // @@ -161,9 +198,20 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va try { data = JSON.parse(this.contentValue); this.objectValid = true; - } catch (ex) {} + this.validationError = ''; + } catch (ex) { + let errorInfo = 'Error:'; + if (ex.name) { + errorInfo += ' ' + ex.name + ':'; + } + if (ex.message) { + errorInfo += ' ' + ex.message; + } + this.validationError = errorInfo; + } } else { this.objectValid = !this.required; + this.validationError = this.required ? 'Json object is required.' : ''; } this.propagateChange(data); } diff --git a/ui-ngx/src/app/shared/components/page.component.ts b/ui-ngx/src/app/shared/components/page.component.ts index 3ed33e3e5f..64922008c4 100644 --- a/ui-ngx/src/app/shared/components/page.component.ts +++ b/ui-ngx/src/app/shared/components/page.component.ts @@ -19,7 +19,7 @@ import { select, Store } from '@ngrx/store'; import { AppState } from '../../core/core.state'; import { Observable, Subscription } from 'rxjs'; import { selectIsLoading } from '../../core/interceptors/load.selectors'; -import { delay } from 'rxjs/operators'; +import { delay, share } from 'rxjs/operators'; import { AbstractControl } from '@angular/forms'; export abstract class PageComponent implements OnDestroy { @@ -29,7 +29,7 @@ export abstract class PageComponent implements OnDestroy { disabledOnLoadFormControls: Array = []; protected constructor(protected store: Store) { - this.isLoading$ = this.store.pipe(delay(0), select(selectIsLoading), delay(100)); + this.isLoading$ = this.store.pipe(delay(0), select(selectIsLoading), share()); } protected registerDisableOnLoadFormControl(control: AbstractControl) { diff --git a/ui-ngx/src/app/shared/components/toast.directive.ts b/ui-ngx/src/app/shared/components/toast.directive.ts index 1d6c9ed24d..de92352874 100644 --- a/ui-ngx/src/app/shared/components/toast.directive.ts +++ b/ui-ngx/src/app/shared/components/toast.directive.ts @@ -45,6 +45,7 @@ export class ToastDirective implements AfterViewInit, OnDestroy { toastTarget = 'root'; private notificationSubscription: Subscription = null; + private hideNotificationSubscription: Subscription = null; constructor(public elementRef: ElementRef, public viewContainerRef: ViewContainerRef, @@ -78,12 +79,26 @@ export class ToastDirective implements AfterViewInit, OnDestroy { } } ); + + this.hideNotificationSubscription = this.notificationService.getHideNotification().subscribe( + (hideNotification) => { + if (hideNotification) { + const target = hideNotification.target || 'root'; + if (this.toastTarget === target) { + this.snackBar.dismiss(); + } + } + } + ); } ngOnDestroy(): void { if (this.notificationSubscription) { this.notificationSubscription.unsubscribe(); } + if (this.hideNotificationSubscription) { + this.hideNotificationSubscription.unsubscribe(); + } } } From e91fd302d6c6a9b598f17b18f1d470f172099758 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 29 Aug 2019 12:08:49 +0300 Subject: [PATCH 025/133] Implement alarms table --- .../server/controller/AlarmController.java | 7 +- .../server/common/data/alarm/AlarmInfo.java | 5 + .../server/dao/alarm/AlarmDao.java | 2 +- .../server/dao/alarm/BaseAlarmService.java | 41 ++--- .../dao/model/sql/AbstractAlarmEntity.java | 157 ++++++++++++++++++ .../dao/model/sql/AbstractDeviceEntity.java | 2 +- .../server/dao/model/sql/AlarmEntity.java | 107 +----------- .../server/dao/model/sql/AlarmInfoEntity.java | 40 +++++ .../server/dao/sql/alarm/AlarmRepository.java | 24 +++ .../server/dao/sql/alarm/JpaAlarmDao.java | 32 ++-- ui-ngx/src/app/core/http/alarm.service.ts | 78 +++++++++ .../alarm/alarm-details-dialog.component.html | 123 ++++++++++++++ .../alarm/alarm-details-dialog.component.ts | 155 +++++++++++++++++ .../components/alarm/alarm-table-config.ts | 137 +++++++++++++++ .../alarm/alarm-table-header.component.html | 27 +++ .../alarm/alarm-table-header.component.scss | 35 ++++ .../alarm/alarm-table-header.component.ts | 47 ++++++ .../alarm/alarm-table.component.html | 18 ++ .../alarm/alarm-table.component.scss | 22 +++ .../components/alarm/alarm-table.component.ts | 90 ++++++++++ .../home/components/home-components.module.ts | 16 +- .../pages/asset/asset-tabs.component.html | 4 + .../customer/customer-tabs.component.html | 4 + .../dashboard/dashboard-form.component.html | 2 +- .../pages/device/device-tabs.component.html | 4 + .../entity-view-tabs.component.html | 4 + .../rulechain/rulechain-tabs.component.html | 4 + .../pages/tenant/tenant-tabs.component.html | 4 + .../app/shared/components/dialog.component.ts | 1 - ui-ngx/src/app/shared/models/alarm.models.ts | 87 ++++++++++ .../app/shared/models/entity-type.models.ts | 13 ++ 31 files changed, 1141 insertions(+), 151 deletions(-) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java create mode 100644 ui-ngx/src/app/core/http/alarm.service.ts create mode 100644 ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts create mode 100644 ui-ngx/src/app/modules/home/components/alarm/alarm-table-header.component.html create mode 100644 ui-ngx/src/app/modules/home/components/alarm/alarm-table-header.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/alarm/alarm-table-header.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.html create mode 100644 ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.ts diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java index 620e7dc104..89896d38ef 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java @@ -150,9 +150,11 @@ public class AlarmController extends BaseController { @RequestParam(required = false) String status, @RequestParam int pageSize, @RequestParam int page, + @RequestParam(required = false) String textSearch, + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder, @RequestParam(required = false) Long startTime, @RequestParam(required = false) Long endTime, - @RequestParam(required = false, defaultValue = "false") boolean ascOrder, @RequestParam(required = false) Boolean fetchOriginator ) throws ThingsboardException { checkParameter("EntityId", strEntityId); @@ -165,9 +167,8 @@ public class AlarmController extends BaseController { "and 'status' can't be specified at the same time!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); } checkEntityId(entityId, Operation.READ); + TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); try { - TimePageLink pageLink = createTimePageLink(pageSize, page, "", - "id", ascOrder ? "asc" : "desc", startTime, endTime); return checkNotNull(alarmService.findAlarms(getCurrentUser().getTenantId(), new AlarmQuery(entityId, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator)).get()); } catch (Exception e) { throw handleException(e); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java index 48bcd2c8d3..76f23ea910 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java @@ -29,6 +29,11 @@ public class AlarmInfo extends Alarm { super(alarm); } + public AlarmInfo(Alarm alarm, String originatorName) { + super(alarm); + this.originatorName = originatorName; + } + public String getOriginatorName() { return originatorName; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java index c9b996cba7..f8036ec763 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java @@ -40,5 +40,5 @@ public interface AlarmDao extends Dao { Alarm save(TenantId tenantId, Alarm alarm); - ListenableFuture> findAlarms(TenantId tenantId, AlarmQuery query); + PageData findAlarms(TenantId tenantId, AlarmQuery query); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java index 83ef00c6af..8fdcf39e4b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java @@ -261,27 +261,25 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ @Override public ListenableFuture> findAlarms(TenantId tenantId, AlarmQuery query) { - ListenableFuture> alarms = alarmDao.findAlarms(tenantId, query); + PageData alarms = alarmDao.findAlarms(tenantId, query); if (query.getFetchOriginator() != null && query.getFetchOriginator().booleanValue()) { - alarms = Futures.transformAsync(alarms, input -> { - List> alarmFutures = new ArrayList<>(input.getData().size()); - for (AlarmInfo alarmInfo : input.getData()) { - alarmFutures.add(Futures.transform( - entityService.fetchEntityNameAsync(tenantId, alarmInfo.getOriginator()), originatorName -> { - if (originatorName == null) { - originatorName = "Deleted"; - } - alarmInfo.setOriginatorName(originatorName); - return alarmInfo; + List> alarmFutures = new ArrayList<>(alarms.getData().size()); + for (AlarmInfo alarmInfo : alarms.getData()) { + alarmFutures.add(Futures.transform( + entityService.fetchEntityNameAsync(tenantId, alarmInfo.getOriginator()), originatorName -> { + if (originatorName == null) { + originatorName = "Deleted"; } - )); - } - return Futures.transform(Futures.successfulAsList(alarmFutures), alarmInfos -> { - return new PageData(alarmInfos, input.getTotalPages(), input.getTotalElements(), input.hasNext()); - }); + alarmInfo.setOriginatorName(originatorName); + return alarmInfo; + } + )); + } + return Futures.transform(Futures.successfulAsList(alarmFutures), alarmInfos -> { + return new PageData(alarmInfos, alarms.getTotalPages(), alarms.getTotalElements(), alarms.hasNext()); }); } - return alarms; + return Futures.immediateFuture(alarms); } @Override @@ -293,14 +291,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ AlarmQuery query; while (hasNext && AlarmSeverity.CRITICAL != highestSeverity) { query = new AlarmQuery(entityId, nextPageLink, alarmSearchStatus, alarmStatus, false); - PageData alarms; - try { - alarms = alarmDao.findAlarms(tenantId, query).get(); - } catch (ExecutionException | InterruptedException e) { - log.warn("Failed to find highest alarm severity. EntityId: [{}], AlarmSearchStatus: [{}], AlarmStatus: [{}]", - entityId, alarmSearchStatus, alarmStatus); - throw new RuntimeException(e); - } + PageData alarms = alarmDao.findAlarms(tenantId, query); if (alarms.hasNext()) { nextPageLink = nextPageLink.nextPageLink(); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java new file mode 100644 index 0000000000..7f281c777b --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java @@ -0,0 +1,157 @@ +/** + * Copyright © 2016-2019 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.model.sql; + +import com.datastax.driver.core.utils.UUIDs; +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.hibernate.annotations.Type; +import org.hibernate.annotations.TypeDef; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.UUIDConverter; +import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmId; +import org.thingsboard.server.common.data.alarm.AlarmSeverity; +import org.thingsboard.server.common.data.alarm.AlarmStatus; +import org.thingsboard.server.common.data.id.EntityIdFactory; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.model.BaseEntity; +import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.util.mapping.JsonStringType; + +import javax.persistence.*; + +import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ACK_TS_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.ALARM_CLEAR_TS_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.ALARM_COLUMN_FAMILY_NAME; +import static org.thingsboard.server.dao.model.ModelConstants.ALARM_END_TS_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_ID_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_TYPE_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.ALARM_PROPAGATE_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.ALARM_SEVERITY_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.ALARM_START_TS_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.ALARM_STATUS_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.ALARM_TENANT_ID_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.ALARM_TYPE_PROPERTY; + +@Data +@EqualsAndHashCode(callSuper = true) +@TypeDef(name = "json", typeClass = JsonStringType.class) +@MappedSuperclass +public abstract class AbstractAlarmEntity extends BaseSqlEntity implements BaseEntity { + + @Column(name = ALARM_TENANT_ID_PROPERTY) + private String tenantId; + + @Column(name = ALARM_ORIGINATOR_ID_PROPERTY) + private String originatorId; + + @Column(name = ALARM_ORIGINATOR_TYPE_PROPERTY) + private EntityType originatorType; + + @Column(name = ALARM_TYPE_PROPERTY) + private String type; + + @Enumerated(EnumType.STRING) + @Column(name = ALARM_SEVERITY_PROPERTY) + private AlarmSeverity severity; + + @Enumerated(EnumType.STRING) + @Column(name = ALARM_STATUS_PROPERTY) + private AlarmStatus status; + + @Column(name = ALARM_START_TS_PROPERTY) + private Long startTs; + + @Column(name = ALARM_END_TS_PROPERTY) + private Long endTs; + + @Column(name = ALARM_ACK_TS_PROPERTY) + private Long ackTs; + + @Column(name = ALARM_CLEAR_TS_PROPERTY) + private Long clearTs; + + @Type(type = "json") + @Column(name = ModelConstants.ASSET_ADDITIONAL_INFO_PROPERTY) + private JsonNode details; + + @Column(name = ALARM_PROPAGATE_PROPERTY) + private Boolean propagate; + + public AbstractAlarmEntity() { + super(); + } + + public AbstractAlarmEntity(Alarm alarm) { + if (alarm.getId() != null) { + this.setId(alarm.getId().getId()); + } + if (alarm.getTenantId() != null) { + this.tenantId = UUIDConverter.fromTimeUUID(alarm.getTenantId().getId()); + } + this.type = alarm.getType(); + this.originatorId = UUIDConverter.fromTimeUUID(alarm.getOriginator().getId()); + this.originatorType = alarm.getOriginator().getEntityType(); + this.type = alarm.getType(); + this.severity = alarm.getSeverity(); + this.status = alarm.getStatus(); + this.propagate = alarm.isPropagate(); + this.startTs = alarm.getStartTs(); + this.endTs = alarm.getEndTs(); + this.ackTs = alarm.getAckTs(); + this.clearTs = alarm.getClearTs(); + this.details = alarm.getDetails(); + } + + public AbstractAlarmEntity(AlarmEntity alarmEntity) { + this.setId(alarmEntity.getId()); + this.tenantId = alarmEntity.getTenantId(); + this.type = alarmEntity.getType(); + this.originatorId = alarmEntity.getOriginatorId(); + this.originatorType = alarmEntity.getOriginatorType(); + this.type = alarmEntity.getType(); + this.severity = alarmEntity.getSeverity(); + this.status = alarmEntity.getStatus(); + this.propagate = alarmEntity.getPropagate(); + this.startTs = alarmEntity.getStartTs(); + this.endTs = alarmEntity.getEndTs(); + this.ackTs = alarmEntity.getAckTs(); + this.clearTs = alarmEntity.getClearTs(); + this.details = alarmEntity.getDetails(); + } + + protected Alarm toAlarm() { + Alarm alarm = new Alarm(new AlarmId(UUIDConverter.fromString(id))); + alarm.setCreatedTime(UUIDs.unixTimestamp(UUIDConverter.fromString(id))); + if (tenantId != null) { + alarm.setTenantId(new TenantId(UUIDConverter.fromString(tenantId))); + } + alarm.setOriginator(EntityIdFactory.getByTypeAndUuid(originatorType, UUIDConverter.fromString(originatorId))); + alarm.setType(type); + alarm.setSeverity(severity); + alarm.setStatus(status); + alarm.setPropagate(propagate); + alarm.setStartTs(startTs); + alarm.setEndTs(endTs); + alarm.setAckTs(ackTs); + alarm.setClearTs(clearTs); + alarm.setDetails(details); + return alarm; + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java index b023f127de..4ee62a1413 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java @@ -83,7 +83,7 @@ public abstract class AbstractDeviceEntity extends BaseSqlEnti } public AbstractDeviceEntity(DeviceEntity deviceEntity) { - this.setId(deviceEntity.getId());; + this.setId(deviceEntity.getId()); this.tenantId = deviceEntity.getTenantId(); this.customerId = deviceEntity.getCustomerId(); this.type = deviceEntity.getType(); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmEntity.java index f6cfb7189c..a0655cd507 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmEntity.java @@ -15,133 +15,34 @@ */ package org.thingsboard.server.dao.model.sql; -import com.datastax.driver.core.utils.UUIDs; -import com.fasterxml.jackson.databind.JsonNode; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; -import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.UUIDConverter; import org.thingsboard.server.common.data.alarm.Alarm; -import org.thingsboard.server.common.data.alarm.AlarmId; -import org.thingsboard.server.common.data.alarm.AlarmSeverity; -import org.thingsboard.server.common.data.alarm.AlarmStatus; -import org.thingsboard.server.common.data.id.EntityIdFactory; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.dao.model.BaseEntity; -import org.thingsboard.server.dao.model.BaseSqlEntity; -import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.util.mapping.JsonStringType; -import javax.persistence.Column; import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; import javax.persistence.Table; -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ACK_TS_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_CLEAR_TS_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_COLUMN_FAMILY_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_END_TS_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_TYPE_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_PROPAGATE_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_SEVERITY_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_START_TS_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_STATUS_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_TENANT_ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_TYPE_PROPERTY; @Data @EqualsAndHashCode(callSuper = true) @Entity @TypeDef(name = "json", typeClass = JsonStringType.class) @Table(name = ALARM_COLUMN_FAMILY_NAME) -public final class AlarmEntity extends BaseSqlEntity implements BaseEntity { - - @Column(name = ALARM_TENANT_ID_PROPERTY) - private String tenantId; - - @Column(name = ALARM_ORIGINATOR_ID_PROPERTY) - private String originatorId; - - @Column(name = ALARM_ORIGINATOR_TYPE_PROPERTY) - private EntityType originatorType; - - @Column(name = ALARM_TYPE_PROPERTY) - private String type; - - @Enumerated(EnumType.STRING) - @Column(name = ALARM_SEVERITY_PROPERTY) - private AlarmSeverity severity; - - @Enumerated(EnumType.STRING) - @Column(name = ALARM_STATUS_PROPERTY) - private AlarmStatus status; - - @Column(name = ALARM_START_TS_PROPERTY) - private Long startTs; - - @Column(name = ALARM_END_TS_PROPERTY) - private Long endTs; - - @Column(name = ALARM_ACK_TS_PROPERTY) - private Long ackTs; - - @Column(name = ALARM_CLEAR_TS_PROPERTY) - private Long clearTs; - - @Type(type = "json") - @Column(name = ModelConstants.ASSET_ADDITIONAL_INFO_PROPERTY) - private JsonNode details; - - @Column(name = ALARM_PROPAGATE_PROPERTY) - private Boolean propagate; +public final class AlarmEntity extends AbstractAlarmEntity { public AlarmEntity() { super(); } public AlarmEntity(Alarm alarm) { - if (alarm.getId() != null) { - this.setId(alarm.getId().getId()); - } - if (alarm.getTenantId() != null) { - this.tenantId = UUIDConverter.fromTimeUUID(alarm.getTenantId().getId()); - } - this.type = alarm.getType(); - this.originatorId = UUIDConverter.fromTimeUUID(alarm.getOriginator().getId()); - this.originatorType = alarm.getOriginator().getEntityType(); - this.type = alarm.getType(); - this.severity = alarm.getSeverity(); - this.status = alarm.getStatus(); - this.propagate = alarm.isPropagate(); - this.startTs = alarm.getStartTs(); - this.endTs = alarm.getEndTs(); - this.ackTs = alarm.getAckTs(); - this.clearTs = alarm.getClearTs(); - this.details = alarm.getDetails(); + super(alarm); } @Override public Alarm toData() { - Alarm alarm = new Alarm(new AlarmId(UUIDConverter.fromString(id))); - alarm.setCreatedTime(UUIDs.unixTimestamp(UUIDConverter.fromString(id))); - if (tenantId != null) { - alarm.setTenantId(new TenantId(UUIDConverter.fromString(tenantId))); - } - alarm.setOriginator(EntityIdFactory.getByTypeAndUuid(originatorType, UUIDConverter.fromString(originatorId))); - alarm.setType(type); - alarm.setSeverity(severity); - alarm.setStatus(status); - alarm.setPropagate(propagate); - alarm.setStartTs(startTs); - alarm.setEndTs(endTs); - alarm.setAckTs(ackTs); - alarm.setClearTs(clearTs); - alarm.setDetails(details); - return alarm; + return super.toAlarm(); } - -} \ No newline at end of file +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java new file mode 100644 index 0000000000..0802121416 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java @@ -0,0 +1,40 @@ +/** + * Copyright © 2016-2019 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.model.sql; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.alarm.AlarmInfo; + +@Data +@EqualsAndHashCode(callSuper = true) +public class AlarmInfoEntity extends AbstractAlarmEntity { + + private String originatorName; + + public AlarmInfoEntity() { + super(); + } + + public AlarmInfoEntity(AlarmEntity alarmEntity) { + super(alarmEntity); + } + + @Override + public AlarmInfo toData() { + return new AlarmInfo(super.toAlarm(), this.originatorName); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java index a3d5b45a39..9f843ad069 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java @@ -15,12 +15,14 @@ */ package org.thingsboard.server.dao.sql.alarm; +import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.dao.model.sql.AlarmEntity; +import org.thingsboard.server.dao.model.sql.AlarmInfoEntity; import org.thingsboard.server.dao.util.SqlDao; import java.util.List; @@ -38,4 +40,26 @@ public interface AlarmRepository extends CrudRepository { @Param("entityType") EntityType entityType, @Param("alarmType") String alarmType, Pageable pageable); + + @Query("SELECT new org.thingsboard.server.dao.model.sql.AlarmInfoEntity(a) FROM AlarmEntity a, " + + "RelationEntity re " + + "WHERE a.tenantId = :tenantId " + + "AND a.id = re.toId AND re.toType = 'ALARM' " + + "AND re.relationTypeGroup = 'ALARM' " + + "AND re.relationType = :relationType " + + "AND re.fromId = :affectedEntityId " + + "AND re.fromType = :affectedEntityType " + + "AND (:startId IS NULL OR a.id >= :startId) " + + "AND (:endId IS NULL OR a.id <= :endId) " + + "AND (LOWER(a.type) LIKE LOWER(CONCAT(:searchText, '%'))" + + "OR LOWER(a.severity) LIKE LOWER(CONCAT(:searchText, '%'))" + + "OR LOWER(a.status) LIKE LOWER(CONCAT(:searchText, '%')))") + Page findAlarms(@Param("tenantId") String tenantId, + @Param("affectedEntityId") String affectedEntityId, + @Param("affectedEntityType") String affectedEntityType, + @Param("relationType") String relationType, + @Param("startId") String startId, + @Param("endId") String endId, + @Param("searchText") String searchText, + Pageable pageable); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java index 5f4259dd8f..5ec7ab364a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java @@ -43,8 +43,13 @@ import org.thingsboard.server.dao.util.SqlDao; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.UUID; +import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID; +import static org.thingsboard.server.dao.DaoUtil.endTimeToId; +import static org.thingsboard.server.dao.DaoUtil.startTimeToId; + /** * Created by Valerii Sosliuk on 5/19/2017. */ @@ -93,7 +98,7 @@ public class JpaAlarmDao extends JpaAbstractDao implements A } @Override - public ListenableFuture> findAlarms(TenantId tenantId, AlarmQuery query) { + public PageData findAlarms(TenantId tenantId, AlarmQuery query) { log.trace("Try to find alarms by entity [{}], status [{}] and pageLink [{}]", query.getAffectedEntityId(), query.getStatus(), query.getPageLink()); EntityId affectedEntity = query.getAffectedEntityId(); String searchStatusName; @@ -105,17 +110,18 @@ public class JpaAlarmDao extends JpaAbstractDao implements A searchStatusName = query.getStatus().name(); } String relationType = BaseAlarmService.ALARM_RELATION_PREFIX + searchStatusName; - ListenableFuture> relations = relationDao.findRelations(tenantId, affectedEntity, relationType, RelationTypeGroup.ALARM, EntityType.ALARM, query.getPageLink()); - return Futures.transformAsync(relations, input -> { - List> alarmFutures = new ArrayList<>(input.getData().size()); - for (EntityRelation relation : input.getData()) { - alarmFutures.add(Futures.transform( - findAlarmByIdAsync(tenantId, relation.getTo().getId()), - AlarmInfo::new)); - } - return Futures.transform(Futures.successfulAsList(alarmFutures), alarmInfos -> { - return new PageData(alarmInfos, input.getTotalPages(), input.getTotalElements(), input.hasNext()); - }); - }); + + return DaoUtil.toPageData( + alarmRepository.findAlarms( + fromTimeUUID(tenantId.getId()), + fromTimeUUID(affectedEntity.getId()), + affectedEntity.getEntityType().name(), + relationType, + startTimeToId(query.getPageLink().getStartTime()), + endTimeToId(query.getPageLink().getEndTime()), + Objects.toString(query.getPageLink().getTextSearch(), ""), + DaoUtil.toPageable(query.getPageLink()) + ) + ); } } diff --git a/ui-ngx/src/app/core/http/alarm.service.ts b/ui-ngx/src/app/core/http/alarm.service.ts new file mode 100644 index 0000000000..bdc2ed7950 --- /dev/null +++ b/ui-ngx/src/app/core/http/alarm.service.ts @@ -0,0 +1,78 @@ +/// +/// Copyright © 2016-2019 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 { Injectable } from '@angular/core'; +import { defaultHttpOptions } from './http-utils'; +import { Observable } from 'rxjs/index'; +import { HttpClient } from '@angular/common/http'; +import { PageData } from '@shared/models/page/page-data'; +import { EntityId } from '@shared/models/id/entity-id'; +import { + Alarm, + AlarmInfo, + AlarmQuery, + AlarmSearchStatus, + AlarmSeverity, + AlarmStatus +} from '@shared/models/alarm.models'; + +@Injectable({ + providedIn: 'root' +}) +export class AlarmService { + + constructor( + private http: HttpClient + ) { } + + public getAlarm(alarmId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.get(`/api/alarm/${alarmId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public getAlarmInfo(alarmId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.get(`/api/alarm/info/${alarmId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public saveAlarm(alarm: Alarm, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.post('/api/alarm', alarm, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public ackAlarm(alarmId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.post(`/api/alarm/${alarmId}/ack`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public clearAlarm(alarmId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.post(`/api/alarm/${alarmId}/clear`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public getAlarms(query: AlarmQuery, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + return this.http.get>(`/api/alarm${query.toQuery()}`, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public getHighestAlarmSeverity(entityId: EntityId, alarmSearchStatus: AlarmSearchStatus, alarmStatus: AlarmStatus, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + let url = `/api/alarm/highestSeverity/${entityId.entityType}/${entityId.entityType}`; + if (alarmSearchStatus) { + url += `?searchStatus=${alarmSearchStatus}`; + } else if (alarmStatus) { + url += `?status=${alarmStatus}`; + } + return this.http.get(url, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } +} diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.html b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.html new file mode 100644 index 0000000000..7d0f82b39c --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.html @@ -0,0 +1,123 @@ + +
+ +

{{ 'alarm.alarm-details' | translate }}

+ + +
+ + +
+
+
+
+ + alarm.created-time + + + + alarm.originator + + +
+
+ + alarm.start-time + + + + alarm.end-time + + + +
+
+ + alarm.ack-time + + + + alarm.clear-time + + + +
+
+ + alarm.type + + + + alarm.severity + + + + alarm.status + + +
+ + +
+
+
+
+ + +
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts new file mode 100644 index 0000000000..8d8efcd472 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts @@ -0,0 +1,155 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Inject, OnInit } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { Observable, Subject } from 'rxjs'; +import { Router } from '@angular/router'; +import { DialogComponent } from '@app/shared/components/dialog.component'; +import { + AlarmInfo, + alarmSeverityColors, + alarmSeverityTranslations, + AlarmStatus, + alarmStatusTranslations +} from '@app/shared/models/alarm.models'; +import { AlarmService } from '@core/http/alarm.service'; +import { share, tap } from 'rxjs/operators'; +import { DatePipe } from '@angular/common'; +import { TranslateService } from '@ngx-translate/core'; + +export interface AlarmDetailsDialogData { + alarmId: string; + allowAcknowledgment: boolean; + allowClear: boolean; + displayDetails: boolean; +} + +@Component({ + selector: 'tb-alarm-details-dialog', + templateUrl: './alarm-details-dialog.component.html', + styleUrls: [] +}) +export class AlarmDetailsDialogComponent extends DialogComponent implements OnInit { + + alarmFormGroup: FormGroup; + + allowAcknowledgment: boolean; + allowClear: boolean; + displayDetails: boolean; + + loadAlarmSubject = new Subject(); + alarm$: Observable = this.loadAlarmSubject.asObservable().pipe( + tap(alarm => this.loadAlarmFields(alarm)), + share() + ); + + alarmSeverityColorsMap = alarmSeverityColors; + alarmStatuses = AlarmStatus; + + alarmUpdated = false; + + constructor(protected store: Store, + protected router: Router, + private datePipe: DatePipe, + private translate: TranslateService, + @Inject(MAT_DIALOG_DATA) public data: AlarmDetailsDialogData, + private alarmService: AlarmService, + public dialogRef: MatDialogRef, + public fb: FormBuilder) { + super(store, router, dialogRef); + + this.allowAcknowledgment = data.allowAcknowledgment; + this.allowClear = data.allowClear; + this.displayDetails = data.displayDetails; + + this.alarmFormGroup = this.fb.group( + { + createdTime: [''], + originatorName: [''], + startTime: [''], + endTime: [''], + ackTime: [''], + clearTime: [''], + type: [''], + alarmSeverity: [''], + alarmStatus: [''], + alarmDetails: [null] + } + ); + + this.loadAlarm(); + + } + + loadAlarm() { + this.alarmService.getAlarmInfo(this.data.alarmId).subscribe( + alarm => this.loadAlarmSubject.next(alarm) + ); + } + + loadAlarmFields(alarm: AlarmInfo) { + this.alarmFormGroup.get('createdTime') + .patchValue(this.datePipe.transform(alarm.createdTime, 'yyyy-MM-dd HH:mm:ss')); + this.alarmFormGroup.get('originatorName') + .patchValue(alarm.originatorName); + if (alarm.startTs) { + this.alarmFormGroup.get('startTime') + .patchValue(this.datePipe.transform(alarm.startTs, 'yyyy-MM-dd HH:mm:ss')); + } + if (alarm.endTs) { + this.alarmFormGroup.get('endTime') + .patchValue(this.datePipe.transform(alarm.endTs, 'yyyy-MM-dd HH:mm:ss')); + } + if (alarm.ackTs) { + this.alarmFormGroup.get('ackTime') + .patchValue(this.datePipe.transform(alarm.ackTs, 'yyyy-MM-dd HH:mm:ss')); + } + if (alarm.clearTs) { + this.alarmFormGroup.get('clearTime') + .patchValue(this.datePipe.transform(alarm.clearTs, 'yyyy-MM-dd HH:mm:ss')); + } + this.alarmFormGroup.get('type').patchValue(alarm.type); + this.alarmFormGroup.get('alarmSeverity') + .patchValue(this.translate.instant(alarmSeverityTranslations.get(alarm.severity))); + this.alarmFormGroup.get('alarmStatus') + .patchValue(this.translate.instant(alarmStatusTranslations.get(alarm.status))); + this.alarmFormGroup.get('alarmDetails').patchValue(alarm.details); + } + + ngOnInit(): void { + } + + close(): void { + this.dialogRef.close(this.alarmUpdated); + } + + acknowledge(): void { + this.alarmService.ackAlarm(this.data.alarmId).subscribe( + () => { this.alarmUpdated = true; this.loadAlarm(); } + ); + } + + clear(): void { + this.alarmService.clearAlarm(this.data.alarmId).subscribe( + () => { this.alarmUpdated = true; this.loadAlarm(); } + ); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts new file mode 100644 index 0000000000..632cd8ecd3 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts @@ -0,0 +1,137 @@ +/// +/// Copyright © 2016-2019 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 { + DateEntityTableColumn, + EntityTableColumn, + EntityTableConfig +} from '@home/models/entity/entities-table-config.models'; +import { EntityType, EntityTypeResource, entityTypeTranslations } from '@shared/models/entity-type.models'; +import { TranslateService } from '@ngx-translate/core'; +import { DatePipe } from '@angular/common'; +import { Direction } from '@shared/models/page/sort-order'; +import { MatDialog } from '@angular/material'; +import { TimePageLink } from '@shared/models/page/page-link'; +import { Observable } from 'rxjs'; +import { PageData } from '@shared/models/page/page-data'; +import { EntityId } from '@shared/models/id/entity-id'; +import { + AlarmInfo, + AlarmQuery, + AlarmSearchStatus, + alarmSeverityColors, + alarmSeverityTranslations, + alarmStatusTranslations +} from '@app/shared/models/alarm.models'; +import { AlarmService } from '@app/core/http/alarm.service'; +import { DialogService } from '@core/services/dialog.service'; +import { AlarmTableHeaderComponent } from '@home/components/alarm/alarm-table-header.component'; +import { + AuditLogDetailsDialogComponent, + AuditLogDetailsDialogData +} from '@home/components/audit-log/audit-log-details-dialog.component'; +import { + AlarmDetailsDialogComponent, + AlarmDetailsDialogData +} from '@home/components/alarm/alarm-details-dialog.component'; + +export class AlarmTableConfig extends EntityTableConfig { + + searchStatus: AlarmSearchStatus; + + constructor(private alarmService: AlarmService, + private dialogService: DialogService, + private translate: TranslateService, + private datePipe: DatePipe, + private dialog: MatDialog, + public entityId: EntityId = null, + private defaultSearchStatus: AlarmSearchStatus = AlarmSearchStatus.ANY) { + super(); + this.loadDataOnInit = false; + this.tableTitle = ''; + this.useTimePageLink = true; + this.detailsPanelEnabled = false; + this.selectionEnabled = false; + this.searchEnabled = true; + this.addEnabled = false; + this.entitiesDeleteEnabled = false; + this.actionsColumnTitle = 'alarm.details'; + this.entityType = EntityType.ALARM; + this.entityTranslations = entityTypeTranslations.get(EntityType.ALARM); + this.entityResources = { + } as EntityTypeResource; + this.searchStatus = defaultSearchStatus; + + this.headerComponent = AlarmTableHeaderComponent; + + this.entitiesFetchFunction = pageLink => this.fetchAlarms(pageLink); + + this.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC}; + + this.columns.push( + new DateEntityTableColumn('createdTime', 'alarm.created-time', this.datePipe, '150px')); + this.columns.push( + new EntityTableColumn('originatorName', 'alarm.originator', '100%', + (entity) => entity.originatorName, entity => ({}), false)); + this.columns.push( + new EntityTableColumn('type', 'alarm.type', '100%')); + this.columns.push( + new EntityTableColumn('severity', 'alarm.severity', '100%', + (entity) => this.translate.instant(alarmSeverityTranslations.get(entity.severity)), + entity => ({ + fontWeight: 'bold', + color: alarmSeverityColors.get(entity.severity) + }))); + this.columns.push( + new EntityTableColumn('status', 'alarm.status', '100%', + (entity) => this.translate.instant(alarmStatusTranslations.get(entity.status)))); + + this.cellActionDescriptors.push( + { + name: this.translate.instant('alarm.details'), + icon: 'more_horiz', + isEnabled: () => true, + onAction: ($event, entity) => this.showAlarmDetails(entity) + } + ); + } + + fetchAlarms(pageLink: TimePageLink): Observable> { + const query = new AlarmQuery(this.entityId, pageLink, this.searchStatus, null, true); + return this.alarmService.getAlarms(query); + } + + showAlarmDetails(entity: AlarmInfo) { + this.dialog.open + (AlarmDetailsDialogComponent, + { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + alarmId: entity.id.id, + allowAcknowledgment: true, + allowClear: true, + displayDetails: true + } + }).afterClosed().subscribe( + (res) => { + if (res) { + this.table.updateData(); + } + } + ); + } +} diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-table-header.component.html b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-header.component.html new file mode 100644 index 0000000000..e30afb1baf --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-header.component.html @@ -0,0 +1,27 @@ + + + + alarm.alarm-status + + + {{ alarmSearchStatusTranslationsMap.get(status) | translate }} + + + diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-table-header.component.scss b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-header.component.scss new file mode 100644 index 0000000000..8f552a7e43 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-header.component.scss @@ -0,0 +1,35 @@ +/** + * Copyright © 2016-2019 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 { + /* flex: 1; + display: flex; + justify-content: flex-start; */ + padding-right: 8px; +} + +:host ::ng-deep { + mat-form-field { + font-size: 16px; + + .mat-form-field-wrapper { + padding-bottom: 0; + } + + .mat-form-field-underline { + bottom: 0; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-table-header.component.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-header.component.ts new file mode 100644 index 0000000000..6f075f8517 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-header.component.ts @@ -0,0 +1,47 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { EntityTableHeaderComponent } from '../../components/entity/entity-table-header.component'; +import { AlarmInfo, AlarmSearchStatus, alarmSearchStatusTranslations } from '@shared/models/alarm.models'; +import { AlarmTableConfig } from './alarm-table-config'; + +@Component({ + selector: 'tb-alarm-table-header', + templateUrl: './alarm-table-header.component.html', + styleUrls: ['./alarm-table-header.component.scss'] +}) +export class AlarmTableHeaderComponent extends EntityTableHeaderComponent { + + alarmSearchStatusTranslationsMap = alarmSearchStatusTranslations; + + alarmSearchStatusTypes = Object.keys(AlarmSearchStatus); + + get alarmTableConfig(): AlarmTableConfig { + return this.entitiesTableConfig as AlarmTableConfig; + } + + constructor(protected store: Store) { + super(store); + } + + searchStatusChanged(searchStatus: AlarmSearchStatus) { + this.alarmTableConfig.searchStatus = searchStatus; + this.alarmTableConfig.table.updateData(); + } +} diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.html b/ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.html new file mode 100644 index 0000000000..9421e1c767 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.html @@ -0,0 +1,18 @@ + + diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.scss b/ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.scss new file mode 100644 index 0000000000..0d3cc92455 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.scss @@ -0,0 +1,22 @@ +/** + * Copyright © 2016-2019 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 ::ng-deep { + tb-entities-table { + .mat-drawer-container { + background-color: white; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.ts new file mode 100644 index 0000000000..cbd749631e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.ts @@ -0,0 +1,90 @@ +/// +/// Copyright © 2016-2019 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, Input, OnInit, ViewChild } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { DatePipe } from '@angular/common'; +import { MatDialog } from '@angular/material'; +import { EntityId } from '@shared/models/id/entity-id'; +import { EntitiesTableComponent } from '@home/components/entity/entities-table.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { DialogService } from '@core/services/dialog.service'; +import { AlarmTableConfig } from './alarm-table-config'; +import { AlarmSearchStatus } from '@shared/models/alarm.models'; +import { AlarmService } from '@app/core/http/alarm.service'; + +@Component({ + selector: 'tb-alarm-table', + templateUrl: './alarm-table.component.html', + styleUrls: ['./alarm-table.component.scss'] +}) +export class AlarmTableComponent implements OnInit { + + activeValue = false; + dirtyValue = false; + entityIdValue: EntityId; + + @Input() + set active(active: boolean) { + if (this.activeValue !== active) { + this.activeValue = active; + if (this.activeValue && this.dirtyValue) { + this.dirtyValue = false; + this.entitiesTable.updateData(); + } + } + } + + @Input() + set entityId(entityId: EntityId) { + this.entityIdValue = entityId; + if (this.alarmTableConfig && this.alarmTableConfig.entityId !== entityId) { + this.alarmTableConfig.searchStatus = AlarmSearchStatus.ANY; + this.alarmTableConfig.entityId = entityId; + this.entitiesTable.resetSortAndFilter(this.activeValue); + if (!this.activeValue) { + this.dirtyValue = true; + } + } + } + + @ViewChild(EntitiesTableComponent, {static: true}) entitiesTable: EntitiesTableComponent; + + alarmTableConfig: AlarmTableConfig; + + constructor(private alarmService: AlarmService, + private dialogService: DialogService, + private translate: TranslateService, + private datePipe: DatePipe, + private dialog: MatDialog, + private store: Store) { + } + + ngOnInit() { + this.dirtyValue = !this.activeValue; + this.alarmTableConfig = new AlarmTableConfig( + this.alarmService, + this.dialogService, + this.translate, + this.datePipe, + this.dialog, + this.entityIdValue, + AlarmSearchStatus.ANY + ); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index 3cf870f96d..61c2c0f458 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -28,13 +28,18 @@ import { EventTableHeaderComponent } from '@home/components/event/event-table-he import { EventTableComponent } from '@home/components/event/event-table.component'; import { RelationTableComponent } from '@home/components/relation/relation-table.component'; import { RelationDialogComponent } from './relation/relation-dialog.component'; +import { AlarmTableHeaderComponent } from '@home/components/alarm/alarm-table-header.component'; +import { AlarmTableComponent } from '@home/components/alarm/alarm-table.component'; +import { AlarmDetailsDialogComponent } from '@home/components/alarm/alarm-details-dialog.component'; @NgModule({ entryComponents: [ AddEntityDialogComponent, AuditLogDetailsDialogComponent, EventTableHeaderComponent, - RelationDialogComponent + RelationDialogComponent, + AlarmTableHeaderComponent, + AlarmDetailsDialogComponent ], declarations: [ @@ -48,7 +53,10 @@ import { RelationDialogComponent } from './relation/relation-dialog.component'; EventTableHeaderComponent, EventTableComponent, RelationTableComponent, - RelationDialogComponent + RelationDialogComponent, + AlarmTableHeaderComponent, + AlarmTableComponent, + AlarmDetailsDialogComponent ], imports: [ CommonModule, @@ -62,7 +70,9 @@ import { RelationDialogComponent } from './relation/relation-dialog.component'; ContactComponent, AuditLogTableComponent, EventTableComponent, - RelationTableComponent + RelationTableComponent, + AlarmTableComponent, + AlarmDetailsDialogComponent ] }) export class HomeComponentsModule { } diff --git a/ui-ngx/src/app/modules/home/pages/asset/asset-tabs.component.html b/ui-ngx/src/app/modules/home/pages/asset/asset-tabs.component.html index 85e41ac114..7b5ff69b80 100644 --- a/ui-ngx/src/app/modules/home/pages/asset/asset-tabs.component.html +++ b/ui-ngx/src/app/modules/home/pages/asset/asset-tabs.component.html @@ -15,6 +15,10 @@ limitations under the License. --> + + + + + +
dashboard.assignedToCustomers diff --git a/ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html b/ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html index cdcc19e08c..84ecb1bfab 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html @@ -15,6 +15,10 @@ limitations under the License. --> + + + + + + + + + + + + extends PageComponent implemen } ngOnDestroy(): void { - console.log('Dialog destroy called'); super.ngOnDestroy(); if (this.routerSubscription) { this.routerSubscription.unsubscribe(); diff --git a/ui-ngx/src/app/shared/models/alarm.models.ts b/ui-ngx/src/app/shared/models/alarm.models.ts index 8fbd24de3b..4265041835 100644 --- a/ui-ngx/src/app/shared/models/alarm.models.ts +++ b/ui-ngx/src/app/shared/models/alarm.models.ts @@ -20,6 +20,8 @@ import {TenantId} from '@shared/models/id/tenant-id'; import {CustomerId} from '@shared/models/id/customer-id'; import {AlarmId} from '@shared/models/id/alarm-id'; import {EntityId} from '@shared/models/id/entity-id'; +import { ActionStatus } from '@shared/models/audit-log.models'; +import { TimePageLink } from '@shared/models/page/page-link'; export enum AlarmSeverity { CRITICAL = 'CRITICAL', @@ -36,6 +38,53 @@ export enum AlarmStatus { CLEARED_ACK = 'CLEARED_ACK' } +export enum AlarmSearchStatus { + ANY = 'ANY', + ACTIVE = 'ACTIVE', + CLEARED = 'CLEARED', + ACK = 'ACK', + UNACK = 'UNACK' +} + +export const alarmSeverityTranslations = new Map( + [ + [AlarmSeverity.CRITICAL, 'alarm.severity-critical'], + [AlarmSeverity.MAJOR, 'alarm.severity-major'], + [AlarmSeverity.MINOR, 'alarm.severity-minor'], + [AlarmSeverity.WARNING, 'alarm.severity-warning'], + [AlarmSeverity.INDETERMINATE, 'alarm.severity-indeterminate'] + ] +); + +export const alarmStatusTranslations = new Map( + [ + [AlarmStatus.ACTIVE_UNACK, 'alarm.display-status.ACTIVE_UNACK'], + [AlarmStatus.ACTIVE_ACK, 'alarm.display-status.ACTIVE_ACK'], + [AlarmStatus.CLEARED_UNACK, 'alarm.display-status.CLEARED_UNACK'], + [AlarmStatus.CLEARED_ACK, 'alarm.display-status.CLEARED_ACK'], + ] +); + +export const alarmSearchStatusTranslations = new Map( + [ + [AlarmSearchStatus.ANY, 'alarm.search-status.ANY'], + [AlarmSearchStatus.ACTIVE, 'alarm.search-status.ACTIVE'], + [AlarmSearchStatus.CLEARED, 'alarm.search-status.CLEARED'], + [AlarmSearchStatus.ACK, 'alarm.search-status.ACK'], + [AlarmSearchStatus.UNACK, 'alarm.search-status.UNACK'] + ] +); + +export const alarmSeverityColors = new Map( + [ + [AlarmSeverity.CRITICAL, 'red'], + [AlarmSeverity.MAJOR, 'orange'], + [AlarmSeverity.MINOR, '#ffca3d'], + [AlarmSeverity.WARNING, '#abab00'], + [AlarmSeverity.INDETERMINATE, 'green'] + ] +); + export interface Alarm extends BaseData { tenantId: TenantId; type: string; @@ -49,3 +98,41 @@ export interface Alarm extends BaseData { propagate: boolean; details?: any; } + +export interface AlarmInfo extends Alarm { + originatorName: string; +} + +export class AlarmQuery { + + affectedEntityId: EntityId; + pageLink: TimePageLink; + searchStatus: AlarmSearchStatus; + status: AlarmStatus; + fetchOriginator: boolean; + + constructor(entityId: EntityId, pageLink: TimePageLink, + searchStatus: AlarmSearchStatus, status: AlarmStatus, + fetchOriginator: boolean) { + this.affectedEntityId = entityId; + this.pageLink = pageLink; + this.searchStatus = searchStatus; + this.status = status; + this.fetchOriginator = fetchOriginator; + } + + public toQuery(): string { + let query = `/${this.affectedEntityId.entityType}/${this.affectedEntityId.id}`; + query += this.pageLink.toQuery(); + if (this.searchStatus) { + query += `&searchStatus=${this.searchStatus}`; + } else if (this.status) { + query += `&status=${this.status}`; + } + if (typeof this.fetchOriginator !== 'undefined' && this.fetchOriginator !== null) { + query += `&fetchOriginator=${this.fetchOriginator}`; + } + return query; + } + +} diff --git a/ui-ngx/src/app/shared/models/entity-type.models.ts b/ui-ngx/src/app/shared/models/entity-type.models.ts index 1d8b72cb0f..598fa983a1 100644 --- a/ui-ngx/src/app/shared/models/entity-type.models.ts +++ b/ui-ngx/src/app/shared/models/entity-type.models.ts @@ -165,6 +165,19 @@ export const entityTypeTranslations = new Map Date: Thu, 29 Aug 2019 20:04:59 +0300 Subject: [PATCH 026/133] Attributes table --- ui-ngx/src/app/core/http/attribute.service.ts | 74 ++++ .../add-attribute-dialog.component.html | 61 ++++ .../add-attribute-dialog.component.ts | 96 +++++ .../attribute/attribute-table.component.html | 192 ++++++++++ .../attribute/attribute-table.component.scss | 63 ++++ .../attribute/attribute-table.component.ts | 341 ++++++++++++++++++ .../edit-attribute-value-panel.component.html | 42 +++ .../edit-attribute-value-panel.component.scss | 19 + .../edit-attribute-value-panel.component.ts | 88 +++++ .../entity-details-panel.component.scss | 3 + .../entity/entity-tabs.component.ts | 4 + .../home/components/home-components.module.ts | 15 +- .../models/datasource/attribute-datasource.ts | 135 +++++++ .../pages/device/device-tabs.component.html | 15 + .../components/value-input.component.html | 63 ++++ .../components/value-input.component.scss | 28 ++ .../components/value-input.component.ts | 107 ++++++ .../models/telemetry/telemetry.models.ts | 38 ++ ui-ngx/src/app/shared/shared.module.ts | 7 +- .../assets/locale/locale.constant-en_US.json | 7 +- 20 files changed, 1390 insertions(+), 8 deletions(-) create mode 100644 ui-ngx/src/app/core/http/attribute.service.ts create mode 100644 ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html create mode 100644 ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/attribute/edit-attribute-value-panel.component.html create mode 100644 ui-ngx/src/app/modules/home/components/attribute/edit-attribute-value-panel.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/attribute/edit-attribute-value-panel.component.ts create mode 100644 ui-ngx/src/app/modules/home/models/datasource/attribute-datasource.ts create mode 100644 ui-ngx/src/app/shared/components/value-input.component.html create mode 100644 ui-ngx/src/app/shared/components/value-input.component.scss create mode 100644 ui-ngx/src/app/shared/components/value-input.component.ts diff --git a/ui-ngx/src/app/core/http/attribute.service.ts b/ui-ngx/src/app/core/http/attribute.service.ts new file mode 100644 index 0000000000..c05d10de45 --- /dev/null +++ b/ui-ngx/src/app/core/http/attribute.service.ts @@ -0,0 +1,74 @@ +/// +/// Copyright © 2016-2019 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 { Injectable } from '@angular/core'; +import { defaultHttpOptions } from './http-utils'; +import { forkJoin, Observable, of } from 'rxjs/index'; +import { HttpClient } from '@angular/common/http'; +import { EntityId } from '@shared/models/id/entity-id'; +import { AttributeData, AttributeScope } from '@shared/models/telemetry/telemetry.models'; + +@Injectable({ + providedIn: 'root' +}) +export class AttributeService { + + constructor( + private http: HttpClient + ) { } + + public getEntityAttributes(entityId: EntityId, attributeScope: AttributeScope, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + return this.http.get>(`/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/values/attributes/` + + `${attributeScope}`, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public deleteEntityAttributes(entityId: EntityId, attributeScope: AttributeScope, attributes: Array, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + const keys = attributes.map(attribute => attribute.key).join(','); + return this.http.delete(`/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/${attributeScope}` + + `?keys=${keys}`, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public saveEntityAttributes(entityId: EntityId, attributeScope: AttributeScope, attributes: Array, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + const attributesData: {[key: string]: any} = {}; + const deleteAttributes: AttributeData[] = []; + attributes.forEach((attribute) => { + if (attribute.value !== null) { + attributesData[attribute.key] = attribute.value; + } else { + deleteAttributes.push(attribute); + } + }); + let deleteEntityAttributesObservable: Observable; + if (deleteAttributes.length) { + deleteEntityAttributesObservable = this.deleteEntityAttributes(entityId, attributeScope, deleteAttributes); + } else { + deleteEntityAttributesObservable = of(null); + } + let saveEntityAttributesObservable: Observable; + if (Object.keys(attributesData).length) { + saveEntityAttributesObservable = this.http.post(`/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/${attributeScope}`, + attributesData, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } else { + saveEntityAttributesObservable = of(null); + } + return forkJoin(saveEntityAttributesObservable, deleteEntityAttributesObservable); + } +} diff --git a/ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.html b/ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.html new file mode 100644 index 0000000000..5d621986d8 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.html @@ -0,0 +1,61 @@ + +
+ +

{{ 'attribute.add' | translate }}

+ + +
+ + +
+
+
+ + attribute.key + + + {{ 'attribute.key-required' | translate }} + + + + +
+
+
+ + + +
+
diff --git a/ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.ts b/ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.ts new file mode 100644 index 0000000000..7b13618063 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.ts @@ -0,0 +1,96 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Inject, OnInit, SkipSelf, ViewChild } from '@angular/core'; +import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; +import { + CONTAINS_TYPE, + EntityRelation, + EntitySearchDirection, + RelationTypeGroup +} from '@shared/models/relation.models'; +import { EntityRelationService } from '@core/http/entity-relation.service'; +import { EntityId } from '@shared/models/id/entity-id'; +import { forkJoin, Observable } from 'rxjs'; +import { JsonObjectEditComponent } from '@app/shared/components/json-object-edit.component'; +import { Router } from '@angular/router'; +import { DialogComponent } from '@app/shared/components/dialog.component'; +import { AttributeData, AttributeScope } from '@shared/models/telemetry/telemetry.models'; +import { AttributeService } from '@core/http/attribute.service'; + +export interface AddAttributeDialogData { + entityId: EntityId; + attributeScope: AttributeScope; +} + +@Component({ + selector: 'tb-add-attribute-dialog', + templateUrl: './add-attribute-dialog.component.html', + providers: [{provide: ErrorStateMatcher, useExisting: AddAttributeDialogComponent}], + styleUrls: [] +}) +export class AddAttributeDialogComponent extends DialogComponent + implements OnInit, ErrorStateMatcher { + + attributeFormGroup: FormGroup; + + submitted = false; + + constructor(protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) public data: AddAttributeDialogData, + private attributeService: AttributeService, + @SkipSelf() private errorStateMatcher: ErrorStateMatcher, + public dialogRef: MatDialogRef, + public fb: FormBuilder) { + super(store, router, dialogRef); + } + + ngOnInit(): void { + this.attributeFormGroup = this.fb.group({ + key: ['', [Validators.required]], + value: [null, [Validators.required]] + }); + } + + isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { + const originalErrorState = this.errorStateMatcher.isErrorState(control, form); + const customErrorState = !!(control && control.invalid && this.submitted); + return originalErrorState || customErrorState; + } + + cancel(): void { + this.dialogRef.close(false); + } + + add(): void { + this.submitted = true; + const attribute: AttributeData = { + lastUpdateTs: null, + key: this.attributeFormGroup.get('key').value, + value: this.attributeFormGroup.get('value').value + }; + this.attributeService.saveEntityAttributes(this.data.entityId, + this.data.attributeScope, [attribute]).subscribe( + () => { + this.dialogRef.close(true); + } + ); + } +} diff --git a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html new file mode 100644 index 0000000000..834cb262fc --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html @@ -0,0 +1,192 @@ + +
+
+ +
+ {{telemetryTypeTranslationsMap.get(attributeScope) | translate}} + + attribute.attributes-scope + + + {{ telemetryTypeTranslationsMap.get(scope) | translate }} + + + + + + + +
+
+ +
+ + +   + + + +
+
+ +
+ + {{ translate.get( + attributeScope === latestTelemetryTypes.LATEST_TELEMETRY + ? 'attribute.selected-telemetry' : 'attribute.selected-attributes', + {count: dataSource.selection.selected.length}) | async }} + + + + +
+
+ +
+
+ {{ 'widgets-bundle.current' | translate }} + TODO: +
+ + +
+
+
+ + + + + + + + + + + + + {{ 'attribute.last-update-time' | translate }} + + {{ attribute.lastUpdateTs | date:'yyyy-MM-dd HH:mm:ss' }} + + + + {{ 'attribute.key' | translate }} + + {{ attribute.key }} + + + + {{ 'attribute.value' | translate }} + + {{attribute.value}} + + edit + + + + + + + {{ + attributeScope === latestTelemetryTypes.LATEST_TELEMETRY + ? 'attribute.no-telemetry-text' + : 'attribute.no-attributes-text' + }} +
+ + +
+ Coming soon! +
+
+
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 new file mode 100644 index 0000000000..96a70f6e8c --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.scss @@ -0,0 +1,63 @@ +/** + * Copyright © 2016-2019 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 { + width: 100%; + height: 100%; + .tb-entity-table { + .tb-entity-table-content { + width: 100%; + height: 100%; + background: #fff; + + .tb-entity-table-title { + padding-right: 20px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .table-container { + overflow: auto; + } + } + } +} + +:host ::ng-deep { + .mat-sort-header-sorted .mat-sort-header-arrow { + opacity: 1 !important; + } + mat-form-field.tb-attribute-scope { + font-size: 16px; + + .mat-form-field-wrapper { + padding-bottom: 0; + } + + .mat-form-field-underline { + bottom: 0; + } + } + mat-cell.tb-value-cell { + cursor: pointer; + mat-icon { + height: 16px; + width: 16px; + font-size: 16px; + color: #757575 + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts new file mode 100644 index 0000000000..70c5154615 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts @@ -0,0 +1,341 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + AfterViewInit, + ChangeDetectionStrategy, + Component, + ElementRef, + Input, + OnInit, + ViewChild, + ViewContainerRef +} from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { PageLink } from '@shared/models/page/page-link'; +import { MatPaginator } from '@angular/material/paginator'; +import { MatSort } from '@angular/material/sort'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { TranslateService } from '@ngx-translate/core'; +import { MatDialog } from '@angular/material/dialog'; +import { DialogService } from '@core/services/dialog.service'; +import { Direction, SortOrder } from '@shared/models/page/sort-order'; +import { fromEvent, merge } from 'rxjs'; +import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators'; +import { EntityId } from '@shared/models/id/entity-id'; +import { + AttributeData, + AttributeScope, + isClientSideTelemetryType, LatestTelemetry, + TelemetryType, + telemetryTypeTranslations +} from '@shared/models/telemetry/telemetry.models'; +import { AttributeDatasource } from '@home/models/datasource/attribute-datasource'; +import { AttributeService } from '@app/core/http/attribute.service'; +import { EntityType } from '@shared/models/entity-type.models'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { RelationDialogComponent, RelationDialogData } from '@home/components/relation/relation-dialog.component'; +import { + AddAttributeDialogComponent, + AddAttributeDialogData +} from '@home/components/attribute/add-attribute-dialog.component'; +import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; +import { TIMEWINDOW_PANEL_DATA, TimewindowPanelComponent } from '@shared/components/time/timewindow-panel.component'; +import { + EDIT_ATTRIBUTE_VALUE_PANEL_DATA, + EditAttributeValuePanelComponent, + EditAttributeValuePanelData +} from './edit-attribute-value-panel.component'; +import { ComponentPortal, PortalInjector } from '@angular/cdk/portal'; + + +@Component({ + selector: 'tb-attribute-table', + templateUrl: './attribute-table.component.html', + styleUrls: ['./attribute-table.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class AttributeTableComponent extends PageComponent implements AfterViewInit, OnInit { + + telemetryTypeTranslationsMap = telemetryTypeTranslations; + isClientSideTelemetryTypeMap = isClientSideTelemetryType; + + latestTelemetryTypes = LatestTelemetry; + + mode: 'default' | 'widget' = 'default'; + + attributeScopes: Array = []; + attributeScope: TelemetryType; + + displayedColumns = ['select', 'lastUpdateTs', 'key', 'value']; + pageLink: PageLink; + textSearchMode = false; + dataSource: AttributeDatasource; + + activeValue = false; + dirtyValue = false; + entityIdValue: EntityId; + + attributeScopeSelectionReadonly = false; + + viewsInited = false; + + private disableAttributeScopeSelectionValue: boolean; + get disableAttributeScopeSelection(): boolean { + return this.disableAttributeScopeSelectionValue; + } + @Input() + set disableAttributeScopeSelection(value: boolean) { + this.disableAttributeScopeSelectionValue = coerceBooleanProperty(value); + } + + @Input() + defaultAttributeScope: TelemetryType; + + @Input() + set active(active: boolean) { + if (this.activeValue !== active) { + this.activeValue = active; + if (this.activeValue && this.dirtyValue) { + this.dirtyValue = false; + if (this.viewsInited) { + this.updateData(true); + } + } + } + } + + @Input() + set entityId(entityId: EntityId) { + if (this.entityIdValue !== entityId) { + this.entityIdValue = entityId; + this.resetSortAndFilter(this.activeValue); + if (!this.activeValue) { + this.dirtyValue = true; + } + } + } + + @ViewChild('searchInput', {static: false}) searchInputField: ElementRef; + + @ViewChild(MatPaginator, {static: false}) paginator: MatPaginator; + @ViewChild(MatSort, {static: false}) sort: MatSort; + + constructor(protected store: Store, + private attributeService: AttributeService, + public translate: TranslateService, + public dialog: MatDialog, + private overlay: Overlay, + private viewContainerRef: ViewContainerRef, + private dialogService: DialogService) { + super(store); + this.dirtyValue = !this.activeValue; + const sortOrder: SortOrder = { property: 'key', direction: Direction.ASC }; + this.pageLink = new PageLink(10, 0, null, sortOrder); + this.dataSource = new AttributeDatasource(this.attributeService, this.translate); + } + + ngOnInit() { + } + + attributeScopeChanged(attributeScope: TelemetryType) { + this.attributeScope = attributeScope; + this.mode = 'default'; + this.updateData(true); + } + + ngAfterViewInit() { + + fromEvent(this.searchInputField.nativeElement, 'keyup') + .pipe( + debounceTime(150), + distinctUntilChanged(), + tap(() => { + this.paginator.pageIndex = 0; + this.updateData(); + }) + ) + .subscribe(); + + this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0); + + merge(this.sort.sortChange, this.paginator.page) + .pipe( + tap(() => this.updateData()) + ) + .subscribe(); + + this.viewsInited = true; + if (this.activeValue && this.entityIdValue) { + this.updateData(true); + } + } + + updateData(reload: boolean = false) { + this.pageLink.page = this.paginator.pageIndex; + this.pageLink.pageSize = this.paginator.pageSize; + this.pageLink.sortOrder.property = this.sort.active; + this.pageLink.sortOrder.direction = Direction[this.sort.direction.toUpperCase()]; + this.dataSource.loadAttributes(this.entityIdValue, this.attributeScope, this.pageLink, reload); + } + + enterFilterMode() { + this.textSearchMode = true; + this.pageLink.textSearch = ''; + setTimeout(() => { + this.searchInputField.nativeElement.focus(); + this.searchInputField.nativeElement.setSelectionRange(0, 0); + }, 10); + } + + exitFilterMode() { + this.textSearchMode = false; + this.pageLink.textSearch = null; + this.paginator.pageIndex = 0; + this.updateData(); + } + + resetSortAndFilter(update: boolean = true) { + const entityType = this.entityIdValue.entityType; + if (entityType === EntityType.DEVICE || entityType === EntityType.ENTITY_VIEW) { + this.attributeScopes = Object.keys(AttributeScope); + this.attributeScopeSelectionReadonly = false; + } else { + this.attributeScopes = [AttributeScope.SERVER_SCOPE]; + this.attributeScopeSelectionReadonly = true; + } + this.mode = 'default'; + this.attributeScope = this.defaultAttributeScope; + this.pageLink.textSearch = null; + if (this.viewsInited) { + this.paginator.pageIndex = 0; + const sortable = this.sort.sortables.get('key'); + this.sort.active = sortable.id; + this.sort.direction = 'asc'; + if (update) { + this.updateData(true); + } + } + } + + reloadAttributes() { + this.updateData(true); + } + + addAttribute($event: Event) { + if ($event) { + $event.stopPropagation(); + } + this.dialog.open(AddAttributeDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + entityId: this.entityIdValue, + attributeScope: this.attributeScope as AttributeScope + } + }).afterClosed().subscribe( + (res) => { + if (res) { + this.reloadAttributes(); + } + } + ); + } + + editAttribute($event: Event, attribute: AttributeData) { + if ($event) { + $event.stopPropagation(); + } + const target = $event.target || $event.srcElement || $event.currentTarget; + const config = new OverlayConfig(); + config.backdropClass = 'cdk-overlay-transparent-backdrop'; + config.hasBackdrop = true; + const connectedPosition: ConnectedPosition = { + originX: 'end', + originY: 'center', + overlayX: 'end', + overlayY: 'center' + }; + config.positionStrategy = this.overlay.position().flexibleConnectedTo(target as HTMLElement) + .withPositions([connectedPosition]); + + const overlayRef = this.overlay.create(config); + overlayRef.backdropClick().subscribe(() => { + overlayRef.dispose(); + }); + const injectionTokens = new WeakMap([ + [EDIT_ATTRIBUTE_VALUE_PANEL_DATA, { + attributeValue: attribute.value + } as EditAttributeValuePanelData], + [OverlayRef, overlayRef] + ]); + const injector = new PortalInjector(this.viewContainerRef.injector, injectionTokens); + const componentRef = overlayRef.attach(new ComponentPortal(EditAttributeValuePanelComponent, + this.viewContainerRef, injector)); + componentRef.onDestroy(() => { + if (componentRef.instance.result !== null) { + const attributeValue = componentRef.instance.result; + const updatedAttribute = {...attribute}; + updatedAttribute.value = attributeValue; + this.attributeService.saveEntityAttributes(this.entityIdValue, + this.attributeScope as AttributeScope, [updatedAttribute]).subscribe( + () => { + this.reloadAttributes(); + } + ); + } + }); + } + + deleteAttributes($event: Event) { + if ($event) { + $event.stopPropagation(); + } + if (this.dataSource.selection.selected.length > 0) { + this.dialogService.confirm( + this.translate.instant('attribute.delete-attributes-title', {count: this.dataSource.selection.selected.length}), + this.translate.instant('attribute.delete-attributes-text'), + this.translate.instant('action.no'), + this.translate.instant('action.yes'), + true + ).subscribe((result) => { + if (result) { + this.attributeService.deleteEntityAttributes(this.entityIdValue, + this.attributeScope as AttributeScope, this.dataSource.selection.selected).subscribe( + () => { + this.reloadAttributes(); + } + ); + } + }); + } + } + + enterWidgetMode() { + this.mode = 'widget'; + + // TODO: + } + + exitWidgetMode() { + this.mode = 'default'; + this.reloadAttributes(); + + // TODO: + } + +} diff --git a/ui-ngx/src/app/modules/home/components/attribute/edit-attribute-value-panel.component.html b/ui-ngx/src/app/modules/home/components/attribute/edit-attribute-value-panel.component.html new file mode 100644 index 0000000000..ad2945f6bd --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/attribute/edit-attribute-value-panel.component.html @@ -0,0 +1,42 @@ + +
+
+ + +
+
+ + + +
+
diff --git a/ui-ngx/src/app/modules/home/components/attribute/edit-attribute-value-panel.component.scss b/ui-ngx/src/app/modules/home/components/attribute/edit-attribute-value-panel.component.scss new file mode 100644 index 0000000000..4ee628e7d3 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/attribute/edit-attribute-value-panel.component.scss @@ -0,0 +1,19 @@ +/** + * Copyright © 2016-2019 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 { + background-color: #f9f9f9; +} + diff --git a/ui-ngx/src/app/modules/home/components/attribute/edit-attribute-value-panel.component.ts b/ui-ngx/src/app/modules/home/components/attribute/edit-attribute-value-panel.component.ts new file mode 100644 index 0000000000..d2912dda2e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/attribute/edit-attribute-value-panel.component.ts @@ -0,0 +1,88 @@ +/// +/// Copyright © 2016-2019 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, InjectionToken, OnInit, SkipSelf, ViewChild } from '@angular/core'; +import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; +import { + CONTAINS_TYPE, + EntityRelation, + EntitySearchDirection, + RelationTypeGroup +} from '@shared/models/relation.models'; +import { EntityRelationService } from '@core/http/entity-relation.service'; +import { EntityId } from '@shared/models/id/entity-id'; +import { forkJoin, Observable } from 'rxjs'; +import { JsonObjectEditComponent } from '@app/shared/components/json-object-edit.component'; +import { Router } from '@angular/router'; +import { DialogComponent } from '@app/shared/components/dialog.component'; +import { AttributeData, AttributeScope } from '@shared/models/telemetry/telemetry.models'; +import { AttributeService } from '@core/http/attribute.service'; +import { PageComponent } from '@shared/components/page.component'; +import { OverlayRef } from '@angular/cdk/overlay'; + +export const EDIT_ATTRIBUTE_VALUE_PANEL_DATA = new InjectionToken('EditAttributeValuePanelData'); + +export interface EditAttributeValuePanelData { + attributeValue: any; +} + +@Component({ + selector: 'tb-edit-attribute-value-panel', + templateUrl: './edit-attribute-value-panel.component.html', + providers: [{provide: ErrorStateMatcher, useExisting: EditAttributeValuePanelComponent}], + styleUrls: ['./edit-attribute-value-panel.component.scss'] +}) +export class EditAttributeValuePanelComponent extends PageComponent implements OnInit, ErrorStateMatcher { + + attributeFormGroup: FormGroup; + + result: any = null; + + submitted = false; + + constructor(protected store: Store, + @Inject(EDIT_ATTRIBUTE_VALUE_PANEL_DATA) public data: EditAttributeValuePanelData, + @SkipSelf() private errorStateMatcher: ErrorStateMatcher, + public overlayRef: OverlayRef, + public fb: FormBuilder) { + super(store); + } + + ngOnInit(): void { + this.attributeFormGroup = this.fb.group({ + value: [this.data.attributeValue, [Validators.required]] + }); + } + + isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { + const originalErrorState = this.errorStateMatcher.isErrorState(control, form); + const customErrorState = !!(control && control.invalid && this.submitted); + return originalErrorState || customErrorState; + } + + cancel(): void { + this.overlayRef.dispose(); + } + + update(): void { + this.submitted = true; + this.result = this.attributeFormGroup.get('value').value; + this.overlayRef.dispose(); + } +} diff --git a/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.scss b/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.scss index d30159bdfa..1197999f23 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.scss +++ b/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.scss @@ -28,4 +28,7 @@ right: 0; bottom: 0; } + .mat-tab-label { + min-width: 40px; + } } diff --git a/ui-ngx/src/app/modules/home/components/entity/entity-tabs.component.ts b/ui-ngx/src/app/modules/home/components/entity/entity-tabs.component.ts index dd8a02c8bc..bd37d8808a 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entity-tabs.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/entity-tabs.component.ts @@ -38,10 +38,14 @@ import { AuthUser } from '@shared/models/user.model'; import { EntityType } from '@shared/models/entity-type.models'; import { AuditLogMode } from '@shared/models/audit-log.models'; import { DebugEventType, EventType } from '@shared/models/event.models'; +import { AttributeScope, LatestTelemetry } from '@shared/models/telemetry/telemetry.models'; import { NULL_UUID } from '@shared/models/id/has-uuid'; export abstract class EntityTabsComponent> extends PageComponent implements OnInit, AfterViewInit { + attributeScopes = AttributeScope; + latestTelemetryTypes = LatestTelemetry; + authorities = Authority; entityTypes = EntityType; diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index 61c2c0f458..d10e52dec8 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -31,6 +31,9 @@ import { RelationDialogComponent } from './relation/relation-dialog.component'; import { AlarmTableHeaderComponent } from '@home/components/alarm/alarm-table-header.component'; import { AlarmTableComponent } from '@home/components/alarm/alarm-table.component'; import { AlarmDetailsDialogComponent } from '@home/components/alarm/alarm-details-dialog.component'; +import { AttributeTableComponent } from '@home/components/attribute/attribute-table.component'; +import { AddAttributeDialogComponent } from './attribute/add-attribute-dialog.component'; +import { EditAttributeValuePanelComponent } from './attribute/edit-attribute-value-panel.component'; @NgModule({ entryComponents: [ @@ -39,7 +42,9 @@ import { AlarmDetailsDialogComponent } from '@home/components/alarm/alarm-detail EventTableHeaderComponent, RelationDialogComponent, AlarmTableHeaderComponent, - AlarmDetailsDialogComponent + AlarmDetailsDialogComponent, + AddAttributeDialogComponent, + EditAttributeValuePanelComponent ], declarations: [ @@ -56,7 +61,10 @@ import { AlarmDetailsDialogComponent } from '@home/components/alarm/alarm-detail RelationDialogComponent, AlarmTableHeaderComponent, AlarmTableComponent, - AlarmDetailsDialogComponent + AlarmDetailsDialogComponent, + AttributeTableComponent, + AddAttributeDialogComponent, + EditAttributeValuePanelComponent ], imports: [ CommonModule, @@ -72,7 +80,8 @@ import { AlarmDetailsDialogComponent } from '@home/components/alarm/alarm-detail EventTableComponent, RelationTableComponent, AlarmTableComponent, - AlarmDetailsDialogComponent + AlarmDetailsDialogComponent, + AttributeTableComponent ] }) export class HomeComponentsModule { } diff --git a/ui-ngx/src/app/modules/home/models/datasource/attribute-datasource.ts b/ui-ngx/src/app/modules/home/models/datasource/attribute-datasource.ts new file mode 100644 index 0000000000..6c709c5181 --- /dev/null +++ b/ui-ngx/src/app/modules/home/models/datasource/attribute-datasource.ts @@ -0,0 +1,135 @@ +/// +/// Copyright © 2016-2019 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 { CollectionViewer, DataSource } from '@angular/cdk/typings/collections'; +import { BehaviorSubject, Observable, of, ReplaySubject } from 'rxjs'; +import { emptyPageData, PageData } from '@shared/models/page/page-data'; +import { SelectionModel } from '@angular/cdk/collections'; +import { PageLink } from '@shared/models/page/page-link'; +import { catchError, map, publishReplay, refCount, take, tap } from 'rxjs/operators'; +import { EntityId } from '@app/shared/models/id/entity-id'; +import { TranslateService } from '@ngx-translate/core'; +import { + AttributeData, + AttributeScope, + isClientSideTelemetryType, + TelemetryType +} from '@shared/models/telemetry/telemetry.models'; +import { AttributeService } from '@core/http/attribute.service'; + +export class AttributeDatasource implements DataSource { + + private attributesSubject = new BehaviorSubject([]); + private pageDataSubject = new BehaviorSubject>(emptyPageData()); + + public pageData$ = this.pageDataSubject.asObservable(); + + public selection = new SelectionModel(true, []); + + private allAttributes: Observable>; + + constructor(private attributeService: AttributeService, + private translate: TranslateService) {} + + connect(collectionViewer: CollectionViewer): Observable> { + return this.attributesSubject.asObservable(); + } + + disconnect(collectionViewer: CollectionViewer): void { + this.attributesSubject.complete(); + this.pageDataSubject.complete(); + } + + loadAttributes(entityId: EntityId, attributesScope: TelemetryType, + pageLink: PageLink, reload: boolean = false): Observable> { + if (reload) { + this.allAttributes = null; + } + const result = new ReplaySubject>(); + this.fetchAttributes(entityId, attributesScope, pageLink).pipe( + tap(() => { + this.selection.clear(); + }), + catchError(() => of(emptyPageData())), + ).subscribe( + (pageData) => { + this.attributesSubject.next(pageData.data); + this.pageDataSubject.next(pageData); + result.next(pageData); + } + ); + return result; + } + + fetchAttributes(entityId: EntityId, attributesScope: TelemetryType, + pageLink: PageLink): Observable> { + return this.getAllAttributes(entityId, attributesScope).pipe( + map((data) => pageLink.filterData(data)) + ); + } + + getAllAttributes(entityId: EntityId, attributesScope: TelemetryType): Observable> { + if (!this.allAttributes) { + let attributesObservable: Observable>; + if (isClientSideTelemetryType.get(attributesScope)) { + attributesObservable = of([]); + // TODO: + } else { + attributesObservable = this.attributeService.getEntityAttributes(entityId, attributesScope as AttributeScope); + } + this.allAttributes = attributesObservable.pipe( + publishReplay(1), + refCount() + ); + } + return this.allAttributes; + } + + isAllSelected(): Observable { + const numSelected = this.selection.selected.length; + return this.attributesSubject.pipe( + map((attributes) => numSelected === attributes.length) + ); + } + + isEmpty(): Observable { + return this.attributesSubject.pipe( + map((attributes) => !attributes.length) + ); + } + + total(): Observable { + return this.pageDataSubject.pipe( + map((pageData) => pageData.totalElements) + ); + } + + masterToggle() { + this.attributesSubject.pipe( + tap((attributes) => { + const numSelected = this.selection.selected.length; + if (numSelected === attributes.length) { + this.selection.clear(); + } else { + attributes.forEach(row => { + this.selection.select(row); + }); + } + }), + take(1) + ).subscribe(); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html b/ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html index 84ecb1bfab..a4d260f91b 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html @@ -15,6 +15,21 @@ limitations under the License. --> + + + + + + + + diff --git a/ui-ngx/src/app/shared/components/value-input.component.html b/ui-ngx/src/app/shared/components/value-input.component.html new file mode 100644 index 0000000000..741b0f49d5 --- /dev/null +++ b/ui-ngx/src/app/shared/components/value-input.component.html @@ -0,0 +1,63 @@ + +
+
+ + value.type + + + + {{ valueTypes.get(valueType).name | translate }} + + + + {{ valueTypes.get(valueType).name | translate }} + + + + + value.string-value + + + {{ (requiredText ? requiredText : 'value.string-value-required') | translate }} + + + + value.integer-value + + + {{ (requiredText ? requiredText : 'value.integer-value-required') | translate }} + + + {{ 'value.invalid-integer-value' | translate }} + + + + value.double-value + + + {{ (requiredText ? requiredText : 'value.double-value-required') | translate }} + + +
+ + {{ (modelValue ? 'value.true' : 'value.false') | translate }} + +
+
+
diff --git a/ui-ngx/src/app/shared/components/value-input.component.scss b/ui-ngx/src/app/shared/components/value-input.component.scss new file mode 100644 index 0000000000..fb05bf73dc --- /dev/null +++ b/ui-ngx/src/app/shared/components/value-input.component.scss @@ -0,0 +1,28 @@ +/** + * Copyright © 2016-2019 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 ::ng-deep { + mat-form-field.tb-value-type { + .mat-form-field-infix { + padding-bottom: 1px; + } + mat-select-trigger { + mat-icon { + margin-right: 16px; + } + } + } +} diff --git a/ui-ngx/src/app/shared/components/value-input.component.ts b/ui-ngx/src/app/shared/components/value-input.component.ts new file mode 100644 index 0000000000..ef2f328b24 --- /dev/null +++ b/ui-ngx/src/app/shared/components/value-input.component.ts @@ -0,0 +1,107 @@ +/// +/// Copyright © 2016-2019 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, ViewChild } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR, NgForm } from '@angular/forms'; +import { ValueType, valueTypesMap } from '@shared/models/constants'; + +@Component({ + selector: 'tb-value-input', + templateUrl: './value-input.component.html', + styleUrls: ['./value-input.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => ValueInputComponent), + multi: true + } + ] +}) +export class ValueInputComponent implements OnInit, ControlValueAccessor { + + @Input() disabled: boolean; + + @Input() requiredText: string; + + @ViewChild('inputForm', {static: true}) inputForm: NgForm; + + modelValue: any; + + valueType: ValueType; + + public valueTypeEnum = ValueType; + + valueTypeKeys = Object.keys(ValueType); + + valueTypes = valueTypesMap; + + private propagateChange = null; + + constructor() { + + } + + ngOnInit(): void { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + writeValue(value: any): void { + this.modelValue = value; + if (this.modelValue === true || this.modelValue === false) { + this.valueType = ValueType.BOOLEAN; + } else if (typeof this.modelValue === 'number') { + if (this.modelValue.toString().indexOf('.') === -1) { + this.valueType = ValueType.INTEGER; + } else { + this.valueType = ValueType.DOUBLE; + } + } else { + this.valueType = ValueType.STRING; + } + } + + updateView() { + if (this.inputForm.valid || this.valueType === ValueType.BOOLEAN) { + this.propagateChange(this.modelValue); + } else { + this.propagateChange(null); + } + } + + onValueTypeChanged() { + if (this.valueType === ValueType.BOOLEAN) { + this.modelValue = false; + } else { + this.modelValue = null; + } + this.updateView(); + } + + onValueChanged() { + this.updateView(); + } + +} diff --git a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts index 2e91be8e11..ace2326549 100644 --- a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts +++ b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts @@ -15,9 +15,47 @@ /// +import { AlarmSeverity } from '@shared/models/alarm.models'; + export enum DataKeyType { timeseries = 'timeseries', attribute = 'attribute', function = 'function', alarm = 'alarm' } + +export enum LatestTelemetry { + LATEST_TELEMETRY = 'LATEST_TELEMETRY' +} + +export enum AttributeScope { + CLIENT_SCOPE = 'CLIENT_SCOPE', + SERVER_SCOPE = 'SERVER_SCOPE', + SHARED_SCOPE = 'SHARED_SCOPE' +} + +export type TelemetryType = LatestTelemetry | AttributeScope; + +export const telemetryTypeTranslations = new Map( + [ + [LatestTelemetry.LATEST_TELEMETRY, 'attribute.scope-latest-telemetry'], + [AttributeScope.CLIENT_SCOPE, 'attribute.scope-client'], + [AttributeScope.SERVER_SCOPE, 'attribute.scope-server'], + [AttributeScope.SHARED_SCOPE, 'attribute.scope-shared'] + ] +); + +export const isClientSideTelemetryType = new Map( + [ + [LatestTelemetry.LATEST_TELEMETRY, true], + [AttributeScope.CLIENT_SCOPE, true], + [AttributeScope.SERVER_SCOPE, false], + [AttributeScope.SHARED_SCOPE, false] + ] +); + +export interface AttributeData { + lastUpdateTs: number; + key: string; + value: any; +} diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index 78b3bad20d..44c5b27ac8 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -70,7 +70,7 @@ import {TimeintervalComponent} from '@shared/components/time/timeinterval.compon import {DatetimePeriodComponent} from '@shared/components/time/datetime-period.component'; import {EnumToArrayPipe} from '@shared/pipe/enum-to-array.pipe'; import {ClipboardModule} from 'ngx-clipboard'; -// import { ValueInputComponent } from '@shared/components/value-input.component'; +import { ValueInputComponent } from '@shared/components/value-input.component'; import {FullscreenDirective} from '@shared/components/fullscreen.directive'; import {HighlightPipe} from '@shared/pipe/highlight.pipe'; import {DashboardAutocompleteComponent} from '@shared/components/dashboard-autocomplete.component'; @@ -93,7 +93,6 @@ import { JsonObjectEditComponent } from './components/json-object-edit.component MillisecondsToTimeStringPipe, EnumToArrayPipe, HighlightPipe -// IntervalCountPipe, ], entryComponents: [ TbSnackBarComponent, @@ -116,7 +115,7 @@ import { JsonObjectEditComponent } from './components/json-object-edit.component TimeintervalComponent, DatetimePeriodComponent, DatetimeComponent, -// ValueInputComponent, + ValueInputComponent, DashboardAutocompleteComponent, EntitySubTypeAutocompleteComponent, EntitySubTypeSelectComponent, @@ -202,7 +201,7 @@ import { JsonObjectEditComponent } from './components/json-object-edit.component RelationTypeAutocompleteComponent, SocialSharePanelComponent, JsonObjectEditComponent, -// ValueInputComponent, + ValueInputComponent, MatButtonModule, MatCheckboxModule, MatIconModule, 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 c87564f227..f87e2027ac 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -296,7 +296,9 @@ "add-to-dashboard": "Add to dashboard", "add-widget-to-dashboard": "Add widget to dashboard", "selected-attributes": "{ count, plural, 1 {1 attribute} other {# attributes} } selected", - "selected-telemetry": "{ count, plural, 1 {1 telemetry unit} other {# telemetry units} } selected" + "selected-telemetry": "{ count, plural, 1 {1 telemetry unit} other {# telemetry units} } selected", + "no-attributes-text": "No attributes found", + "no-telemetry-text": "No telemetry found" }, "audit-log": { "audit": "Audit", @@ -1491,11 +1493,14 @@ "type": "Value type", "string": "String", "string-value": "String value", + "string-value-required": "String value is required", "integer": "Integer", "integer-value": "Integer value", + "integer-value-required": "Integer value is required", "invalid-integer-value": "Invalid integer value", "double": "Double", "double-value": "Double value", + "double-value-required": "Double value is required", "boolean": "Boolean", "boolean-value": "Boolean value", "false": "False", From 22d8ce7189f8a32cfd69de2df1d250a8a0cc67f2 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 30 Aug 2019 19:19:45 +0300 Subject: [PATCH 027/133] Implement telemetry websocket service --- ui-ngx/proxy.conf.json | 4 + .../core/ws/telemetry-websocket.service.ts | 309 ++++++++++++++++++ .../attribute/attribute-table.component.html | 3 +- .../attribute/attribute-table.component.ts | 6 +- .../models/datasource/attribute-datasource.ts | 24 +- .../pages/asset/asset-tabs.component.html | 15 + .../customer/customer-tabs.component.html | 15 + .../entity-view-tabs.component.html | 15 + .../rulechain/rulechain-tabs.component.html | 15 + .../pages/tenant/tenant-tabs.component.html | 15 + .../models/telemetry/telemetry.models.ts | 230 ++++++++++++- 11 files changed, 641 insertions(+), 10 deletions(-) create mode 100644 ui-ngx/src/app/core/ws/telemetry-websocket.service.ts diff --git a/ui-ngx/proxy.conf.json b/ui-ngx/proxy.conf.json index 7d4976bc4b..5c77d20da8 100644 --- a/ui-ngx/proxy.conf.json +++ b/ui-ngx/proxy.conf.json @@ -2,5 +2,9 @@ "/api": { "target": "http://localhost:8080", "secure": false + }, + "/api/ws": { + "target": "ws://localhost:8080", + "ws": true } } diff --git a/ui-ngx/src/app/core/ws/telemetry-websocket.service.ts b/ui-ngx/src/app/core/ws/telemetry-websocket.service.ts new file mode 100644 index 0000000000..3eee1d4f02 --- /dev/null +++ b/ui-ngx/src/app/core/ws/telemetry-websocket.service.ts @@ -0,0 +1,309 @@ +/// +/// Copyright © 2016-2019 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 { Inject, Injectable } from '@angular/core'; +import { + AttributesSubscriptionCmd, + GetHistoryCmd, + SubscriptionCmd, + SubscriptionUpdate, + SubscriptionUpdateMsg, + TelemetryFeature, + TelemetryPluginCmdsWrapper, + TelemetryService, + TelemetrySubscriber, + TimeseriesSubscriptionCmd +} from '@app/shared/models/telemetry/telemetry.models'; +import { select, Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { AuthService } from '@core/auth/auth.service'; +import { selectIsAuthenticated } from '@core/auth/auth.selectors'; +import { WINDOW } from '@core/services/window.service'; +import { webSocket, WebSocketSubject } from 'rxjs/webSocket'; +import { ActionNotificationShow } from '@core/notification/notification.actions'; +import Timeout = NodeJS.Timeout; + +const RECONNECT_INTERVAL = 2000; +const WS_IDLE_TIMEOUT = 90000; +const MAX_PUBLISH_COMMANDS = 10; + +@Injectable({ + providedIn: 'root' +}) +export class TelemetryWebsocketService implements TelemetryService { + + isActive = false; + isOpening = false; + isOpened = false; + isReconnect = false; + + socketCloseTimer: Timeout; + reconnectTimer: Timeout; + + lastCmdId = 0; + subscribersCount = 0; + subscribersMap = new Map(); + + reconnectSubscribers = new Set(); + + cmdsWrapper = new TelemetryPluginCmdsWrapper(); + telemetryUri: string; + + dataStream: WebSocketSubject; + + constructor(private store: Store, + private authService: AuthService, + @Inject(WINDOW) private window: Window) { + this.store.pipe(select(selectIsAuthenticated)).subscribe( + (authenticated: boolean) => { + if (!authenticated) { + this.reset(true); + } + } + ); + + let port = this.window.location.port; + if (this.window.location.protocol === 'https:') { + if (!port) { + port = '443'; + } + this.telemetryUri = 'wss:'; + } else { + if (!port) { + port = '80'; + } + this.telemetryUri = 'ws:'; + } + this.telemetryUri += `//${this.window.location.hostname}:${port}/api/ws/plugins/telemetry`; + } + + public subscribe(subscriber: TelemetrySubscriber) { + this.isActive = true; + subscriber.subscriptionCommands.forEach( + (subscriptionCommand) => { + const cmdId = this.nextCmdId(); + this.subscribersMap.set(cmdId, subscriber); + subscriptionCommand.cmdId = cmdId; + if (subscriptionCommand instanceof SubscriptionCmd) { + if (subscriptionCommand.getType() === TelemetryFeature.TIMESERIES) { + this.cmdsWrapper.tsSubCmds.push(subscriptionCommand as TimeseriesSubscriptionCmd); + } else { + this.cmdsWrapper.attrSubCmds.push(subscriptionCommand as AttributesSubscriptionCmd); + } + } else if (subscriptionCommand instanceof GetHistoryCmd) { + this.cmdsWrapper.historyCmds.push(subscriptionCommand); + } + } + ); + this.subscribersCount++; + this.publishCommands(); + } + + public unsubscribe(subscriber: TelemetrySubscriber) { + if (this.isActive) { + subscriber.subscriptionCommands.forEach( + (subscriptionCommand) => { + if (subscriptionCommand instanceof SubscriptionCmd) { + subscriptionCommand.unsubscribe = true; + if (subscriptionCommand.getType() === TelemetryFeature.TIMESERIES) { + this.cmdsWrapper.tsSubCmds.push(subscriptionCommand as TimeseriesSubscriptionCmd); + } else { + this.cmdsWrapper.attrSubCmds.push(subscriptionCommand as AttributesSubscriptionCmd); + } + } + const cmdId = subscriptionCommand.cmdId; + if (cmdId) { + this.subscribersMap.delete(cmdId); + } + } + ); + this.reconnectSubscribers.delete(subscriber); + this.subscribersCount--; + this.publishCommands(); + } + } + + private nextCmdId(): number { + this.lastCmdId++; + return this.lastCmdId; + } + + private publishCommands() { + while (this.isOpened && this.cmdsWrapper.hasCommands()) { + this.dataStream.next(this.cmdsWrapper.preparePublishCommands(MAX_PUBLISH_COMMANDS)); + this.checkToClose(); + } + this.tryOpenSocket(); + } + + private checkToClose() { + if (this.subscribersCount === 0 && this.isOpened) { + if (!this.socketCloseTimer) { + this.socketCloseTimer = setTimeout( + () => this.closeSocket(), WS_IDLE_TIMEOUT); + } + } + } + + private reset(close: boolean) { + if (this.socketCloseTimer) { + clearTimeout(this.socketCloseTimer); + this.socketCloseTimer = null; + } + this.lastCmdId = 0; + this.subscribersMap.clear(); + this.subscribersCount = 0; + this.cmdsWrapper.clear(); + if (close) { + this.closeSocket(); + } + } + + private closeSocket() { + this.isActive = false; + if (this.isOpened) { + this.dataStream.unsubscribe(); + } + } + + private tryOpenSocket() { + if (this.isActive) { + if (!this.isOpened && !this.isOpening) { + this.isOpening = true; + if (AuthService.isJwtTokenValid()) { + this.openSocket(AuthService.getJwtToken()); + } else { + this.authService.refreshJwtToken().subscribe(() => { + this.openSocket(AuthService.getJwtToken()); + }, + () => { + this.isOpening = false; + this.authService.logout(true); + } + ); + } + } + if (this.socketCloseTimer) { + clearTimeout(this.socketCloseTimer); + this.socketCloseTimer = null; + } + } + } + + private openSocket(token: string) { + const uri = `${this.telemetryUri}?token=${token}`; + this.dataStream = webSocket( + { + url: uri, + openObserver: { + next: (e: Event) => { + this.onOpen(); + } + }, + closeObserver: { + next: (e: CloseEvent) => { + this.onClose(e); + } + } + } + ); + + this.dataStream.subscribe((message) => { + this.onMessage(message as SubscriptionUpdateMsg); + }, + (error) => { + this.onError(error); + }); + } + + private onOpen() { + this.isOpening = false; + this.isOpened = true; + if (this.reconnectTimer) { + clearTimeout(this.reconnectTimer); + this.reconnectTimer = null; + } + if (this.isReconnect) { + this.isReconnect = false; + this.reconnectSubscribers.forEach( + (reconnectSubscriber) => { + reconnectSubscriber.onReconnected(); + this.subscribe(reconnectSubscriber); + } + ); + this.reconnectSubscribers.clear(); + } else { + this.publishCommands(); + } + } + + private onMessage(message: SubscriptionUpdateMsg) { + if (message.errorCode) { + this.showWsError(message.errorCode, message.errorMsg); + } else if (message.subscriptionId) { + const subscriber = this.subscribersMap.get(message.subscriptionId); + if (subscriber) { + subscriber.onData(new SubscriptionUpdate(message)); + } + } + this.checkToClose(); + } + + private onError(errorEvent) { + if (errorEvent) { + console.warn('WebSocket error event', errorEvent); + } + this.isOpening = false; + } + + private onClose(closeEvent: CloseEvent) { + if (closeEvent && closeEvent.code > 1000 && closeEvent.code !== 1006) { + this.showWsError(closeEvent.code, closeEvent.reason); + } + this.isOpening = false; + this.isOpened = false; + if (this.isActive) { + if (!this.isReconnect) { + this.reconnectSubscribers.clear(); + this.subscribersMap.forEach( + (subscriber) => { + this.reconnectSubscribers.add(subscriber); + } + ); + this.reset(false); + this.isReconnect = true; + } + if (this.reconnectTimer) { + clearTimeout(this.reconnectTimer); + } + this.reconnectTimer = setTimeout(() => this.tryOpenSocket(), RECONNECT_INTERVAL); + } + } + + private showWsError(errorCode: number, errorMsg: string) { + let message = 'WebSocket Error: '; + if (errorMsg) { + message += errorMsg; + } else { + message += `error code - ${errorCode}.`; + } + this.store.dispatch(new ActionNotificationShow( + { + message, type: 'error' + })); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html index 834cb262fc..4a6f72b54f 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html +++ b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html @@ -85,7 +85,8 @@ {count: dataSource.selection.selected.length}) | async }} - + + + + + +
+
+
+ +
+
+ + + + diff --git a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.scss b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.scss new file mode 100644 index 0000000000..642cec4192 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.scss @@ -0,0 +1,125 @@ +/** + * Copyright © 2016-2019 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 { + + .tb-progress-cover { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 6; + opacity: 1; + } + + .tb-dashboard-content { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background: none; + outline: none; + + gridster-item { + transition: none; + overflow: visible; + } + } + + #gridster-child { + background: none; + } +} + +div.tb-widget { + position: relative; + height: 100%; + margin: 0; + overflow: hidden; + outline: none; + + transition: all .2s ease-in-out; + + .tb-widget-title { + max-height: 65px; + padding-top: 5px; + padding-left: 5px; + overflow: hidden; + + tb-timewindow { + font-size: 14px; + opacity: .85; + } + + .title { + width: 100%; + overflow: hidden; + text-overflow: ellipsis; + line-height: 24px; + letter-spacing: .01em; + margin: 0; + } + } + + .tb-widget-actions { + z-index: 19; + margin: 5px 0 0; + + &-absolute { + position: absolute; + top: 3px; + right: 8px; + } + + button.mat-icon-button { + width: 32px; + min-width: 32px; + height: 32px; + min-height: 32px; + padding: 0 !important; + margin: 0 !important; + line-height: 20px; + + mat-icon { + width: 20px; + min-width: 20px; + height: 20px; + min-height: 20px; + font-size: 20px; + line-height: 20px; + } + } + } + + .tb-widget-content { + tb-widget { + position: relative; + width: 100%; + } + } + + &.tb-highlighted { + border: 1px solid #039be5; + box-shadow: 0 0 20px #039be5; + } + + &.tb-not-highlighted { + opacity: .5; + } +} diff --git a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts new file mode 100644 index 0000000000..9c60de90e4 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts @@ -0,0 +1,312 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, OnInit, Input, ViewChild, AfterViewInit, ViewChildren, QueryList, ElementRef } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { PageComponent } from '@shared/components/page.component'; +import { AuthUser } from '@shared/models/user.model'; +import { getCurrentAuthUser } from '@core/auth/auth.selectors'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { Timewindow } from '@shared/models/time/time.models'; +import { TimeService } from '@core/services/time.service'; +import { GridsterComponent, GridsterConfig, GridsterItemComponent } from 'angular-gridster2'; +import { GridsterResizable } from 'angular-gridster2/lib/gridsterResizable.service'; +import { IDashboardComponent, DashboardConfig, DashboardWidget } from '../../models/dashboard-component.models'; +import { MatSort } from '@angular/material/sort'; +import { Observable, ReplaySubject, merge } from 'rxjs'; +import { map, share, tap } from 'rxjs/operators'; +import { WidgetLayout } from '@shared/models/dashboard.models'; +import { DialogService } from '@core/services/dialog.service'; +import { Widget } from '@app/shared/models/widget.models'; +import { MatTab } from '@angular/material/tabs'; +import { animatedScroll, isDefined } from '@app/core/utils'; +import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; +import { MediaBreakpoints } from '@shared/models/constants'; + +@Component({ + selector: 'tb-dashboard', + templateUrl: './dashboard.component.html', + styleUrls: ['./dashboard.component.scss'] +}) +export class DashboardComponent extends PageComponent implements IDashboardComponent, OnInit, AfterViewInit { + + authUser: AuthUser; + + @Input() + options: DashboardConfig; + + gridsterOpts: GridsterConfig; + + dashboardLoading = true; + + highlightedMode = false; + highlightedWidget: DashboardWidget = null; + selectedWidget: DashboardWidget = null; + + isWidgetExpanded = false; + isMobileSize = false; + + @ViewChild('gridster', {static: true}) gridster: GridsterComponent; + + @ViewChildren(GridsterItemComponent) gridsterItems: QueryList; + + widgets$: Observable>; + + widgets: Array; + + constructor(protected store: Store, + private timeService: TimeService, + private dialogService: DialogService, + private breakpointObserver: BreakpointObserver) { + super(store); + this.authUser = getCurrentAuthUser(store); + } + + ngOnInit(): void { + if (!this.options.dashboardTimewindow) { + this.options.dashboardTimewindow = this.timeService.defaultTimewindow(); + } + this.gridsterOpts = { + gridType: 'scrollVertical', + keepFixedHeightInMobile: true, + pushItems: false, + swap: false, + maxRows: 100, + minCols: this.options.columns ? this.options.columns : 24, + outerMargin: true, + outerMarginLeft: this.options.margins ? this.options.margins[0] : 10, + outerMarginRight: this.options.margins ? this.options.margins[0] : 10, + outerMarginTop: this.options.margins ? this.options.margins[1] : 10, + outerMarginBottom: this.options.margins ? this.options.margins[1] : 10, + minItemCols: 1, + minItemRows: 1, + defaultItemCols: 8, + defaultItemRows: 6, + resizable: {enabled: this.options.isEdit}, + draggable: {enabled: this.options.isEdit} + }; + + this.updateGridsterOpts(); + + this.loadDashboard(); + + merge(this.breakpointObserver + .observe(MediaBreakpoints['gt-sm']), this.options.layoutChange$).subscribe( + () => { + this.updateGridsterOpts(); + this.sortWidgets(this.widgets); + } + ); + } + + loadDashboard() { + this.widgets$ = this.options.widgetsData.pipe( + map(widgetsData => { + const dashboardWidgets = new Array(); + let maxRows = this.gridsterOpts.maxRows; + widgetsData.widgets.forEach( + (widget) => { + let widgetLayout: WidgetLayout; + if (widgetsData.widgetLayouts && widget.id) { + widgetLayout = widgetsData.widgetLayouts[widget.id]; + } + const dashboardWidget = new DashboardWidget(this, widget, widgetLayout); + const bottom = dashboardWidget.y + dashboardWidget.rows; + maxRows = Math.max(maxRows, bottom); + dashboardWidgets.push(dashboardWidget); + } + ); + this.sortWidgets(dashboardWidgets); + this.gridsterOpts.maxRows = maxRows; + return dashboardWidgets; + }), + tap((widgets) => { + this.widgets = widgets; + this.dashboardLoading = false; + }) + ); + } + + reload() { + this.loadDashboard(); + } + + sortWidgets(widgets?: Array) { + if (widgets) { + widgets.sort((widget1, widget2) => { + const row1 = widget1.widgetOrder; + const row2 = widget2.widgetOrder; + let res = row1 - row2; + if (res === 0) { + res = widget1.x - widget2.x; + } + return res; + }); + } + } + + ngAfterViewInit(): void { + } + + isAutofillHeight(): boolean { + if (this.isMobileSize) { + return isDefined(this.options.mobileAutofillHeight) ? this.options.mobileAutofillHeight : false; + } else { + return isDefined(this.options.autofillHeight) ? this.options.autofillHeight : false; + } + } + + loading(): Observable { + return this.isLoading$.pipe( + map(loading => (!this.options.ignoreLoading && loading) || this.dashboardLoading), + share() + ); + } + + openDashboardContextMenu($event: Event) { + // TODO: + // this.dialogService.todo(); + } + + openWidgetContextMenu($event: Event, widget: DashboardWidget) { + // TODO: + // this.dialogService.todo(); + } + + onWidgetFullscreenChanged(expanded: boolean, widget: DashboardWidget) { + this.isWidgetExpanded = expanded; + } + + widgetMouseDown($event: Event, widget: DashboardWidget) { + if (this.options.onWidgetMouseDown) { + this.options.onWidgetMouseDown($event, widget.widget); + } + } + + widgetClicked($event: Event, widget: DashboardWidget) { + if (this.options.onWidgetClicked) { + this.options.onWidgetClicked($event, widget.widget); + } + } + + editWidget($event: Event, widget: DashboardWidget) { + if ($event) { + $event.stopPropagation(); + } + if (this.options.isEditActionEnabled && this.options.onEditWidget) { + this.options.onEditWidget($event, widget.widget); + } + } + + exportWidget($event: Event, widget: DashboardWidget) { + if ($event) { + $event.stopPropagation(); + } + if (this.options.isExportActionEnabled && this.options.onExportWidget) { + this.options.onExportWidget($event, widget.widget); + } + } + + removeWidget($event: Event, widget: DashboardWidget) { + if ($event) { + $event.stopPropagation(); + } + if (this.options.isRemoveActionEnabled && this.options.onRemoveWidget) { + this.options.onRemoveWidget($event, widget.widget); + } + } + + highlightWidget(widget: DashboardWidget, delay?: number) { + if (!this.highlightedMode || this.highlightedWidget !== widget) { + this.highlightedMode = true; + this.highlightedWidget = widget; + this.scrollToWidget(widget, delay); + } + } + + selectWidget(widget: DashboardWidget, delay?: number) { + if (this.selectedWidget !== widget) { + this.selectedWidget = widget; + this.scrollToWidget(widget, delay); + } + } + + resetHighlight() { + this.highlightedMode = false; + this.highlightedWidget = null; + this.selectedWidget = null; + } + + isHighlighted(widget: DashboardWidget) { + return (this.highlightedMode && this.highlightedWidget === widget) || (this.selectedWidget === widget); + } + + isNotHighlighted(widget: DashboardWidget) { + return this.highlightedMode && this.highlightedWidget !== widget; + } + + scrollToWidget(widget: DashboardWidget, delay?: number) { + if (this.gridsterItems) { + const gridsterItem = this.gridsterItems.find((item => item.item === widget)); + const offset = (this.gridster.curHeight - gridsterItem.height) / 2; + let scrollTop = gridsterItem.top; + if (offset > 0) { + scrollTop -= offset; + } + const parentElement = this.gridster.el as HTMLElement; + animatedScroll(parentElement, scrollTop, delay); + } + } + + private updateGridsterOpts() { + this.isMobileSize = this.checkIsMobileSize(); + const mobileBreakPoint = this.isMobileSize ? 20000 : 0; + this.gridsterOpts.mobileBreakpoint = mobileBreakPoint; + const rowSize = this.detectRowSize(this.isMobileSize); + if (this.gridsterOpts.fixedRowHeight !== rowSize) { + this.gridsterOpts.fixedRowHeight = rowSize; + } + if (this.isAutofillHeight()) { + this.gridsterOpts.gridType = 'fit'; + } else { + this.gridsterOpts.gridType = this.isMobileSize ? 'fixed' : 'scrollVertical'; + } + if (this.gridster && this.gridster.options) { + this.gridster.optionsChanged(); + } + } + + private detectRowSize(isMobile: boolean): number | null { + let rowHeight = null; + if (!this.isAutofillHeight()) { + if (isMobile) { + rowHeight = isDefined(this.options.mobileRowHeight) ? this.options.mobileRowHeight : 70; + } + } + return rowHeight; + } + + private checkIsMobileSize(): boolean { + const isMobileDisabled = this.options.isMobileDisabled === true; + let isMobileSize = this.options.isMobile === true && !isMobileDisabled; + if (!isMobileSize && !isMobileDisabled) { + isMobileSize = !this.breakpointObserver.isMatched(MediaBreakpoints['gt-sm']); + } + return isMobileSize; + } + +} diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index d10e52dec8..18970ea7ad 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -34,6 +34,7 @@ import { AlarmDetailsDialogComponent } from '@home/components/alarm/alarm-detail import { AttributeTableComponent } from '@home/components/attribute/attribute-table.component'; import { AddAttributeDialogComponent } from './attribute/add-attribute-dialog.component'; import { EditAttributeValuePanelComponent } from './attribute/edit-attribute-value-panel.component'; +import { DashboardComponent } from '@home/components/dashboard/dashboard.component'; @NgModule({ entryComponents: [ @@ -64,7 +65,8 @@ import { EditAttributeValuePanelComponent } from './attribute/edit-attribute-val AlarmDetailsDialogComponent, AttributeTableComponent, AddAttributeDialogComponent, - EditAttributeValuePanelComponent + EditAttributeValuePanelComponent, + DashboardComponent ], imports: [ CommonModule, @@ -81,7 +83,8 @@ import { EditAttributeValuePanelComponent } from './attribute/edit-attribute-val RelationTableComponent, AlarmTableComponent, AlarmDetailsDialogComponent, - AttributeTableComponent + AttributeTableComponent, + DashboardComponent ] }) export class HomeComponentsModule { } diff --git a/ui-ngx/src/app/modules/home/home.component.ts b/ui-ngx/src/app/modules/home/home.component.ts index 13462e42e2..4d83abb163 100644 --- a/ui-ngx/src/app/modules/home/home.component.ts +++ b/ui-ngx/src/app/modules/home/home.component.ts @@ -54,9 +54,6 @@ export class HomeComponent extends PageComponent implements OnInit { authUser$: Observable; userDetails$: Observable; userDetailsString: Observable; - testUser1$: Observable; - testUser2$: Observable; - testUser3$: Observable; constructor(protected store: Store, private authService: AuthService, 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 new file mode 100644 index 0000000000..d7e7b664bd --- /dev/null +++ b/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts @@ -0,0 +1,310 @@ +/// +/// Copyright © 2016-2019 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 { GridsterConfig, GridsterItem, GridsterComponent } from 'angular-gridster2'; +import { Widget, widgetType } from '@app/shared/models/widget.models'; +import { WidgetLayout, WidgetLayouts } from '@app/shared/models/dashboard.models'; +import { WidgetAction, WidgetContext, WidgetHeaderAction } from './widget-component.models'; +import { Timewindow } from '@shared/models/time/time.models'; +import { Observable } from 'rxjs'; +import { isDefined, isUndefined } from '@app/core/utils'; +import { EventEmitter } from '@angular/core'; + +export interface IAliasController { + [key: string]: any | null; + // TODO: +} + +export interface WidgetsData { + widgets: Array; + widgetLayouts?: WidgetLayouts; +} + +export class DashboardConfig { + widgetsData?: Observable; + isEdit: boolean; + isEditActionEnabled: boolean; + isExportActionEnabled: boolean; + isRemoveActionEnabled: boolean; + onEditWidget?: ($event: Event, widget: Widget) => void; + onExportWidget?: ($event: Event, widget: Widget) => void; + onRemoveWidget?: ($event: Event, widget: Widget) => void; + onWidgetMouseDown?: ($event: Event, widget: Widget) => void; + onWidgetClicked?: ($event: Event, widget: Widget) => void; + aliasController?: IAliasController; + autofillHeight?: boolean; + mobileAutofillHeight?: boolean; + dashboardStyle?: {[klass: string]: any} | null; + columns?: number; + margins?: [number, number]; + dashboardTimewindow?: Timewindow; + ignoreLoading?: boolean; + dashboardClass?: string; + mobileRowHeight?: number; + + private isMobileValue: boolean; + private isMobileDisabledValue: boolean; + + private layoutChange = new EventEmitter(); + layoutChange$ = this.layoutChange.asObservable(); + layoutChangeTimeout = null; + + set isMobile(isMobile: boolean) { + if (this.isMobileValue !== isMobile) { + const changed = isDefined(this.isMobileValue); + this.isMobileValue = isMobile; + if (changed) { + this.notifyLayoutChanged(); + } + } + } + get isMobile(): boolean { + return this.isMobileValue; + } + + set isMobileDisabled(isMobileDisabled: boolean) { + if (this.isMobileDisabledValue !== isMobileDisabled) { + const changed = isDefined(this.isMobileDisabledValue); + this.isMobileDisabledValue = isMobileDisabled; + if (changed) { + this.notifyLayoutChanged(); + } + } + } + get isMobileDisabled(): boolean { + return this.isMobileDisabledValue; + } + + private notifyLayoutChanged() { + if (this.layoutChangeTimeout) { + clearTimeout(this.layoutChangeTimeout); + } + this.layoutChangeTimeout = setTimeout(() => { + this.doNotifyLayoutChanged(); + }, 0); + } + + private doNotifyLayoutChanged() { + this.layoutChange.emit(); + this.layoutChangeTimeout = null; + } +} + +export interface IDashboardComponent { + options: DashboardConfig; + gridsterOpts: GridsterConfig; + gridster: GridsterComponent; + isMobileSize: boolean; +} + +export class DashboardWidget implements GridsterItem { + + isFullscreen = false; + + color: string; + backgroundColor: string; + padding: string; + margin: string; + + title: string; + showTitle: boolean; + titleStyle: {[klass: string]: any}; + + titleIcon: string; + showTitleIcon: boolean; + titleIconStyle: {[klass: string]: any}; + + dropShadow: boolean; + enableFullscreen: boolean; + + hasTimewindow: boolean; + + hasAggregation: boolean; + + style: {[klass: string]: any}; + + hasWidgetTitleTemplate: boolean; + widgetTitleTemplate: string; + + showWidgetTitlePanel: boolean; + showWidgetActions: boolean; + + customHeaderActions: Array; + widgetActions: Array; + + widgetContext: WidgetContext = {}; + + constructor( + private dashboard: IDashboardComponent, + public widget: Widget, + private widgetLayout?: WidgetLayout) { + this.updateWidgetParams(); + } + + updateWidgetParams() { + this.color = this.widget.config.color || 'rgba(0, 0, 0, 0.87)'; + this.backgroundColor = this.widget.config.backgroundColor || '#fff'; + this.padding = this.widget.config.padding || '8px'; + this.margin = this.widget.config.margin || '0px'; + + this.title = isDefined(this.widgetContext.widgetTitle) + && this.widgetContext.widgetTitle.length ? this.widgetContext.widgetTitle : this.widget.config.title; + this.showTitle = isDefined(this.widget.config.showTitle) ? this.widget.config.showTitle : true; + this.titleStyle = this.widget.config.titleStyle ? this.widget.config.titleStyle : {}; + + this.titleIcon = isDefined(this.widget.config.titleIcon) ? this.widget.config.titleIcon : ''; + this.showTitleIcon = isDefined(this.widget.config.showTitleIcon) ? this.widget.config.showTitleIcon : false; + this.titleIconStyle = {}; + if (this.widget.config.iconColor) { + this.titleIconStyle.color = this.widget.config.iconColor; + } + if (this.widget.config.iconSize) { + this.titleIconStyle.fontSize = this.widget.config.iconSize; + } + + this.dropShadow = isDefined(this.widget.config.dropShadow) ? this.widget.config.dropShadow : true; + this.enableFullscreen = isDefined(this.widget.config.enableFullscreen) ? this.widget.config.enableFullscreen : true; + + this.hasTimewindow = (this.widget.type === widgetType.timeseries || this.widget.type === widgetType.alarm) ? + (isDefined(this.widget.config.useDashboardTimewindow) ? + (!this.widget.config.useDashboardTimewindow && (isUndefined(this.widget.config.displayTimewindow) + || this.widget.config.displayTimewindow)) : false) + : false; + + this.hasAggregation = this.widget.type === widgetType.timeseries; + + this.style = {cursor: 'pointer', + color: this.color, + backgroundColor: this.backgroundColor, + padding: this.padding, + margin: this.margin}; + if (this.widget.config.widgetStyle) { + this.style = {...this.widget.config.widgetStyle, ...this.style}; + } + + this.hasWidgetTitleTemplate = this.widgetContext.widgetTitleTemplate ? true : false; + this.widgetTitleTemplate = this.widgetContext.widgetTitleTemplate ? this.widgetContext.widgetTitleTemplate : ''; + + this.showWidgetTitlePanel = this.widgetContext.hideTitlePanel ? false : + this.hasWidgetTitleTemplate || this.showTitle || this.hasTimewindow; + + this.showWidgetActions = this.widgetContext.hideTitlePanel ? false : true; + + this.customHeaderActions = this.widgetContext.customHeaderActions ? this.widgetContext.customHeaderActions : []; + this.widgetActions = this.widgetContext.widgetActions ? this.widgetContext.widgetActions : []; + } + + get x(): number { + if (this.widgetLayout) { + return this.widgetLayout.col; + } else { + return this.widget.col; + } + } + + set x(x: number) { + if (!this.dashboard.isMobileSize) { + if (this.widgetLayout) { + this.widgetLayout.col = x; + } else { + this.widget.col = x; + } + } + } + + get y(): number { + if (this.widgetLayout) { + return this.widgetLayout.row; + } else { + return this.widget.row; + } + } + + set y(y: number) { + if (!this.dashboard.isMobileSize) { + if (this.widgetLayout) { + this.widgetLayout.row = y; + } else { + this.widget.row = y; + } + } + } + + get cols(): number { + if (this.widgetLayout) { + return this.widgetLayout.sizeX; + } else { + return this.widget.sizeX; + } + } + + set cols(cols: number) { + if (!this.dashboard.isMobileSize) { + if (this.widgetLayout) { + this.widgetLayout.sizeX = cols; + } else { + this.widget.sizeX = cols; + } + } + } + + get rows(): number { + if (this.dashboard.isMobileSize && !this.dashboard.options.mobileAutofillHeight) { + let mobileHeight; + if (this.widgetLayout) { + mobileHeight = this.widgetLayout.mobileHeight; + } + if (!mobileHeight && this.widget.config.mobileHeight) { + mobileHeight = this.widget.config.mobileHeight; + } + if (mobileHeight) { + return mobileHeight; + } else { + return this.widget.sizeY * 24 / this.dashboard.gridsterOpts.minCols; + } + } else { + if (this.widgetLayout) { + return this.widgetLayout.sizeY; + } else { + return this.widget.sizeY; + } + } + } + + set rows(rows: number) { + if (!this.dashboard.isMobileSize && !this.dashboard.options.autofillHeight) { + if (this.widgetLayout) { + this.widgetLayout.sizeY = rows; + } else { + this.widget.sizeY = rows; + } + } + } + + get widgetOrder(): number { + let order; + if (this.widgetLayout && isDefined(this.widgetLayout.mobileOrder) && this.widgetLayout.mobileOrder >= 0) { + order = this.widgetLayout.mobileOrder; + } else if (isDefined(this.widget.config.mobileOrder) && this.widget.config.mobileOrder >= 0) { + order = this.widget.config.mobileOrder; + } else if (this.widgetLayout) { + order = this.widgetLayout.row; + } else { + order = this.widget.row; + } + return order; + } +} diff --git a/ui-ngx/src/app/shared/models/widget-type.models.ts b/ui-ngx/src/app/modules/home/models/widget-component.models.ts similarity index 58% rename from ui-ngx/src/app/shared/models/widget-type.models.ts rename to ui-ngx/src/app/modules/home/models/widget-component.models.ts index f081e0942f..4edc6101c3 100644 --- a/ui-ngx/src/app/shared/models/widget-type.models.ts +++ b/ui-ngx/src/app/modules/home/models/widget-component.models.ts @@ -14,20 +14,24 @@ /// limitations under the License. /// -import {BaseData} from '@shared/models/base-data'; -import {TenantId} from '@shared/models/id/tenant-id'; -import {WidgetsBundleId} from '@shared/models/id/widgets-bundle-id'; -import {WidgetTypeId} from '@shared/models/id/widget-type-id'; +export interface IWidgetAction { + icon: string; + onAction: ($event: Event) => void; +} -export interface WidgetTypeDescriptor { - todo: Array; - // TODO: +export interface WidgetHeaderAction extends IWidgetAction { + displayName: string; } -export interface WidgetType extends BaseData { - tenantId: TenantId; - bundleAlias: string; - alias: string; +export interface WidgetAction extends IWidgetAction { name: string; - descriptor: WidgetTypeDescriptor; + show: boolean; +} + +export interface WidgetContext { + widgetTitleTemplate?: string; + hideTitlePanel?: boolean; + widgetTitle?: string; + customHeaderActions?: Array; + widgetActions?: Array; } diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts b/ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts index f5915c8019..01c9784034 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts @@ -14,13 +14,35 @@ /// limitations under the License. /// -import {NgModule} from '@angular/core'; -import {RouterModule, Routes} from '@angular/router'; +import { Injectable, NgModule } from '@angular/core'; +import { ActivatedRouteSnapshot, Resolve, RouterModule, Routes } from '@angular/router'; import {EntitiesTableComponent} from '../../components/entity/entities-table.component'; import {Authority} from '@shared/models/authority.enum'; import {RuleChainsTableConfigResolver} from '@modules/home/pages/rulechain/rulechains-table-config.resolver'; import {WidgetsBundlesTableConfigResolver} from '@modules/home/pages/widget/widgets-bundles-table-config.resolver'; +import { WidgetLibraryComponent } from '@home/pages/widget/widget-library.component'; +import { BreadCrumbConfig } from '@shared/components/breadcrumb'; +import { User } from '@shared/models/user.model'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { UserService } from '@core/http/user.service'; +import { Observable } from 'rxjs'; +import { getCurrentAuthUser } from '@core/auth/auth.selectors'; +import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; +import { WidgetService } from '@core/http/widget.service'; + +@Injectable() +export class WidgetsBundleResolver implements Resolve { + + constructor(private widgetsService: WidgetService) { + } + + resolve(route: ActivatedRouteSnapshot): Observable { + const widgetsBundleId = route.params.widgetsBundleId; + return this.widgetsService.getWidgetsBundle(widgetsBundleId); + } +} const routes: Routes = [ { @@ -42,6 +64,21 @@ const routes: Routes = [ resolve: { entitiesTableConfig: WidgetsBundlesTableConfigResolver } + }, + { + path: ':widgetsBundleId/widgetTypes', + component: WidgetLibraryComponent, + data: { + auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN], + title: 'widget.widget-library', + breadcrumb: { + labelFunction: ((route, translate) => route.data.widgetsBundle.title), + icon: 'now_widgets' + } as BreadCrumbConfig + }, + resolve: { + widgetsBundle: WidgetsBundleResolver + } } ] } @@ -51,7 +88,8 @@ const routes: Routes = [ imports: [RouterModule.forChild(routes)], exports: [RouterModule], providers: [ - WidgetsBundlesTableConfigResolver + WidgetsBundlesTableConfigResolver, + WidgetsBundleResolver ] }) export class WidgetLibraryRoutingModule { } diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.html b/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.html new file mode 100644 index 0000000000..c37b47d155 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.html @@ -0,0 +1,32 @@ + +
+ + widgets-bundle.empty +
+ + + diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.scss b/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.scss new file mode 100644 index 0000000000..a4bed664d4 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.scss @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2019 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 { + button.tb-add-new-widget { + padding-right: 12px; + font-size: 24px; + border-style: dashed; + border-width: 2px; + } +} diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.ts b/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.ts new file mode 100644 index 0000000000..86f15e6b93 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.ts @@ -0,0 +1,194 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, OnInit } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { PageComponent } from '@shared/components/page.component'; +import { AuthUser } from '@shared/models/user.model'; +import { getCurrentAuthUser } from '@core/auth/auth.selectors'; +import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; +import { ActivatedRoute } from '@angular/router'; +import { Authority } from '@shared/models/authority.enum'; +import { NULL_UUID } from '@shared/models/id/has-uuid'; +import { Observable, of } from 'rxjs'; +import { toWidgetInfo, Widget, widgetType } from '@app/shared/models/widget.models'; +import { WidgetService } from '@core/http/widget.service'; +import { map, share } from 'rxjs/operators'; +import { DialogService } from '@core/services/dialog.service'; +import { speedDialFabAnimations } from '@shared/animations/speed-dial-fab.animations'; +import { FooterFabButtons } from '@app/shared/components/footer-fab-buttons.component'; +import { DashboardConfig } from '@home/models/dashboard-component.models'; + +@Component({ + selector: 'tb-widget-library', + templateUrl: './widget-library.component.html', + styleUrls: ['./widget-library.component.scss'] +}) +export class WidgetLibraryComponent extends PageComponent implements OnInit { + + authUser: AuthUser; + + isReadOnly: boolean; + + widgetsBundle: WidgetsBundle; + + widgetTypes$: Observable>; + + footerFabButtons: FooterFabButtons = { + fabTogglerName: 'widget.add-widget-type', + fabTogglerIcon: 'add', + buttons: [ + { + name: 'widget-type.create-new-widget-type', + icon: 'insert_drive_file', + onAction: ($event) => { + this.addWidgetType($event); + } + }, + { + name: 'widget-type.import', + icon: 'file_upload', + onAction: ($event) => { + this.importWidgetType($event); + } + } + ] + }; + + dashboardOptions: DashboardConfig = new DashboardConfig(); + + constructor(protected store: Store, + private route: ActivatedRoute, + private widgetService: WidgetService, + private dialogService: DialogService) { + super(store); + this.dashboardOptions.isEdit = false; + this.dashboardOptions.isEditActionEnabled = true; + this.dashboardOptions.isExportActionEnabled = true; + this.dashboardOptions.onEditWidget = ($event, widget) => { this.openWidgetType($event, widget); }; + this.dashboardOptions.onExportWidget = ($event, widget) => { this.exportWidgetType($event, widget); }; + this.dashboardOptions.onRemoveWidget = ($event, widget) => { this.removeWidgetType($event, widget); }; + + this.authUser = getCurrentAuthUser(store); + this.widgetsBundle = this.route.snapshot.data.widgetsBundle; + if (this.authUser.authority === Authority.TENANT_ADMIN) { + this.isReadOnly = !this.widgetsBundle || this.widgetsBundle.tenantId.id === NULL_UUID; + } else { + this.isReadOnly = this.authUser.authority !== Authority.SYS_ADMIN; + } + this.dashboardOptions.isRemoveActionEnabled = !this.isReadOnly; + this.loadWidgetTypes(); + this.dashboardOptions.widgetsData = this.widgetTypes$.pipe( + map(widgets => ({ widgets }))); + } + + loadWidgetTypes() { + const bundleAlias = this.widgetsBundle.alias; + const isSystem = this.widgetsBundle.tenantId.id === NULL_UUID; + this.widgetTypes$ = this.widgetService.getBundleWidgetTypes(bundleAlias, + isSystem).pipe( + map((types) => { + types = types.sort((a, b) => { + let result = widgetType[b.descriptor.type].localeCompare(widgetType[a.descriptor.type]); + if (result === 0) { + result = b.createdTime - a.createdTime; + } + return result; + }); + const widgetTypes = new Array(types.length); + let top = 0; + const lastTop = [0, 0, 0]; + let col = 0; + let column = 0; + types.forEach((type) => { + const widgetTypeInfo = toWidgetInfo(type); + const sizeX = 8; + const sizeY = Math.floor(widgetTypeInfo.sizeY); + const widget: Widget = { + typeId: type.id, + isSystemType: isSystem, + bundleAlias, + typeAlias: widgetTypeInfo.alias, + type: widgetTypeInfo.type, + title: widgetTypeInfo.widgetName, + sizeX, + sizeY, + row: top, + col, + config: JSON.parse(widgetTypeInfo.defaultConfig) + }; + + widget.config.title = widgetTypeInfo.widgetName; + + widgetTypes.push(widget); + top += sizeY; + if (top > lastTop[column] + 10) { + lastTop[column] = top; + column++; + if (column > 2) { + column = 0; + } + top = lastTop[column]; + col = column * 8; + } + }); + return widgetTypes; + } + ), + share()); + } + + ngOnInit(): void { + } + + addWidgetType($event: Event): void { + this.openWidgetType($event); + } + + importWidgetType($event: Event): void { + if (event) { + event.stopPropagation(); + } + this.dialogService.todo(); + } + + openWidgetType($event: Event, widget?: Widget): void { + if (event) { + event.stopPropagation(); + } + if (widget) { + this.dialogService.todo(); + } else { + this.dialogService.todo(); + } + } + + exportWidgetType($event: Event, widget: Widget): void { + if (event) { + event.stopPropagation(); + } + this.dialogService.todo(); + } + + removeWidgetType($event: Event, widget: Widget): void { + if (event) { + event.stopPropagation(); + } + this.dialogService.todo(); + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-library.module.ts b/ui-ngx/src/app/modules/home/pages/widget/widget-library.module.ts index 713d414dae..e0a1234fae 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widget-library.module.ts +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-library.module.ts @@ -20,13 +20,15 @@ import {SharedModule} from '@shared/shared.module'; import {WidgetsBundleComponent} from '@modules/home/pages/widget/widgets-bundle.component'; import {WidgetLibraryRoutingModule} from '@modules/home/pages/widget/widget-library-routing.module'; import {HomeComponentsModule} from '@modules/home/components/home-components.module'; +import { WidgetLibraryComponent } from './widget-library.component'; @NgModule({ entryComponents: [ WidgetsBundleComponent ], declarations: [ - WidgetsBundleComponent + WidgetsBundleComponent, + WidgetLibraryComponent ], imports: [ CommonModule, diff --git a/ui-ngx/src/app/modules/home/pages/widget/widgets-bundles-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/widget/widgets-bundles-table-config.resolver.ts index cd35f42c19..47f7128af9 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widgets-bundles-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/widget/widgets-bundles-table-config.resolver.ts @@ -135,9 +135,7 @@ export class WidgetsBundlesTableConfigResolver implements Resolve *', animate('200ms cubic-bezier(0.4, 0.0, 0.2, 1)')), + ]), + trigger('speedDialStagger', [ + transition('* => *', [ + + query(':enter', style({ opacity: 0 }), {optional: true}), + + query(':enter', stagger('40ms', + [ + animate('200ms cubic-bezier(0.4, 0.0, 0.2, 1)', + keyframes( + [ + style({opacity: 0, transform: 'translateY(10px)'}), + style({opacity: 1, transform: 'translateY(0)'}), + ] + ) + ) + ] + ), {optional: true}), + + query(':leave', + animate('200ms cubic-bezier(0.4, 0.0, 0.2, 1)', + keyframes([ + style({opacity: 1}), + style({opacity: 0}), + ]) + ), {optional: true} + ) + + ]) + ]) +]; diff --git a/ui-ngx/src/app/shared/components/breadcrumb.component.html b/ui-ngx/src/app/shared/components/breadcrumb.component.html index 2a2de92b2b..7f895270b4 100644 --- a/ui-ngx/src/app/shared/components/breadcrumb.component.html +++ b/ui-ngx/src/app/shared/components/breadcrumb.component.html @@ -16,7 +16,9 @@ -->
'; + if (widgetInfoSubject) { + widgetInfoSubject.next(widgetInfo); + widgetInfoSubject.complete(); + } + this.resolveWidgetsInfoFetchQueue(cacheKey, widgetInfo); + } + + private resolveWidgetsInfoFetchQueue(key: string, widgetInfo: WidgetInfo) { + const fetchQueue = this.widgetsInfoFetchQueue.get(key); + if (fetchQueue) { + fetchQueue.forEach(subject => { + subject.next(widgetInfo); + subject.complete(); + }); + this.widgetsInfoFetchQueue.delete(key); + } + } + + // Cache functions + + private createWidgetInfoCacheKey(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean): string { + return `${isSystem ? 'sys_' : ''}${bundleAlias}_${widgetTypeAlias}`; + } + + private getWidgetInfoFromCache(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean): WidgetInfo | undefined { + const key = this.createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem); + return this.widgetsInfoInMemoryCache.get(key); + } + + private putWidgetInfoToCache(widgetInfo: WidgetInfo, bundleAlias: string, widgetTypeAlias: string, isSystem: boolean) { + const key = this.createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem); + this.widgetsInfoInMemoryCache.set(key, widgetInfo); + } + } diff --git a/ui-ngx/src/app/core/services/resources.service.ts b/ui-ngx/src/app/core/services/resources.service.ts new file mode 100644 index 0000000000..8b1b94d227 --- /dev/null +++ b/ui-ngx/src/app/core/services/resources.service.ts @@ -0,0 +1,83 @@ +/// +/// Copyright © 2016-2019 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 { Injectable, Inject } from '@angular/core'; +import { DOCUMENT } from '@angular/common'; +import { ReplaySubject, Observable, throwError } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class ResourcesService { + + private loadedResources: { [url: string]: ReplaySubject } = {}; + + private anchor = this.document.getElementsByTagName('head')[0] || this.document.getElementsByTagName('body')[0]; + + constructor(@Inject(DOCUMENT) private readonly document: any) {} + + public loadResource(url: string): Observable { + if (this.loadedResources[url]) { + return this.loadedResources[url].asObservable(); + } + + let fileType; + const match = /[.](css|less|html|htm|js)?((\?|#).*)?$/.exec(url); + if (match !== null) { + fileType = match[1]; + } + if (!fileType) { + return throwError(new Error(`Unable to detect file type from url: ${url}`)); + } else if (fileType !== 'css' && fileType !== 'js') { + return throwError(new Error(`Unsupported file type: ${fileType}`)); + } + return this.loadResourceByType(fileType, url); + } + + private loadResourceByType(type: 'css' | 'js', url: string): Observable { + const subject = new ReplaySubject(); + this.loadedResources[url] = subject; + let el; + let loaded = false; + switch (type) { + case 'js': + el = this.document.createElement('script'); + el.type = 'text/javascript'; + el.async = true; + el.src = url; + break; + case 'css': + el = this.document.createElement('link'); + el.type = 'text/css'; + el.rel = 'stylesheet'; + el.href = url; + break; + } + el.onload = el.onreadystatechange = (e) => { + if (el.readyState && !/^c|loade/.test(el.readyState) || loaded) { return; } + el.onload = el.onreadystatechange = null; + loaded = true; + this.loadedResources[url].next(); + this.loadedResources[url].complete(); + }; + el.onerror = () => { + this.loadedResources[url].error(new Error(`Unable to load ${url}`)); + delete this.loadedResources[url]; + }; + this.anchor.appendChild(el); + return subject.asObservable(); + } +} diff --git a/ui-ngx/src/app/core/services/utils.service.ts b/ui-ngx/src/app/core/services/utils.service.ts new file mode 100644 index 0000000000..44f2a25fd0 --- /dev/null +++ b/ui-ngx/src/app/core/services/utils.service.ts @@ -0,0 +1,116 @@ +/// +/// Copyright © 2016-2019 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 { Inject, Injectable } from '@angular/core'; +import { WINDOW } from '@core/services/window.service'; +import { WidgetInfo } from '@shared/models/widget.models'; +import { ExceptionData } from '@app/shared/models/error.models'; +import { isUndefined } from '@core/utils'; +import { WindowMessage } from '@shared/models/window-message.model'; +import { TranslateService } from '@ngx-translate/core'; +import { customTranslationsPrefix } from '@app/shared/models/constants'; + +@Injectable({ + providedIn: 'root' +}) +export class UtilsService { + + iframeMode = false; + widgetEditMode = false; + editWidgetInfo: WidgetInfo = null; + + constructor(@Inject(WINDOW) private window: Window, + private translate: TranslateService) { + let frame: Element = null; + try { + frame = window.frameElement; + } catch (e) { + // ie11 fix + } + if (frame) { + this.iframeMode = true; + const dataWidgetAttr = frame.getAttribute('data-widget'); + if (dataWidgetAttr && dataWidgetAttr.length) { + this.editWidgetInfo = JSON.parse(dataWidgetAttr); + this.widgetEditMode = true; + } + } + } + + public processWidgetException(exception: any): ExceptionData { + const data = this.parseException(exception, -5); + if (this.widgetEditMode) { + const message: WindowMessage = { + type: 'widgetException', + data + }; + this.window.parent.postMessage(message, '*'); + } + return data; + } + + public parseException(exception: any, lineOffset?: number): ExceptionData { + const data: ExceptionData = {}; + if (exception) { + if (typeof exception === 'string') { + data.message = exception; + } else if (exception instanceof String) { + data.message = exception.toString(); + } else { + if (exception.name) { + data.name = exception.name; + } else { + data.name = 'UnknownError'; + } + if (exception.message) { + data.message = exception.message; + } + if (exception.lineNumber) { + data.lineNumber = exception.lineNumber; + if (exception.columnNumber) { + data.columnNumber = exception.columnNumber; + } + } else if (exception.stack) { + const lineInfoRegexp = /(.*):(\d*)(:)?(\d*)?/g; + const lineInfoGroups = lineInfoRegexp.exec(exception.stack); + if (lineInfoGroups != null && lineInfoGroups.length >= 3) { + if (isUndefined(lineOffset)) { + lineOffset = -2; + } + data.lineNumber = Number(lineInfoGroups[2]) + lineOffset; + if (lineInfoGroups.length >= 5) { + data.columnNumber = Number(lineInfoGroups[4]); + } + } + } + } + } + return data; + } + + public customTranslation(translationValue: string, defaultValue: string): string { + let result = ''; + const translationId = customTranslationsPrefix + translationValue; + const translation = this.translate.instant(translationId); + if (translation !== translationId) { + result = translation + ''; + } else { + result = defaultValue; + } + return result; + } + +} diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index 6df5484c7d..e6b60dde17 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -16,6 +16,7 @@ import { BehaviorSubject, Observable, Subject } from 'rxjs'; import { finalize, share } from 'rxjs/operators'; +import base64js from 'base64-js'; export function onParentScrollOrWindowResize(el: Node): Observable { const scrollSubject = new Subject(); @@ -78,6 +79,38 @@ export function isDefined(value: any): boolean { return typeof value !== 'undefined'; } +export function isFunction(value: any): boolean { + return typeof value === 'function'; +} + +export function objToBase64(obj: any): string { + const json = JSON.stringify(obj); + const encoded = utf8Encode(json); + const b64Encoded: string = base64js.fromByteArray(encoded); + return b64Encoded; +} + +export function base64toObj(b64Encoded: string): any { + const encoded: Uint8Array | number[] = base64js.toByteArray(b64Encoded); + const json = utf8Decode(encoded); + const obj = JSON.parse(json); + return obj; +} + +function utf8Encode(str: string): Uint8Array | number[] { + let result: Uint8Array | number[]; + if (isUndefined(Uint8Array)) { + result = utf8ToBytes(str); + } else { + result = new Uint8Array(utf8ToBytes(str)); + } + return result; +} + +function utf8Decode(bytes: Uint8Array | number[]): string { + return utf8Slice(bytes, 0, bytes.length); +} + const scrollRegex = /(auto|scroll)/; function parentNodes(node: Node, nodes: Node[]): Node[] { @@ -138,3 +171,126 @@ function easeInOut( (-remainingTime / 2) * (currentTime * (currentTime - 2) - 1) + startTime ); } + +function utf8Slice(buf: Uint8Array | number[], start: number, end: number): string { + let res = ''; + let tmp = ''; + end = Math.min(buf.length, end || Infinity); + start = start || 0; + + for (let i = start; i < end; i++) { + if (buf[i] <= 0x7F) { + res += decodeUtf8Char(tmp) + String.fromCharCode(buf[i]); + tmp = ''; + } else { + tmp += '%' + buf[i].toString(16); + } + } + return res + decodeUtf8Char(tmp); +} + +function decodeUtf8Char(str: string): string { + try { + return decodeURIComponent(str); + } catch (err) { + return String.fromCharCode(0xFFFD); // UTF 8 invalid char + } +} + +function utf8ToBytes(input: string, units?: number): number[] { + units = units || Infinity; + let codePoint: number; + const length = input.length; + let leadSurrogate: number = null; + const bytes: number[] = []; + let i = 0; + + for (; i < length; i++) { + codePoint = input.charCodeAt(i); + + // is surrogate component + if (codePoint > 0xD7FF && codePoint < 0xE000) { + // last char was a lead + if (leadSurrogate) { + // 2 leads in a row + if (codePoint < 0xDC00) { + units -= 3; + if (units > -1) { bytes.push(0xEF, 0xBF, 0xBD); } + leadSurrogate = codePoint; + continue; + } else { + // valid surrogate pair + // tslint:disable-next-line:no-bitwise + codePoint = leadSurrogate - 0xD800 << 10 | codePoint - 0xDC00 | 0x10000; + leadSurrogate = null; + } + } else { + // no lead yet + + if (codePoint > 0xDBFF) { + // unexpected trail + units -= 3; + if (units > -1) { bytes.push(0xEF, 0xBF, 0xBD); } + continue; + } else if (i + 1 === length) { + // unpaired lead + units -= 3; + if (units > -1) { bytes.push(0xEF, 0xBF, 0xBD); } + continue; + } else { + // valid lead + leadSurrogate = codePoint; + continue; + } + } + } else if (leadSurrogate) { + // valid bmp char, but last char was a lead + units -= 3; + if (units > -1) { bytes.push(0xEF, 0xBF, 0xBD); } + leadSurrogate = null; + } + + // encode utf8 + if (codePoint < 0x80) { + units -= 1; + if (units < 0) { break; } + bytes.push(codePoint); + } else if (codePoint < 0x800) { + units -= 2; + if (units < 0) { break; } + bytes.push( + // tslint:disable-next-line:no-bitwise + codePoint >> 0x6 | 0xC0, + // tslint:disable-next-line:no-bitwise + codePoint & 0x3F | 0x80 + ); + } else if (codePoint < 0x10000) { + units -= 3; + if (units < 0) { break; } + bytes.push( + // tslint:disable-next-line:no-bitwise + codePoint >> 0xC | 0xE0, + // tslint:disable-next-line:no-bitwise + codePoint >> 0x6 & 0x3F | 0x80, + // tslint:disable-next-line:no-bitwise + codePoint & 0x3F | 0x80 + ); + } else if (codePoint < 0x200000) { + units -= 4; + if (units < 0) { break; } + bytes.push( + // tslint:disable-next-line:no-bitwise + codePoint >> 0x12 | 0xF0, + // tslint:disable-next-line:no-bitwise + codePoint >> 0xC & 0x3F | 0x80, + // tslint:disable-next-line:no-bitwise + codePoint >> 0x6 & 0x3F | 0x80, + // tslint:disable-next-line:no-bitwise + codePoint & 0x3F | 0x80 + ); + } else { + throw new Error('Invalid code point'); + } + } + return bytes; +} diff --git a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html index 6017068f6a..c554621a85 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html @@ -16,15 +16,15 @@ -->
+ [ngStyle]="dashboardStyle" + [fxShow]="(((isLoading$ | async) && !this.ignoreLoading) || this.dashboardLoading) && !isEdit">
-
+
- + +
diff --git a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts index 9c60de90e4..f81707aa5f 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts @@ -14,40 +14,108 @@ /// limitations under the License. /// -import { Component, OnInit, Input, ViewChild, AfterViewInit, ViewChildren, QueryList, ElementRef } from '@angular/core'; +import { + AfterViewInit, + Component, + Input, + OnChanges, + OnInit, + QueryList, + SimpleChanges, + ViewChild, + ViewChildren +} from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { PageComponent } from '@shared/components/page.component'; import { AuthUser } from '@shared/models/user.model'; import { getCurrentAuthUser } from '@core/auth/auth.selectors'; -import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { Timewindow } from '@shared/models/time/time.models'; import { TimeService } from '@core/services/time.service'; import { GridsterComponent, GridsterConfig, GridsterItemComponent } from 'angular-gridster2'; -import { GridsterResizable } from 'angular-gridster2/lib/gridsterResizable.service'; -import { IDashboardComponent, DashboardConfig, DashboardWidget } from '../../models/dashboard-component.models'; -import { MatSort } from '@angular/material/sort'; -import { Observable, ReplaySubject, merge } from 'rxjs'; +import { + DashboardCallbacks, + DashboardWidget, + IDashboardComponent, + WidgetsData +} from '../../models/dashboard-component.models'; +import { merge, Observable } from 'rxjs'; import { map, share, tap } from 'rxjs/operators'; import { WidgetLayout } from '@shared/models/dashboard.models'; import { DialogService } from '@core/services/dialog.service'; -import { Widget } from '@app/shared/models/widget.models'; -import { MatTab } from '@angular/material/tabs'; import { animatedScroll, isDefined } from '@app/core/utils'; -import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; +import { BreakpointObserver } from '@angular/cdk/layout'; import { MediaBreakpoints } from '@shared/models/constants'; +import { IAliasController, IStateController } from '@app/core/api/widget-api.models'; @Component({ selector: 'tb-dashboard', templateUrl: './dashboard.component.html', styleUrls: ['./dashboard.component.scss'] }) -export class DashboardComponent extends PageComponent implements IDashboardComponent, OnInit, AfterViewInit { +export class DashboardComponent extends PageComponent implements IDashboardComponent, OnInit, AfterViewInit, OnChanges { authUser: AuthUser; @Input() - options: DashboardConfig; + widgetsData: Observable; + + @Input() + callbacks: DashboardCallbacks; + + @Input() + aliasController: IAliasController; + + @Input() + stateController: IStateController; + + @Input() + columns: number; + + @Input() + horizontalMargin: number; + + @Input() + verticalMargin: number; + + @Input() + isEdit: boolean; + + @Input() + autofillHeight: boolean; + + @Input() + mobileAutofillHeight: boolean; + + @Input() + mobileRowHeight: number; + + @Input() + isMobile: boolean; + + @Input() + isMobileDisabled: boolean; + + @Input() + isEditActionEnabled: boolean; + + @Input() + isExportActionEnabled: boolean; + + @Input() + isRemoveActionEnabled: boolean; + + @Input() + dashboardStyle: {[klass: string]: any}; + + @Input() + dashboardClass: string; + + @Input() + ignoreLoading: boolean; + + @Input() + dashboardTimewindow: Timewindow; gridsterOpts: GridsterConfig; @@ -77,8 +145,8 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo } ngOnInit(): void { - if (!this.options.dashboardTimewindow) { - this.options.dashboardTimewindow = this.timeService.defaultTimewindow(); + if (!this.dashboardTimewindow) { + this.dashboardTimewindow = this.timeService.defaultTimewindow(); } this.gridsterOpts = { gridType: 'scrollVertical', @@ -86,35 +154,65 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo pushItems: false, swap: false, maxRows: 100, - minCols: this.options.columns ? this.options.columns : 24, + minCols: this.columns ? this.columns : 24, outerMargin: true, - outerMarginLeft: this.options.margins ? this.options.margins[0] : 10, - outerMarginRight: this.options.margins ? this.options.margins[0] : 10, - outerMarginTop: this.options.margins ? this.options.margins[1] : 10, - outerMarginBottom: this.options.margins ? this.options.margins[1] : 10, + outerMarginLeft: this.horizontalMargin ? this.horizontalMargin : 10, + outerMarginRight: this.horizontalMargin ? this.horizontalMargin : 10, + outerMarginTop: this.verticalMargin ? this.verticalMargin : 10, + outerMarginBottom: this.horizontalMargin ? this.horizontalMargin : 10, minItemCols: 1, minItemRows: 1, defaultItemCols: 8, defaultItemRows: 6, - resizable: {enabled: this.options.isEdit}, - draggable: {enabled: this.options.isEdit} + resizable: {enabled: this.isEdit}, + draggable: {enabled: this.isEdit}, + itemChangeCallback: item => this.sortWidgets(this.widgets) }; - this.updateGridsterOpts(); + this.updateMobileOpts(); this.loadDashboard(); - merge(this.breakpointObserver - .observe(MediaBreakpoints['gt-sm']), this.options.layoutChange$).subscribe( + this.breakpointObserver + .observe(MediaBreakpoints['gt-sm']).subscribe( () => { - this.updateGridsterOpts(); - this.sortWidgets(this.widgets); + this.updateMobileOpts(); } ); } + ngOnChanges(changes: SimpleChanges): void { + let updateMobileOpts = false; + let updateLayoutOpts = false; + let updateEditingOpts = false; + for (const propName of Object.keys(changes)) { + const change = changes[propName]; + if (!change.firstChange && change.currentValue !== change.previousValue) { + if (['isMobile', 'isMobileDisabled', 'autofillHeight', 'mobileAutofillHeight', 'mobileRowHeight'].includes(propName)) { + updateMobileOpts = true; + } else if (['horizontalMargin', 'verticalMargin'].includes(propName)) { + updateLayoutOpts = true; + } else if (propName === 'isEdit') { + updateEditingOpts = true; + } + } + } + if (updateMobileOpts) { + this.updateMobileOpts(); + } + if (updateLayoutOpts) { + this.updateLayoutOpts(); + } + if (updateEditingOpts) { + this.updateEditingOpts(); + } + if (updateMobileOpts || updateLayoutOpts || updateEditingOpts) { + this.notifyGridsterOptionsChanged(); + } + } + loadDashboard() { - this.widgets$ = this.options.widgetsData.pipe( + this.widgets$ = this.widgetsData.pipe( map(widgetsData => { const dashboardWidgets = new Array(); let maxRows = this.gridsterOpts.maxRows; @@ -164,19 +262,12 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo isAutofillHeight(): boolean { if (this.isMobileSize) { - return isDefined(this.options.mobileAutofillHeight) ? this.options.mobileAutofillHeight : false; + return isDefined(this.mobileAutofillHeight) ? this.mobileAutofillHeight : false; } else { - return isDefined(this.options.autofillHeight) ? this.options.autofillHeight : false; + return isDefined(this.autofillHeight) ? this.autofillHeight : false; } } - loading(): Observable { - return this.isLoading$.pipe( - map(loading => (!this.options.ignoreLoading && loading) || this.dashboardLoading), - share() - ); - } - openDashboardContextMenu($event: Event) { // TODO: // this.dialogService.todo(); @@ -192,14 +283,14 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo } widgetMouseDown($event: Event, widget: DashboardWidget) { - if (this.options.onWidgetMouseDown) { - this.options.onWidgetMouseDown($event, widget.widget); + if (this.callbacks && this.callbacks.onWidgetMouseDown) { + this.callbacks.onWidgetMouseDown($event, widget.widget); } } widgetClicked($event: Event, widget: DashboardWidget) { - if (this.options.onWidgetClicked) { - this.options.onWidgetClicked($event, widget.widget); + if (this.callbacks && this.callbacks.onWidgetClicked) { + this.callbacks.onWidgetClicked($event, widget.widget); } } @@ -207,8 +298,8 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo if ($event) { $event.stopPropagation(); } - if (this.options.isEditActionEnabled && this.options.onEditWidget) { - this.options.onEditWidget($event, widget.widget); + if (this.isEditActionEnabled && this.callbacks && this.callbacks.onEditWidget) { + this.callbacks.onEditWidget($event, widget.widget); } } @@ -216,8 +307,8 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo if ($event) { $event.stopPropagation(); } - if (this.options.isExportActionEnabled && this.options.onExportWidget) { - this.options.onExportWidget($event, widget.widget); + if (this.isExportActionEnabled && this.callbacks && this.callbacks.onExportWidget) { + this.callbacks.onExportWidget($event, widget.widget); } } @@ -225,8 +316,8 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo if ($event) { $event.stopPropagation(); } - if (this.options.isRemoveActionEnabled && this.options.onRemoveWidget) { - this.options.onRemoveWidget($event, widget.widget); + if (this.isRemoveActionEnabled && this.callbacks && this.callbacks.onRemoveWidget) { + this.callbacks.onRemoveWidget($event, widget.widget); } } @@ -272,7 +363,7 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo } } - private updateGridsterOpts() { + private updateMobileOpts() { this.isMobileSize = this.checkIsMobileSize(); const mobileBreakPoint = this.isMobileSize ? 20000 : 0; this.gridsterOpts.mobileBreakpoint = mobileBreakPoint; @@ -285,6 +376,21 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo } else { this.gridsterOpts.gridType = this.isMobileSize ? 'fixed' : 'scrollVertical'; } + } + + private updateLayoutOpts() { + this.gridsterOpts.outerMarginLeft = this.horizontalMargin ? this.horizontalMargin : 10; + this.gridsterOpts.outerMarginRight = this.horizontalMargin ? this.horizontalMargin : 10; + this.gridsterOpts.outerMarginTop = this.verticalMargin ? this.verticalMargin : 10; + this.gridsterOpts.outerMarginBottom = this.horizontalMargin ? this.horizontalMargin : 10; + } + + private updateEditingOpts() { + this.gridsterOpts.resizable.enabled = this.isEdit; + this.gridsterOpts.draggable.enabled = this.isEdit; + } + + private notifyGridsterOptionsChanged() { if (this.gridster && this.gridster.options) { this.gridster.optionsChanged(); } @@ -294,15 +400,15 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo let rowHeight = null; if (!this.isAutofillHeight()) { if (isMobile) { - rowHeight = isDefined(this.options.mobileRowHeight) ? this.options.mobileRowHeight : 70; + rowHeight = isDefined(this.mobileRowHeight) ? this.mobileRowHeight : 70; } } return rowHeight; } private checkIsMobileSize(): boolean { - const isMobileDisabled = this.options.isMobileDisabled === true; - let isMobileSize = this.options.isMobile === true && !isMobileDisabled; + const isMobileDisabled = this.isMobileDisabled === true; + let isMobileSize = this.isMobile === true && !isMobileDisabled; if (!isMobileSize && !isMobileDisabled) { isMobileSize = !this.breakpointObserver.isMatched(MediaBreakpoints['gt-sm']); } diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index 18970ea7ad..9418afc161 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -14,14 +14,14 @@ /// limitations under the License. /// -import {NgModule} from '@angular/core'; -import {CommonModule} from '@angular/common'; -import {SharedModule} from '@app/shared/shared.module'; -import {AddEntityDialogComponent} from './entity/add-entity-dialog.component'; -import {EntitiesTableComponent} from './entity/entities-table.component'; -import {DetailsPanelComponent} from './details-panel.component'; -import {EntityDetailsPanelComponent} from './entity/entity-details-panel.component'; -import {ContactComponent} from './contact.component'; +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from '@app/shared/shared.module'; +import { AddEntityDialogComponent } from './entity/add-entity-dialog.component'; +import { EntitiesTableComponent } from './entity/entities-table.component'; +import { DetailsPanelComponent } from './details-panel.component'; +import { EntityDetailsPanelComponent } from './entity/entity-details-panel.component'; +import { ContactComponent } from './contact.component'; import { AuditLogDetailsDialogComponent } from './audit-log/audit-log-details-dialog.component'; import { AuditLogTableComponent } from './audit-log/audit-log-table.component'; import { EventTableHeaderComponent } from '@home/components/event/event-table-header.component'; @@ -35,6 +35,8 @@ import { AttributeTableComponent } from '@home/components/attribute/attribute-ta import { AddAttributeDialogComponent } from './attribute/add-attribute-dialog.component'; import { EditAttributeValuePanelComponent } from './attribute/edit-attribute-value-panel.component'; import { DashboardComponent } from '@home/components/dashboard/dashboard.component'; +import { WidgetComponent } from '@home/components/widget/widget.component'; +import { DynamicWidgetComponentFactoryService } from './widget/dynamic-widget-component-factory.service'; @NgModule({ entryComponents: [ @@ -66,7 +68,8 @@ import { DashboardComponent } from '@home/components/dashboard/dashboard.compone AttributeTableComponent, AddAttributeDialogComponent, EditAttributeValuePanelComponent, - DashboardComponent + DashboardComponent, + WidgetComponent ], imports: [ CommonModule, @@ -84,7 +87,11 @@ import { DashboardComponent } from '@home/components/dashboard/dashboard.compone AlarmTableComponent, AlarmDetailsDialogComponent, AttributeTableComponent, - DashboardComponent + DashboardComponent, + WidgetComponent + ], + providers: [ + DynamicWidgetComponentFactoryService ] }) export class HomeComponentsModule { } diff --git a/ui-ngx/src/app/modules/home/components/widget/dynamic-widget-component-factory.service.ts b/ui-ngx/src/app/modules/home/components/widget/dynamic-widget-component-factory.service.ts new file mode 100644 index 0000000000..a6d39a1d45 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/dynamic-widget-component-factory.service.ts @@ -0,0 +1,100 @@ +/// +/// Copyright © 2016-2019 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 { + Compiler, + Component, + ComponentFactory, + Injectable, + Injector, + NgModule, + NgModuleRef, + Type, + ViewEncapsulation +} from '@angular/core'; +import { + DynamicWidgetComponent, + DynamicWidgetComponentModule +} from '@home/components/widget/dynamic-widget.component'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from '@shared/shared.module'; +import { Observable, ReplaySubject } from 'rxjs'; +import { HomeComponentsModule } from '../home-components.module'; +import { WidgetComponentsModule } from './widget-components.module'; + +interface DynamicWidgetComponentModuleData { + moduleRef: NgModuleRef; + moduleType: Type; +} + +@Injectable() +export class DynamicWidgetComponentFactoryService { + + private dynamicComponentModulesMap = new Map, DynamicWidgetComponentModuleData>(); + + constructor(private compiler: Compiler, + private injector: Injector) { + } + + public createDynamicWidgetComponentFactory(template: string): Observable> { + const dymamicWidgetComponentFactorySubject = new ReplaySubject>(); + const comp = this.createDynamicWidgetComponent(template); + // noinspection AngularInvalidImportedOrDeclaredSymbol,AngularInvalidEntryComponent + @NgModule({ + declarations: [comp], + entryComponents: [comp], + imports: [CommonModule, SharedModule, WidgetComponentsModule], + }) + class DynamicWidgetComponentInstanceModule extends DynamicWidgetComponentModule {} + this.compiler.compileModuleAsync(DynamicWidgetComponentInstanceModule).then( + (module) => { + const moduleRef = module.create(this.injector); + const factory = moduleRef.componentFactoryResolver.resolveComponentFactory(comp); + this.dynamicComponentModulesMap.set(factory, { + moduleRef, + moduleType: module.moduleType + }); + dymamicWidgetComponentFactorySubject.next(factory); + dymamicWidgetComponentFactorySubject.complete(); + } + ).catch( + (e) => { + dymamicWidgetComponentFactorySubject.error(`Failed to create dynamic widget component factory: ${e}`); + } + ); + return dymamicWidgetComponentFactorySubject.asObservable(); + } + + public destroyDynamicWidgetComponentFactory(factory: ComponentFactory) { + const moduleData = this.dynamicComponentModulesMap.get(factory); + if (moduleData) { + moduleData.moduleRef.destroy(); + this.compiler.clearCacheFor(moduleData.moduleType); + this.dynamicComponentModulesMap.delete(factory); + } + } + + private createDynamicWidgetComponent(template: string): Type { + // noinspection AngularMissingOrInvalidDeclarationInModule + @Component({ + template + }) + class DynamicWidgetInstanceComponent extends DynamicWidgetComponent { } + + return DynamicWidgetInstanceComponent; + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts new file mode 100644 index 0000000000..baa2972456 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts @@ -0,0 +1,63 @@ +/// +/// Copyright © 2016-2019 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 { Input, OnDestroy, OnInit } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { WidgetContext, IDynamicWidgetComponent } from '@home/models/widget-component.models'; +import { ExceptionData } from '@shared/models/error.models'; + +export abstract class DynamicWidgetComponentModule implements OnDestroy { + + ngOnDestroy(): void { + console.log('Module destroyed!'); + } + +} + +export abstract class DynamicWidgetComponent extends PageComponent implements IDynamicWidgetComponent, OnInit, OnDestroy { + + @Input() + widgetContext: WidgetContext; + + @Input() + widgetErrorData: ExceptionData; + + @Input() + loadingData: boolean; + + [key: string]: any; + + constructor(protected store: Store) { + super(store); + } + + ngOnInit() { + + } + + ngOnDestroy(): void { + console.log('Component destroyed!'); + } + + clearRpcError() { + if (this.widgetContext.defaultSubscription) { + this.widgetContext.defaultSubscription.clearRpcError(); + } + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/legend.component.html b/ui-ngx/src/app/modules/home/components/widget/legend.component.html new file mode 100644 index 0000000000..5d538936ef --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/legend.component.html @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + +
{{ 'legend.min' | translate }}{{ 'legend.max' | translate }}{{ 'legend.avg' | translate }}{{ 'legend.total' | translate }}
+ {{ legendKey.dataKey.label }} + {{ legendData.data[legendKey.dataIndex].min }}{{ legendData.data[legendKey.dataIndex].max }}{{ legendData.data[legendKey.dataIndex].avg }}{{ legendData.data[legendKey.dataIndex].total }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/legend.component.scss b/ui-ngx/src/app/modules/home/components/widget/legend.component.scss new file mode 100644 index 0000000000..8df6fc5077 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/legend.component.scss @@ -0,0 +1,68 @@ +/** + * Copyright © 2016-2019 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 { + table.tb-legend { + width: 100%; + font-size: 12px; + + .tb-legend-header, + .tb-legend-value { + text-align: right; + } + + .tb-legend-header { + th { + padding: 0 10px 1px 0; + color: rgb(255, 110, 64); + white-space: nowrap; + } + } + + .tb-legend-keys { + td.tb-legend-label, + td.tb-legend-value { + padding: 2px 10px; + white-space: nowrap; + } + + .tb-legend-line { + display: inline-block; + width: 15px; + height: 3px; + vertical-align: middle; + } + + .tb-legend-label { + text-align: left; + outline: none; + + &.tb-horizontal { + width: 95%; + } + + &.tb-hidden-label { + text-decoration: line-through; + opacity: .6; + } + } + + &.tb-row-direction { + display: inline-block; + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/legend.component.ts b/ui-ngx/src/app/modules/home/components/widget/legend.component.ts new file mode 100644 index 0000000000..aab873e419 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/legend.component.ts @@ -0,0 +1,55 @@ +/// +/// Copyright © 2016-2019 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, Input, OnInit } from '@angular/core'; +import { LegendConfig, LegendData, LegendDirection, LegendPosition } from '@shared/models/widget.models'; + +@Component({ + selector: 'tb-legend', + templateUrl: './legend.component.html', + styleUrls: ['./legend.component.scss'] +}) +export class LegendComponent implements OnInit { + + @Input() + legendConfig: LegendConfig; + + @Input() + legendData: LegendData; + + displayHeader: boolean; + + isHorizontal: boolean; + + isRowDirection: boolean; + + ngOnInit(): void { + this.displayHeader = this.legendConfig.showMin === true || + this.legendConfig.showMax === true || + this.legendConfig.showAvg === true || + this.legendConfig.showTotal === true; + + this.isHorizontal = this.legendConfig.position === LegendPosition.bottom || + this.legendConfig.position === LegendPosition.top; + + this.isRowDirection = this.legendConfig.direction === LegendDirection.row; + } + + toggleHideData(index: number) { + this.legendData.keys[index].dataKey.hidden = !this.legendData.keys[index].dataKey.hidden; + } + +} 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 new file mode 100644 index 0000000000..06e30fab0a --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts @@ -0,0 +1,38 @@ +/// +/// Copyright © 2016-2019 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 { CommonModule } from '@angular/common'; +import { SharedModule } from '@app/shared/shared.module'; +import { AlarmDetailsDialogComponent } from '@home/components/alarm/alarm-details-dialog.component'; +import { LegendComponent } from '@home/components/widget/legend.component'; + +@NgModule({ + entryComponents: [ + ], + declarations: + [ + LegendComponent + ], + imports: [ + CommonModule, + SharedModule + ], + exports: [ + LegendComponent + ] +}) +export class WidgetComponentsModule { } diff --git a/ui-ngx/src/app/modules/home/components/widget/widget.component.html b/ui-ngx/src/app/modules/home/components/widget/widget.component.html new file mode 100644 index 0000000000..2ec95ac4c8 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/widget.component.html @@ -0,0 +1,18 @@ + + diff --git a/ui-ngx/src/app/modules/home/components/widget/widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/widget.component.scss new file mode 100644 index 0000000000..251c070267 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/widget.component.scss @@ -0,0 +1,47 @@ +/** + * Copyright © 2016-2019 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-widget { + .tb-widget-error { + display: flex; + align-items: center; + justify-content: center; + background: rgba(255, 255, 255, .5); + + span { + color: #f00; + } + } + + .tb-widget-loading { + z-index: 3; + background: rgba(255, 255, 255, .15); + } + + .tb-widget-error-container { + position: absolute; + width: 100%; + height: 100%; + background-color: #fff; + } + + .tb-widget-error-msg { + padding: 5px; + font-size: 16px; + color: #f00; + word-wrap: break-word; + } +} 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 new file mode 100644 index 0000000000..99005a25f4 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/widget.component.ts @@ -0,0 +1,758 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + AfterViewInit, + Component, + ComponentFactory, + ComponentFactoryResolver, + ComponentRef, + ElementRef, + Injector, + Input, + OnChanges, + OnDestroy, + OnInit, + SimpleChanges, + ViewChild, + ViewContainerRef, + ViewEncapsulation +} from '@angular/core'; +import { DashboardWidget, IDashboardComponent } from '@home/models/dashboard-component.models'; +import { + LegendConfig, + LegendData, + LegendPosition, + Widget, + WidgetActionDescriptor, + WidgetActionType, + WidgetInfo, WidgetResource, + widgetType, + WidgetTypeInstance, + widgetActionSources +} from '@shared/models/widget.models'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { WidgetService } from '@core/http/widget.service'; +import { UtilsService } from '@core/services/utils.service'; +import { DynamicWidgetComponent } from '@home/components/widget/dynamic-widget.component'; +import { forkJoin, Observable, of, ReplaySubject, throwError } from 'rxjs'; +import { DynamicWidgetComponentFactoryService } from '@home/components/widget/dynamic-widget-component-factory.service'; +import { isDefined, objToBase64 } from '@core/utils'; +import * as $ from 'jquery'; +import { WidgetContext, WidgetHeaderAction } from '@home/models/widget-component.models'; +import { + EntityInfo, + IWidgetSubscription, + SubscriptionInfo, + WidgetSubscriptionOptions, + StateObject, + StateParams, + WidgetSubscriptionContext +} from '@core/api/widget-api.models'; +import { EntityId } from '@shared/models/id/entity-id'; +import { ActivatedRoute, Router, UrlSegment } from '@angular/router'; +import cssjs from '@core/css/css'; +import { ResourcesService } from '@core/services/resources.service'; +import { catchError, switchMap } from 'rxjs/operators'; +import { ActionNotificationShow } from '@core/notification/notification.actions'; +import { TimeService } from '@core/services/time.service'; +import { DeviceService } from '@app/core/http/device.service'; +import { AlarmService } from '@app/core/http/alarm.service'; +import { ExceptionData } from '@shared/models/error.models'; + +@Component({ + selector: 'tb-widget', + templateUrl: './widget.component.html', + styleUrls: ['./widget.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class WidgetComponent extends PageComponent implements OnInit, AfterViewInit, OnChanges, OnDestroy { + + @Input() + isEdit: boolean; + + @Input() + isMobile: boolean; + + @Input() + dashboard: IDashboardComponent; + + @Input() + dashboardWidget: DashboardWidget; + + @ViewChild('widgetContent', {read: ViewContainerRef, static: true}) widgetContentContainer: ViewContainerRef; + + widget: Widget; + widgetInfo: WidgetInfo; + widgetContext: WidgetContext; + widgetType: any; + widgetTypeInstance: WidgetTypeInstance; + widgetErrorData: ExceptionData; + + dynamicWidgetComponentFactory: ComponentFactory; + dynamicWidgetComponentRef: ComponentRef; + dynamicWidgetComponent: DynamicWidgetComponent; + + subscriptionContext: WidgetSubscriptionContext; + + subscriptionInited = false; + widgetSizeDetected = false; + + onResizeListener = this.onResize.bind(this); + + private cssParser = new cssjs(); + + constructor(protected store: Store, + private route: ActivatedRoute, + private router: Router, + private dynamicWidgetComponentFactoryService: DynamicWidgetComponentFactoryService, + private componentFactoryResolver: ComponentFactoryResolver, + private elementRef: ElementRef, + private injector: Injector, + private widgetService: WidgetService, + private resources: ResourcesService, + private timeService: TimeService, + private deviceService: DeviceService, + private alarmService: AlarmService, + private utils: UtilsService) { + super(store); + } + + ngOnInit(): void { + this.widget = this.dashboardWidget.widget; + + const actionDescriptorsBySourceId: {[actionSourceId: string]: Array} = {}; + if (this.widget.config.actions) { + for (const actionSourceId of Object.keys(this.widget.config.actions)) { + const descriptors = this.widget.config.actions[actionSourceId]; + const actionDescriptors: Array = []; + descriptors.forEach((descriptor) => { + const actionDescriptor: WidgetActionDescriptor = {...descriptor}; + actionDescriptor.displayName = this.utils.customTranslation(descriptor.name, descriptor.name); + actionDescriptors.push(actionDescriptor); + }); + actionDescriptorsBySourceId[actionSourceId] = actionDescriptors; + } + } + + this.widgetContext = this.dashboardWidget.widgetContext; + this.widgetContext.inited = false; + this.widgetContext.hideTitlePanel = false; + this.widgetContext.isEdit = this.isEdit; + this.widgetContext.isMobile = this.isMobile; + this.widgetContext.dashboard = this.dashboard; + this.widgetContext.widgetConfig = this.widget.config; + this.widgetContext.settings = this.widget.config.settings; + this.widgetContext.units = this.widget.config.units || ''; + this.widgetContext.decimals = isDefined(this.widget.config.decimals) ? this.widget.config.decimals : 2; + this.widgetContext.subscriptions = {}; + this.widgetContext.defaultSubscription = null; + this.widgetContext.dashboardTimewindow = this.dashboard.dashboardTimewindow; + this.widgetContext.timewindowFunctions = { + onUpdateTimewindow: (startTimeMs, endTimeMs, interval) => { + if (this.widgetContext.defaultSubscription) { + this.widgetContext.defaultSubscription.onUpdateTimewindow(startTimeMs, endTimeMs, interval); + } + }, + onResetTimewindow: () => { + if (this.widgetContext.defaultSubscription) { + this.widgetContext.defaultSubscription.onResetTimewindow(); + } + } + }; + this.widgetContext.subscriptionApi = { + createSubscription: this.createSubscription.bind(this), + createSubscriptionFromInfo: this.createSubscriptionFromInfo.bind(this), + removeSubscription: (id) => { + const subscription = this.widgetContext.subscriptions[id]; + if (subscription) { + subscription.destroy(); + delete this.widgetContext.subscriptions[id]; + } + } + }; + this.widgetContext.controlApi = { + sendOneWayCommand: (method, params, timeout) => { + if (this.widgetContext.defaultSubscription) { + return this.widgetContext.defaultSubscription.sendOneWayCommand(method, params, timeout); + } else { + return of(null); + } + }, + sendTwoWayCommand: (method, params, timeout) => { + if (this.widgetContext.defaultSubscription) { + return this.widgetContext.defaultSubscription.sendTwoWayCommand(method, params, timeout); + } else { + return of(null); + } + } + }; + this.widgetContext.utils = { + formatValue: this.formatValue + }; + this.widgetContext.actionsApi = { + actionDescriptorsBySourceId, + getActionDescriptors: this.getActionDescriptors.bind(this), + handleWidgetAction: this.handleWidgetAction.bind(this), + elementClick: this.elementClick.bind(this) + }; + this.widgetContext.stateController = this.dashboard.stateController; + this.widgetContext.aliasController = this.dashboard.aliasController; + + this.widgetContext.customHeaderActions = []; + const headerActionsDescriptors = this.getActionDescriptors(widgetActionSources.headerButton.value); + headerActionsDescriptors.forEach((descriptor) => { + const headerAction: WidgetHeaderAction = { + name: descriptor.name, + displayName: descriptor.displayName, + icon: descriptor.icon, + descriptor, + onAction: $event => { + const entityInfo = this.getActiveEntityInfo(); + const entityId = entityInfo ? entityInfo.entityId : null; + const entityName = entityInfo ? entityInfo.entityName : null; + this.handleWidgetAction($event, descriptor, entityId, entityName); + } + }; + this.widgetContext.customHeaderActions.push(headerAction); + }); + + + this.subscriptionContext = { + timeService: this.timeService, + deviceService: this.deviceService, + alarmService: this.alarmService, + utils: this.utils, + widgetUtils: this.widgetContext.utils, + dashboardTimewindowApi: null, // TODO: + getServerTimeDiff: null, // TODO: + aliasController: this.dashboard.aliasController + }; + + this.widgetService.getWidgetInfo(this.widget.bundleAlias, this.widget.typeAlias, this.widget.isSystemType).subscribe( + (widgetInfo) => { + this.widgetInfo = widgetInfo; + this.loadFromWidgetInfo(); + } + ); + + } + + ngAfterViewInit(): void { + } + + ngOnDestroy(): void { + + for (const id of Object.keys(this.widgetContext.subscriptions)) { + const subscription = this.widgetContext.subscriptions[id]; + subscription.destroy(); + } + this.subscriptionInited = false; + this.widgetContext.subscriptions = {}; + if (this.widgetContext.inited) { + this.widgetContext.inited = false; + // TODO: + try { + this.widgetTypeInstance.onDestroy(); + } catch (e) { + this.handleWidgetException(e); + } + } + this.destroyDynamicWidgetComponent(); + } + + ngOnChanges(changes: SimpleChanges): void { + for (const propName of Object.keys(changes)) { + const change = changes[propName]; + if (!change.firstChange && change.currentValue !== change.previousValue) { + if (propName === 'isEdit') { + console.log(`isEdit changed: ${this.isEdit}`); + this.onEditModeChanged(); + } else if (propName === 'isMobile') { + console.log(`isMobile changed: ${this.isMobile}`); + this.onMobileModeChanged(); + } + } + } + } + + private onEditModeChanged() { + if (this.widgetContext.isEdit !== this.isEdit) { + this.widgetContext.isEdit = this.isEdit; + if (this.widgetContext.inited) { + // TODO: + } + } + } + + private onMobileModeChanged() { + if (this.widgetContext.isMobile !== this.isMobile) { + this.widgetContext.isMobile = this.isMobile; + if (this.widgetContext.inited) { + // TODO: + } + } + } + + private onResize() { + if (this.checkSize()) { + if (this.widgetContext.inited) { + // TODO: + } + } + } + + private loadFromWidgetInfo() { + const widgetNamespace = `widget-type-${(this.widget.isSystemType ? 'sys-' : '')}${this.widget.bundleAlias}-${this.widget.typeAlias}`; + const elem = this.elementRef.nativeElement; + elem.classList.add('tb-widget'); + elem.classList.add(widgetNamespace); + this.widgetType = this.widgetInfo.widgetTypeFunction; + + if (!this.widgetType) { + this.widgetTypeInstance = {}; + } else { + try { + this.widgetTypeInstance = new this.widgetType(this.widgetContext); + } catch (e) { + this.handleWidgetException(e); + this.widgetTypeInstance = {}; + } + } + if (!this.widgetTypeInstance.onInit) { + this.widgetTypeInstance.onInit = () => {}; + } + if (!this.widgetTypeInstance.onDataUpdated) { + this.widgetTypeInstance.onDataUpdated = () => {}; + } + if (!this.widgetTypeInstance.onResize) { + this.widgetTypeInstance.onResize = () => {}; + } + if (!this.widgetTypeInstance.onEditModeChanged) { + this.widgetTypeInstance.onEditModeChanged = () => {}; + } + if (!this.widgetTypeInstance.onMobileModeChanged) { + this.widgetTypeInstance.onMobileModeChanged = () => {}; + } + if (!this.widgetTypeInstance.onDestroy) { + this.widgetTypeInstance.onDestroy = () => {}; + } + + this.initialize(); + } + + private reInit() { + this.ngOnDestroy(); + this.initialize(); + // TODO: + } + + private initialize() { + this.configureDynamicWidgetComponent().subscribe( + () => { + this.dynamicWidgetComponent.loadingData = false; + }, + (error) => { + // TODO: + } + ); + } + + private destroyDynamicWidgetComponent() { + if (this.widgetContext.$containerParent) { + // @ts-ignore + removeResizeListener(this.widgetContext.$containerParent[0], this.onResizeListener); + } + if (this.dynamicWidgetComponentRef) { + this.dynamicWidgetComponentRef.destroy(); + } + if (this.dynamicWidgetComponentFactory) { + this.dynamicWidgetComponentFactoryService.destroyDynamicWidgetComponentFactory(this.dynamicWidgetComponentFactory); + } + } + + private handleWidgetException(e) { + console.error(e); + this.widgetErrorData = this.utils.processWidgetException(e); + if (this.dynamicWidgetComponent) { + this.dynamicWidgetComponent.widgetErrorData = this.widgetErrorData; + } + } + + private configureDynamicWidgetComponent(): Observable { + + const dynamicWidgetComponentSubject = new ReplaySubject(); + + let html = '
' + + 'Widget Error: {{ widgetErrorData.name + ": " + widgetErrorData.message}}' + + '
' + + '
' + + '' + + '
'; + + let containerHtml = `
${this.widgetInfo.templateHtml}
`; + + const displayLegend = isDefined(this.widget.config.showLegend) ? this.widget.config.showLegend + : this.widget.type === widgetType.timeseries; + + let legendConfig: LegendConfig; + let legendData: LegendData; + if (displayLegend) { + legendConfig = this.widget.config.legendConfig || + { + position: LegendPosition.bottom, + showMin: false, + showMax: false, + showAvg: this.widget.type === widgetType.timeseries, + showTotal: false + }; + legendData = { + keys: [], + data: [] + }; + let layoutType; + if (legendConfig.position === LegendPosition.top || + legendConfig.position === LegendPosition.bottom) { + layoutType = 'column'; + } else { + layoutType = 'row'; + } + let legendStyle; + switch (legendConfig.position) { + case LegendPosition.top: + legendStyle = 'padding-bottom: 8px; max-height: 50%; overflow-y: auto;'; + break; + case LegendPosition.bottom: + legendStyle = 'padding-top: 8px; max-height: 50%; overflow-y: auto;'; + break; + case LegendPosition.left: + legendStyle = 'padding-right: 0px; max-width: 50%; overflow-y: auto;'; + break; + case LegendPosition.right: + legendStyle = 'padding-left: 0px; max-width: 50%; overflow-y: auto;'; + break; + } + + const legendHtml = ``; + containerHtml = `
${containerHtml}
`; + html += `
`; + if (legendConfig.position === LegendPosition.top || + legendConfig.position === LegendPosition.left) { + html += legendHtml; + html += containerHtml; + } else { + html += containerHtml; + html += legendHtml; + } + html += '
'; + } else { + html += containerHtml; + } + + this.dynamicWidgetComponentFactoryService.createDynamicWidgetComponentFactory(html).subscribe( + (componentFactory) => { + this.dynamicWidgetComponentFactory = componentFactory; + this.widgetContentContainer.clear(); + this.dynamicWidgetComponentRef = this.widgetContentContainer.createComponent(this.dynamicWidgetComponentFactory); + this.dynamicWidgetComponent = this.dynamicWidgetComponentRef.instance; + + this.dynamicWidgetComponent.loadingData = true; + this.dynamicWidgetComponent.widgetContext = this.widgetContext; + this.dynamicWidgetComponent.widgetErrorData = this.widgetErrorData; + this.dynamicWidgetComponent.displayLegend = displayLegend; + this.dynamicWidgetComponent.legendConfig = legendConfig; + this.dynamicWidgetComponent.legendData = legendData; + + this.widgetContext.$scope = this.dynamicWidgetComponent; + + const containerElement = displayLegend ? $(this.elementRef.nativeElement.querySelector('#widget-container')) + : $(this.elementRef.nativeElement); + + this.widgetContext.$container = $('#container', containerElement); + this.widgetContext.$containerParent = $(containerElement); + + if (this.widgetSizeDetected) { + this.widgetContext.$container.css('height', this.widgetContext.height + 'px'); + this.widgetContext.$container.css('width', this.widgetContext.width + 'px'); + } + + // @ts-ignore + addResizeListener(this.widgetContext.$containerParent[0], this.onResizeListener); + + dynamicWidgetComponentSubject.next(); + dynamicWidgetComponentSubject.complete(); + }, + (e) => { + dynamicWidgetComponentSubject.error(e); + } + ); + return dynamicWidgetComponentSubject.asObservable(); + } + + private createSubscription(options: WidgetSubscriptionOptions, subscribe: boolean): Observable { + // TODO: + return of(null); + } + + private createSubscriptionFromInfo(type: widgetType, subscriptionsInfo: Array, + options: WidgetSubscriptionOptions, useDefaultComponents: boolean, + subscribe: boolean): Observable { + // TODO: + return of(null); + } + + private isNumeric(value: any): boolean { + return (value - parseFloat( value ) + 1) >= 0; + } + + private formatValue(value: any, dec?: number, units?: string, showZeroDecimals?: boolean): string | undefined { + if (isDefined(value) && + value != null && this.isNumeric(value)) { + let formatted: string | number = Number(value); + if (isDefined(dec)) { + formatted = formatted.toFixed(dec); + } + if (!showZeroDecimals) { + formatted = (Number(formatted) * 1); + } + formatted = formatted.toString(); + if (isDefined(units) && units.length > 0) { + formatted += ' ' + units; + } + return formatted; + } else { + return value; + } + } + + private getActionDescriptors(actionSourceId: string): Array { + let result = this.widgetContext.actionsApi.actionDescriptorsBySourceId[actionSourceId]; + if (!result) { + result = []; + } + return result; + } + + private handleWidgetAction($event: Event, descriptor: WidgetActionDescriptor, + entityId?: EntityId, entityName?: string, additionalParams?: any): void { + const type = descriptor.type; + const targetEntityParamName = descriptor.stateEntityParamName; + let targetEntityId: EntityId; + if (descriptor.setEntityId) { + targetEntityId = entityId; + } + switch (type) { + case WidgetActionType.openDashboardState: + case WidgetActionType.updateDashboardState: + let targetDashboardStateId = descriptor.targetDashboardStateId; + const params = {...this.widgetContext.stateController.getStateParams()}; + this.updateEntityParams(params, targetEntityParamName, targetEntityId, entityName); + if (type === WidgetActionType.openDashboardState) { + this.widgetContext.stateController.openState(targetDashboardStateId, params, descriptor.openRightLayout); + } else { + this.widgetContext.stateController.updateState(targetDashboardStateId, params, descriptor.openRightLayout); + } + break; + case WidgetActionType.openDashboard: + const targetDashboardId = descriptor.targetDashboardId; + targetDashboardStateId = descriptor.targetDashboardStateId; + const stateObject: StateObject = {}; + stateObject.params = {}; + this.updateEntityParams(stateObject.params, targetEntityParamName, targetEntityId, entityName); + if (targetDashboardStateId) { + stateObject.id = targetDashboardStateId; + } + const stateParams = { + dashboardId: targetDashboardId, + state: objToBase64([ stateObject ]) + }; + const state = objToBase64([ stateObject ]); + const currentUrl = this.route.snapshot.url; + let url; + if (currentUrl.length > 1) { + if (currentUrl[currentUrl.length - 2].path === 'dashboard') { + url = `/dashboard/${targetDashboardId}?state=${state}`; + } else { + url = `/dashboards/${targetDashboardId}?state=${state}`; + } + } + if (url) { + const urlTree = this.router.parseUrl(url); + this.router.navigateByUrl(url); + } + break; + case WidgetActionType.custom: + const customFunction = descriptor.customFunction; + if (isDefined(customFunction) && customFunction.length > 0) { + try { + if (!additionalParams) { + additionalParams = {}; + } + const customActionFunction = new Function('$event', 'widgetContext', 'entityId', + 'entityName', 'additionalParams', customFunction); + customActionFunction($event, this.widgetContext, entityId, entityName, additionalParams); + } catch (e) { + // + } + } + break; + case WidgetActionType.customPretty: + const customPrettyFunction = descriptor.customFunction; + const customHtml = descriptor.customHtml; + const customCss = descriptor.customCss; + const customResources = descriptor.customResources; + const actionNamespace = `custom-action-pretty-${descriptor.name.toLowerCase()}`; + let htmlTemplate = ''; + if (isDefined(customHtml) && customHtml.length > 0) { + htmlTemplate = customHtml; + } + this.loadCustomActionResources(actionNamespace, customCss, customResources).subscribe( + () => { + if (isDefined(customPrettyFunction) && customPrettyFunction.length > 0) { + try { + if (!additionalParams) { + additionalParams = {}; + } + const customActionPrettyFunction = new Function('$event', 'widgetContext', 'entityId', + 'entityName', 'htmlTemplate', 'additionalParams', customPrettyFunction); + customActionPrettyFunction($event, this.widgetContext, entityId, entityName, htmlTemplate, additionalParams); + } catch (e) { + // + } + } + }, + (errorMessages: string[]) => { + this.processResourcesLoadErrors(errorMessages); + } + ); + break; + } + } + + private elementClick($event: Event) { + $event.stopPropagation(); + const e = ($event.target || $event.srcElement) as Element; + if (e.id) { + const descriptors = this.getActionDescriptors('elementClick'); + if (descriptors.length) { + descriptors.forEach((descriptor) => { + if (descriptor.name === e.id) { + const entityInfo = this.getActiveEntityInfo(); + const entityId = entityInfo ? entityInfo.entityId : null; + const entityName = entityInfo ? entityInfo.entityName : null; + this.handleWidgetAction(event, descriptor, entityId, entityName); + } + }); + } + } + } + + private updateEntityParams(params: StateParams, targetEntityParamName?: string, targetEntityId?: EntityId, entityName?: string) { + if (targetEntityId) { + let targetEntityParams: StateParams; + if (targetEntityParamName && targetEntityParamName.length) { + targetEntityParams = params[targetEntityParamName]; + if (!targetEntityParams) { + targetEntityParams = {}; + params[targetEntityParamName] = targetEntityParams; + params.targetEntityParamName = targetEntityParamName; + } + } else { + targetEntityParams = params; + } + targetEntityParams.entityId = targetEntityId; + if (entityName) { + targetEntityParams.entityName = entityName; + } + } + } + + private loadCustomActionResources(actionNamespace: string, customCss: string, customResources: Array): Observable { + if (isDefined(customCss) && customCss.length > 0) { + this.cssParser.cssPreviewNamespace = actionNamespace; + this.cssParser.createStyleElement(actionNamespace, customCss, 'nonamespace'); + } + const resourceTasks: Observable[] = []; + if (customResources.length > 0) { + customResources.forEach((resource) => { + resourceTasks.push( + this.resources.loadResource(resource.url).pipe( + catchError(e => of(`Failed to load custom action resource: '${resource.url}'`)) + ) + ); + }); + return forkJoin(resourceTasks).pipe( + switchMap(msgs => { + let errors: string[]; + if (msgs && msgs.length) { + errors = msgs.filter(msg => msg && msg.length > 0); + } + if (errors && errors.length) { + return throwError(errors); + } else { + return of(null); + } + } + )); + } else { + return of(null); + } + } + + private processResourcesLoadErrors(errorMessages: string[]) { + let messageToShow = ''; + errorMessages.forEach(error => { + messageToShow += `
${error}
`; + }); + this.store.dispatch(new ActionNotificationShow({message: messageToShow, type: 'error'})); + } + + private getActiveEntityInfo(): EntityInfo { + let entityInfo = this.widgetContext.activeEntityInfo; + if (!entityInfo) { + for (const id of Object.keys(this.widgetContext.subscriptions)) { + const subscription = this.widgetContext.subscriptions[id]; + entityInfo = subscription.getFirstEntityInfo(); + if (entityInfo) { + break; + } + } + } + return entityInfo; + } + + private checkSize(): boolean { + const width = this.widgetContext.$containerParent.width(); + const height = this.widgetContext.$containerParent.height(); + let sizeChanged = false; + + if (!this.widgetContext.width || this.widgetContext.width !== width || + !this.widgetContext.height || this.widgetContext.height !== height) { + if (width > 0 && height > 0) { + this.widgetContext.$container.css('height', height + 'px'); + this.widgetContext.$container.css('width', width + 'px'); + this.widgetContext.width = width; + this.widgetContext.height = height; + sizeChanged = true; + this.widgetSizeDetected = true; + } + } + return sizeChanged; + } + +} 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 d7e7b664bd..4520319c6c 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 @@ -22,92 +22,33 @@ import { Timewindow } from '@shared/models/time/time.models'; import { Observable } from 'rxjs'; import { isDefined, isUndefined } from '@app/core/utils'; import { EventEmitter } from '@angular/core'; - -export interface IAliasController { - [key: string]: any | null; - // TODO: -} +import { EntityId } from '@app/shared/models/id/entity-id'; +import { IAliasController, IStateController } from '@app/core/api/widget-api.models'; export interface WidgetsData { widgets: Array; widgetLayouts?: WidgetLayouts; } -export class DashboardConfig { - widgetsData?: Observable; - isEdit: boolean; - isEditActionEnabled: boolean; - isExportActionEnabled: boolean; - isRemoveActionEnabled: boolean; +export interface DashboardCallbacks { onEditWidget?: ($event: Event, widget: Widget) => void; onExportWidget?: ($event: Event, widget: Widget) => void; onRemoveWidget?: ($event: Event, widget: Widget) => void; onWidgetMouseDown?: ($event: Event, widget: Widget) => void; onWidgetClicked?: ($event: Event, widget: Widget) => void; - aliasController?: IAliasController; - autofillHeight?: boolean; - mobileAutofillHeight?: boolean; - dashboardStyle?: {[klass: string]: any} | null; - columns?: number; - margins?: [number, number]; - dashboardTimewindow?: Timewindow; - ignoreLoading?: boolean; - dashboardClass?: string; - mobileRowHeight?: number; - - private isMobileValue: boolean; - private isMobileDisabledValue: boolean; - - private layoutChange = new EventEmitter(); - layoutChange$ = this.layoutChange.asObservable(); - layoutChangeTimeout = null; - - set isMobile(isMobile: boolean) { - if (this.isMobileValue !== isMobile) { - const changed = isDefined(this.isMobileValue); - this.isMobileValue = isMobile; - if (changed) { - this.notifyLayoutChanged(); - } - } - } - get isMobile(): boolean { - return this.isMobileValue; - } - - set isMobileDisabled(isMobileDisabled: boolean) { - if (this.isMobileDisabledValue !== isMobileDisabled) { - const changed = isDefined(this.isMobileDisabledValue); - this.isMobileDisabledValue = isMobileDisabled; - if (changed) { - this.notifyLayoutChanged(); - } - } - } - get isMobileDisabled(): boolean { - return this.isMobileDisabledValue; - } - - private notifyLayoutChanged() { - if (this.layoutChangeTimeout) { - clearTimeout(this.layoutChangeTimeout); - } - this.layoutChangeTimeout = setTimeout(() => { - this.doNotifyLayoutChanged(); - }, 0); - } - - private doNotifyLayoutChanged() { - this.layoutChange.emit(); - this.layoutChangeTimeout = null; - } + prepareDashboardContextMenu?: ($event: Event) => void; + prepareWidgetContextMenu?: ($event: Event, widget: Widget) => void; } export interface IDashboardComponent { - options: DashboardConfig; gridsterOpts: GridsterConfig; gridster: GridsterComponent; + mobileAutofillHeight: boolean; isMobileSize: boolean; + autofillHeight: boolean; + dashboardTimewindow: Timewindow; + aliasController: IAliasController; + stateController: IStateController; } export class DashboardWidget implements GridsterItem { @@ -262,7 +203,7 @@ export class DashboardWidget implements GridsterItem { } get rows(): number { - if (this.dashboard.isMobileSize && !this.dashboard.options.mobileAutofillHeight) { + if (this.dashboard.isMobileSize && !this.dashboard.mobileAutofillHeight) { let mobileHeight; if (this.widgetLayout) { mobileHeight = this.widgetLayout.mobileHeight; @@ -285,7 +226,7 @@ export class DashboardWidget implements GridsterItem { } set rows(rows: number) { - if (!this.dashboard.isMobileSize && !this.dashboard.options.autofillHeight) { + if (!this.dashboard.isMobileSize && !this.dashboard.autofillHeight) { if (this.widgetLayout) { this.widgetLayout.sizeY = rows; } else { 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 4edc6101c3..63a959c3d0 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 @@ -14,23 +14,75 @@ /// limitations under the License. /// +import { ExceptionData } from '@shared/models/error.models'; +import { IDashboardComponent } from '@home/models/dashboard-component.models'; +import { WidgetActionDescriptor, WidgetConfig, WidgetConfigSettings, widgetType } from '@shared/models/widget.models'; +import { Timewindow } from '@shared/models/time/time.models'; +import { + EntityInfo, + IWidgetSubscription, + SubscriptionInfo, + WidgetSubscriptionOptions, + IStateController, + IAliasController, + TimewindowFunctions, + WidgetSubscriptionApi, + RpcApi, + WidgetActionsApi, + IWidgetUtils +} from '@core/api/widget-api.models'; +import { Observable } from 'rxjs'; +import { EntityId } from '@shared/models/id/entity-id'; + export interface IWidgetAction { + name: string; icon: string; onAction: ($event: Event) => void; } export interface WidgetHeaderAction extends IWidgetAction { displayName: string; + descriptor: WidgetActionDescriptor; } export interface WidgetAction extends IWidgetAction { - name: string; show: boolean; } +export interface IDynamicWidgetComponent { + widgetContext: WidgetContext; + widgetErrorData: ExceptionData; + loadingData: boolean; + [key: string]: any; +} + export interface WidgetContext { - widgetTitleTemplate?: string; + inited?: boolean; + $container?: any; + $containerParent?: any; + width?: number; + height?: number; + $scope?: IDynamicWidgetComponent; hideTitlePanel?: boolean; + isEdit?: boolean; + isMobile?: boolean; + dashboard?: IDashboardComponent; + widgetConfig?: WidgetConfig; + settings?: WidgetConfigSettings; + units?: string; + decimals?: number; + subscriptions?: {[id: string]: IWidgetSubscription}; + defaultSubscription?: IWidgetSubscription; + dashboardTimewindow?: Timewindow; + timewindowFunctions?: TimewindowFunctions; + subscriptionApi?: WidgetSubscriptionApi; + controlApi?: RpcApi; + utils?: IWidgetUtils; + actionsApi?: WidgetActionsApi; + stateController?: IStateController; + aliasController?: IAliasController; + activeEntityInfo?: EntityInfo; + widgetTitleTemplate?: string; widgetTitle?: string; customHeaderActions?: Array; widgetActions?: Array; diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts b/ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts index 01c9784034..98d8fcb797 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts @@ -22,7 +22,7 @@ import {Authority} from '@shared/models/authority.enum'; import {RuleChainsTableConfigResolver} from '@modules/home/pages/rulechain/rulechains-table-config.resolver'; import {WidgetsBundlesTableConfigResolver} from '@modules/home/pages/widget/widgets-bundles-table-config.resolver'; import { WidgetLibraryComponent } from '@home/pages/widget/widget-library.component'; -import { BreadCrumbConfig } from '@shared/components/breadcrumb'; +import { BreadCrumbConfig, BreadCrumbLabelFunction } from '@shared/components/breadcrumb'; import { User } from '@shared/models/user.model'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; @@ -44,7 +44,9 @@ export class WidgetsBundleResolver implements Resolve { } } -const routes: Routes = [ +export const widgetTypesBreadcumbLabelFunction: BreadCrumbLabelFunction = ((route, translate) => route.data.widgetsBundle.title); + +export const routes: Routes = [ { path: 'widgets-bundles', data: { @@ -72,7 +74,7 @@ const routes: Routes = [ auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN], title: 'widget.widget-library', breadcrumb: { - labelFunction: ((route, translate) => route.data.widgetsBundle.title), + labelFunction: widgetTypesBreadcumbLabelFunction, icon: 'now_widgets' } as BreadCrumbConfig }, diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.html b/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.html index c37b47d155..ebb93167ed 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.html +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.html @@ -27,6 +27,12 @@ style="text-transform: uppercase; display: flex;" class="mat-headline tb-absolute-fill">widgets-bundle.empty - + diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.ts b/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.ts index 86f15e6b93..3707e0c277 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.ts +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.ts @@ -24,14 +24,14 @@ import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; import { ActivatedRoute } from '@angular/router'; import { Authority } from '@shared/models/authority.enum'; import { NULL_UUID } from '@shared/models/id/has-uuid'; -import { Observable, of } from 'rxjs'; +import { Observable } from 'rxjs'; import { toWidgetInfo, Widget, widgetType } from '@app/shared/models/widget.models'; import { WidgetService } from '@core/http/widget.service'; import { map, share } from 'rxjs/operators'; import { DialogService } from '@core/services/dialog.service'; -import { speedDialFabAnimations } from '@shared/animations/speed-dial-fab.animations'; import { FooterFabButtons } from '@app/shared/components/footer-fab-buttons.component'; -import { DashboardConfig } from '@home/models/dashboard-component.models'; +import { DashboardCallbacks, WidgetsData } from '@home/models/dashboard-component.models'; +import { IAliasController } from '@app/core/api/widget-api.models'; @Component({ selector: 'tb-widget-library', @@ -69,19 +69,21 @@ export class WidgetLibraryComponent extends PageComponent implements OnInit { ] }; - dashboardOptions: DashboardConfig = new DashboardConfig(); + dashboardCallbacks: DashboardCallbacks = { + onEditWidget: this.openWidgetType.bind(this), + onExportWidget: this.exportWidgetType.bind(this), + onRemoveWidget: this.removeWidgetType.bind(this) + }; + + widgetsData: Observable; + + aliasController: IAliasController = {}; constructor(protected store: Store, private route: ActivatedRoute, private widgetService: WidgetService, private dialogService: DialogService) { super(store); - this.dashboardOptions.isEdit = false; - this.dashboardOptions.isEditActionEnabled = true; - this.dashboardOptions.isExportActionEnabled = true; - this.dashboardOptions.onEditWidget = ($event, widget) => { this.openWidgetType($event, widget); }; - this.dashboardOptions.onExportWidget = ($event, widget) => { this.exportWidgetType($event, widget); }; - this.dashboardOptions.onRemoveWidget = ($event, widget) => { this.removeWidgetType($event, widget); }; this.authUser = getCurrentAuthUser(store); this.widgetsBundle = this.route.snapshot.data.widgetsBundle; @@ -90,9 +92,8 @@ export class WidgetLibraryComponent extends PageComponent implements OnInit { } else { this.isReadOnly = this.authUser.authority !== Authority.SYS_ADMIN; } - this.dashboardOptions.isRemoveActionEnabled = !this.isReadOnly; this.loadWidgetTypes(); - this.dashboardOptions.widgetsData = this.widgetTypes$.pipe( + this.widgetsData = this.widgetTypes$.pipe( map(widgets => ({ widgets }))); } diff --git a/ui-ngx/src/app/shared/models/constants.ts b/ui-ngx/src/app/shared/models/constants.ts index b3538306d7..121848a2f7 100644 --- a/ui-ngx/src/app/shared/models/constants.ts +++ b/ui-ngx/src/app/shared/models/constants.ts @@ -113,3 +113,5 @@ export const valueTypesMap = new Map( ] ] ); + +export const customTranslationsPrefix = 'custom.'; diff --git a/ui-ngx/src/app/shared/models/error.models.ts b/ui-ngx/src/app/shared/models/error.models.ts new file mode 100644 index 0000000000..856617616f --- /dev/null +++ b/ui-ngx/src/app/shared/models/error.models.ts @@ -0,0 +1,23 @@ +/// +/// Copyright © 2016-2019 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 interface ExceptionData { + message?: string; + name?: string; + lineNumber?: number; + columnNumber?: number; +} diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index 85516edf00..f40e380364 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -87,19 +87,50 @@ export interface WidgetResource { url: string; } +export interface WidgetActionSource { + name: string; + value: string; + multiple: boolean; +} + +export const widgetActionSources: {[key: string]: WidgetActionSource} = { + headerButton: + { + name: 'widget-action.header-button', + value: 'headerButton', + multiple: true, + } +}; + export interface WidgetTypeDescriptor { type: widgetType; resources: Array; templateHtml: string; templateCss: string; controllerScript: string; - settingsSchema: string; - dataKeySettingsSchema: string; + settingsSchema?: string; + dataKeySettingsSchema?: string; defaultConfig: string; sizeX: number; sizeY: number; } +export interface WidgetTypeParameters { + useCustomDatasources?: boolean; + maxDatasources?: number; + maxDataKeys?: number; + dataKeysOptional?: boolean; + stateData?: boolean; +} + +export interface WidgetControllerDescriptor { + widgetTypeFunction?: any; + settingsSchema?: string; + dataKeySettingsSchema?: string; + typeParameters?: WidgetTypeParameters; + actionSources?: {[key: string]: WidgetActionSource}; +} + export interface WidgetType extends BaseData { tenantId: TenantId; bundleAlias: string; @@ -108,9 +139,67 @@ export interface WidgetType extends BaseData { descriptor: WidgetTypeDescriptor; } -export interface WidgetInfo extends WidgetTypeDescriptor { +export interface WidgetInfo extends WidgetTypeDescriptor, WidgetControllerDescriptor { widgetName: string; alias: string; + typeSettingsSchema?: string; + typeDataKeySettingsSchema?: string; +} + +export const MissingWidgetType: WidgetInfo = { + type: widgetType.latest, + widgetName: 'Widget type not found', + alias: 'undefined', + sizeX: 8, + sizeY: 6, + resources: [], + templateHtml: '
' + + '
widget.widget-type-not-found
' + + '
', + templateCss: '', + controllerScript: 'self.onInit = function() {}', + settingsSchema: '{}\n', + dataKeySettingsSchema: '{}\n', + defaultConfig: '{\n' + + '"title": "Widget type not found",\n' + + '"datasources": [],\n' + + '"settings": {}\n' + + '}\n' +}; + +export const ErrorWidgetType: WidgetInfo = { + type: widgetType.latest, + widgetName: 'Error loading widget', + alias: 'error', + sizeX: 8, + sizeY: 6, + resources: [], + templateHtml: '
' + + '
widget.widget-type-load-error
', + templateCss: '', + controllerScript: 'self.onInit = function() {}', + settingsSchema: '{}\n', + dataKeySettingsSchema: '{}\n', + defaultConfig: '{\n' + + '"title": "Widget failed to load",\n' + + '"datasources": [],\n' + + '"settings": {}\n' + + '}\n' +}; + +export interface WidgetTypeInstance { + getSettingsSchema?: () => string; + getDataKeySettingsSchema?: () => string; + typeParameters?: () => WidgetTypeParameters; + useCustomDatasources?: () => boolean; + actionSources?: () => {[key: string]: WidgetActionSource}; + + onInit?: () => void; + onDataUpdated?: () => void; + onResize?: () => void; + onEditModeChanged?: () => void; + onMobileModeChanged?: () => void; + onDestroy?: () => void; } export function toWidgetInfo(widgetTypeEntity: WidgetType): WidgetInfo { @@ -130,6 +219,107 @@ export function toWidgetInfo(widgetTypeEntity: WidgetType): WidgetInfo { }; } +export enum LegendDirection { + column = 'column', + row = 'row' +} + +export const legendDirectionTranslationMap = new Map( + [ + [ LegendDirection.column, 'direction.column' ], + [ LegendDirection.row, 'direction.row' ] + ] +); + +export enum LegendPosition { + top = 'top', + bottom = 'bottom', + left = 'left', + right = 'right' +} + +export const legendPositionTranslationMap = new Map( + [ + [ LegendPosition.top, 'position.top' ], + [ LegendPosition.bottom, 'position.bottom' ], + [ LegendPosition.left, 'position.left' ], + [ LegendPosition.right, 'position.right' ] + ] +); + +export interface LegendConfig { + position: LegendPosition; + direction?: LegendDirection; + showMin: boolean; + showMax: boolean; + showAvg: boolean; + showTotal: boolean; +} + +export interface DataKey { + label: string; + color: string; + hidden?: boolean; + [key: string]: any; + // TODO: +} + +export interface LegendKey { + dataKey: DataKey; + dataIndex: number; +} + +export interface LegendKeyData { + min: number; + max: number; + avg: number; + total: number; +} + +export interface LegendData { + keys: Array; + data: Array; +} + +export interface WidgetConfigSettings { + [key: string]: any; + // TODO: +} + +export enum WidgetActionType { + openDashboardState = 'openDashboardState', + updateDashboardState = 'updateDashboardState', + openDashboard = 'openDashboard', + custom = 'custom', + customPretty = 'customPretty' +} + +export const widgetActionTypeTranslationMap = new Map( + [ + [ WidgetActionType.openDashboardState, 'widget-action.open-dashboard-state' ], + [ WidgetActionType.updateDashboardState, 'widget-action.update-dashboard-state' ], + [ WidgetActionType.openDashboard, 'widget-action.open-dashboard' ], + [ WidgetActionType.custom, 'widget-action.custom' ], + [ WidgetActionType.customPretty, 'widget-action.custom-pretty' ] + ] +); + +export interface WidgetActionDescriptor { + name: string; + icon: string; + displayName?: string; + type: WidgetActionType; + targetDashboardId?: string; + targetDashboardStateId?: string; + openRightLayout?: boolean; + setEntityId?: boolean; + stateEntityParamName?: string; + customFunction?: string; + customResources?: Array; + customHtml?: string; + customCss?: string; +} + export interface WidgetConfig { title?: string; titleIcon?: string; @@ -141,6 +331,8 @@ export interface WidgetConfig { enableFullscreen?: boolean; useDashboardTimewindow?: boolean; displayTimewindow?: boolean; + showLegend?: boolean; + legendConfig?: LegendConfig; timewindow?: Timewindow; mobileHeight?: number; mobileOrder?: number; @@ -150,6 +342,10 @@ export interface WidgetConfig { margin?: string; widgetStyle?: {[klass: string]: any}; titleStyle?: {[klass: string]: any}; + units?: string; + decimals?: number; + actions?: {[actionSourceId: string]: Array}; + settings?: WidgetConfigSettings; [key: string]: any; // TODO: diff --git a/ui-ngx/src/app/shared/models/window-message.model.ts b/ui-ngx/src/app/shared/models/window-message.model.ts new file mode 100644 index 0000000000..adb53b2c42 --- /dev/null +++ b/ui-ngx/src/app/shared/models/window-message.model.ts @@ -0,0 +1,22 @@ +/// +/// Copyright © 2016-2019 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 type WindowMessageType = 'widgetException'; + +export interface WindowMessage { + type: WindowMessageType; + data: any; +} From ab0a1d1ea7b491b144bf37b4324686c6200be015 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 6 Sep 2019 20:17:45 +0300 Subject: [PATCH 030/133] Improve dynamic widgets loading. --- ui-ngx/angular.json | 12 +- ui-ngx/extra-webpack.config.js | 2 +- ui-ngx/src/app/core/http/widget.service.ts | 291 +------------- .../dynamic-component-factory.service.ts | 113 ++++++ ui-ngx/src/app/core/services/utils.service.ts | 3 +- .../home/components/home-components.module.ts | 11 +- ...ynamic-widget-component-factory.service.ts | 100 ----- .../widget/dynamic-widget.component.ts | 17 +- .../widget/widget-component.service.ts | 366 ++++++++++++++++++ .../widget/widget-components.module.ts | 2 - .../components/widget/widget.component.html | 14 +- .../components/widget/widget.component.ts | 246 ++++++------ .../home/models/widget-component.models.ts | 124 +++++- .../pages/widget/widget-library.component.ts | 3 +- ui-ngx/src/app/shared/models/widget.models.ts | 88 +---- 15 files changed, 743 insertions(+), 649 deletions(-) create mode 100644 ui-ngx/src/app/core/services/dynamic-component-factory.service.ts delete mode 100644 ui-ngx/src/app/modules/home/components/widget/dynamic-widget-component-factory.service.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts diff --git a/ui-ngx/angular.json b/ui-ngx/angular.json index 113233b012..7165edf085 100644 --- a/ui-ngx/angular.json +++ b/ui-ngx/angular.json @@ -24,7 +24,11 @@ "tsConfig": "src/tsconfig.app.json", "assets": [ "src/thingsboard.ico", - "src/assets" + "src/assets", + { "glob": "worker-html.js", "input": "./node_modules/ace-builds/src-min/", "output": "/" }, + { "glob": "worker-css.js", "input": "./node_modules/ace-builds/src-min/", "output": "/" }, + { "glob": "worker-json.js", "input": "./node_modules/ace-builds/src-min/", "output": "/" }, + { "glob": "worker-javascript.js", "input": "./node_modules/ace-builds/src-min/", "output": "/" } ], "styles": [ "src/styles.scss" @@ -49,11 +53,7 @@ "node_modules/ace-builds/src-min/snippets/css.js", "node_modules/ace-builds/src-min/snippets/json.js", "node_modules/ace-builds/src-min/snippets/java.js", - "node_modules/ace-builds/src-min/snippets/javascript.js", - { "bundleName": "worker-html", "input": "node_modules/ace-builds/src-min/worker-html.js" }, - { "bundleName": "worker-css", "input": "node_modules/ace-builds/src-min/worker-css.js" }, - { "bundleName": "worker-json", "input": "node_modules/ace-builds/src-min/worker-json.js" }, - { "bundleName": "worker-javascript", "input": "node_modules/ace-builds/src-min/worker-javascript.js" } + "node_modules/ace-builds/src-min/snippets/javascript.js" ], "es5BrowserSupport": true, "customWebpackConfig": { diff --git a/ui-ngx/extra-webpack.config.js b/ui-ngx/extra-webpack.config.js index c86417512a..527470d162 100644 --- a/ui-ngx/extra-webpack.config.js +++ b/ui-ngx/extra-webpack.config.js @@ -34,7 +34,7 @@ module.exports = { new CompressionPlugin({ filename: "[path].gz[query]", algorithm: "gzip", - test: /\.js$|\.css$|\.svg$|\.ttf$|\.woff$|\.woff2$|\.eot$|\.json$/, + test: /\.js$|\.css$|\.html$|\.svg?.+$|\.jpg$|\.ttf?.+$|\.woff?.+$|\.eot?.+$|\.json$/, threshold: 10240, minRatio: 0.8, deleteOriginalAssets: false diff --git a/ui-ngx/src/app/core/http/widget.service.ts b/ui-ngx/src/app/core/http/widget.service.ts index 151a2865eb..4c0606b599 100644 --- a/ui-ngx/src/app/core/http/widget.service.ts +++ b/ui-ngx/src/app/core/http/widget.service.ts @@ -14,47 +14,29 @@ /// limitations under the License. /// -import {Injectable} from '@angular/core'; -import {defaultHttpOptions} from './http-utils'; -import { Observable, ReplaySubject, Subject, of, forkJoin, throwError } from 'rxjs/index'; -import {HttpClient} from '@angular/common/http'; -import {PageLink} from '@shared/models/page/page-link'; -import {PageData} from '@shared/models/page/page-data'; -import {WidgetsBundle} from '@shared/models/widgets-bundle.model'; -import { - WidgetControllerDescriptor, - WidgetInfo, - WidgetType, - WidgetTypeInstance, - widgetActionSources, - MissingWidgetType, toWidgetInfo, ErrorWidgetType -} from '@shared/models/widget.models'; +import { Injectable } from '@angular/core'; +import { defaultHttpOptions } from './http-utils'; +import { Observable } from 'rxjs/index'; +import { HttpClient } from '@angular/common/http'; +import { PageLink } from '@shared/models/page/page-link'; +import { PageData } from '@shared/models/page/page-data'; +import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; +import { WidgetType } from '@shared/models/widget.models'; import { UtilsService } from '@core/services/utils.service'; -import { isFunction, isUndefined } from '@core/utils'; import { TranslateService } from '@ngx-translate/core'; -import { AuthPayload } from '@core/auth/auth.models'; -import cssjs from '@core/css/css'; import { ResourcesService } from '../services/resources.service'; -import { catchError, map, switchMap } from 'rxjs/operators'; @Injectable({ providedIn: 'root' }) export class WidgetService { - private cssParser = new cssjs(); - - private widgetsInfoInMemoryCache = new Map(); - - private widgetsInfoFetchQueue = new Map>>(); - constructor( private http: HttpClient, private utils: UtilsService, private resources: ResourcesService, private translate: TranslateService ) { - this.cssParser.testMode = false; } public getWidgetBundles(pageLink: PageLink, ignoreErrors: boolean = false, @@ -88,261 +70,4 @@ export class WidgetService { return this.http.get(`/api/widgetType?isSystem=${isSystem}&bundleAlias=${bundleAlias}&alias=${widgetTypeAlias}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); } - - public getWidgetInfo(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean): Observable { - const widgetInfoSubject = new ReplaySubject(); - const widgetInfo = this.getWidgetInfoFromCache(bundleAlias, widgetTypeAlias, isSystem); - if (widgetInfo) { - widgetInfoSubject.next(widgetInfo); - widgetInfoSubject.complete(); - } else { - if (this.utils.widgetEditMode) { - // TODO: - } else { - const key = this.createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem); - let fetchQueue = this.widgetsInfoFetchQueue.get(key); - if (fetchQueue) { - fetchQueue.push(widgetInfoSubject); - } else { - fetchQueue = new Array>(); - this.widgetsInfoFetchQueue.set(key, fetchQueue); - this.getWidgetType(bundleAlias, widgetTypeAlias, isSystem).subscribe( - (widgetType) => { - this.loadWidget(widgetType, bundleAlias, isSystem, widgetInfoSubject); - }, - () => { - widgetInfoSubject.next(MissingWidgetType); - widgetInfoSubject.complete(); - this.resolveWidgetsInfoFetchQueue(key, MissingWidgetType); - } - ); - } - } - } - return widgetInfoSubject.asObservable(); - } - - private loadWidget(widgetType: WidgetType, bundleAlias: string, isSystem: boolean, widgetInfoSubject: Subject) { - const widgetInfo = toWidgetInfo(widgetType); - const key = this.createWidgetInfoCacheKey(bundleAlias, widgetInfo.alias, isSystem); - this.loadWidgetResources(widgetInfo, bundleAlias, isSystem).subscribe( - () => { - let widgetControllerDescriptor: WidgetControllerDescriptor = null; - try { - widgetControllerDescriptor = this.createWidgetControllerDescriptor(widgetInfo, key); - } catch (e) { - const details = this.utils.parseException(e); - const errorMessage = `Failed to compile widget script. \n Error: ${details.message}`; - this.processWidgetLoadError([errorMessage], key, widgetInfoSubject); - } - if (widgetControllerDescriptor) { - if (widgetControllerDescriptor.settingsSchema) { - widgetInfo.typeSettingsSchema = widgetControllerDescriptor.settingsSchema; - } - if (widgetControllerDescriptor.dataKeySettingsSchema) { - widgetInfo.typeDataKeySettingsSchema = widgetControllerDescriptor.dataKeySettingsSchema; - } - widgetInfo.typeParameters = widgetControllerDescriptor.typeParameters; - widgetInfo.actionSources = widgetControllerDescriptor.actionSources; - widgetInfo.widgetTypeFunction = widgetControllerDescriptor.widgetTypeFunction; - this.putWidgetInfoToCache(widgetInfo, bundleAlias, widgetInfo.alias, isSystem); - if (widgetInfoSubject) { - widgetInfoSubject.next(widgetInfo); - widgetInfoSubject.complete(); - } - this.resolveWidgetsInfoFetchQueue(key, widgetInfo); - } - }, - (errorMessages: string[]) => { - this.processWidgetLoadError(errorMessages, key, widgetInfoSubject); - } - ); - } - - private loadWidgetResources(widgetInfo: WidgetInfo, bundleAlias: string, isSystem: boolean): Observable { - const widgetNamespace = `widget-type-${(isSystem ? 'sys-' : '')}${bundleAlias}-${widgetInfo.alias}`; - this.cssParser.cssPreviewNamespace = widgetNamespace; - this.cssParser.createStyleElement(widgetNamespace, widgetInfo.templateCss); - const resourceTasks: Observable[] = []; - if (widgetInfo.resources.length > 0) { - widgetInfo.resources.forEach((resource) => { - resourceTasks.push( - this.resources.loadResource(resource.url).pipe( - catchError(e => of(`Failed to load widget resource: '${resource.url}'`)) - ) - ); - }); - return forkJoin(resourceTasks).pipe( - switchMap(msgs => { - let errors: string[]; - if (msgs && msgs.length) { - errors = msgs.filter(msg => msg && msg.length > 0); - } - if (errors && errors.length) { - return throwError(errors); - } else { - return of(null); - } - } - )); - } else { - return of(null); - } - } - - private createWidgetControllerDescriptor(widgetInfo: WidgetInfo, name: string): WidgetControllerDescriptor { - let widgetTypeFunctionBody = `return function ${name} (ctx) {\n` + - ' var self = this;\n' + - ' self.ctx = ctx;\n\n'; /*+ - - ' self.onInit = function() {\n\n' + - - ' }\n\n' + - - ' self.onDataUpdated = function() {\n\n' + - - ' }\n\n' + - - ' self.useCustomDatasources = function() {\n\n' + - - ' }\n\n' + - - ' self.typeParameters = function() {\n\n' + - return { - useCustomDatasources: false, - maxDatasources: -1, //unlimited - maxDataKeys: -1, //unlimited - dataKeysOptional: false, - stateData: false - }; - ' }\n\n' + - - ' self.actionSources = function() {\n\n' + - return { - 'headerButton': { - name: 'Header button', - multiple: true - } - }; - }\n\n' + - ' self.onResize = function() {\n\n' + - - ' }\n\n' + - - ' self.onEditModeChanged = function() {\n\n' + - - ' }\n\n' + - - ' self.onMobileModeChanged = function() {\n\n' + - - ' }\n\n' + - - ' self.getSettingsSchema = function() {\n\n' + - - ' }\n\n' + - - ' self.getDataKeySettingsSchema = function() {\n\n' + - - ' }\n\n' + - - ' self.onDestroy = function() {\n\n' + - - ' }\n\n' + - '}';*/ - - widgetTypeFunctionBody += widgetInfo.controllerScript; - widgetTypeFunctionBody += '\n};\n'; - - try { - - const widgetTypeFunction = new Function(widgetTypeFunctionBody); - const widgetType = widgetTypeFunction.apply(this); - const widgetTypeInstance: WidgetTypeInstance = new widgetType(); - const result: WidgetControllerDescriptor = { - widgetTypeFunction: widgetType - }; - if (isFunction(widgetTypeInstance.getSettingsSchema)) { - result.settingsSchema = widgetTypeInstance.getSettingsSchema(); - } - if (isFunction(widgetTypeInstance.getDataKeySettingsSchema)) { - result.dataKeySettingsSchema = widgetTypeInstance.getDataKeySettingsSchema(); - } - if (isFunction(widgetTypeInstance.typeParameters)) { - result.typeParameters = widgetTypeInstance.typeParameters(); - } else { - result.typeParameters = {}; - } - if (isFunction(widgetTypeInstance.useCustomDatasources)) { - result.typeParameters.useCustomDatasources = widgetTypeInstance.useCustomDatasources(); - } else { - result.typeParameters.useCustomDatasources = false; - } - if (isUndefined(result.typeParameters.maxDatasources)) { - result.typeParameters.maxDatasources = -1; - } - if (isUndefined(result.typeParameters.maxDataKeys)) { - result.typeParameters.maxDataKeys = -1; - } - if (isUndefined(result.typeParameters.dataKeysOptional)) { - result.typeParameters.dataKeysOptional = false; - } - if (isUndefined(result.typeParameters.stateData)) { - result.typeParameters.stateData = false; - } - if (isFunction(widgetTypeInstance.actionSources)) { - result.actionSources = widgetTypeInstance.actionSources(); - } else { - result.actionSources = {}; - } - for (const actionSourceId of Object.keys(widgetActionSources)) { - result.actionSources[actionSourceId] = {...widgetActionSources[actionSourceId]}; - result.actionSources[actionSourceId].name = this.translate.instant(result.actionSources[actionSourceId].name); - } - return result; - } catch (e) { - this.utils.processWidgetException(e); - throw e; - } - } - - private processWidgetLoadError(errorMessages: string[], cacheKey: string, widgetInfoSubject: Subject) { - const widgetInfo = {...ErrorWidgetType}; - errorMessages.forEach(error => { - widgetInfo.templateHtml += `
${error}
`; - }); - widgetInfo.templateHtml += '
'; - if (widgetInfoSubject) { - widgetInfoSubject.next(widgetInfo); - widgetInfoSubject.complete(); - } - this.resolveWidgetsInfoFetchQueue(cacheKey, widgetInfo); - } - - private resolveWidgetsInfoFetchQueue(key: string, widgetInfo: WidgetInfo) { - const fetchQueue = this.widgetsInfoFetchQueue.get(key); - if (fetchQueue) { - fetchQueue.forEach(subject => { - subject.next(widgetInfo); - subject.complete(); - }); - this.widgetsInfoFetchQueue.delete(key); - } - } - - // Cache functions - - private createWidgetInfoCacheKey(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean): string { - return `${isSystem ? 'sys_' : ''}${bundleAlias}_${widgetTypeAlias}`; - } - - private getWidgetInfoFromCache(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean): WidgetInfo | undefined { - const key = this.createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem); - return this.widgetsInfoInMemoryCache.get(key); - } - - private putWidgetInfoToCache(widgetInfo: WidgetInfo, bundleAlias: string, widgetTypeAlias: string, isSystem: boolean) { - const key = this.createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem); - this.widgetsInfoInMemoryCache.set(key, widgetInfo); - } - } diff --git a/ui-ngx/src/app/core/services/dynamic-component-factory.service.ts b/ui-ngx/src/app/core/services/dynamic-component-factory.service.ts new file mode 100644 index 0000000000..1bfd9c6507 --- /dev/null +++ b/ui-ngx/src/app/core/services/dynamic-component-factory.service.ts @@ -0,0 +1,113 @@ +/// +/// Copyright © 2016-2019 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 { + Compiler, + Component, + ComponentFactory, + Injectable, + Injector, + NgModule, + NgModuleRef, + OnDestroy, + Type +} from '@angular/core'; +import { Observable, ReplaySubject } from 'rxjs'; +import { CommonModule } from '@angular/common'; + +export abstract class DynamicComponentModule implements OnDestroy { + + ngOnDestroy(): void { + console.log('Module destroyed!'); + } + +} + +interface DynamicComponentModuleData { + moduleRef: NgModuleRef; + moduleType: Type; +} + +@Injectable( + { + providedIn: 'root' + } +) +export class DynamicComponentFactoryService { + + private dynamicComponentModulesMap = new Map, DynamicComponentModuleData>(); + + constructor(private compiler: Compiler, + private injector: Injector) { + } + + public createDynamicComponentFactory( + componentType: Type, + template: string, + modules?: Type[]): Observable> { + const dymamicComponentFactorySubject = new ReplaySubject>(); + const comp = this.createDynamicComponent(componentType, template); + let moduleImports = [CommonModule]; + if (modules) { + moduleImports = [...moduleImports, ...modules]; + } + // noinspection AngularInvalidImportedOrDeclaredSymbol,AngularInvalidEntryComponent + @NgModule({ + declarations: [comp], + entryComponents: [comp], + imports: moduleImports + }) + class DynamicComponentInstanceModule extends DynamicComponentModule {} + try { + this.compiler.compileModuleAsync(DynamicComponentInstanceModule).then( + (module) => { + const moduleRef = module.create(this.injector); + const factory = moduleRef.componentFactoryResolver.resolveComponentFactory(comp); + this.dynamicComponentModulesMap.set(factory, { + moduleRef, + moduleType: module.moduleType + }); + dymamicComponentFactorySubject.next(factory); + dymamicComponentFactorySubject.complete(); + } + ).catch( + (e) => { + dymamicComponentFactorySubject.error(e); + } + ); + } catch (e) { + dymamicComponentFactorySubject.error(e); + } + return dymamicComponentFactorySubject.asObservable(); + } + + public destroyDynamicComponentFactory(factory: ComponentFactory) { + const moduleData = this.dynamicComponentModulesMap.get(factory); + if (moduleData) { + moduleData.moduleRef.destroy(); + this.compiler.clearCacheFor(moduleData.moduleType); + this.dynamicComponentModulesMap.delete(factory); + } + } + + private createDynamicComponent(componentType: Type, template: string): Type { + // noinspection AngularMissingOrInvalidDeclarationInModule + return Component({ + template + })(componentType); + } + +} diff --git a/ui-ngx/src/app/core/services/utils.service.ts b/ui-ngx/src/app/core/services/utils.service.ts index 44f2a25fd0..687f53061a 100644 --- a/ui-ngx/src/app/core/services/utils.service.ts +++ b/ui-ngx/src/app/core/services/utils.service.ts @@ -16,7 +16,6 @@ import { Inject, Injectable } from '@angular/core'; import { WINDOW } from '@core/services/window.service'; -import { WidgetInfo } from '@shared/models/widget.models'; import { ExceptionData } from '@app/shared/models/error.models'; import { isUndefined } from '@core/utils'; import { WindowMessage } from '@shared/models/window-message.model'; @@ -30,7 +29,7 @@ export class UtilsService { iframeMode = false; widgetEditMode = false; - editWidgetInfo: WidgetInfo = null; + editWidgetInfo: any = null; constructor(@Inject(WINDOW) private window: Window, private translate: TranslateService) { diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index 9418afc161..d306bc6a26 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -36,7 +36,8 @@ import { AddAttributeDialogComponent } from './attribute/add-attribute-dialog.co import { EditAttributeValuePanelComponent } from './attribute/edit-attribute-value-panel.component'; import { DashboardComponent } from '@home/components/dashboard/dashboard.component'; import { WidgetComponent } from '@home/components/widget/widget.component'; -import { DynamicWidgetComponentFactoryService } from './widget/dynamic-widget-component-factory.service'; +import { WidgetComponentService } from './widget/widget-component.service'; +import { LegendComponent } from '@home/components/widget/legend.component'; @NgModule({ entryComponents: [ @@ -69,7 +70,8 @@ import { DynamicWidgetComponentFactoryService } from './widget/dynamic-widget-co AddAttributeDialogComponent, EditAttributeValuePanelComponent, DashboardComponent, - WidgetComponent + WidgetComponent, + LegendComponent ], imports: [ CommonModule, @@ -88,10 +90,11 @@ import { DynamicWidgetComponentFactoryService } from './widget/dynamic-widget-co AlarmDetailsDialogComponent, AttributeTableComponent, DashboardComponent, - WidgetComponent + WidgetComponent, + LegendComponent ], providers: [ - DynamicWidgetComponentFactoryService + WidgetComponentService ] }) export class HomeComponentsModule { } diff --git a/ui-ngx/src/app/modules/home/components/widget/dynamic-widget-component-factory.service.ts b/ui-ngx/src/app/modules/home/components/widget/dynamic-widget-component-factory.service.ts deleted file mode 100644 index a6d39a1d45..0000000000 --- a/ui-ngx/src/app/modules/home/components/widget/dynamic-widget-component-factory.service.ts +++ /dev/null @@ -1,100 +0,0 @@ -/// -/// Copyright © 2016-2019 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 { - Compiler, - Component, - ComponentFactory, - Injectable, - Injector, - NgModule, - NgModuleRef, - Type, - ViewEncapsulation -} from '@angular/core'; -import { - DynamicWidgetComponent, - DynamicWidgetComponentModule -} from '@home/components/widget/dynamic-widget.component'; -import { CommonModule } from '@angular/common'; -import { SharedModule } from '@shared/shared.module'; -import { Observable, ReplaySubject } from 'rxjs'; -import { HomeComponentsModule } from '../home-components.module'; -import { WidgetComponentsModule } from './widget-components.module'; - -interface DynamicWidgetComponentModuleData { - moduleRef: NgModuleRef; - moduleType: Type; -} - -@Injectable() -export class DynamicWidgetComponentFactoryService { - - private dynamicComponentModulesMap = new Map, DynamicWidgetComponentModuleData>(); - - constructor(private compiler: Compiler, - private injector: Injector) { - } - - public createDynamicWidgetComponentFactory(template: string): Observable> { - const dymamicWidgetComponentFactorySubject = new ReplaySubject>(); - const comp = this.createDynamicWidgetComponent(template); - // noinspection AngularInvalidImportedOrDeclaredSymbol,AngularInvalidEntryComponent - @NgModule({ - declarations: [comp], - entryComponents: [comp], - imports: [CommonModule, SharedModule, WidgetComponentsModule], - }) - class DynamicWidgetComponentInstanceModule extends DynamicWidgetComponentModule {} - this.compiler.compileModuleAsync(DynamicWidgetComponentInstanceModule).then( - (module) => { - const moduleRef = module.create(this.injector); - const factory = moduleRef.componentFactoryResolver.resolveComponentFactory(comp); - this.dynamicComponentModulesMap.set(factory, { - moduleRef, - moduleType: module.moduleType - }); - dymamicWidgetComponentFactorySubject.next(factory); - dymamicWidgetComponentFactorySubject.complete(); - } - ).catch( - (e) => { - dymamicWidgetComponentFactorySubject.error(`Failed to create dynamic widget component factory: ${e}`); - } - ); - return dymamicWidgetComponentFactorySubject.asObservable(); - } - - public destroyDynamicWidgetComponentFactory(factory: ComponentFactory) { - const moduleData = this.dynamicComponentModulesMap.get(factory); - if (moduleData) { - moduleData.moduleRef.destroy(); - this.compiler.clearCacheFor(moduleData.moduleType); - this.dynamicComponentModulesMap.delete(factory); - } - } - - private createDynamicWidgetComponent(template: string): Type { - // noinspection AngularMissingOrInvalidDeclarationInModule - @Component({ - template - }) - class DynamicWidgetInstanceComponent extends DynamicWidgetComponent { } - - return DynamicWidgetInstanceComponent; - } - -} diff --git a/ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts index baa2972456..20fbc803fe 100644 --- a/ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts @@ -21,24 +21,13 @@ import { AppState } from '@core/core.state'; import { WidgetContext, IDynamicWidgetComponent } from '@home/models/widget-component.models'; import { ExceptionData } from '@shared/models/error.models'; -export abstract class DynamicWidgetComponentModule implements OnDestroy { - - ngOnDestroy(): void { - console.log('Module destroyed!'); - } - -} - -export abstract class DynamicWidgetComponent extends PageComponent implements IDynamicWidgetComponent, OnInit, OnDestroy { +export class DynamicWidgetComponent extends PageComponent implements IDynamicWidgetComponent, OnInit, OnDestroy { @Input() widgetContext: WidgetContext; @Input() - widgetErrorData: ExceptionData; - - @Input() - loadingData: boolean; + errorMessages: string[]; [key: string]: any; @@ -51,7 +40,7 @@ export abstract class DynamicWidgetComponent extends PageComponent implements ID } ngOnDestroy(): void { - console.log('Component destroyed!'); + console.log('Widget component destroyed!'); } clearRpcError() { 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 new file mode 100644 index 0000000000..3d8adfa186 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts @@ -0,0 +1,366 @@ +/// +/// Copyright © 2016-2019 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 { Injectable } from '@angular/core'; +import { DynamicComponentFactoryService } from '@core/services/dynamic-component-factory.service'; +import { WidgetService } from '@core/http/widget.service'; +import { forkJoin, Observable, of, ReplaySubject, Subject, throwError } from 'rxjs'; +import { WidgetInfo, MissingWidgetType, toWidgetInfo, WidgetTypeInstance, ErrorWidgetType } from '@home/models/widget-component.models'; +import cssjs from '@core/css/css'; +import { UtilsService } from '@core/services/utils.service'; +import { ResourcesService } from '@core/services/resources.service'; +import { + widgetActionSources, + WidgetControllerDescriptor, + WidgetType +} from '@shared/models/widget.models'; +import { catchError, switchMap, map, mergeMap } from 'rxjs/operators'; +import { isFunction, isUndefined } from '@core/utils'; +import { TranslateService } from '@ngx-translate/core'; +import { DynamicWidgetComponent } from '@home/components/widget/dynamic-widget.component'; +import { SharedModule } from '@shared/shared.module'; +import { WidgetComponentsModule } from '@home/components/widget/widget-components.module'; + +@Injectable() +export class WidgetComponentService { + + private cssParser = new cssjs(); + + private widgetsInfoInMemoryCache = new Map(); + + private widgetsInfoFetchQueue = new Map>>(); + + private init$: Observable; + + private missingWidgetType: WidgetInfo; + private errorWidgetType: WidgetInfo; + + constructor(private dynamicComponentFactoryService: DynamicComponentFactoryService, + private widgetService: WidgetService, + private utils: UtilsService, + private resources: ResourcesService, + private translate: TranslateService) { + this.cssParser.testMode = false; + this.init(); + } + + private init(): Observable { + if (this.init$) { + return this.init$; + } else { + this.missingWidgetType = {...MissingWidgetType}; + this.errorWidgetType = {...ErrorWidgetType}; + const initSubject = new ReplaySubject(); + this.init$ = initSubject.asObservable(); + const loadDefaultWidgetInfoTasks = [ + this.loadWidgetResources(this.missingWidgetType, 'global-widget-missing-type'), + this.loadWidgetResources(this.errorWidgetType, 'global-widget-error-type'), + ]; + forkJoin(loadDefaultWidgetInfoTasks).subscribe( + () => { + initSubject.next(); + }, + () => { + console.error('Failed to load default widget types!'); + initSubject.error('Failed to load default widget types!'); + } + ); + return this.init$; + } + } + + public getWidgetInfo(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean): Observable { + return this.init().pipe( + mergeMap(() => this.getWidgetInfoInternal(bundleAlias, widgetTypeAlias, isSystem)) + ); + } + + private getWidgetInfoInternal(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean): Observable { + const widgetInfoSubject = new ReplaySubject(); + const widgetInfo = this.getWidgetInfoFromCache(bundleAlias, widgetTypeAlias, isSystem); + if (widgetInfo) { + widgetInfoSubject.next(widgetInfo); + widgetInfoSubject.complete(); + } else { + if (this.utils.widgetEditMode) { + // TODO: + } else { + const key = this.createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem); + let fetchQueue = this.widgetsInfoFetchQueue.get(key); + if (fetchQueue) { + fetchQueue.push(widgetInfoSubject); + } else { + fetchQueue = new Array>(); + this.widgetsInfoFetchQueue.set(key, fetchQueue); + this.widgetService.getWidgetType(bundleAlias, widgetTypeAlias, isSystem).subscribe( + (widgetType) => { + this.loadWidget(widgetType, bundleAlias, isSystem, widgetInfoSubject); + }, + () => { + widgetInfoSubject.next(this.missingWidgetType); + widgetInfoSubject.complete(); + this.resolveWidgetsInfoFetchQueue(key, this.missingWidgetType); + } + ); + } + } + } + return widgetInfoSubject.asObservable(); + } + + private loadWidget(widgetType: WidgetType, bundleAlias: string, isSystem: boolean, widgetInfoSubject: Subject) { + const widgetInfo = toWidgetInfo(widgetType); + const key = this.createWidgetInfoCacheKey(bundleAlias, widgetInfo.alias, isSystem); + let widgetControllerDescriptor: WidgetControllerDescriptor = null; + try { + widgetControllerDescriptor = this.createWidgetControllerDescriptor(widgetInfo, key); + } catch (e) { + const details = this.utils.parseException(e); + const errorMessage = `Failed to compile widget script. \n Error: ${details.message}`; + this.processWidgetLoadError([errorMessage], key, widgetInfoSubject); + } + if (widgetControllerDescriptor) { + const widgetNamespace = `widget-type-${(isSystem ? 'sys-' : '')}${bundleAlias}-${widgetInfo.alias}`; + this.loadWidgetResources(widgetInfo, widgetNamespace).subscribe( + () => { + if (widgetControllerDescriptor.settingsSchema) { + widgetInfo.typeSettingsSchema = widgetControllerDescriptor.settingsSchema; + } + if (widgetControllerDescriptor.dataKeySettingsSchema) { + widgetInfo.typeDataKeySettingsSchema = widgetControllerDescriptor.dataKeySettingsSchema; + } + widgetInfo.typeParameters = widgetControllerDescriptor.typeParameters; + widgetInfo.actionSources = widgetControllerDescriptor.actionSources; + widgetInfo.widgetTypeFunction = widgetControllerDescriptor.widgetTypeFunction; + this.putWidgetInfoToCache(widgetInfo, bundleAlias, widgetInfo.alias, isSystem); + if (widgetInfoSubject) { + widgetInfoSubject.next(widgetInfo); + widgetInfoSubject.complete(); + } + this.resolveWidgetsInfoFetchQueue(key, widgetInfo); + }, + (errorMessages: string[]) => { + this.processWidgetLoadError(errorMessages, key, widgetInfoSubject); + } + ); + } + } + + private loadWidgetResources(widgetInfo: WidgetInfo, widgetNamespace: string): Observable { + this.cssParser.cssPreviewNamespace = widgetNamespace; + this.cssParser.createStyleElement(widgetNamespace, widgetInfo.templateCss); + const resourceTasks: Observable[] = []; + if (widgetInfo.resources.length > 0) { + widgetInfo.resources.forEach((resource) => { + resourceTasks.push( + this.resources.loadResource(resource.url).pipe( + catchError(e => of(`Failed to load widget resource: '${resource.url}'`)) + ) + ); + }); + } + resourceTasks.push( + this.dynamicComponentFactoryService.createDynamicComponentFactory( + class DynamicWidgetComponentInstance extends DynamicWidgetComponent {}, + widgetInfo.templateHtml, + [SharedModule, WidgetComponentsModule] + ).pipe( + map((factory) => { + widgetInfo.componentFactory = factory; + return null; + }), + catchError(e => { + const details = this.utils.parseException(e); + const errorMessage = `Failed to compile widget html. \n Error: ${details.message}`; + return of(errorMessage); + }) + ) + ); + return forkJoin(resourceTasks).pipe( + switchMap(msgs => { + let errors: string[]; + if (msgs && msgs.length) { + errors = msgs.filter(msg => msg && msg.length > 0); + } + if (errors && errors.length) { + return throwError(errors); + } else { + return of(null); + } + } + )); + } + + private createWidgetControllerDescriptor(widgetInfo: WidgetInfo, name: string): WidgetControllerDescriptor { + let widgetTypeFunctionBody = `return function ${name} (ctx) {\n` + + ' var self = this;\n' + + ' self.ctx = ctx;\n\n'; /*+ + + ' self.onInit = function() {\n\n' + + + ' }\n\n' + + + ' self.onDataUpdated = function() {\n\n' + + + ' }\n\n' + + + ' self.useCustomDatasources = function() {\n\n' + + + ' }\n\n' + + + ' self.typeParameters = function() {\n\n' + + return { + useCustomDatasources: false, + maxDatasources: -1, //unlimited + maxDataKeys: -1, //unlimited + dataKeysOptional: false, + stateData: false + }; + ' }\n\n' + + + ' self.actionSources = function() {\n\n' + + return { + 'headerButton': { + name: 'Header button', + multiple: true + } + }; + }\n\n' + + ' self.onResize = function() {\n\n' + + + ' }\n\n' + + + ' self.onEditModeChanged = function() {\n\n' + + + ' }\n\n' + + + ' self.onMobileModeChanged = function() {\n\n' + + + ' }\n\n' + + + ' self.getSettingsSchema = function() {\n\n' + + + ' }\n\n' + + + ' self.getDataKeySettingsSchema = function() {\n\n' + + + ' }\n\n' + + + ' self.onDestroy = function() {\n\n' + + + ' }\n\n' + + '}';*/ + + widgetTypeFunctionBody += widgetInfo.controllerScript; + widgetTypeFunctionBody += '\n};\n'; + + try { + + const widgetTypeFunction = new Function(widgetTypeFunctionBody); + const widgetType = widgetTypeFunction.apply(this); + const widgetTypeInstance: WidgetTypeInstance = new widgetType(); + const result: WidgetControllerDescriptor = { + widgetTypeFunction: widgetType + }; + if (isFunction(widgetTypeInstance.getSettingsSchema)) { + result.settingsSchema = widgetTypeInstance.getSettingsSchema(); + } + if (isFunction(widgetTypeInstance.getDataKeySettingsSchema)) { + result.dataKeySettingsSchema = widgetTypeInstance.getDataKeySettingsSchema(); + } + if (isFunction(widgetTypeInstance.typeParameters)) { + result.typeParameters = widgetTypeInstance.typeParameters(); + } else { + result.typeParameters = {}; + } + if (isFunction(widgetTypeInstance.useCustomDatasources)) { + result.typeParameters.useCustomDatasources = widgetTypeInstance.useCustomDatasources(); + } else { + result.typeParameters.useCustomDatasources = false; + } + if (isUndefined(result.typeParameters.maxDatasources)) { + result.typeParameters.maxDatasources = -1; + } + if (isUndefined(result.typeParameters.maxDataKeys)) { + result.typeParameters.maxDataKeys = -1; + } + if (isUndefined(result.typeParameters.dataKeysOptional)) { + result.typeParameters.dataKeysOptional = false; + } + if (isUndefined(result.typeParameters.stateData)) { + result.typeParameters.stateData = false; + } + if (isFunction(widgetTypeInstance.actionSources)) { + result.actionSources = widgetTypeInstance.actionSources(); + } else { + result.actionSources = {}; + } + for (const actionSourceId of Object.keys(widgetActionSources)) { + result.actionSources[actionSourceId] = {...widgetActionSources[actionSourceId]}; + result.actionSources[actionSourceId].name = this.translate.instant(result.actionSources[actionSourceId].name); + } + return result; + } catch (e) { + this.utils.processWidgetException(e); + throw e; + } + } + + private processWidgetLoadError(errorMessages: string[], cacheKey: string, widgetInfoSubject: Subject) { + if (widgetInfoSubject) { + widgetInfoSubject.error({ + widgetInfo: this.errorWidgetType, + errorMessages + }); + } + this.resolveWidgetsInfoFetchQueue(cacheKey, this.errorWidgetType, errorMessages); + } + + private resolveWidgetsInfoFetchQueue(key: string, widgetInfo: WidgetInfo, errorMessages?: string[]) { + const fetchQueue = this.widgetsInfoFetchQueue.get(key); + if (fetchQueue) { + fetchQueue.forEach(subject => { + if (!errorMessages) { + subject.next(widgetInfo); + subject.complete(); + } else { + subject.error({ + widgetInfo, + errorMessages + }); + } + }); + this.widgetsInfoFetchQueue.delete(key); + } + } + + // Cache functions + + private createWidgetInfoCacheKey(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean): string { + return `${isSystem ? 'sys_' : ''}${bundleAlias}_${widgetTypeAlias}`; + } + + private getWidgetInfoFromCache(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean): WidgetInfo | undefined { + const key = this.createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem); + return this.widgetsInfoInMemoryCache.get(key); + } + + private putWidgetInfoToCache(widgetInfo: WidgetInfo, bundleAlias: string, widgetTypeAlias: string, isSystem: boolean) { + const key = this.createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem); + this.widgetsInfoInMemoryCache.set(key, widgetInfo); + } + +} 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 06e30fab0a..d81dcca350 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 @@ -25,14 +25,12 @@ import { LegendComponent } from '@home/components/widget/legend.component'; ], declarations: [ - LegendComponent ], imports: [ CommonModule, SharedModule ], exports: [ - LegendComponent ] }) export class WidgetComponentsModule { } diff --git a/ui-ngx/src/app/modules/home/components/widget/widget.component.html b/ui-ngx/src/app/modules/home/components/widget/widget.component.html index 2ec95ac4c8..2f943d93d3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/widget.component.html @@ -15,4 +15,16 @@ limitations under the License. --> - +
+ Widget Error: {{ widgetErrorData.name + ": " + widgetErrorData.message}} +
+
+ +
+
+ +
+ +
+ +
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 99005a25f4..db0bb2b953 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 @@ -17,7 +17,6 @@ import { AfterViewInit, Component, - ComponentFactory, ComponentFactoryResolver, ComponentRef, ElementRef, @@ -38,34 +37,37 @@ import { LegendPosition, Widget, WidgetActionDescriptor, + widgetActionSources, WidgetActionType, - WidgetInfo, WidgetResource, - widgetType, - WidgetTypeInstance, - widgetActionSources + WidgetResource, + widgetType } from '@shared/models/widget.models'; import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { WidgetService } from '@core/http/widget.service'; import { UtilsService } from '@core/services/utils.service'; -import { DynamicWidgetComponent } from '@home/components/widget/dynamic-widget.component'; -import { forkJoin, Observable, of, ReplaySubject, throwError } from 'rxjs'; -import { DynamicWidgetComponentFactoryService } from '@home/components/widget/dynamic-widget-component-factory.service'; +import { forkJoin, Observable, of, throwError } from 'rxjs'; import { isDefined, objToBase64 } from '@core/utils'; import * as $ from 'jquery'; -import { WidgetContext, WidgetHeaderAction } from '@home/models/widget-component.models'; +import { + IDynamicWidgetComponent, + WidgetContext, + WidgetHeaderAction, + WidgetInfo, + WidgetTypeInstance +} from '@home/models/widget-component.models'; import { EntityInfo, IWidgetSubscription, - SubscriptionInfo, - WidgetSubscriptionOptions, StateObject, StateParams, - WidgetSubscriptionContext + SubscriptionInfo, + WidgetSubscriptionContext, + WidgetSubscriptionOptions } from '@core/api/widget-api.models'; import { EntityId } from '@shared/models/id/entity-id'; -import { ActivatedRoute, Router, UrlSegment } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import cssjs from '@core/css/css'; import { ResourcesService } from '@core/services/resources.service'; import { catchError, switchMap } from 'rxjs/operators'; @@ -74,6 +76,7 @@ import { TimeService } from '@core/services/time.service'; import { DeviceService } from '@app/core/http/device.service'; import { AlarmService } from '@app/core/http/alarm.service'; import { ExceptionData } from '@shared/models/error.models'; +import { WidgetComponentService } from './widget-component.service'; @Component({ selector: 'tb-widget', @@ -99,14 +102,22 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI widget: Widget; widgetInfo: WidgetInfo; + errorMessages: string[]; widgetContext: WidgetContext; widgetType: any; widgetTypeInstance: WidgetTypeInstance; widgetErrorData: ExceptionData; + loadingData: boolean; - dynamicWidgetComponentFactory: ComponentFactory; - dynamicWidgetComponentRef: ComponentRef; - dynamicWidgetComponent: DynamicWidgetComponent; + displayLegend: boolean; + legendConfig: LegendConfig; + legendData: LegendData; + isLegendFirst: boolean; + legendContainerLayoutType: string; + legendStyle: {[klass: string]: any}; + + dynamicWidgetComponentRef: ComponentRef; + dynamicWidgetComponent: IDynamicWidgetComponent; subscriptionContext: WidgetSubscriptionContext; @@ -120,7 +131,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI constructor(protected store: Store, private route: ActivatedRoute, private router: Router, - private dynamicWidgetComponentFactoryService: DynamicWidgetComponentFactoryService, + private widgetComponentService: WidgetComponentService, private componentFactoryResolver: ComponentFactoryResolver, private elementRef: ElementRef, private injector: Injector, @@ -134,8 +145,67 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI } ngOnInit(): void { + + this.loadingData = true; + this.widget = this.dashboardWidget.widget; + this.displayLegend = isDefined(this.widget.config.showLegend) ? this.widget.config.showLegend + : this.widget.type === widgetType.timeseries; + + this.legendContainerLayoutType = 'column'; + + if (this.displayLegend) { + this.legendConfig = this.widget.config.legendConfig || + { + position: LegendPosition.bottom, + showMin: false, + showMax: false, + showAvg: this.widget.type === widgetType.timeseries, + showTotal: false + }; + this.legendData = { + keys: [], + data: [] + }; + if (this.legendConfig.position === LegendPosition.top || + this.legendConfig.position === LegendPosition.bottom) { + this.legendContainerLayoutType = 'column'; + } else { + this.legendContainerLayoutType = 'row'; + } + switch (this.legendConfig.position) { + case LegendPosition.top: + this.legendStyle = { + paddingBottom: '8px', + maxHeight: '50%', + overflowY: 'auto' + }; + break; + case LegendPosition.bottom: + this.legendStyle = { + paddingTop: '8px', + maxHeight: '50%', + overflowY: 'auto' + }; + break; + case LegendPosition.left: + this.legendStyle = { + paddingRight: '0px', + maxWidth: '50%', + overflowY: 'auto' + }; + break; + case LegendPosition.right: + this.legendStyle = { + paddingLeft: '0px', + maxWidth: '50%', + overflowY: 'auto' + }; + break; + } + } + const actionDescriptorsBySourceId: {[actionSourceId: string]: Array} = {}; if (this.widget.config.actions) { for (const actionSourceId of Object.keys(this.widget.config.actions)) { @@ -244,10 +314,15 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI aliasController: this.dashboard.aliasController }; - this.widgetService.getWidgetInfo(this.widget.bundleAlias, this.widget.typeAlias, this.widget.isSystemType).subscribe( + this.widgetComponentService.getWidgetInfo(this.widget.bundleAlias, this.widget.typeAlias, this.widget.isSystemType).subscribe( (widgetInfo) => { this.widgetInfo = widgetInfo; this.loadFromWidgetInfo(); + }, + (errorData) => { + this.widgetInfo = errorData.widgetInfo; + this.errorMessages = errorData.errorMessages; + this.loadFromWidgetInfo(); } ); @@ -363,14 +438,9 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI } private initialize() { - this.configureDynamicWidgetComponent().subscribe( - () => { - this.dynamicWidgetComponent.loadingData = false; - }, - (error) => { - // TODO: - } - ); + this.configureDynamicWidgetComponent(); + // TODO: + this.loadingData = false; } private destroyDynamicWidgetComponent() { @@ -381,127 +451,37 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI if (this.dynamicWidgetComponentRef) { this.dynamicWidgetComponentRef.destroy(); } - if (this.dynamicWidgetComponentFactory) { - this.dynamicWidgetComponentFactoryService.destroyDynamicWidgetComponentFactory(this.dynamicWidgetComponentFactory); - } } private handleWidgetException(e) { console.error(e); this.widgetErrorData = this.utils.processWidgetException(e); - if (this.dynamicWidgetComponent) { - this.dynamicWidgetComponent.widgetErrorData = this.widgetErrorData; - } } - private configureDynamicWidgetComponent(): Observable { + private configureDynamicWidgetComponent() { + this.widgetContentContainer.clear(); + this.dynamicWidgetComponentRef = this.widgetContentContainer.createComponent(this.widgetInfo.componentFactory); + this.dynamicWidgetComponent = this.dynamicWidgetComponentRef.instance; - const dynamicWidgetComponentSubject = new ReplaySubject(); + this.dynamicWidgetComponent.widgetContext = this.widgetContext; + this.dynamicWidgetComponent.errorMessages = this.errorMessages; - let html = '
' + - 'Widget Error: {{ widgetErrorData.name + ": " + widgetErrorData.message}}' + - '
' + - '
' + - '' + - '
'; + this.widgetContext.$scope = this.dynamicWidgetComponent; - let containerHtml = `
${this.widgetInfo.templateHtml}
`; + const containerElement = $(this.elementRef.nativeElement.querySelector('#widget-container')); - const displayLegend = isDefined(this.widget.config.showLegend) ? this.widget.config.showLegend - : this.widget.type === widgetType.timeseries; + this.widgetContext.$container = $('> ng-component', containerElement); + this.widgetContext.$container.css('display', 'block'); + this.widgetContext.$container.attr('id', 'container'); + this.widgetContext.$containerParent = $(containerElement); - let legendConfig: LegendConfig; - let legendData: LegendData; - if (displayLegend) { - legendConfig = this.widget.config.legendConfig || - { - position: LegendPosition.bottom, - showMin: false, - showMax: false, - showAvg: this.widget.type === widgetType.timeseries, - showTotal: false - }; - legendData = { - keys: [], - data: [] - }; - let layoutType; - if (legendConfig.position === LegendPosition.top || - legendConfig.position === LegendPosition.bottom) { - layoutType = 'column'; - } else { - layoutType = 'row'; + if (this.widgetSizeDetected) { + this.widgetContext.$container.css('height', this.widgetContext.height + 'px'); + this.widgetContext.$container.css('width', this.widgetContext.width + 'px'); } - let legendStyle; - switch (legendConfig.position) { - case LegendPosition.top: - legendStyle = 'padding-bottom: 8px; max-height: 50%; overflow-y: auto;'; - break; - case LegendPosition.bottom: - legendStyle = 'padding-top: 8px; max-height: 50%; overflow-y: auto;'; - break; - case LegendPosition.left: - legendStyle = 'padding-right: 0px; max-width: 50%; overflow-y: auto;'; - break; - case LegendPosition.right: - legendStyle = 'padding-left: 0px; max-width: 50%; overflow-y: auto;'; - break; - } - - const legendHtml = ``; - containerHtml = `
${containerHtml}
`; - html += `
`; - if (legendConfig.position === LegendPosition.top || - legendConfig.position === LegendPosition.left) { - html += legendHtml; - html += containerHtml; - } else { - html += containerHtml; - html += legendHtml; - } - html += '
'; - } else { - html += containerHtml; - } - - this.dynamicWidgetComponentFactoryService.createDynamicWidgetComponentFactory(html).subscribe( - (componentFactory) => { - this.dynamicWidgetComponentFactory = componentFactory; - this.widgetContentContainer.clear(); - this.dynamicWidgetComponentRef = this.widgetContentContainer.createComponent(this.dynamicWidgetComponentFactory); - this.dynamicWidgetComponent = this.dynamicWidgetComponentRef.instance; - - this.dynamicWidgetComponent.loadingData = true; - this.dynamicWidgetComponent.widgetContext = this.widgetContext; - this.dynamicWidgetComponent.widgetErrorData = this.widgetErrorData; - this.dynamicWidgetComponent.displayLegend = displayLegend; - this.dynamicWidgetComponent.legendConfig = legendConfig; - this.dynamicWidgetComponent.legendData = legendData; - - this.widgetContext.$scope = this.dynamicWidgetComponent; - const containerElement = displayLegend ? $(this.elementRef.nativeElement.querySelector('#widget-container')) - : $(this.elementRef.nativeElement); - - this.widgetContext.$container = $('#container', containerElement); - this.widgetContext.$containerParent = $(containerElement); - - if (this.widgetSizeDetected) { - this.widgetContext.$container.css('height', this.widgetContext.height + 'px'); - this.widgetContext.$container.css('width', this.widgetContext.width + 'px'); - } - - // @ts-ignore - addResizeListener(this.widgetContext.$containerParent[0], this.onResizeListener); - - dynamicWidgetComponentSubject.next(); - dynamicWidgetComponentSubject.complete(); - }, - (e) => { - dynamicWidgetComponentSubject.error(e); - } - ); - return dynamicWidgetComponentSubject.asObservable(); + // @ts-ignore + addResizeListener(this.widgetContext.$containerParent[0], this.onResizeListener); } private createSubscription(options: WidgetSubscriptionOptions, subscribe: boolean): Observable { 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 63a959c3d0..8bc55612e2 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 @@ -16,23 +16,30 @@ import { ExceptionData } from '@shared/models/error.models'; import { IDashboardComponent } from '@home/models/dashboard-component.models'; -import { WidgetActionDescriptor, WidgetConfig, WidgetConfigSettings, widgetType } from '@shared/models/widget.models'; +import { + WidgetActionDescriptor, + WidgetActionSource, + WidgetConfig, + WidgetConfigSettings, + WidgetControllerDescriptor, + WidgetType, + widgetType, + WidgetTypeDescriptor, + WidgetTypeParameters +} from '@shared/models/widget.models'; import { Timewindow } from '@shared/models/time/time.models'; import { EntityInfo, - IWidgetSubscription, - SubscriptionInfo, - WidgetSubscriptionOptions, - IStateController, IAliasController, - TimewindowFunctions, - WidgetSubscriptionApi, + IStateController, + IWidgetSubscription, + IWidgetUtils, RpcApi, + TimewindowFunctions, WidgetActionsApi, - IWidgetUtils + WidgetSubscriptionApi } from '@core/api/widget-api.models'; -import { Observable } from 'rxjs'; -import { EntityId } from '@shared/models/id/entity-id'; +import { ComponentFactory } from '@angular/core'; export interface IWidgetAction { name: string; @@ -49,13 +56,6 @@ export interface WidgetAction extends IWidgetAction { show: boolean; } -export interface IDynamicWidgetComponent { - widgetContext: WidgetContext; - widgetErrorData: ExceptionData; - loadingData: boolean; - [key: string]: any; -} - export interface WidgetContext { inited?: boolean; $container?: any; @@ -87,3 +87,93 @@ export interface WidgetContext { customHeaderActions?: Array; widgetActions?: Array; } + +export interface IDynamicWidgetComponent { + widgetContext: WidgetContext; + errorMessages: string[]; + [key: string]: any; +} + +export interface WidgetInfo extends WidgetTypeDescriptor, WidgetControllerDescriptor { + widgetName: string; + alias: string; + typeSettingsSchema?: string; + typeDataKeySettingsSchema?: string; + componentFactory?: ComponentFactory; +} + +export const MissingWidgetType: WidgetInfo = { + type: widgetType.latest, + widgetName: 'Widget type not found', + alias: 'undefined', + sizeX: 8, + sizeY: 6, + resources: [], + templateHtml: '
' + + '
widget.widget-type-not-found
' + + '
', + templateCss: '', + controllerScript: 'self.onInit = function() {}', + settingsSchema: '{}\n', + dataKeySettingsSchema: '{}\n', + defaultConfig: '{\n' + + '"title": "Widget type not found",\n' + + '"datasources": [],\n' + + '"settings": {}\n' + + '}\n' +}; + +export const ErrorWidgetType: WidgetInfo = { + type: widgetType.latest, + widgetName: 'Error loading widget', + alias: 'error', + sizeX: 8, + sizeY: 6, + resources: [], + templateHtml: '
' + + '
widget.widget-type-load-error
' + + '
{{ error }}
' + + '
', + templateCss: '', + controllerScript: 'self.onInit = function() {}', + settingsSchema: '{}\n', + dataKeySettingsSchema: '{}\n', + defaultConfig: '{\n' + + '"title": "Widget failed to load",\n' + + '"datasources": [],\n' + + '"settings": {}\n' + + '}\n' +}; + +export interface WidgetTypeInstance { + getSettingsSchema?: () => string; + getDataKeySettingsSchema?: () => string; + typeParameters?: () => WidgetTypeParameters; + useCustomDatasources?: () => boolean; + actionSources?: () => {[key: string]: WidgetActionSource}; + + onInit?: () => void; + onDataUpdated?: () => void; + onResize?: () => void; + onEditModeChanged?: () => void; + onMobileModeChanged?: () => void; + onDestroy?: () => void; +} + +export function toWidgetInfo(widgetTypeEntity: WidgetType): WidgetInfo { + return { + widgetName: widgetTypeEntity.name, + alias: widgetTypeEntity.alias, + type: widgetTypeEntity.descriptor.type, + sizeX: widgetTypeEntity.descriptor.sizeX, + sizeY: widgetTypeEntity.descriptor.sizeY, + resources: widgetTypeEntity.descriptor.resources, + templateHtml: widgetTypeEntity.descriptor.templateHtml, + templateCss: widgetTypeEntity.descriptor.templateCss, + controllerScript: widgetTypeEntity.descriptor.controllerScript, + settingsSchema: widgetTypeEntity.descriptor.settingsSchema, + dataKeySettingsSchema: widgetTypeEntity.descriptor.dataKeySettingsSchema, + defaultConfig: widgetTypeEntity.descriptor.defaultConfig + }; +} + diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.ts b/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.ts index 3707e0c277..a1ba47ee1c 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.ts +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.ts @@ -25,13 +25,14 @@ import { ActivatedRoute } from '@angular/router'; import { Authority } from '@shared/models/authority.enum'; import { NULL_UUID } from '@shared/models/id/has-uuid'; import { Observable } from 'rxjs'; -import { toWidgetInfo, Widget, widgetType } from '@app/shared/models/widget.models'; +import { Widget, widgetType } from '@app/shared/models/widget.models'; import { WidgetService } from '@core/http/widget.service'; import { map, share } from 'rxjs/operators'; import { DialogService } from '@core/services/dialog.service'; import { FooterFabButtons } from '@app/shared/components/footer-fab-buttons.component'; import { DashboardCallbacks, WidgetsData } from '@home/models/dashboard-component.models'; import { IAliasController } from '@app/core/api/widget-api.models'; +import { toWidgetInfo } from '@home/models/widget-component.models'; @Component({ selector: 'tb-widget-library', diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index f40e380364..daba2d232a 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -14,11 +14,9 @@ /// limitations under the License. /// -import {BaseData} from '@shared/models/base-data'; -import {TenantId} from '@shared/models/id/tenant-id'; -import {WidgetsBundleId} from '@shared/models/id/widgets-bundle-id'; -import {WidgetTypeId} from '@shared/models/id/widget-type-id'; -import { AliasEntityType, EntityType, EntityTypeTranslation } from '@shared/models/entity-type.models'; +import { BaseData } from '@shared/models/base-data'; +import { TenantId } from '@shared/models/id/tenant-id'; +import { WidgetTypeId } from '@shared/models/id/widget-type-id'; import { Timewindow } from '@shared/models/time/time.models'; export enum widgetType { @@ -139,86 +137,6 @@ export interface WidgetType extends BaseData { descriptor: WidgetTypeDescriptor; } -export interface WidgetInfo extends WidgetTypeDescriptor, WidgetControllerDescriptor { - widgetName: string; - alias: string; - typeSettingsSchema?: string; - typeDataKeySettingsSchema?: string; -} - -export const MissingWidgetType: WidgetInfo = { - type: widgetType.latest, - widgetName: 'Widget type not found', - alias: 'undefined', - sizeX: 8, - sizeY: 6, - resources: [], - templateHtml: '
' + - '
widget.widget-type-not-found
' + - '
', - templateCss: '', - controllerScript: 'self.onInit = function() {}', - settingsSchema: '{}\n', - dataKeySettingsSchema: '{}\n', - defaultConfig: '{\n' + - '"title": "Widget type not found",\n' + - '"datasources": [],\n' + - '"settings": {}\n' + - '}\n' -}; - -export const ErrorWidgetType: WidgetInfo = { - type: widgetType.latest, - widgetName: 'Error loading widget', - alias: 'error', - sizeX: 8, - sizeY: 6, - resources: [], - templateHtml: '
' + - '
widget.widget-type-load-error
', - templateCss: '', - controllerScript: 'self.onInit = function() {}', - settingsSchema: '{}\n', - dataKeySettingsSchema: '{}\n', - defaultConfig: '{\n' + - '"title": "Widget failed to load",\n' + - '"datasources": [],\n' + - '"settings": {}\n' + - '}\n' -}; - -export interface WidgetTypeInstance { - getSettingsSchema?: () => string; - getDataKeySettingsSchema?: () => string; - typeParameters?: () => WidgetTypeParameters; - useCustomDatasources?: () => boolean; - actionSources?: () => {[key: string]: WidgetActionSource}; - - onInit?: () => void; - onDataUpdated?: () => void; - onResize?: () => void; - onEditModeChanged?: () => void; - onMobileModeChanged?: () => void; - onDestroy?: () => void; -} - -export function toWidgetInfo(widgetTypeEntity: WidgetType): WidgetInfo { - return { - widgetName: widgetTypeEntity.name, - alias: widgetTypeEntity.alias, - type: widgetTypeEntity.descriptor.type, - sizeX: widgetTypeEntity.descriptor.sizeX, - sizeY: widgetTypeEntity.descriptor.sizeY, - resources: widgetTypeEntity.descriptor.resources, - templateHtml: widgetTypeEntity.descriptor.templateHtml, - templateCss: widgetTypeEntity.descriptor.templateCss, - controllerScript: widgetTypeEntity.descriptor.controllerScript, - settingsSchema: widgetTypeEntity.descriptor.settingsSchema, - dataKeySettingsSchema: widgetTypeEntity.descriptor.dataKeySettingsSchema, - defaultConfig: widgetTypeEntity.descriptor.defaultConfig - }; -} - export enum LegendDirection { column = 'column', row = 'row' From b48663bab9079322e2e4367fef05ed7e368e4b6a Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 10 Sep 2019 15:12:10 +0300 Subject: [PATCH 031/133] Widget API implementation. --- ui-ngx/angular.json | 4 +- ui-ngx/package-lock.json | 109 +++++ ui-ngx/package.json | 3 + ui-ngx/src/app/core/api/alias-controller.ts | 39 ++ ui-ngx/src/app/core/api/datasource.service.ts | 42 ++ ui-ngx/src/app/core/api/widget-api.models.ts | 116 ++++- .../src/app/core/api/widget-subscription.ts | 396 ++++++++++++++++ ui-ngx/src/app/core/http/alarm.service.ts | 17 + ui-ngx/src/app/core/http/dashboard.service.ts | 39 +- ui-ngx/src/app/core/http/entity.service.ts | 202 ++++++-- ui-ngx/src/app/core/services/raf.service.ts | 68 +++ ui-ngx/src/app/core/services/time.service.ts | 31 +- ui-ngx/src/app/core/services/utils.service.ts | 93 +++- ui-ngx/src/app/core/utils.ts | 8 + .../dashboard/dashboard.component.html | 7 +- .../dashboard/dashboard.component.ts | 21 +- .../entity/entities-table.component.ts | 6 +- .../widget/dynamic-widget.component.ts | 6 + .../widget/widget-component.service.ts | 15 +- .../components/widget/widget.component.html | 12 +- .../components/widget/widget.component.scss | 4 + .../components/widget/widget.component.ts | 443 +++++++++++++++--- .../home/models/dashboard-component.models.ts | 4 +- .../home/models/widget-component.models.ts | 18 +- .../pages/widget/widget-library.component.ts | 3 +- .../time/datetime-period.component.ts | 7 +- .../time/timewindow-panel.component.ts | 25 +- .../components/time/timewindow.component.ts | 10 +- ui-ngx/src/app/shared/models/alarm.models.ts | 65 +++ .../src/app/shared/models/material.models.ts | 367 +++++++++++++++ .../src/app/shared/models/time/time.models.ts | 295 ++++++------ ui-ngx/src/app/shared/models/widget.models.ts | 65 ++- ui-ngx/src/tsconfig.app.json | 2 +- ui-ngx/src/typings.d.ts | 19 + ui-ngx/tsconfig.json | 3 +- 35 files changed, 2269 insertions(+), 295 deletions(-) create mode 100644 ui-ngx/src/app/core/api/alias-controller.ts create mode 100644 ui-ngx/src/app/core/api/datasource.service.ts create mode 100644 ui-ngx/src/app/core/api/widget-subscription.ts create mode 100644 ui-ngx/src/app/core/services/raf.service.ts create mode 100644 ui-ngx/src/app/shared/models/material.models.ts create mode 100644 ui-ngx/src/typings.d.ts diff --git a/ui-ngx/angular.json b/ui-ngx/angular.json index 7165edf085..629a0fd8df 100644 --- a/ui-ngx/angular.json +++ b/ui-ngx/angular.json @@ -31,11 +31,13 @@ { "glob": "worker-javascript.js", "input": "./node_modules/ace-builds/src-min/", "output": "/" } ], "styles": [ - "src/styles.scss" + "src/styles.scss", + "node_modules/jquery.terminal/css/jquery.terminal.min.css" ], "scripts": [ "node_modules/javascript-detect-element-resize/detect-element-resize.js", "node_modules/jquery/dist/jquery.min.js", + "node_modules/jquery.terminal/js/jquery.terminal.min.js", "node_modules/ace-builds/src-min/ace.js", "node_modules/ace-builds/src-min/ext-language_tools.js", "node_modules/ace-builds/src-min/ext-searchbox.js", diff --git a/ui-ngx/package-lock.json b/ui-ngx/package-lock.json index 71c03da869..a5f864c71c 100644 --- a/ui-ngx/package-lock.json +++ b/ui-ngx/package-lock.json @@ -1152,6 +1152,15 @@ "@types/jasmine": "*" } }, + "@types/jquery": { + "version": "3.3.31", + "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.3.31.tgz", + "integrity": "sha512-Lz4BAJihoFw5nRzKvg4nawXPzutkv7wmfQ5121avptaSIXlDNJCUuxZxX/G+9EVidZGuO0UBlk+YjKbwRKJigg==", + "dev": true, + "requires": { + "@types/sizzle": "*" + } + }, "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", @@ -1176,6 +1185,11 @@ "integrity": "sha512-lMC2G0ItF2xv4UCiwbJGbnJlIuUixHrioOhNGHSCsYCJ8l4t9hMCUimCytvFv7qy6AfSzRxhRHoGa+UqaqwyeA==", "dev": true }, + "@types/sizzle": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.2.tgz", + "integrity": "sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg==" + }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -2666,6 +2680,17 @@ "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", "dev": true }, + "clipboard": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.4.tgz", + "integrity": "sha512-Vw26VSLRpJfBofiVaFb/I8PVfdI1OxKcYShe6fm0sP/DtmiWQNCjhM/okTvdCo0G+lMMm1rMYbk4IK4x1X+kgQ==", + "optional": true, + "requires": { + "good-listener": "^1.2.2", + "select": "^1.1.2", + "tiny-emitter": "^2.0.0" + } + }, "cliui": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", @@ -3427,6 +3452,21 @@ } } }, + "defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "requires": { + "clone": "^1.0.2" + }, + "dependencies": { + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" + } + } + }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -3545,6 +3585,12 @@ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", "dev": true }, + "delegate": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", + "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==", + "optional": true + }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -5173,6 +5219,15 @@ } } }, + "good-listener": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", + "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=", + "optional": true, + "requires": { + "delegate": "^3.1.2" + } + }, "graceful-fs": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.1.tgz", @@ -6345,6 +6400,27 @@ "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.4.1.tgz", "integrity": "sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw==" }, + "jquery.terminal": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jquery.terminal/-/jquery.terminal-2.8.0.tgz", + "integrity": "sha512-veyI105Vvro7MEInnfm7ZivToJCtFl6t2wSiV26CODl+1yv+zkbzibbYqAXQIG9Cpye2DvH0+aOUfSjnzCBV/A==", + "requires": { + "@types/jquery": "3.3.29", + "jquery": "~3", + "prismjs": "^1.16.0", + "wcwidth": "^1.0.1" + }, + "dependencies": { + "@types/jquery": { + "version": "3.3.29", + "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.3.29.tgz", + "integrity": "sha512-FhJvBninYD36v3k6c+bVk1DSZwh7B5Dpb/Pyk3HKVsiohn0nhbefZZ+3JXbWQhFyt0MxSl2jRDdGQPHeOHFXrQ==", + "requires": { + "@types/sizzle": "*" + } + } + } + }, "js-tokens": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", @@ -8315,6 +8391,14 @@ "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", "dev": true }, + "prismjs": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.17.1.tgz", + "integrity": "sha512-PrEDJAFdUGbOP6xK/UsfkC5ghJsPJviKgnQOoxaDbBjwc8op68Quupwt1DeAFoG8GImPhiKXAvvsH7wDSLsu1Q==", + "requires": { + "clipboard": "^2.0.0" + } + }, "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -9049,6 +9133,12 @@ "resolved": "https://registry.npmjs.org/screenfull/-/screenfull-4.2.1.tgz", "integrity": "sha512-PLSp6f5XdhvjCCCO8OjavRfzkSGL3Qmdm7P82bxyU8HDDDBhDV3UckRaYcRa/NDNTYt8YBpzjoLWHUAejmOjLg==" }, + "select": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", + "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=", + "optional": true + }, "select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -10138,6 +10228,17 @@ "setimmediate": "^1.0.4" } }, + "tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==", + "optional": true + }, + "tinycolor2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz", + "integrity": "sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g=" + }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -10687,6 +10788,14 @@ "minimalistic-assert": "^1.0.0" } }, + "wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", + "requires": { + "defaults": "^1.0.3" + } + }, "webdriver-js-extender": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/webdriver-js-extender/-/webdriver-js-extender-2.1.0.tgz", diff --git a/ui-ngx/package.json b/ui-ngx/package.json index 5548100061..b76d0686c0 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -41,12 +41,14 @@ "hammerjs": "^2.0.8", "javascript-detect-element-resize": "^0.5.3", "jquery": "^3.4.1", + "jquery.terminal": "^2.8.0", "material-design-icons": "^3.0.1", "messageformat": "^2.3.0", "ngx-clipboard": "^12.2.0", "ngx-translate-messageformat-compiler": "^4.5.0", "rxjs": "~6.5.2", "screenfull": "^4.2.1", + "tinycolor2": "^1.4.1", "tslib": "^1.10.0", "typeface-roboto": "^0.0.75", "zone.js": "~0.9.1" @@ -59,6 +61,7 @@ "@angular/language-service": "~8.2.0", "@types/jasmine": "~3.4.0", "@types/jasminewd2": "~2.0.6", + "@types/jquery": "^3.3.31", "@types/node": "~10.14.15", "codelyzer": "~5.1.0", "compression-webpack-plugin": "^3.0.0", diff --git a/ui-ngx/src/app/core/api/alias-controller.ts b/ui-ngx/src/app/core/api/alias-controller.ts new file mode 100644 index 0000000000..12e7232b26 --- /dev/null +++ b/ui-ngx/src/app/core/api/alias-controller.ts @@ -0,0 +1,39 @@ +/// +/// Copyright © 2016-2019 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 { IAliasController, AliasInfo } from '@core/api/widget-api.models'; +import { Observable, of, Subject } from 'rxjs'; +import { Datasource } from '@app/shared/models/widget.models'; +import { deepClone } from '@core/utils'; + +export class DummyAliasController implements IAliasController { + + entityAliasesChanged: Observable>; + + [key: string]: any | null; + + constructor() { + this.entityAliasesChanged = new Subject>().asObservable(); + } + + getAliasInfo(aliasId): Observable { + return of(null); + } + + resolveDatasources(datasources: Array): Observable> { + return of(deepClone(datasources)); + } +} diff --git a/ui-ngx/src/app/core/api/datasource.service.ts b/ui-ngx/src/app/core/api/datasource.service.ts new file mode 100644 index 0000000000..279466d386 --- /dev/null +++ b/ui-ngx/src/app/core/api/datasource.service.ts @@ -0,0 +1,42 @@ +/// +/// Copyright © 2016-2019 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 { Injectable } from '@angular/core'; +import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service'; +import { UtilsService } from '@core/services/utils.service'; +import { EntityType } from '@app/shared/models/entity-type.models'; + +export interface DatasourceListener { + entityType: EntityType; + entityId: string; +} + +@Injectable({ + providedIn: 'root' +}) +export class DatasourceService { + + constructor(private telemetryService: TelemetryWebsocketService, + private utils: UtilsService) {} + + public subscribeToDatasource(listener: DatasourceListener) { + // TODO: + } + + public unsubscribeFromDatasource(listener: DatasourceListener) { + // TODO: + } +} diff --git a/ui-ngx/src/app/core/api/widget-api.models.ts b/ui-ngx/src/app/core/api/widget-api.models.ts index 0f035cd195..2b52a73494 100644 --- a/ui-ngx/src/app/core/api/widget-api.models.ts +++ b/ui-ngx/src/app/core/api/widget-api.models.ts @@ -16,11 +16,23 @@ import { Observable } from 'rxjs'; import { EntityId } from '@app/shared/models/id/entity-id'; -import { WidgetActionDescriptor, widgetType } from '@shared/models/widget.models'; +import { + WidgetActionDescriptor, + widgetType, + LegendConfig, + LegendData, + Datasource, + DatasourceData, DataSet, DatasourceType, KeyInfo +} from '@shared/models/widget.models'; import { TimeService } from '../services/time.service'; import { DeviceService } from '../http/device.service'; import { AlarmService } from '../http/alarm.service'; import { UtilsService } from '@core/services/utils.service'; +import { Timewindow } from '@shared/models/time/time.models'; +import { EntityType } from '@shared/models/entity-type.models'; +import { AlarmSearchStatus } from '@shared/models/alarm.models'; +import { HttpErrorResponse } from '@angular/common/http'; +import { DatasourceService } from '@core/api/datasource.service'; export interface TimewindowFunctions { onUpdateTimewindow: (startTimeMs: number, endTimeMs: number, interval: number) => void; @@ -28,7 +40,7 @@ export interface TimewindowFunctions { } export interface WidgetSubscriptionApi { - createSubscription: (options: WidgetSubscriptionOptions, subscribe: boolean) => Observable; + createSubscription: (options: WidgetSubscriptionOptions, subscribe?: boolean) => Observable; createSubscriptionFromInfo: (type: widgetType, subscriptionsInfo: Array, options: WidgetSubscriptionOptions, useDefaultComponents: boolean, subscribe: boolean) => Observable; @@ -52,7 +64,21 @@ export interface WidgetActionsApi { elementClick: ($event: Event) => void; } +export interface AliasInfo { + stateEntity?: boolean; + currentEntity?: { + id: string; + entityType: EntityType; + name?: string; + }; + [key: string]: any | null; + // TODO: +} + export interface IAliasController { + entityAliasesChanged: Observable>; + getAliasInfo(aliasId): Observable; + resolveDatasources(datasources: Array): Observable>; [key: string]: any | null; // TODO: } @@ -82,39 +108,109 @@ export interface EntityInfo { } export interface SubscriptionInfo { + type: DatasourceType; + name?: string; + entityType?: EntityType; + entityId?: string; + entityIds?: Array; + entityName?: string; + entityNamePrefix?: string; + timeseries?: Array; + attributes?: Array; + functions?: Array; + alarmFields?: Array; [key: string]: any; - // TODO: } export interface WidgetSubscriptionContext { timeService: TimeService; deviceService: DeviceService; alarmService: AlarmService; + datasourceService: DatasourceService; utils: UtilsService; widgetUtils: IWidgetUtils; dashboardTimewindowApi: TimewindowFunctions; - getServerTimeDiff: Observable; + getServerTimeDiff: () => Observable; aliasController: IAliasController; [key: string]: any; // TODO: } +export interface WidgetSubscriptionCallbacks { + onDataUpdated?: (subscription: IWidgetSubscription) => void; + onDataUpdateError?: (subscription: IWidgetSubscription, e: any) => void; + dataLoading?: (subscription: IWidgetSubscription) => void; + legendDataUpdated?: (subscription: IWidgetSubscription) => void; + timeWindowUpdated?: (subscription: IWidgetSubscription, timeWindowConfig: Timewindow) => void; + rpcStateChanged?: (subscription: IWidgetSubscription) => void; + onRpcSuccess?: (subscription: IWidgetSubscription) => void; + onRpcFailed?: (subscription: IWidgetSubscription) => void; + onRpcErrorCleared?: (subscription: IWidgetSubscription) => void; +} + export interface WidgetSubscriptionOptions { + type: widgetType; + stateData?: boolean; + alarmSource?: Datasource; + alarmSearchStatus?: AlarmSearchStatus; + alarmsPollingInterval?: number; + datasources?: Array; + targetDeviceAliasIds?: Array; + targetDeviceIds?: Array; + useDashboardTimewindow?: boolean; + displayTimewindow?: boolean; + timeWindowConfig?: Timewindow; + dashboardTimewindow?: Timewindow; + legendConfig?: LegendConfig; + decimals?: number; + units?: string; + callbacks?: WidgetSubscriptionCallbacks; [key: string]: any; // TODO: } export interface IWidgetSubscription { - onUpdateTimewindow: (startTimeMs: number, endTimeMs: number, interval: number) => void; - onResetTimewindow: () => void; + id: string; + init$: Observable; + ctx: WidgetSubscriptionContext; + type: widgetType; + callbacks: WidgetSubscriptionCallbacks; - sendOneWayCommand: (method: string, params?: any, timeout?: number) => Observable; - sendTwoWayCommand: (method: string, params?: any, timeout?: number) => Observable; + loadingData: boolean; + useDashboardTimewindow: boolean; + + legendData: LegendData; + datasources?: Array; + data?: Array; + hiddenData?: Array<{data: DataSet}>; + timeWindow?: Timewindow; + + alarmSource?: Datasource; + alarmSearchStatus?: AlarmSearchStatus; + alarmsPollingInterval?: number; + + targetDeviceAliasIds?: Array; + targetDeviceIds?: Array; + + rpcEnabled?: boolean; + executingRpcRequest?: boolean; + rpcErrorText?: string; + rpcRejection?: HttpErrorResponse; + + getFirstEntityInfo(): EntityInfo; + + onAliasesChanged(aliasIds: Array): boolean; + + onUpdateTimewindow(startTimeMs: number, endTimeMs: number, interval: number): void; + onResetTimewindow(): void; + updateTimewindowConfig(newTimewindow: Timewindow): void; - clearRpcError: () => void; + sendOneWayCommand(method: string, params?: any, timeout?: number): Observable; + sendTwoWayCommand(method: string, params?: any, timeout?: number): Observable; + clearRpcError(): void; - getFirstEntityInfo: () => EntityInfo; + subscribe(): void; destroy(): void; diff --git a/ui-ngx/src/app/core/api/widget-subscription.ts b/ui-ngx/src/app/core/api/widget-subscription.ts new file mode 100644 index 0000000000..b4cd76d0c0 --- /dev/null +++ b/ui-ngx/src/app/core/api/widget-subscription.ts @@ -0,0 +1,396 @@ +/// +/// Copyright © 2016-2019 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 { + EntityInfo, + IWidgetSubscription, + WidgetSubscriptionCallbacks, + WidgetSubscriptionContext, + WidgetSubscriptionOptions +} from '@core/api/widget-api.models'; +import { + DataSet, + Datasource, + DatasourceData, + LegendConfig, + LegendData, + LegendKey, + LegendKeyData, + widgetType +} from '@app/shared/models/widget.models'; +import { HttpErrorResponse } from '@angular/common/http'; +import { Timewindow } from '@app/shared/models/time/time.models'; +import { Observable, of, ReplaySubject, Subject } from 'rxjs'; +import { CancelAnimationFrame } from '@core/services/raf.service'; +import { EntityType } from '@shared/models/entity-type.models'; +import { AlarmInfo, AlarmSearchStatus } from '@shared/models/alarm.models'; +import { deepClone, isDefined } from '@core/utils'; +import { AlarmSourceListener } from '@core/http/alarm.service'; +import { DatasourceListener } from '@core/api/datasource.service'; + +export class WidgetSubscription implements IWidgetSubscription { + + id: string; + ctx: WidgetSubscriptionContext; + type: widgetType; + callbacks: WidgetSubscriptionCallbacks; + + timeWindow: Timewindow; + originalTimewindow: Timewindow; + timeWindowConfig: Timewindow; + subscriptionTimewindow: Timewindow; + useDashboardTimewindow: boolean; + + data: Array; + datasources: Array; + datasourceListeners: Array; + hiddenData: Array<{ data: DataSet }>; + legendData: LegendData; + legendConfig: LegendConfig; + caulculateLegendData: boolean; + displayLegend: boolean; + stateData: boolean; + decimals: number; + units: string; + + alarms: Array; + alarmSource: Datasource; + alarmSearchStatus: AlarmSearchStatus; + alarmsPollingInterval: number; + alarmSourceListener: AlarmSourceListener; + + loadingData: boolean; + + targetDeviceAliasIds?: Array; + targetDeviceIds?: Array; + + executingRpcRequest: boolean; + rpcEnabled: boolean; + rpcErrorText: string; + rpcRejection: HttpErrorResponse; + + init$: Observable; + + cafs: {[cafId: string]: CancelAnimationFrame} = {}; + + targetDeviceAliasId: string; + targetDeviceId: string; + targetDeviceName: string; + executingSubjects: Array>; + + constructor(subscriptionContext: WidgetSubscriptionContext, options: WidgetSubscriptionOptions) { + const subscriptionSubject = new ReplaySubject(); + this.init$ = subscriptionSubject.asObservable(); + this.ctx = subscriptionContext; + this.type = options.type; + this.id = this.ctx.utils.guid(); + this.callbacks = options.callbacks; + + if (this.type === widgetType.rpc) { + this.callbacks.rpcStateChanged = this.callbacks.rpcStateChanged || (() => {}); + this.callbacks.onRpcSuccess = this.callbacks.onRpcSuccess || (() => {}); + this.callbacks.onRpcFailed = this.callbacks.onRpcFailed || (() => {}); + this.callbacks.onRpcErrorCleared = this.callbacks.onRpcErrorCleared || (() => {}); + + this.targetDeviceAliasIds = options.targetDeviceAliasIds; + this.targetDeviceIds = options.targetDeviceIds; + + this.targetDeviceAliasId = null; + this.targetDeviceId = null; + + this.rpcRejection = null; + this.rpcErrorText = null; + this.rpcEnabled = false; + this.executingRpcRequest = false; + this.executingSubjects = []; + this.initRpc().subscribe(() => { + subscriptionSubject.next(this); + subscriptionSubject.complete(); + }); + } else if (this.type === widgetType.alarm) { + this.callbacks.onDataUpdated = this.callbacks.onDataUpdated || (() => {}); + this.callbacks.onDataUpdateError = this.callbacks.onDataUpdateError || (() => {}); + this.callbacks.dataLoading = this.callbacks.dataLoading || (() => {}); + this.callbacks.timeWindowUpdated = this.callbacks.timeWindowUpdated || (() => {}); + this.alarmSource = options.alarmSource; + this.alarmSearchStatus = isDefined(options.alarmSearchStatus) ? + options.alarmSearchStatus : AlarmSearchStatus.ANY; + this.alarmsPollingInterval = isDefined(options.alarmsPollingInterval) ? + options.alarmsPollingInterval : 5000; + this.alarmSourceListener = null; + this.alarms = []; + this.originalTimewindow = null; + this.timeWindow = {}; + this.useDashboardTimewindow = options.useDashboardTimewindow; + if (this.useDashboardTimewindow) { + this.timeWindowConfig = deepClone(options.dashboardTimewindow); + } else { + this.timeWindowConfig = deepClone(options.timeWindowConfig); + } + this.subscriptionTimewindow = null; + this.loadingData = false; + this.displayLegend = false; + this.initAlarmSubscription().subscribe(() => { + subscriptionSubject.next(this); + subscriptionSubject.complete(); + }, + () => { + subscriptionSubject.error(null); + }); + } else { + this.callbacks.onDataUpdated = this.callbacks.onDataUpdated || (() => {}); + this.callbacks.onDataUpdateError = this.callbacks.onDataUpdateError || (() => {}); + this.callbacks.dataLoading = this.callbacks.dataLoading || (() => {}); + this.callbacks.legendDataUpdated = this.callbacks.legendDataUpdated || (() => {}); + this.callbacks.timeWindowUpdated = this.callbacks.timeWindowUpdated || (() => {}); + + this.datasources = this.ctx.utils.validateDatasources(options.datasources); + this.datasourceListeners = []; + this.data = []; + this.hiddenData = []; + this.originalTimewindow = null; + this.timeWindow = {}; + this.useDashboardTimewindow = options.useDashboardTimewindow; + this.stateData = options.stateData; + if (this.useDashboardTimewindow) { + this.timeWindowConfig = deepClone(options.dashboardTimewindow); + } else { + this.timeWindowConfig = deepClone(options.timeWindowConfig); + } + + this.subscriptionTimewindow = null; + + this.units = options.units || ''; + this.decimals = isDefined(options.decimals) ? options.decimals : 2; + + this.loadingData = false; + + if (options.legendConfig) { + this.legendConfig = options.legendConfig; + this.legendData = { + keys: [], + data: [] + }; + this.displayLegend = true; + } else { + this.displayLegend = false; + } + this.caulculateLegendData = this.displayLegend && + this.type === widgetType.timeseries && + (this.legendConfig.showMin === true || + this.legendConfig.showMax === true || + this.legendConfig.showAvg === true || + this.legendConfig.showTotal === true); + this.initDataSubscription().subscribe(() => { + subscriptionSubject.next(this); + subscriptionSubject.complete(); + }, + () => { + subscriptionSubject.error(null); + }); + } + } + + private initRpc(): Observable { + const initRpcSubject = new ReplaySubject(); + if (this.targetDeviceAliasIds && this.targetDeviceAliasIds.length > 0) { + this.targetDeviceAliasId = this.targetDeviceAliasIds[0]; + this.ctx.aliasController.getAliasInfo(this.targetDeviceAliasId).subscribe( + (aliasInfo) => { + if (aliasInfo.currentEntity && aliasInfo.currentEntity.entityType === EntityType.DEVICE) { + this.targetDeviceId = aliasInfo.currentEntity.id; + this.targetDeviceName = aliasInfo.currentEntity.name; + if (this.targetDeviceId) { + this.rpcEnabled = true; + } else { + this.rpcEnabled = this.ctx.utils.widgetEditMode ? true : false; + } + this.callbacks.rpcStateChanged(this); + initRpcSubject.next(); + initRpcSubject.complete(); + } else { + this.rpcEnabled = false; + this.callbacks.rpcStateChanged(this); + initRpcSubject.next(); + initRpcSubject.complete(); + } + }, + () => { + this.rpcEnabled = false; + this.callbacks.rpcStateChanged(this); + initRpcSubject.next(); + initRpcSubject.complete(); + } + ); + } else { + if (this.targetDeviceIds && this.targetDeviceIds.length > 0) { + this.targetDeviceId = this.targetDeviceIds[0]; + } + if (this.targetDeviceId) { + this.rpcEnabled = true; + } else { + this.rpcEnabled = this.ctx.utils.widgetEditMode ? true : false; + } + this.callbacks.rpcStateChanged(this); + initRpcSubject.next(); + initRpcSubject.complete(); + } + return initRpcSubject.asObservable(); + } + + private initAlarmSubscription(): Observable { + // TODO: + return of(null); + } + + private initDataSubscription(): Observable { + const initDataSubscriptionSubject = new ReplaySubject(1); + this.loadStDiff().subscribe(() => { + if (!this.ctx.aliasController) { + this.configureData(); + initDataSubscriptionSubject.next(); + initDataSubscriptionSubject.complete(); + } else { + this.ctx.aliasController.resolveDatasources(this.datasources).subscribe( + (datasources) => { + this.datasources = datasources; + this.configureData(); + initDataSubscriptionSubject.next(); + initDataSubscriptionSubject.complete(); + }, + () => { + initDataSubscriptionSubject.error(null); + } + ); + } + }); + return initDataSubscriptionSubject.asObservable(); + } + + private configureData() { + let dataIndex = 0; + this.datasources.forEach((datasource) => { + datasource.dataKeys.forEach((dataKey) => { + dataKey.hidden = false; + dataKey.pattern = dataKey.label; + const datasourceData: DatasourceData = { + datasource, + dataKey, + data: [] + }; + this.data.push(datasourceData); + this.hiddenData.push({data: []}); + if (this.displayLegend) { + const legendKey: LegendKey = { + dataKey, + dataIndex: dataIndex++ + }; + this.legendData.keys.push(legendKey); + const legendKeyData: LegendKeyData = { + min: null, + max: null, + avg: null, + total: null, + hidden: false + }; + this.legendData.data.push(legendKeyData); + } + }); + }); + if (this.displayLegend) { + this.legendData.keys = this.legendData.keys.sort((key1, key2) => key1.dataKey.label.localeCompare(key2.dataKey.label)); + // TODO: + } + if (this.type === widgetType.timeseries) { + if (this.useDashboardTimewindow) { + // TODO: + } else { + // TODO: + } + } + } + + getFirstEntityInfo(): EntityInfo { + return undefined; + } + + updateTimewindowConfig(newTimewindow: Timewindow): void { + } + + onResetTimewindow(): void { + } + + onUpdateTimewindow(startTimeMs: number, endTimeMs: number, interval: number): void { + } + + sendOneWayCommand(method: string, params?: any, timeout?: number): Observable { + return undefined; + } + + sendTwoWayCommand(method: string, params?: any, timeout?: number): Observable { + return undefined; + } + + clearRpcError(): void { + } + + subscribe(): void { + // TODO: + this.notifyDataLoaded(); + } + + destroy(): void { + } + + private notifyDataLoading() { + this.loadingData = true; + this.callbacks.dataLoading(this); + } + + private notifyDataLoaded() { + this.loadingData = false; + this.callbacks.dataLoading(this); + } + + onAliasesChanged(aliasIds: Array): boolean { + return false; + } + + private loadStDiff(): Observable { + const loadSubject = new ReplaySubject(1); + if (this.ctx.getServerTimeDiff && this.timeWindow) { + this.ctx.getServerTimeDiff().subscribe( + (stDiff) => { + this.timeWindow.stDiff = stDiff; + loadSubject.next(); + loadSubject.complete(); + }, + () => { + this.timeWindow.stDiff = 0; + loadSubject.next(); + loadSubject.complete(); + } + ); + } else { + if (this.timeWindow) { + this.timeWindow.stDiff = 0; + } + loadSubject.next(); + loadSubject.complete(); + } + return loadSubject.asObservable(); + } +} diff --git a/ui-ngx/src/app/core/http/alarm.service.ts b/ui-ngx/src/app/core/http/alarm.service.ts index bdc2ed7950..ed6d783118 100644 --- a/ui-ngx/src/app/core/http/alarm.service.ts +++ b/ui-ngx/src/app/core/http/alarm.service.ts @@ -28,6 +28,23 @@ import { AlarmSeverity, AlarmStatus } from '@shared/models/alarm.models'; +import { EntityType } from '@shared/models/entity-type.models'; +import { Datasource } from '@shared/models/widget.models'; + +export interface AlarmSourceListener { + id?: string; + alarmSource: Datasource; + alarmsPollingInterval: number; + alarmSearchStatus: AlarmSearchStatus; + alarmsQuery: { + entityType: EntityType; + entityId: string; + alarmSearchStatus: AlarmSearchStatus; + alarmStatus: AlarmStatus; + fetchOriginator?: boolean; + onAlarms?: (alarms: Array) => void; + }; +} @Injectable({ providedIn: 'root' diff --git a/ui-ngx/src/app/core/http/dashboard.service.ts b/ui-ngx/src/app/core/http/dashboard.service.ts index ee30421842..9bd719663d 100644 --- a/ui-ngx/src/app/core/http/dashboard.service.ts +++ b/ui-ngx/src/app/core/http/dashboard.service.ts @@ -16,22 +16,36 @@ import {Inject, Injectable} from '@angular/core'; import {defaultHttpOptions} from './http-utils'; -import {Observable} from 'rxjs/index'; +import { Observable, ReplaySubject, Subject } from 'rxjs/index'; import {HttpClient} from '@angular/common/http'; import {PageLink} from '@shared/models/page/page-link'; import {PageData} from '@shared/models/page/page-data'; import {Dashboard, DashboardInfo} from '@shared/models/dashboard.models'; import {WINDOW} from '@core/services/window.service'; +import { ActivationEnd, Router } from '@angular/router'; +import { filter } from 'rxjs/operators'; @Injectable({ providedIn: 'root' }) export class DashboardService { + stDiffSubject: Subject; + constructor( private http: HttpClient, + private router: Router, @Inject(WINDOW) private window: Window - ) { } + ) { + this.router.events.pipe(filter(event => event instanceof ActivationEnd)).subscribe( + () => { + if (this.stDiffSubject) { + this.stDiffSubject.complete(); + this.stDiffSubject = null; + } + } + ); + } public getTenantDashboards(pageLink: PageLink, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { @@ -124,4 +138,25 @@ export class DashboardService { return null; } + public getServerTimeDiff(): Observable { + if (this.stDiffSubject) { + return this.stDiffSubject.asObservable(); + } else { + this.stDiffSubject = new ReplaySubject(1); + const url = '/api/dashboard/serverTime'; + const ct1 = Date.now(); + this.http.get(url, defaultHttpOptions(true)).subscribe( + (st) => { + const ct2 = Date.now(); + const stDiff = Math.ceil(st - (ct1 + ct2) / 2); + this.stDiffSubject.next(stDiff); + }, + () => { + this.stDiffSubject.error(null); + } + ); + return this.stDiffSubject.asObservable(); + } + } + } diff --git a/ui-ngx/src/app/core/http/entity.service.ts b/ui-ngx/src/app/core/http/entity.service.ts index 1622de0d20..c37f3ecefe 100644 --- a/ui-ngx/src/app/core/http/entity.service.ts +++ b/ui-ngx/src/app/core/http/entity.service.ts @@ -14,33 +14,35 @@ /// limitations under the License. /// -import {Injectable} from '@angular/core'; -import {EMPTY, forkJoin, Observable, of, throwError} from 'rxjs/index'; -import {HttpClient} from '@angular/common/http'; -import {PageLink} from '@shared/models/page/page-link'; -import {AliasEntityType, EntityType} from '@shared/models/entity-type.models'; -import {BaseData} from '@shared/models/base-data'; -import {EntityId} from '@shared/models/id/entity-id'; -import {DeviceService} from '@core/http/device.service'; -import {TenantService} from '@core/http/tenant.service'; -import {CustomerService} from '@core/http/customer.service'; -import {UserService} from './user.service'; -import {DashboardService} from '@core/http/dashboard.service'; -import {Direction} from '@shared/models/page/sort-order'; -import {PageData} from '@shared/models/page/page-data'; -import {getCurrentAuthUser} from '../auth/auth.selectors'; -import {Store} from '@ngrx/store'; -import {AppState} from '@core/core.state'; -import {Authority} from '@shared/models/authority.enum'; -import {Tenant} from '@shared/models/tenant.model'; -import {concatMap, expand, map, toArray} from 'rxjs/operators'; -import {Customer} from '@app/shared/models/customer.model'; -import {AssetService} from '@core/http/asset.service'; -import {EntityViewService} from '@core/http/entity-view.service'; -import {DataKeyType} from '@shared/models/telemetry/telemetry.models'; -import {DeviceInfo} from '@shared/models/device.models'; -import {defaultHttpOptions} from '@core/http/http-utils'; -import {RuleChainService} from '@core/http/rule-chain.service'; +import { Injectable } from '@angular/core'; +import { EMPTY, forkJoin, Observable, of, throwError } from 'rxjs/index'; +import { HttpClient } from '@angular/common/http'; +import { PageLink } from '@shared/models/page/page-link'; +import { AliasEntityType, EntityType } from '@shared/models/entity-type.models'; +import { BaseData } from '@shared/models/base-data'; +import { EntityId } from '@shared/models/id/entity-id'; +import { DeviceService } from '@core/http/device.service'; +import { TenantService } from '@core/http/tenant.service'; +import { CustomerService } from '@core/http/customer.service'; +import { UserService } from './user.service'; +import { DashboardService } from '@core/http/dashboard.service'; +import { Direction } from '@shared/models/page/sort-order'; +import { PageData } from '@shared/models/page/page-data'; +import { getCurrentAuthUser } from '../auth/auth.selectors'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { Authority } from '@shared/models/authority.enum'; +import { Tenant } from '@shared/models/tenant.model'; +import { catchError, concatMap, expand, map, toArray } from 'rxjs/operators'; +import { Customer } from '@app/shared/models/customer.model'; +import { AssetService } from '@core/http/asset.service'; +import { EntityViewService } from '@core/http/entity-view.service'; +import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; +import { defaultHttpOptions } from '@core/http/http-utils'; +import { RuleChainService } from '@core/http/rule-chain.service'; +import { SubscriptionInfo } from '@core/api/widget-api.models'; +import { Datasource, DatasourceType, KeyInfo } from '@app/shared/models/widget.models'; +import { UtilsService } from '@core/services/utils.service'; @Injectable({ providedIn: 'root' @@ -57,7 +59,8 @@ export class EntityService { private customerService: CustomerService, private userService: UserService, private ruleChainService: RuleChainService, - private dashboardService: DashboardService + private dashboardService: DashboardService, + private utils: UtilsService ) { } private getEntityObservable(entityType: EntityType, entityId: string, @@ -408,4 +411,147 @@ export class EntityService { ) ); } + + public createDatasourcesFromSubscriptionsInfo(subscriptionsInfo: Array): Observable> { + const observables = new Array>>(); + subscriptionsInfo.forEach((subscriptionInfo) => { + observables.push(this.createDatasourcesFromSubscriptionInfo(subscriptionInfo)); + }); + return forkJoin(observables).pipe( + map((arrayOfDatasources) => { + const result = new Array(); + arrayOfDatasources.forEach((datasources) => { + result.push(...datasources); + }); + this.utils.generateColors(result); + return result; + }) + ); + } + + public createAlarmSourceFromSubscriptionInfo(subscriptionInfo: SubscriptionInfo): Observable { + if (subscriptionInfo.entityId && subscriptionInfo.entityType) { + return this.getEntity(subscriptionInfo.entityType, subscriptionInfo.entityId, + true, true).pipe( + map((entity) => { + const alarmSource = this.createDatasourceFromSubscription(subscriptionInfo, entity); + this.utils.generateColors([alarmSource]); + return alarmSource; + }) + ); + } else { + return throwError(null); + } + } + + private createDatasourcesFromSubscriptionInfo(subscriptionInfo: SubscriptionInfo): Observable> { + subscriptionInfo = this.validateSubscriptionInfo(subscriptionInfo); + if (subscriptionInfo.type === DatasourceType.entity) { + return this.resolveEntitiesFromSubscriptionInfo(subscriptionInfo).pipe( + map((entities) => { + const datasources = new Array(); + entities.forEach((entity) => { + datasources.push(this.createDatasourceFromSubscription(subscriptionInfo, entity)); + }); + return datasources; + }) + ); + } else if (subscriptionInfo.type === DatasourceType.function) { + return of([this.createDatasourceFromSubscription(subscriptionInfo)]); + } else { + return of([]); + } + } + + private resolveEntitiesFromSubscriptionInfo(subscriptionInfo: SubscriptionInfo): Observable>> { + if (subscriptionInfo.entityId) { + if (subscriptionInfo.entityName) { + const entity: BaseData = { + id: {id: subscriptionInfo.entityId, entityType: subscriptionInfo.entityType}, + name: subscriptionInfo.entityName + }; + return of([entity]); + } else { + return this.getEntity(subscriptionInfo.entityType, subscriptionInfo.entityId, + true, true).pipe( + map((entity) => [entity]), + catchError(e => of([])) + ); + } + } else if (subscriptionInfo.entityName || subscriptionInfo.entityNamePrefix || subscriptionInfo.entityIds) { + let entitiesObservable: Observable>>; + if (subscriptionInfo.entityName) { + entitiesObservable = this.getEntitiesByNameFilter(subscriptionInfo.entityType, subscriptionInfo.entityName, + 1, null, true, true); + } else if (subscriptionInfo.entityNamePrefix) { + entitiesObservable = this.getEntitiesByNameFilter(subscriptionInfo.entityType, subscriptionInfo.entityNamePrefix, + 100, null, true, true); + } else if (subscriptionInfo.entityIds) { + entitiesObservable = this.getEntities(subscriptionInfo.entityType, subscriptionInfo.entityIds, true, true); + } + return entitiesObservable.pipe( + catchError(e => of([])) + ); + } else { + return of([]); + } + } + + private validateSubscriptionInfo(subscriptionInfo: SubscriptionInfo): SubscriptionInfo { + // @ts-ignore + if (subscriptionInfo.type === 'device') { + subscriptionInfo.type = DatasourceType.entity; + subscriptionInfo.entityType = EntityType.DEVICE; + if (subscriptionInfo.deviceId) { + subscriptionInfo.entityId = subscriptionInfo.deviceId; + } else if (subscriptionInfo.deviceName) { + subscriptionInfo.entityName = subscriptionInfo.deviceName; + } else if (subscriptionInfo.deviceNamePrefix) { + subscriptionInfo.entityNamePrefix = subscriptionInfo.deviceNamePrefix; + } else if (subscriptionInfo.deviceIds) { + subscriptionInfo.entityIds = subscriptionInfo.deviceIds; + } + } + return subscriptionInfo; + } + + private createDatasourceFromSubscription(subscriptionInfo: SubscriptionInfo, entity?: BaseData): Datasource { + let datasource: Datasource; + if (subscriptionInfo.type === DatasourceType.entity) { + datasource = { + type: subscriptionInfo.type, + entityName: entity.name, + name: entity.name, + entityType: subscriptionInfo.entityType, + entityId: entity.id.id, + dataKeys: [] + }; + } else if (subscriptionInfo.type === DatasourceType.function) { + datasource = { + type: subscriptionInfo.type, + name: subscriptionInfo.name || DatasourceType.function, + dataKeys: [] + }; + } + if (subscriptionInfo.timeseries) { + this.createDatasourceKeys(subscriptionInfo.timeseries, DataKeyType.timeseries, datasource); + } + if (subscriptionInfo.attributes) { + this.createDatasourceKeys(subscriptionInfo.attributes, DataKeyType.attribute, datasource); + } + if (subscriptionInfo.functions) { + this.createDatasourceKeys(subscriptionInfo.functions, DataKeyType.function, datasource); + } + if (subscriptionInfo.alarmFields) { + this.createDatasourceKeys(subscriptionInfo.alarmFields, DataKeyType.alarm, datasource); + } + return datasource; + } + + private createDatasourceKeys(keyInfos: Array, type: DataKeyType, datasource: Datasource) { + keyInfos.forEach((keyInfo) => { + const dataKey = this.utils.createKey(keyInfo, type); + datasource.dataKeys.push(dataKey); + }); + } } diff --git a/ui-ngx/src/app/core/services/raf.service.ts b/ui-ngx/src/app/core/services/raf.service.ts new file mode 100644 index 0000000000..b844dd810a --- /dev/null +++ b/ui-ngx/src/app/core/services/raf.service.ts @@ -0,0 +1,68 @@ +/// +/// Copyright © 2016-2019 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 { Inject, Injectable, NgZone } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { WINDOW } from '@core/services/window.service'; + +export type CancelAnimationFrame = () => void; + +@Injectable({ + providedIn: 'root' +}) +export class RafService { + + private rafFunction: (frameCallback: () => void) => CancelAnimationFrame; + private rafSupported: boolean; + + constructor( + @Inject(WINDOW) private window: Window, + private ngZone: NgZone + ) { + const requestAnimationFrame: (frameCallback: () => void) => number = window.requestAnimationFrame || + window.webkitRequestAnimationFrame; + const cancelAnimationFrame = window.cancelAnimationFrame || + window.webkitCancelAnimationFrame || + // @ts-ignore + window.webkitCancelRequestAnimationFrame; + + this.rafSupported = !!requestAnimationFrame; + + if (this.rafSupported) { + this.rafFunction = (frameCallback: () => void) => { + const id = requestAnimationFrame(frameCallback); + return () => { + cancelAnimationFrame(id); + }; + }; + } else { + this.rafFunction = (frameCallback: () => void) => { + const timeoutId = setTimeout(frameCallback, 16.66); + return () => { + clearTimeout(timeoutId); + }; + }; + } + } + + public raf(frameCallback: () => void, runInZone = false): CancelAnimationFrame { + if (runInZone) { + return this.rafFunction(frameCallback); + } else { + return this.ngZone.runOutsideAngular(() => this.rafFunction(frameCallback)); + } + } +} diff --git a/ui-ngx/src/app/core/services/time.service.ts b/ui-ngx/src/app/core/services/time.service.ts index 69bd1f681c..30b15c3b08 100644 --- a/ui-ngx/src/app/core/services/time.service.ts +++ b/ui-ngx/src/app/core/services/time.service.ts @@ -15,11 +15,11 @@ /// import { Injectable } from '@angular/core'; -import { DAY, defaultTimeIntervals, MINUTE, SECOND, Timewindow } from '@shared/models/time/time.models'; -import {HttpClient} from '@angular/common/http'; -import {Observable} from 'rxjs'; -import {defaultHttpOptions} from '@core/http/http-utils'; -import {map} from 'rxjs/operators'; +import { AggregationType, DAY, defaultTimeIntervals, SECOND, Timewindow, defaultTimewindow } from '@shared/models/time/time.models'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { defaultHttpOptions } from '@core/http/http-utils'; +import { map } from 'rxjs/operators'; export interface TimeInterval { name: string; @@ -95,6 +95,20 @@ export class TimeService { return matchedInterval.value; } + public boundIntervalToTimewindow(timewindow: number, intervalMs: number, aggType: AggregationType): number { + if (aggType === AggregationType.NONE) { + return SECOND; + } else { + const min = this.minIntervalLimit(timewindow); + const max = this.maxIntervalLimit(timewindow); + if (intervalMs) { + return this.toBound(intervalMs, min, max, intervalMs); + } else { + return this.boundToPredefinedInterval(min, max, this.avgInterval(timewindow)); + } + } + } + public getMaxDatapointsLimit(): number { return this.maxDatapointsLimit; } @@ -103,6 +117,11 @@ export class TimeService { return MIN_LIMIT; } + public avgInterval(timewindow: number): number { + const avg = timewindow / 200; + return this.boundMinInterval(avg); + } + public minIntervalLimit(timewindowMs: number): number { const min = timewindowMs / 500; return this.boundMinInterval(min); @@ -114,7 +133,7 @@ export class TimeService { } public defaultTimewindow(): Timewindow { - return Timewindow.defaultTimewindow(this); + return defaultTimewindow(this); } private toBound(value: number, min: number, max: number, defValue: number): number { diff --git a/ui-ngx/src/app/core/services/utils.service.ts b/ui-ngx/src/app/core/services/utils.service.ts index 687f53061a..272b5ce3a7 100644 --- a/ui-ngx/src/app/core/services/utils.service.ts +++ b/ui-ngx/src/app/core/services/utils.service.ts @@ -17,10 +17,15 @@ import { Inject, Injectable } from '@angular/core'; import { WINDOW } from '@core/services/window.service'; import { ExceptionData } from '@app/shared/models/error.models'; -import { isUndefined } from '@core/utils'; +import { isUndefined, isDefined } from '@core/utils'; import { WindowMessage } from '@shared/models/window-message.model'; import { TranslateService } from '@ngx-translate/core'; import { customTranslationsPrefix } from '@app/shared/models/constants'; +import { DataKey, Datasource, DatasourceType, KeyInfo } from '@shared/models/widget.models'; +import { EntityType } from '@shared/models/entity-type.models'; +import { DataKeyType } from '@app/shared/models/telemetry/telemetry.models'; +import { alarmFields } from '@shared/models/alarm.models'; +import { materialColors } from '@app/shared/models/material.models'; @Injectable({ providedIn: 'root' @@ -112,4 +117,90 @@ export class UtilsService { return result; } + public guid(): string { + function s4(): string { + return Math.floor((1 + Math.random()) * 0x10000) + .toString(16) + .substring(1); + } + return s4() + s4() + '-' + s4() + '-' + s4() + '-' + + s4() + '-' + s4() + s4() + s4(); + } + + public validateDatasources(datasources: Array): Array { + datasources.forEach((datasource) => { + // @ts-ignore + if (datasource.type === 'device') { + datasource.type = DatasourceType.entity; + datasource.entityType = EntityType.DEVICE; + if (datasource.deviceId) { + datasource.entityId = datasource.deviceId; + } else if (datasource.deviceAliasId) { + datasource.entityAliasId = datasource.deviceAliasId; + } + if (datasource.deviceName) { + datasource.entityName = datasource.deviceName; + } + } + if (datasource.type === DatasourceType.entity && datasource.entityId) { + datasource.name = datasource.entityName; + } + }); + return datasources; + } + + public getMaterialColor(index) { + const colorIndex = index % materialColors.length; + return materialColors[colorIndex].value; + } + + public createKey(keyInfo: KeyInfo, type: DataKeyType, index: number = -1): DataKey { + let label; + if (type === DataKeyType.alarm && !keyInfo.label) { + const alarmField = alarmFields[keyInfo.name]; + if (alarmField) { + label = this.translate.instant(alarmField.name); + } + } + if (!label) { + label = keyInfo.label || keyInfo.name; + } + const dataKey: DataKey = { + name: keyInfo.name, + type, + label, + funcBody: keyInfo.funcBody, + settings: {}, + _hash: Math.random() + }; + if (keyInfo.units) { + dataKey.units = keyInfo.units; + } + if (isDefined(keyInfo.decimals)) { + dataKey.decimals = keyInfo.decimals; + } + if (keyInfo.color) { + dataKey.color = keyInfo.color; + } else if (index > -1) { + dataKey.color = this.getMaterialColor(index); + } + if (keyInfo.postFuncBody && keyInfo.postFuncBody.length) { + dataKey.usePostProcessing = true; + dataKey.postFuncBody = keyInfo.postFuncBody; + } + return dataKey; + } + + public generateColors(datasources: Array) { + let index = 0; + datasources.forEach((datasource) => { + datasource.dataKeys.forEach((dataKey) => { + if (!dataKey.color) { + dataKey.color = this.getMaterialColor(index); + } + index++; + }); + }); + } + } diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index e6b60dde17..b5022eb7cb 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -294,3 +294,11 @@ function utf8ToBytes(input: string, units?: number): number[] { } return bytes; } + +export function deepClone(obj: T): T { + if (obj) { + return JSON.parse(JSON.stringify(obj)); + } else { + return obj; + } +} diff --git a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html index c554621a85..775ee94c05 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html @@ -49,7 +49,11 @@ {{widget.titleIcon}} {{widget.title}} - + +
) { 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 3d8adfa186..764fea65e8 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 @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Injectable } from '@angular/core'; +import { Inject, Injectable } from '@angular/core'; import { DynamicComponentFactoryService } from '@core/services/dynamic-component-factory.service'; import { WidgetService } from '@core/http/widget.service'; import { forkJoin, Observable, of, ReplaySubject, Subject, throwError } from 'rxjs'; @@ -33,6 +33,11 @@ import { TranslateService } from '@ngx-translate/core'; import { DynamicWidgetComponent } from '@home/components/widget/dynamic-widget.component'; import { SharedModule } from '@shared/shared.module'; import { WidgetComponentsModule } from '@home/components/widget/widget-components.module'; +import { WINDOW } from '@core/services/window.service'; + +import * as tinycolor from 'tinycolor2'; + +// declare var jQuery: any; @Injectable() export class WidgetComponentService { @@ -48,11 +53,17 @@ export class WidgetComponentService { private missingWidgetType: WidgetInfo; private errorWidgetType: WidgetInfo; - constructor(private dynamicComponentFactoryService: DynamicComponentFactoryService, + constructor(@Inject(WINDOW) private window: Window, + private dynamicComponentFactoryService: DynamicComponentFactoryService, private widgetService: WidgetService, private utils: UtilsService, private resources: ResourcesService, private translate: TranslateService) { + // @ts-ignore + this.window.tinycolor = tinycolor; + // @ts-ignore + this.window.cssjs = cssjs; + this.cssParser.testMode = false; this.init(); } diff --git a/ui-ngx/src/app/modules/home/components/widget/widget.component.html b/ui-ngx/src/app/modules/home/components/widget/widget.component.html index 2f943d93d3..4d071ba34d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/widget.component.html @@ -15,12 +15,6 @@ limitations under the License. --> -
- Widget Error: {{ widgetErrorData.name + ": " + widgetErrorData.message}} -
-
- -
@@ -28,3 +22,9 @@
+
+ Widget Error: {{ widgetErrorData.name + ": " + widgetErrorData.message}} +
+
+ +
diff --git a/ui-ngx/src/app/modules/home/components/widget/widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/widget.component.scss index 251c070267..e83243f7d7 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/widget.component.scss @@ -44,4 +44,8 @@ color: #f00; word-wrap: break-word; } + + #widget-container { + min-height: 0; + } } 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 db0bb2b953..44d56727c4 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 @@ -32,6 +32,7 @@ import { } from '@angular/core'; import { DashboardWidget, IDashboardComponent } from '@home/models/dashboard-component.models'; import { + Datasource, LegendConfig, LegendData, LegendPosition, @@ -40,16 +41,16 @@ import { widgetActionSources, WidgetActionType, WidgetResource, - widgetType + widgetType, + WidgetTypeParameters } from '@shared/models/widget.models'; import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { WidgetService } from '@core/http/widget.service'; import { UtilsService } from '@core/services/utils.service'; -import { forkJoin, Observable, of, throwError } from 'rxjs'; -import { isDefined, objToBase64 } from '@core/utils'; -import * as $ from 'jquery'; +import { forkJoin, Observable, of, ReplaySubject, Subscription, throwError } from 'rxjs'; +import { isDefined, objToBase64, deepClone } from '@core/utils'; import { IDynamicWidgetComponent, WidgetContext, @@ -77,6 +78,13 @@ import { DeviceService } from '@app/core/http/device.service'; import { AlarmService } from '@app/core/http/alarm.service'; import { ExceptionData } from '@shared/models/error.models'; import { WidgetComponentService } from './widget-component.service'; +import { Timewindow } from '@shared/models/time/time.models'; +import { AlarmSearchStatus } from '@shared/models/alarm.models'; +import { CancelAnimationFrame, RafService } from '@core/services/raf.service'; +import { DashboardService } from '@core/http/dashboard.service'; +import { DatasourceService } from '@core/api/datasource.service'; +import { WidgetSubscription } from '@core/api/widget-subscription'; +import { EntityService } from '@core/http/entity.service'; @Component({ selector: 'tb-widget', @@ -105,6 +113,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI errorMessages: string[]; widgetContext: WidgetContext; widgetType: any; + typeParameters: WidgetTypeParameters; widgetTypeInstance: WidgetTypeInstance; widgetErrorData: ExceptionData; loadingData: boolean; @@ -124,10 +133,14 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI subscriptionInited = false; widgetSizeDetected = false; + cafs: {[cafId: string]: CancelAnimationFrame} = {}; + onResizeListener = this.onResize.bind(this); private cssParser = new cssjs(); + private rxSubscriptions = new Array(); + constructor(protected store: Store, private route: ActivatedRoute, private router: Router, @@ -139,8 +152,12 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI private resources: ResourcesService, private timeService: TimeService, private deviceService: DeviceService, + private entityService: EntityService, private alarmService: AlarmService, - private utils: UtilsService) { + private dashboardService: DashboardService, + private datasourceService: DatasourceService, + private utils: UtilsService, + private raf: RafService) { super(store); } @@ -212,7 +229,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI const descriptors = this.widget.config.actions[actionSourceId]; const actionDescriptors: Array = []; descriptors.forEach((descriptor) => { - const actionDescriptor: WidgetActionDescriptor = {...descriptor}; + const actionDescriptor: WidgetActionDescriptor = deepClone(descriptor); actionDescriptor.displayName = this.utils.customTranslation(descriptor.name, descriptor.name); actionDescriptors.push(actionDescriptor); }); @@ -302,15 +319,18 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI this.widgetContext.customHeaderActions.push(headerAction); }); - this.subscriptionContext = { timeService: this.timeService, deviceService: this.deviceService, alarmService: this.alarmService, + datasourceService: this.datasourceService, utils: this.utils, widgetUtils: this.widgetContext.utils, - dashboardTimewindowApi: null, // TODO: - getServerTimeDiff: null, // TODO: + dashboardTimewindowApi: { + onResetTimewindow: this.dashboard.onResetTimewindow.bind(this.dashboard), + onUpdateTimewindow: this.dashboard.onUpdateTimewindow.bind(this.dashboard) + }, + getServerTimeDiff: this.dashboardService.getServerTimeDiff.bind(this.dashboardService), aliasController: this.dashboard.aliasController }; @@ -331,26 +351,6 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI ngAfterViewInit(): void { } - ngOnDestroy(): void { - - for (const id of Object.keys(this.widgetContext.subscriptions)) { - const subscription = this.widgetContext.subscriptions[id]; - subscription.destroy(); - } - this.subscriptionInited = false; - this.widgetContext.subscriptions = {}; - if (this.widgetContext.inited) { - this.widgetContext.inited = false; - // TODO: - try { - this.widgetTypeInstance.onDestroy(); - } catch (e) { - this.handleWidgetException(e); - } - } - this.destroyDynamicWidgetComponent(); - } - ngOnChanges(changes: SimpleChanges): void { for (const propName of Object.keys(changes)) { const change = changes[propName]; @@ -366,28 +366,43 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI } } - private onEditModeChanged() { - if (this.widgetContext.isEdit !== this.isEdit) { - this.widgetContext.isEdit = this.isEdit; - if (this.widgetContext.inited) { - // TODO: - } - } + ngOnDestroy(): void { + this.rxSubscriptions.forEach((subscription) => { + subscription.unsubscribe(); + }); + this.rxSubscriptions.length = 0; + this.onDestroy(); } - private onMobileModeChanged() { - if (this.widgetContext.isMobile !== this.isMobile) { - this.widgetContext.isMobile = this.isMobile; - if (this.widgetContext.inited) { - // TODO: + private onDestroy() { + for (const id of Object.keys(this.widgetContext.subscriptions)) { + const subscription = this.widgetContext.subscriptions[id]; + subscription.destroy(); + } + this.subscriptionInited = false; + this.widgetContext.subscriptions = {}; + if (this.widgetContext.inited) { + this.widgetContext.inited = false; + for (const cafId of Object.keys(this.cafs)) { + if (this.cafs[cafId]) { + this.cafs[cafId](); + this.cafs[cafId] = null; + } + } + try { + this.widgetTypeInstance.onDestroy(); + } catch (e) { + this.handleWidgetException(e); } } + this.destroyDynamicWidgetComponent(); } - private onResize() { - if (this.checkSize()) { - if (this.widgetContext.inited) { - // TODO: + public onTimewindowChanged(timewindow: Timewindow) { + for (const id of Object.keys(this.widgetContext.subscriptions)) { + const subscription = this.widgetContext.subscriptions[id]; + if (!subscription.useDashboardTimewindow) { + subscription.updateTimewindowConfig(timewindow); } } } @@ -398,6 +413,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI elem.classList.add('tb-widget'); elem.classList.add(widgetNamespace); this.widgetType = this.widgetInfo.widgetTypeFunction; + this.typeParameters = this.widgetInfo.typeParameters; if (!this.widgetType) { this.widgetTypeInstance = {}; @@ -428,19 +444,153 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI this.widgetTypeInstance.onDestroy = () => {}; } - this.initialize(); + this.initialize().subscribe( + () => { + this.onInit(); + } + ); + } + + private isReady(): boolean { + return this.subscriptionInited && this.widgetSizeDetected; + } + + private onInit(skipSizeCheck?: boolean) { + if (!this.widgetContext.$containerParent) { + return; + } + if (!skipSizeCheck) { + this.checkSize(); + } + if (!this.widgetContext.inited && this.isReady()) { + this.widgetContext.inited = true; + try { + this.widgetTypeInstance.onInit(); + } catch (e) { + this.handleWidgetException(e); + } + if (!this.typeParameters.useCustomDatasources && this.widgetContext.defaultSubscription) { + this.widgetContext.defaultSubscription.subscribe(); + } + } + } + + private onResize() { + if (this.checkSize()) { + if (this.widgetContext.inited) { + if (this.cafs.resize) { + this.cafs.resize(); + this.cafs.resize = null; + } + this.cafs.resize = this.raf.raf(() => { + try { + this.widgetTypeInstance.onResize(); + } catch (e) { + this.handleWidgetException(e); + } + }); + } else { + this.onInit(true); + } + } + } + + private onEditModeChanged() { + if (this.widgetContext.isEdit !== this.isEdit) { + this.widgetContext.isEdit = this.isEdit; + if (this.widgetContext.inited) { + if (this.cafs.editMode) { + this.cafs.editMode(); + this.cafs.editMode = null; + } + this.cafs.editMode = this.raf.raf(() => { + try { + this.widgetTypeInstance.onEditModeChanged(); + } catch (e) { + this.handleWidgetException(e); + } + }); + } + } + } + + private onMobileModeChanged() { + if (this.widgetContext.isMobile !== this.isMobile) { + this.widgetContext.isMobile = this.isMobile; + if (this.widgetContext.inited) { + if (this.cafs.mobileMode) { + this.cafs.mobileMode(); + this.cafs.mobileMode = null; + } + this.cafs.mobileMode = this.raf.raf(() => { + try { + this.widgetTypeInstance.onMobileModeChanged(); + } catch (e) { + this.handleWidgetException(e); + } + }); + } + } } private reInit() { - this.ngOnDestroy(); - this.initialize(); - // TODO: + this.onDestroy(); + this.configureDynamicWidgetComponent(); + if (!this.typeParameters.useCustomDatasources) { + this.createDefaultSubscription().subscribe( + () => { + this.subscriptionInited = true; + this.onInit(); + }, + () => { + this.subscriptionInited = true; + this.onInit(); + } + ); + } else { + this.subscriptionInited = true; + this.onInit(); + } } - private initialize() { + private initialize(): Observable { + + const initSubject = new ReplaySubject(); + + this.rxSubscriptions.push(this.dashboard.aliasController.entityAliasesChanged.subscribe( + (aliasIds) => { + let subscriptionChanged = false; + for (const id of Object.keys(this.widgetContext.subscriptions)) { + const subscription = this.widgetContext.subscriptions[id]; + subscriptionChanged = subscriptionChanged || subscription.onAliasesChanged(aliasIds); + } + if (subscriptionChanged && !this.typeParameters.useCustomDatasources) { + this.reInit(); + } + } + )); + this.configureDynamicWidgetComponent(); - // TODO: - this.loadingData = false; + if (!this.typeParameters.useCustomDatasources) { + // this.cre + this.createDefaultSubscription().subscribe( + () => { + this.subscriptionInited = true; + initSubject.next(); + initSubject.complete(); + }, + () => { + this.subscriptionInited = true; + initSubject.error(null); + } + ); + } else { + this.loadingData = false; + this.subscriptionInited = true; + initSubject.next(); + initSubject.complete(); + } + return initSubject.asObservable(); } private destroyDynamicWidgetComponent() { @@ -484,16 +634,193 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI addResizeListener(this.widgetContext.$containerParent[0], this.onResizeListener); } - private createSubscription(options: WidgetSubscriptionOptions, subscribe: boolean): Observable { - // TODO: - return of(null); + private createSubscription(options: WidgetSubscriptionOptions, subscribe?: boolean): Observable { + const createSubscriptionSubject = new ReplaySubject(); + options.dashboardTimewindow = this.dashboard.dashboardTimewindow; + const subscription: IWidgetSubscription = new WidgetSubscription(this.subscriptionContext, options); + subscription.init$.subscribe( + () => { + this.widgetContext.subscriptions[subscription.id] = subscription; + if (subscribe) { + subscription.subscribe(); + } + createSubscriptionSubject.next(subscription); + createSubscriptionSubject.complete(); + }, + () => { + createSubscriptionSubject.error(null); + } + ); + return createSubscriptionSubject.asObservable(); } private createSubscriptionFromInfo(type: widgetType, subscriptionsInfo: Array, options: WidgetSubscriptionOptions, useDefaultComponents: boolean, subscribe: boolean): Observable { - // TODO: - return of(null); + const createSubscriptionSubject = new ReplaySubject(); + options.type = type; + + if (useDefaultComponents) { + this.defaultComponentsOptions(options); + } else { + if (!options.timeWindowConfig) { + options.useDashboardTimewindow = true; + } + } + let createDatasourcesObservable: Observable | Datasource>; + if (options.type === widgetType.alarm) { + createDatasourcesObservable = this.entityService.createAlarmSourceFromSubscriptionInfo(subscriptionsInfo[0]); + } else { + createDatasourcesObservable = this.entityService.createDatasourcesFromSubscriptionsInfo(subscriptionsInfo); + } + createDatasourcesObservable.subscribe( + (result) => { + if (options.type === widgetType.alarm) { + options.alarmSource = result as Datasource; + } else { + options.datasources = result as Array; + } + this.createSubscription(options, subscribe).subscribe( + (subscription) => { + if (useDefaultComponents) { + this.defaultSubscriptionOptions(subscription, options); + } + createSubscriptionSubject.next(subscription); + createSubscriptionSubject.complete(); + }, + () => { + createSubscriptionSubject.error(null); + } + ); + }, + () => { + createSubscriptionSubject.error(null); + } + ); + return createSubscriptionSubject.asObservable(); + } + + private defaultComponentsOptions(options: WidgetSubscriptionOptions) { + options.useDashboardTimewindow = isDefined(this.widget.config.useDashboardTimewindow) + ? this.widget.config.useDashboardTimewindow : true; + options.displayTimewindow = isDefined(this.widget.config.displayTimewindow) + ? this.widget.config.displayTimewindow : !options.useDashboardTimewindow; + options.timeWindowConfig = options.useDashboardTimewindow ? this.dashboard.dashboardTimewindow : this.widget.config.timewindow; + options.legendConfig = null; + if (this.displayLegend) { + options.legendConfig = this.legendConfig; + } + options.decimals = this.widgetContext.decimals; + options.units = this.widgetContext.units; + options.callbacks = { + onDataUpdated: () => { + this.widgetTypeInstance.onDataUpdated(); + }, + onDataUpdateError: (subscription, e) => { + this.handleWidgetException(e); + }, + dataLoading: (subscription) => { + if (this.loadingData !== subscription.loadingData) { + this.loadingData = subscription.loadingData; + } + }, + legendDataUpdated: (subscription) => { + }, + timeWindowUpdated: (subscription, timeWindowConfig) => { + this.widget.config.timewindow = timeWindowConfig; + } + }; + + } + + private defaultSubscriptionOptions(subscription: IWidgetSubscription, options: WidgetSubscriptionOptions) { + if (this.displayLegend) { + this.legendData = subscription.legendData; + } + } + + private createDefaultSubscription(): Observable { + const createSubscriptionSubject = new ReplaySubject(); + let options: WidgetSubscriptionOptions; + if (this.widget.type !== widgetType.rpc && this.widget.type !== widgetType.static) { + options = { + type: this.widget.type, + stateData: this.typeParameters.stateData + }; + if (this.widget.type === widgetType.alarm) { + options.alarmSource = deepClone(this.widget.config.alarmSource); + options.alarmSearchStatus = isDefined(this.widget.config.alarmSearchStatus) ? + this.widget.config.alarmSearchStatus : AlarmSearchStatus.ANY; + options.alarmsPollingInterval = isDefined(this.widget.config.alarmsPollingInterval) ? + this.widget.config.alarmsPollingInterval * 1000 : 5000; + } else { + options.datasources = deepClone(this.widget.config.datasources); + } + + this.defaultComponentsOptions(options); + + this.createSubscription(options).subscribe( + (subscription) => { + this.defaultSubscriptionOptions(subscription, options); + + // backward compatibility + this.widgetContext.datasources = subscription.datasources; + this.widgetContext.data = subscription.data; + this.widgetContext.hiddenData = subscription.hiddenData; + this.widgetContext.timeWindow = subscription.timeWindow; + this.widgetContext.defaultSubscription = subscription; + createSubscriptionSubject.next(); + createSubscriptionSubject.complete(); + }, + () => { + createSubscriptionSubject.error(null); + } + ); + } else if (this.widget.type === widgetType.rpc) { + this.loadingData = false; + options = { + type: this.widget.type, + targetDeviceAliasIds: this.widget.config.targetDeviceAliasIds + }; + options.callbacks = { + rpcStateChanged: (subscription) => { + this.dynamicWidgetComponent.rpcEnabled = subscription.rpcEnabled; + this.dynamicWidgetComponent.executingRpcRequest = subscription.executingRpcRequest; + }, + onRpcSuccess: (subscription) => { + this.dynamicWidgetComponent.executingRpcRequest = subscription.executingRpcRequest; + this.dynamicWidgetComponent.rpcErrorText = subscription.rpcErrorText; + this.dynamicWidgetComponent.rpcRejection = subscription.rpcRejection; + }, + onRpcFailed: (subscription) => { + this.dynamicWidgetComponent.executingRpcRequest = subscription.executingRpcRequest; + this.dynamicWidgetComponent.rpcErrorText = subscription.rpcErrorText; + this.dynamicWidgetComponent.rpcRejection = subscription.rpcRejection; + }, + onRpcErrorCleared: (subscription) => { + this.dynamicWidgetComponent.rpcErrorText = null; + this.dynamicWidgetComponent.rpcRejection = null; + } + }; + this.createSubscription(options).subscribe( + (subscription) => { + this.widgetContext.defaultSubscription = subscription; + createSubscriptionSubject.next(); + createSubscriptionSubject.complete(); + }, + () => { + createSubscriptionSubject.error(null); + } + ); + } else if (this.widget.type === widgetType.static) { + this.loadingData = false; + createSubscriptionSubject.next(); + createSubscriptionSubject.complete(); + } else { + createSubscriptionSubject.next(); + createSubscriptionSubject.complete(); + } + return createSubscriptionSubject.asObservable(); } private isNumeric(value: any): boolean { @@ -540,7 +867,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI case WidgetActionType.openDashboardState: case WidgetActionType.updateDashboardState: let targetDashboardStateId = descriptor.targetDashboardStateId; - const params = {...this.widgetContext.stateController.getStateParams()}; + const params = deepClone(this.widgetContext.stateController.getStateParams()); this.updateEntityParams(params, targetEntityParamName, targetEntityId, entityName); if (type === WidgetActionType.openDashboardState) { this.widgetContext.stateController.openState(targetDashboardStateId, params, descriptor.openRightLayout); 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 4520319c6c..c1a1dc8cea 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 @@ -23,7 +23,7 @@ import { Observable } from 'rxjs'; import { isDefined, isUndefined } from '@app/core/utils'; import { EventEmitter } from '@angular/core'; import { EntityId } from '@app/shared/models/id/entity-id'; -import { IAliasController, IStateController } from '@app/core/api/widget-api.models'; +import { IAliasController, IStateController, TimewindowFunctions } from '@app/core/api/widget-api.models'; export interface WidgetsData { widgets: Array; @@ -49,6 +49,8 @@ export interface IDashboardComponent { dashboardTimewindow: Timewindow; aliasController: IAliasController; stateController: IStateController; + onUpdateTimewindow(startTimeMs: number, endTimeMs: number, interval: number): void; + onResetTimewindow(): void; } export class DashboardWidget implements GridsterItem { 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 8bc55612e2..3aa9a23bd5 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 @@ -17,6 +17,8 @@ import { ExceptionData } from '@shared/models/error.models'; import { IDashboardComponent } from '@home/models/dashboard-component.models'; import { + DataSet, + Datasource, DatasourceData, WidgetActionDescriptor, WidgetActionSource, WidgetConfig, @@ -40,6 +42,7 @@ import { WidgetSubscriptionApi } from '@core/api/widget-api.models'; import { ComponentFactory } from '@angular/core'; +import { HttpErrorResponse } from '@angular/common/http'; export interface IWidgetAction { name: string; @@ -86,11 +89,20 @@ export interface WidgetContext { widgetTitle?: string; customHeaderActions?: Array; widgetActions?: Array; + + datasources?: Array; + data?: Array; + hiddenData?: Array<{data: DataSet}>; + timeWindow?: Timewindow; } export interface IDynamicWidgetComponent { widgetContext: WidgetContext; errorMessages: string[]; + executingRpcRequest: boolean; + rpcEnabled: boolean; + rpcErrorText: string; + rpcRejection: HttpErrorResponse; [key: string]: any; } @@ -120,7 +132,8 @@ export const MissingWidgetType: WidgetInfo = { '"title": "Widget type not found",\n' + '"datasources": [],\n' + '"settings": {}\n' + - '}\n' + '}\n', + typeParameters: {} }; export const ErrorWidgetType: WidgetInfo = { @@ -142,7 +155,8 @@ export const ErrorWidgetType: WidgetInfo = { '"title": "Widget failed to load",\n' + '"datasources": [],\n' + '"settings": {}\n' + - '}\n' + '}\n', + typeParameters: {} }; export interface WidgetTypeInstance { diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.ts b/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.ts index a1ba47ee1c..3400d56514 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.ts +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.ts @@ -33,6 +33,7 @@ import { FooterFabButtons } from '@app/shared/components/footer-fab-buttons.comp import { DashboardCallbacks, WidgetsData } from '@home/models/dashboard-component.models'; import { IAliasController } from '@app/core/api/widget-api.models'; import { toWidgetInfo } from '@home/models/widget-component.models'; +import { DummyAliasController } from '@core/api/alias-controller'; @Component({ selector: 'tb-widget-library', @@ -78,7 +79,7 @@ export class WidgetLibraryComponent extends PageComponent implements OnInit { widgetsData: Observable; - aliasController: IAliasController = {}; + aliasController: IAliasController = new DummyAliasController(); constructor(protected store: Store, private route: ActivatedRoute, diff --git a/ui-ngx/src/app/shared/components/time/datetime-period.component.ts b/ui-ngx/src/app/shared/components/time/datetime-period.component.ts index 8afd0fb8e8..dad8373def 100644 --- a/ui-ngx/src/app/shared/components/time/datetime-period.component.ts +++ b/ui-ngx/src/app/shared/components/time/datetime-period.component.ts @@ -94,9 +94,10 @@ export class DatetimePeriodComponent implements OnInit, ControlValueAccessor { updateView() { let value: FixedWindow = null; if (this.startDate && this.endDate) { - value = new FixedWindow(); - value.startTimeMs = this.startDate.getTime(); - value.endTimeMs = this.endDate.getTime(); + value = { + startTimeMs: this.startDate.getTime(), + endTimeMs: this.endDate.getTime() + }; } this.modelValue = value; if (!this.propagateChange) { diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts index badceb71ea..3dda6cdb80 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts @@ -145,18 +145,21 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { update() { const timewindowFormValue = this.timewindowForm.value; - this.timewindow.realtime = new IntervalWindow(); - this.timewindow.realtime.timewindowMs = timewindowFormValue.realtime.timewindowMs; - this.timewindow.realtime.interval = timewindowFormValue.realtime.interval; - this.timewindow.history = new HistoryWindow(); - this.timewindow.history.historyType = timewindowFormValue.history.historyType; - this.timewindow.history.timewindowMs = timewindowFormValue.history.timewindowMs; - this.timewindow.history.interval = timewindowFormValue.history.interval; - this.timewindow.history.fixedTimewindow = timewindowFormValue.history.fixedTimewindow; + this.timewindow.realtime = { + timewindowMs: timewindowFormValue.realtime.timewindowMs, + interval: timewindowFormValue.realtime.interval + }; + this.timewindow.history = { + historyType: timewindowFormValue.history.historyType, + timewindowMs: timewindowFormValue.history.timewindowMs, + interval: timewindowFormValue.history.interval, + fixedTimewindow: timewindowFormValue.history.fixedTimewindow + }; if (this.aggregation) { - this.timewindow.aggregation = new Aggregation(); - this.timewindow.aggregation.type = timewindowFormValue.aggregation.type; - this.timewindow.aggregation.limit = timewindowFormValue.aggregation.limit; + this.timewindow.aggregation = { + type: timewindowFormValue.aggregation.type, + limit: timewindowFormValue.aggregation.limit + }; } this.result = this.timewindow; this.overlayRef.dispose(); 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 d28629fcdb..fd589a01d6 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow.component.ts @@ -29,7 +29,8 @@ import { MillisecondsToTimeStringPipe } from '@shared/pipe/milliseconds-to-time- import { HistoryWindowType, Timewindow, - TimewindowType + TimewindowType, + initModelFromDefaultTimewindow, cloneSelectedTimewindow } from '@shared/models/time/time.models'; import { DatePipe } from '@angular/common'; import { @@ -50,6 +51,7 @@ import { DOCUMENT } from '@angular/common'; import { WINDOW } from '@core/services/window.service'; import { TimeService } from '@core/services/time.service'; import { TooltipPosition } from '@angular/material/typings/tooltip'; +import { deepClone } from '@core/utils'; @Component({ selector: 'tb-timewindow', @@ -206,7 +208,7 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces const injector = this._createTimewindowPanelInjector( overlayRef, { - timewindow: this.innerValue.clone(), + timewindow: deepClone(this.innerValue), historyOnly: this.historyOnly, aggregation: this.aggregation } @@ -242,12 +244,12 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces } writeValue(obj: Timewindow): void { - this.innerValue = Timewindow.initModelFromDefaultTimewindow(obj, this.timeService); + this.innerValue = initModelFromDefaultTimewindow(obj, this.timeService); this.updateDisplayValue(); } notifyChanged() { - this.propagateChange(this.innerValue.cloneSelectedTimewindow()); + this.propagateChange(cloneSelectedTimewindow(this.innerValue)); } updateDisplayValue() { diff --git a/ui-ngx/src/app/shared/models/alarm.models.ts b/ui-ngx/src/app/shared/models/alarm.models.ts index 4265041835..6d408e1d22 100644 --- a/ui-ngx/src/app/shared/models/alarm.models.ts +++ b/ui-ngx/src/app/shared/models/alarm.models.ts @@ -103,6 +103,71 @@ export interface AlarmInfo extends Alarm { originatorName: string; } +export interface AlarmField { + keyName: string; + value: string; + name: string; + time?: boolean; +} + +export const alarmFields: {[fieldName: string]: AlarmField} = { + createdTime: { + keyName: 'createdTime', + value: 'createdTime', + name: 'alarm.created-time', + time: true + }, + startTime: { + keyName: 'startTime', + value: 'startTs', + name: 'alarm.start-time', + time: true + }, + endTime: { + keyName: 'endTime', + value: 'endTs', + name: 'alarm.end-time', + time: true + }, + ackTime: { + keyName: 'ackTime', + value: 'ackTs', + name: 'alarm.ack-time', + time: true + }, + clearTime: { + keyName: 'clearTime', + value: 'clearTs', + name: 'alarm.clear-time', + time: true + }, + originator: { + keyName: 'originator', + value: 'originatorName', + name: 'alarm.originator' + }, + originatorType: { + keyName: 'originatorType', + value: 'originator.entityType', + name: 'alarm.originator-type' + }, + type: { + keyName: 'type', + value: 'type', + name: 'alarm.type' + }, + severity: { + keyName: 'severity', + value: 'severity', + name: 'alarm.severity' + }, + status: { + keyName: 'status', + value: 'status', + name: 'alarm.status' + } +}; + export class AlarmQuery { affectedEntityId: EntityId; diff --git a/ui-ngx/src/app/shared/models/material.models.ts b/ui-ngx/src/app/shared/models/material.models.ts new file mode 100644 index 0000000000..db5b3774b6 --- /dev/null +++ b/ui-ngx/src/app/shared/models/material.models.ts @@ -0,0 +1,367 @@ +/// +/// Copyright © 2016-2019 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 * as tinycolor from 'tinycolor2'; + +export interface MaterialColorItem { + value: string; + group: string; + label: string; + isDark: boolean; +} + +export const materialColorPalette: {[palette: string]: {[spectrum: string]: string}} = { + red: { + 50: '#ffebee', + 100: '#ffcdd2', + 200: '#ef9a9a', + 300: '#e57373', + 400: '#ef5350', + 500: '#f44336', + 600: '#e53935', + 700: '#d32f2f', + 800: '#c62828', + 900: '#b71c1c', + A100: '#ff8a80', + A200: '#ff5252', + A400: '#ff1744', + A700: '#d50000' + }, + pink: { + 50: '#fce4ec', + 100: '#f8bbd0', + 200: '#f48fb1', + 300: '#f06292', + 400: '#ec407a', + 500: '#e91e63', + 600: '#d81b60', + 700: '#c2185b', + 800: '#ad1457', + 900: '#880e4f', + A100: '#ff80ab', + A200: '#ff4081', + A400: '#f50057', + A700: '#c51162' + }, + purple: { + 50: '#f3e5f5', + 100: '#e1bee7', + 200: '#ce93d8', + 300: '#ba68c8', + 400: '#ab47bc', + 500: '#9c27b0', + 600: '#8e24aa', + 700: '#7b1fa2', + 800: '#6a1b9a', + 900: '#4a148c', + A100: '#ea80fc', + A200: '#e040fb', + A400: '#d500f9', + A700: '#aa00ff' + }, + 'deep-purple': { + 50: '#ede7f6', + 100: '#d1c4e9', + 200: '#b39ddb', + 300: '#9575cd', + 400: '#7e57c2', + 500: '#673ab7', + 600: '#5e35b1', + 700: '#512da8', + 800: '#4527a0', + 900: '#311b92', + A100: '#b388ff', + A200: '#7c4dff', + A400: '#651fff', + A700: '#6200ea' + }, + indigo: { + 50: '#e8eaf6', + 100: '#c5cae9', + 200: '#9fa8da', + 300: '#7986cb', + 400: '#5c6bc0', + 500: '#3f51b5', + 600: '#3949ab', + 700: '#303f9f', + 800: '#283593', + 900: '#1a237e', + A100: '#8c9eff', + A200: '#536dfe', + A400: '#3d5afe', + A700: '#304ffe' + }, + blue: { + 50: '#e3f2fd', + 100: '#bbdefb', + 200: '#90caf9', + 300: '#64b5f6', + 400: '#42a5f5', + 500: '#2196f3', + 600: '#1e88e5', + 700: '#1976d2', + 800: '#1565c0', + 900: '#0d47a1', + A100: '#82b1ff', + A200: '#448aff', + A400: '#2979ff', + A700: '#2962ff' + }, + 'light-blue': { + 50: '#e1f5fe', + 100: '#b3e5fc', + 200: '#81d4fa', + 300: '#4fc3f7', + 400: '#29b6f6', + 500: '#03a9f4', + 600: '#039be5', + 700: '#0288d1', + 800: '#0277bd', + 900: '#01579b', + A100: '#80d8ff', + A200: '#40c4ff', + A400: '#00b0ff', + A700: '#0091ea' + }, + cyan: { + 50: '#e0f7fa', + 100: '#b2ebf2', + 200: '#80deea', + 300: '#4dd0e1', + 400: '#26c6da', + 500: '#00bcd4', + 600: '#00acc1', + 700: '#0097a7', + 800: '#00838f', + 900: '#006064', + A100: '#84ffff', + A200: '#18ffff', + A400: '#00e5ff', + A700: '#00b8d4' + }, + teal: { + 50: '#e0f2f1', + 100: '#b2dfdb', + 200: '#80cbc4', + 300: '#4db6ac', + 400: '#26a69a', + 500: '#009688', + 600: '#00897b', + 700: '#00796b', + 800: '#00695c', + 900: '#004d40', + A100: '#a7ffeb', + A200: '#64ffda', + A400: '#1de9b6', + A700: '#00bfa5' + }, + green: { + 50: '#e8f5e9', + 100: '#c8e6c9', + 200: '#a5d6a7', + 300: '#81c784', + 400: '#66bb6a', + 500: '#4caf50', + 600: '#43a047', + 700: '#388e3c', + 800: '#2e7d32', + 900: '#1b5e20', + A100: '#b9f6ca', + A200: '#69f0ae', + A400: '#00e676', + A700: '#00c853' + }, + 'light-green': { + 50: '#f1f8e9', + 100: '#dcedc8', + 200: '#c5e1a5', + 300: '#aed581', + 400: '#9ccc65', + 500: '#8bc34a', + 600: '#7cb342', + 700: '#689f38', + 800: '#558b2f', + 900: '#33691e', + A100: '#ccff90', + A200: '#b2ff59', + A400: '#76ff03', + A700: '#64dd17' + }, + lime: { + 50: '#f9fbe7', + 100: '#f0f4c3', + 200: '#e6ee9c', + 300: '#dce775', + 400: '#d4e157', + 500: '#cddc39', + 600: '#c0ca33', + 700: '#afb42b', + 800: '#9e9d24', + 900: '#827717', + A100: '#f4ff81', + A200: '#eeff41', + A400: '#c6ff00', + A700: '#aeea00' + }, + yellow: { + 50: '#fffde7', + 100: '#fff9c4', + 200: '#fff59d', + 300: '#fff176', + 400: '#ffee58', + 500: '#ffeb3b', + 600: '#fdd835', + 700: '#fbc02d', + 800: '#f9a825', + 900: '#f57f17', + A100: '#ffff8d', + A200: '#ffff00', + A400: '#ffea00', + A700: '#ffd600' + }, + amber: { + 50: '#fff8e1', + 100: '#ffecb3', + 200: '#ffe082', + 300: '#ffd54f', + 400: '#ffca28', + 500: '#ffc107', + 600: '#ffb300', + 700: '#ffa000', + 800: '#ff8f00', + 900: '#ff6f00', + A100: '#ffe57f', + A200: '#ffd740', + A400: '#ffc400', + A700: '#ffab00' + }, + orange: { + 50: '#fff3e0', + 100: '#ffe0b2', + 200: '#ffcc80', + 300: '#ffb74d', + 400: '#ffa726', + 500: '#ff9800', + 600: '#fb8c00', + 700: '#f57c00', + 800: '#ef6c00', + 900: '#e65100', + A100: '#ffd180', + A200: '#ffab40', + A400: '#ff9100', + A700: '#ff6d00' + }, + 'deep-orange': { + 50: '#fbe9e7', + 100: '#ffccbc', + 200: '#ffab91', + 300: '#ff8a65', + 400: '#ff7043', + 500: '#ff5722', + 600: '#f4511e', + 700: '#e64a19', + 800: '#d84315', + 900: '#bf360c', + A100: '#ff9e80', + A200: '#ff6e40', + A400: '#ff3d00', + A700: '#dd2c00' + }, + brown: { + 50: '#efebe9', + 100: '#d7ccc8', + 200: '#bcaaa4', + 300: '#a1887f', + 400: '#8d6e63', + 500: '#795548', + 600: '#6d4c41', + 700: '#5d4037', + 800: '#4e342e', + 900: '#3e2723', + A100: '#d7ccc8', + A200: '#bcaaa4', + A400: '#8d6e63', + A700: '#5d4037' + }, + grey: { + 50: '#fafafa', + 100: '#f5f5f5', + 200: '#eeeeee', + 300: '#e0e0e0', + 400: '#bdbdbd', + 500: '#9e9e9e', + 600: '#757575', + 700: '#616161', + 800: '#424242', + 900: '#212121', + A100: '#ffffff', + A200: '#000000', + A400: '#303030', + A700: '#616161' + }, + 'blue-grey': { + 50: '#eceff1', + 100: '#cfd8dc', + 200: '#b0bec5', + 300: '#90a4ae', + 400: '#78909c', + 500: '#607d8b', + 600: '#546e7a', + 700: '#455a64', + 800: '#37474f', + 900: '#263238', + A100: '#cfd8dc', + A200: '#b0bec5', + A400: '#78909c', + A700: '#455a64' + } +}; + +export const materialColors = new Array(); + +const colorPalettes = ['blue', 'green', 'red', 'amber', 'blue-grey', 'purple', 'light-green', + 'indigo', 'pink', 'yellow', 'light-blue', 'orange', 'deep-purple', 'lime', 'teal', 'brown', 'cyan', 'deep-orange', 'grey']; +const colorSpectrum = ['500', 'A700', '600', '700', '800', '900', '300', '400', 'A200', 'A400']; + +for (const key of Object.keys(materialColorPalette)) { + const value = materialColorPalette[key]; + for (const label of Object.keys(value)) { + if (colorSpectrum.indexOf(label) > -1) { + const colorValue = value[label]; + const color = tinycolor(colorValue); + const isDark = color.isDark(); + const colorItem = { + value: color.toHexString(), + group: key, + label, + isDark + }; + materialColors.push(colorItem); + } + } +} + +materialColors.sort((colorItem1, colorItem2) => { + const spectrumIndex1 = colorSpectrum.indexOf(colorItem1.label); + const spectrumIndex2 = colorSpectrum.indexOf(colorItem2.label); + let result = spectrumIndex1 - spectrumIndex2; + if (result === 0) { + const paletteIndex1 = colorPalettes.indexOf(colorItem1.group); + const paletteIndex2 = colorPalettes.indexOf(colorItem2.group); + result = paletteIndex1 - paletteIndex2; + } + return result; +}); diff --git a/ui-ngx/src/app/shared/models/time/time.models.ts b/ui-ngx/src/app/shared/models/time/time.models.ts index 4958082d4f..98af5fbf15 100644 --- a/ui-ngx/src/app/shared/models/time/time.models.ts +++ b/ui-ngx/src/app/shared/models/time/time.models.ts @@ -15,6 +15,7 @@ /// import { TimeService } from '@core/services/time.service'; +import { deepClone, isDefined } from '@app/core/utils'; export const SECOND = 1000; export const MINUTE = 60 * SECOND; @@ -31,163 +32,19 @@ export enum HistoryWindowType { FIXED } -export class Timewindow { - - displayValue?: string; - selectedTab?: TimewindowType; - realtime?: IntervalWindow; - history?: HistoryWindow; - aggregation?: Aggregation; - - public static historyInterval(timewindowMs: number): Timewindow { - const timewindow = new Timewindow(); - timewindow.history = new HistoryWindow(); - timewindow.history.timewindowMs = timewindowMs; - return timewindow; - } - - public static defaultTimewindow(timeService: TimeService): Timewindow { - const currentTime = new Date().getTime(); - const timewindow = new Timewindow(); - timewindow.displayValue = ''; - timewindow.selectedTab = TimewindowType.REALTIME; - timewindow.realtime = new IntervalWindow(); - timewindow.realtime.interval = SECOND; - timewindow.realtime.timewindowMs = MINUTE; - timewindow.history = new HistoryWindow(); - timewindow.history.historyType = HistoryWindowType.LAST_INTERVAL; - timewindow.history.interval = SECOND; - timewindow.history.timewindowMs = MINUTE; - timewindow.history.fixedTimewindow = new FixedWindow(); - timewindow.history.fixedTimewindow.startTimeMs = currentTime - DAY; - timewindow.history.fixedTimewindow.endTimeMs = currentTime; - timewindow.aggregation = new Aggregation(); - timewindow.aggregation.type = AggregationType.AVG; - timewindow.aggregation.limit = Math.floor(timeService.getMaxDatapointsLimit() / 2); - return timewindow; - } - - public static initModelFromDefaultTimewindow(value: Timewindow, timeService: TimeService): Timewindow { - const model = Timewindow.defaultTimewindow(timeService); - if (value) { - if (value.realtime) { - model.selectedTab = TimewindowType.REALTIME; - if (typeof value.realtime.interval !== 'undefined') { - model.realtime.interval = value.realtime.interval; - } - model.realtime.timewindowMs = value.realtime.timewindowMs; - } else { - model.selectedTab = TimewindowType.HISTORY; - if (typeof value.history.interval !== 'undefined') { - model.history.interval = value.history.interval; - } - if (typeof value.history.timewindowMs !== 'undefined') { - model.history.historyType = HistoryWindowType.LAST_INTERVAL; - model.history.timewindowMs = value.history.timewindowMs; - } else { - model.history.historyType = HistoryWindowType.FIXED; - model.history.fixedTimewindow.startTimeMs = value.history.fixedTimewindow.startTimeMs; - model.history.fixedTimewindow.endTimeMs = value.history.fixedTimewindow.endTimeMs; - } - } - if (value.aggregation) { - if (value.aggregation.type) { - model.aggregation.type = value.aggregation.type; - } - model.aggregation.limit = value.aggregation.limit || Math.floor(timeService.getMaxDatapointsLimit() / 2); - } - } - return model; - } - - public clone(): Timewindow { - const cloned = new Timewindow(); - cloned.displayValue = this.displayValue; - cloned.selectedTab = this.selectedTab; - cloned.realtime = this.realtime ? this.realtime.clone() : null; - cloned.history = this.history ? this.history.clone() : null; - cloned.aggregation = this.aggregation ? this.aggregation.clone() : null; - return cloned; - } - - public cloneSelectedTimewindow(): Timewindow { - const cloned = new Timewindow(); - if (typeof this.selectedTab !== 'undefined') { - if (this.selectedTab === TimewindowType.REALTIME) { - cloned.realtime = this.realtime ? this.realtime.clone() : null; - } else if (this.selectedTab === TimewindowType.HISTORY) { - cloned.history = this.history ? this.history.cloneSelectedTimewindow() : null; - } - } - cloned.aggregation = this.aggregation ? this.aggregation.clone() : null; - return cloned; - } - -} - -export class IntervalWindow { +export interface IntervalWindow { interval?: number; timewindowMs?: number; - - public clone(): IntervalWindow { - const cloned = new IntervalWindow(); - cloned.interval = this.interval; - cloned.timewindowMs = this.timewindowMs; - return cloned; - } } -export class FixedWindow { +export interface FixedWindow { startTimeMs: number; endTimeMs: number; - - public clone(): FixedWindow { - const cloned = new FixedWindow(); - cloned.startTimeMs = this.startTimeMs; - cloned.endTimeMs = this.endTimeMs; - return cloned; - } } -export class HistoryWindow extends IntervalWindow { +export interface HistoryWindow extends IntervalWindow { historyType?: HistoryWindowType; fixedTimewindow?: FixedWindow; - - public clone(): HistoryWindow { - const cloned = new HistoryWindow(); - cloned.historyType = this.historyType; - if (this.fixedTimewindow) { - cloned.fixedTimewindow = this.fixedTimewindow.clone(); - } - cloned.interval = this.interval; - cloned.timewindowMs = this.timewindowMs; - return cloned; - } - - public cloneSelectedTimewindow(): HistoryWindow { - const cloned = new HistoryWindow(); - if (typeof this.historyType !== 'undefined') { - cloned.interval = this.interval; - if (this.historyType === HistoryWindowType.LAST_INTERVAL) { - cloned.timewindowMs = this.timewindowMs; - } else if (this.historyType === HistoryWindowType.FIXED) { - cloned.fixedTimewindow = this.fixedTimewindow ? this.fixedTimewindow.clone() : null; - } - } - return cloned; - } -} - -export class Aggregation { - type: AggregationType; - limit: number; - - public clone(): Aggregation { - const cloned = new Aggregation(); - cloned.type = this.type; - cloned.limit = this.limit; - return cloned; - } } export enum AggregationType { @@ -210,6 +67,150 @@ export const aggregationTranslations = new Map( ] ); +export interface Aggregation { + type: AggregationType; + limit: number; +} + +export interface Timewindow { + displayValue?: string; + selectedTab?: TimewindowType; + realtime?: IntervalWindow; + history?: HistoryWindow; + aggregation?: Aggregation; + stDiff?: number; +} + +export function historyInterval(timewindowMs: number): Timewindow { + const timewindow: Timewindow = { + history: { + timewindowMs + } + }; + return timewindow; +} + +export function defaultTimewindow(timeService: TimeService): Timewindow { + const currentTime = new Date().getTime(); + const timewindow: Timewindow = { + displayValue: '', + selectedTab: TimewindowType.REALTIME, + realtime: { + interval: SECOND, + timewindowMs: MINUTE + }, + history: { + historyType: HistoryWindowType.LAST_INTERVAL, + interval: SECOND, + timewindowMs: MINUTE, + fixedTimewindow: { + startTimeMs: currentTime - DAY, + endTimeMs: currentTime + } + }, + aggregation: { + type: AggregationType.AVG, + limit: Math.floor(timeService.getMaxDatapointsLimit() / 2) + } + }; + return timewindow; +} + +export function initModelFromDefaultTimewindow(value: Timewindow, timeService: TimeService): Timewindow { + const model = defaultTimewindow(timeService); + if (value) { + if (value.realtime) { + model.selectedTab = TimewindowType.REALTIME; + if (isDefined(value.realtime.interval)) { + model.realtime.interval = value.realtime.interval; + } + model.realtime.timewindowMs = value.realtime.timewindowMs; + } else { + model.selectedTab = TimewindowType.HISTORY; + if (isDefined(value.history.interval)) { + model.history.interval = value.history.interval; + } + if (isDefined(value.history.timewindowMs)) { + model.history.historyType = HistoryWindowType.LAST_INTERVAL; + model.history.timewindowMs = value.history.timewindowMs; + } else { + model.history.historyType = HistoryWindowType.FIXED; + model.history.fixedTimewindow.startTimeMs = value.history.fixedTimewindow.startTimeMs; + model.history.fixedTimewindow.endTimeMs = value.history.fixedTimewindow.endTimeMs; + } + } + if (value.aggregation) { + if (value.aggregation.type) { + model.aggregation.type = value.aggregation.type; + } + model.aggregation.limit = value.aggregation.limit || Math.floor(timeService.getMaxDatapointsLimit() / 2); + } + } + return model; +} + +export function toHistoryTimewindow(timewindow: Timewindow, startTimeMs: number, endTimeMs: number, + interval: number, timeService: TimeService): Timewindow { + if (timewindow.history) { + interval = isDefined(interval) ? interval : timewindow.history.interval; + } else if (timewindow.realtime) { + interval = timewindow.realtime.interval; + } else { + interval = 0; + } + let aggType: AggregationType; + let limit: number; + if (timewindow.aggregation) { + aggType = timewindow.aggregation.type || AggregationType.AVG; + limit = timewindow.aggregation.limit || timeService.getMaxDatapointsLimit(); + } else { + aggType = AggregationType.AVG; + limit = timeService.getMaxDatapointsLimit(); + } + const historyTimewindow: Timewindow = { + history: { + fixedTimewindow: { + startTimeMs, + endTimeMs + }, + interval: timeService.boundIntervalToTimewindow(endTimeMs - startTimeMs, interval, AggregationType.AVG) + }, + aggregation: { + type: aggType, + limit + } + }; + return historyTimewindow; +} + +export function cloneSelectedTimewindow(timewindow: Timewindow): Timewindow { + const cloned: Timewindow = {}; + if (isDefined(timewindow.selectedTab)) { + cloned.selectedTab = timewindow.selectedTab; + if (timewindow.selectedTab === TimewindowType.REALTIME) { + cloned.realtime = deepClone(timewindow.realtime); + } else if (timewindow.selectedTab === TimewindowType.HISTORY) { + cloned.history = deepClone(timewindow.history); + } + } + cloned.aggregation = deepClone(timewindow.aggregation); + return cloned; +} + +export function cloneSelectedHistoryTimewindow(historyWindow: HistoryWindow): HistoryWindow { + const cloned: HistoryWindow = {}; + if (isDefined(historyWindow.historyType)) { + cloned.historyType = historyWindow.historyType; + cloned.interval = historyWindow.interval; + if (historyWindow.historyType === HistoryWindowType.LAST_INTERVAL) { + cloned.timewindowMs = historyWindow.timewindowMs; + } else if (historyWindow.historyType === HistoryWindowType.FIXED) { + cloned.fixedTimewindow = deepClone(historyWindow.fixedTimewindow); + } + } + return cloned; +} + export interface TimeInterval { name: string; translateParams: {[key: string]: any}; diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index daba2d232a..b3e911b298 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -18,12 +18,17 @@ import { BaseData } from '@shared/models/base-data'; import { TenantId } from '@shared/models/id/tenant-id'; import { WidgetTypeId } from '@shared/models/id/widget-type-id'; import { Timewindow } from '@shared/models/time/time.models'; +import { EntityType } from '@shared/models/entity-type.models'; +import { AlarmSearchStatus } from '@shared/models/alarm.models'; +import { Data } from '@angular/router'; +import { DataKeyType } from './telemetry/telemetry.models'; export enum widgetType { timeseries = 'timeseries', latest = 'latest', rpc = 'rpc', - alarm = 'alarm' + alarm = 'alarm', + static = 'static' } export interface WidgetTypeTemplate { @@ -77,6 +82,16 @@ export const widgetTypesData = new Map( alias: 'alarms_table' } } + ], + [ + widgetType.static, + { + name: 'widget.static', + template: { + bundleAlias: 'cards', + alias: 'html_card' + } + } ] ] ); @@ -174,14 +189,50 @@ export interface LegendConfig { showTotal: boolean; } -export interface DataKey { - label: string; - color: string; +export interface KeyInfo { + name: string; + label?: string; + color?: string; + funcBody?: string; + postFuncBody?: string; + units?: string; + decimals?: number; +} + +export interface DataKey extends KeyInfo { + type: DataKeyType; + pattern?: string; + settings?: any; + usePostProcessing?: boolean; hidden?: boolean; + _hash?: number; +} + +export enum DatasourceType { + function = 'function', + entity = 'entity' +} + +export interface Datasource { + type: DatasourceType; + name?: string; + dataKeys?: Array; + entityType?: EntityType; + entityId?: string; + entityName?: string; + entityAliasId?: string; [key: string]: any; // TODO: } +export type DataSet = [number, any][]; + +export interface DatasourceData { + datasource: Datasource; + dataKey: DataKey; + data: DataSet; +} + export interface LegendKey { dataKey: DataKey; dataIndex: number; @@ -192,6 +243,7 @@ export interface LegendKeyData { max: number; avg: number; total: number; + hidden: boolean; } export interface LegendData { @@ -264,6 +316,11 @@ export interface WidgetConfig { decimals?: number; actions?: {[actionSourceId: string]: Array}; settings?: WidgetConfigSettings; + alarmSource?: Datasource; + alarmSearchStatus?: AlarmSearchStatus; + alarmsPollingInterval?: number; + datasources?: Array; + targetDeviceAliasIds?: Array; [key: string]: any; // TODO: diff --git a/ui-ngx/src/tsconfig.app.json b/ui-ngx/src/tsconfig.app.json index d5aa739e74..f74d460ff1 100644 --- a/ui-ngx/src/tsconfig.app.json +++ b/ui-ngx/src/tsconfig.app.json @@ -2,7 +2,7 @@ "extends": "../tsconfig.json", "compilerOptions": { "outDir": "../out-tsc/app", - "types": ["node"] + "types": ["node", "jquery"] }, "exclude": [ "test.ts", diff --git a/ui-ngx/src/typings.d.ts b/ui-ngx/src/typings.d.ts new file mode 100644 index 0000000000..3dc6e35bc4 --- /dev/null +++ b/ui-ngx/src/typings.d.ts @@ -0,0 +1,19 @@ +/// +/// Copyright © 2016-2019 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. +/// + +interface JQuery { + terminal(options?: any): any; +} diff --git a/ui-ngx/tsconfig.json b/ui-ngx/tsconfig.json index 8f4576a7bf..4d36a53659 100644 --- a/ui-ngx/tsconfig.json +++ b/ui-ngx/tsconfig.json @@ -12,7 +12,8 @@ "importHelpers": true, "target": "es5", "typeRoots": [ - "node_modules/@types" + "node_modules/@types", + "src/typings.d.ts" ], "paths": { "@app/*": ["src/app/*"], From 6d0577cc37a7e7495f816c16d221e38abe1e6fd3 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 10 Sep 2019 15:17:38 +0300 Subject: [PATCH 032/133] Improve widget models --- ui-ngx/src/app/modules/home/models/widget-component.models.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 3aa9a23bd5..089ee571f4 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 @@ -61,8 +61,8 @@ export interface WidgetAction extends IWidgetAction { export interface WidgetContext { inited?: boolean; - $container?: any; - $containerParent?: any; + $container?: JQuery; + $containerParent?: JQuery; width?: number; height?: number; $scope?: IDynamicWidgetComponent; From 2bdde375355bfb7f85b455e2910b30234e550762 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 12 Sep 2019 19:58:42 +0300 Subject: [PATCH 033/133] Implement DataSource service, Data aggregator, Flot widget. --- ui-ngx/angular.json | 9 + ui-ngx/package-lock.json | 28 + ui-ngx/package.json | 5 + ui-ngx/src/app/core/api/data-aggregator.ts | 332 +++++ .../app/core/api/datasource-subcription.ts | 666 ++++++++++ ui-ngx/src/app/core/api/datasource.service.ts | 71 +- ui-ngx/src/app/core/api/widget-api.models.ts | 19 +- .../src/app/core/api/widget-subscription.ts | 323 ++++- ui-ngx/src/app/core/services/utils.service.ts | 31 + ui-ngx/src/app/core/utils.ts | 36 +- .../core/ws/telemetry-websocket.service.ts | 7 +- .../dashboard/dashboard.component.ts | 10 +- .../entity/entities-table.component.ts | 4 +- .../widget/dynamic-widget.component.ts | 4 +- .../components/widget/legend.component.scss | 4 + .../components/widget/legend.component.ts | 6 +- .../widget/lib/flot-widget.models.ts | 592 +++++++++ .../home/components/widget/lib/flot-widget.ts | 1074 +++++++++++++++++ .../widget/widget-component.service.ts | 3 + .../components/widget/widget.component.html | 14 +- .../components/widget/widget.component.ts | 46 +- .../home/models/dashboard-component.models.ts | 3 +- .../home/models/widget-component.models.ts | 6 +- .../models/telemetry/telemetry.models.ts | 13 +- .../src/app/shared/models/time/time.models.ts | 84 +- ui-ngx/src/app/shared/models/widget.models.ts | 27 +- ui-ngx/src/styles.scss | 4 + ui-ngx/src/tsconfig.app.json | 2 +- ui-ngx/src/typings/jquery.flot.typings.d.ts | 128 ++ .../jquery.typings.d.ts} | 0 ui-ngx/tsconfig.json | 3 +- 31 files changed, 3500 insertions(+), 54 deletions(-) create mode 100644 ui-ngx/src/app/core/api/data-aggregator.ts create mode 100644 ui-ngx/src/app/core/api/datasource-subcription.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts create mode 100644 ui-ngx/src/typings/jquery.flot.typings.d.ts rename ui-ngx/src/{typings.d.ts => typings/jquery.typings.d.ts} (100%) diff --git a/ui-ngx/angular.json b/ui-ngx/angular.json index 629a0fd8df..fd0555c661 100644 --- a/ui-ngx/angular.json +++ b/ui-ngx/angular.json @@ -38,6 +38,15 @@ "node_modules/javascript-detect-element-resize/detect-element-resize.js", "node_modules/jquery/dist/jquery.min.js", "node_modules/jquery.terminal/js/jquery.terminal.min.js", + "node_modules/flot/lib/jquery.colorhelpers.js", + "node_modules/flot/src/jquery.flot.js", + "node_modules/flot/src/plugins/jquery.flot.time.js", + "node_modules/flot/src/plugins/jquery.flot.selection.js", + "node_modules/flot/src/plugins/jquery.flot.pie.js", + "node_modules/flot/src/plugins/jquery.flot.crosshair.js", + "node_modules/flot/src/plugins/jquery.flot.stack.js", + "node_modules/flot.curvedlines/curvedLines.js", + "node_modules/tinycolor2/dist/tinycolor-min.js", "node_modules/ace-builds/src-min/ace.js", "node_modules/ace-builds/src-min/ext-language_tools.js", "node_modules/ace-builds/src-min/ext-searchbox.js", diff --git a/ui-ngx/package-lock.json b/ui-ngx/package-lock.json index a5f864c71c..5f93537aff 100644 --- a/ui-ngx/package-lock.json +++ b/ui-ngx/package-lock.json @@ -1126,6 +1126,15 @@ "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", "dev": true }, + "@types/flot": { + "version": "0.0.31", + "resolved": "https://registry.npmjs.org/@types/flot/-/flot-0.0.31.tgz", + "integrity": "sha512-X+RcMQCqPlQo8zPT6cUFTd/PoYBShMQlHUeOXf05jWlfYnvLuRmluB9z+2EsOKFgUzqzZve5brx+gnFxBaHEUw==", + "dev": true, + "requires": { + "@types/jquery": "*" + } + }, "@types/glob": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", @@ -1196,6 +1205,12 @@ "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==", "dev": true }, + "@types/tinycolor2": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@types/tinycolor2/-/tinycolor2-1.4.2.tgz", + "integrity": "sha512-PeHg/AtdW6aaIO2a+98Xj7rWY4KC1E6yOy7AFknJQ7VXUGNrMlyxDFxJo7HqLtjQms/ZhhQX52mLVW/EX3JGOw==", + "dev": true + }, "@types/webpack-sources": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-0.1.5.tgz", @@ -4412,6 +4427,14 @@ "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", "dev": true }, + "flot": { + "version": "git://github.com/thingsboard/flot.git#6e1a37095868f174d31d5c627c3659b70f9b92dd", + "from": "git://github.com/thingsboard/flot.git#0.9-work" + }, + "flot.curvedlines": { + "version": "git://github.com/MichaelZinsmaier/CurvedLines.git#22ed1fc2a6ccafc816c2d07b36027cc123825c4b", + "from": "git://github.com/MichaelZinsmaier/CurvedLines.git#master" + }, "flush-write-stream": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", @@ -7358,6 +7381,11 @@ } } }, + "moment": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", + "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" + }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", diff --git a/ui-ngx/package.json b/ui-ngx/package.json index b76d0686c0..12c92c1192 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -37,6 +37,8 @@ "compass-sass-mixins": "^0.12.7", "core-js": "^3.1.4", "deep-equal": "^1.0.1", + "flot": "git://github.com/thingsboard/flot.git#0.9-work", + "flot.curvedlines": "git://github.com/MichaelZinsmaier/CurvedLines.git#master", "font-awesome": "^4.7.0", "hammerjs": "^2.0.8", "javascript-detect-element-resize": "^0.5.3", @@ -44,6 +46,7 @@ "jquery.terminal": "^2.8.0", "material-design-icons": "^3.0.1", "messageformat": "^2.3.0", + "moment": "^2.24.0", "ngx-clipboard": "^12.2.0", "ngx-translate-messageformat-compiler": "^4.5.0", "rxjs": "~6.5.2", @@ -59,10 +62,12 @@ "@angular/cli": "~8.2.0", "@angular/compiler-cli": "~8.2.0", "@angular/language-service": "~8.2.0", + "@types/flot": "0.0.31", "@types/jasmine": "~3.4.0", "@types/jasminewd2": "~2.0.6", "@types/jquery": "^3.3.31", "@types/node": "~10.14.15", + "@types/tinycolor2": "^1.4.2", "codelyzer": "~5.1.0", "compression-webpack-plugin": "^3.0.0", "directory-tree": "^2.2.3", diff --git a/ui-ngx/src/app/core/api/data-aggregator.ts b/ui-ngx/src/app/core/api/data-aggregator.ts new file mode 100644 index 0000000000..bea5709055 --- /dev/null +++ b/ui-ngx/src/app/core/api/data-aggregator.ts @@ -0,0 +1,332 @@ +/// +/// Copyright © 2016-2019 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 { SubscriptionData, SubscriptionUpdateMsg, SubscriptionDataHolder } from '@app/shared/models/telemetry/telemetry.models'; +import { AggregationType } from '@shared/models/time/time.models'; +import { UtilsService } from '@core/services/utils.service'; +import Timeout = NodeJS.Timeout; +import { deepClone } from '@core/utils'; + +export declare type onAggregatedData = (data: SubscriptionData, detectChanges: boolean) => void; + +interface AggData { + count: number; + sum: number; + aggValue: any; +} + +interface AggregationMap { + [key: string]: Map; +} + +declare type AggFunction = (aggData: AggData, value?: any) => void; + +const avg: AggFunction = (aggData: AggData, value?: any) => { + aggData.count++; + aggData.sum += value; + aggData.aggValue = aggData.sum / aggData.count; +}; + +const min: AggFunction = (aggData: AggData, value?: any) => { + aggData.aggValue = Math.min(aggData.aggValue, value); +}; + +const max: AggFunction = (aggData: AggData, value?: any) => { + aggData.aggValue = Math.max(aggData.aggValue, value); +}; + +const sum: AggFunction = (aggData: AggData, value?: any) => { + aggData.aggValue = aggData.aggValue + value; +}; + +const count: AggFunction = (aggData: AggData) => { + aggData.count++; + aggData.aggValue = aggData.count; +}; + +const none: AggFunction = (aggData: AggData, value?: any) => { + aggData.aggValue = value; +}; + +export class DataAggregator { + + private dataBuffer: SubscriptionData = {}; + private data: SubscriptionData; + private lastPrevKvPairData: {[key: string]: [number, any]}; + + private aggregationMap: AggregationMap; + + private dataReceived = false; + private resetPending = false; + + private noAggregation = this.aggregationType === AggregationType.NONE; + private aggregationTimeout = Math.max(this.interval, 1000); + private aggFunction: AggFunction; + + private intervalTimeoutHandle: Timeout; + private intervalScheduledTime: number; + + private endTs: number; + private elapsed: number; + + constructor(private onDataCb: onAggregatedData, + private tsKeyNames: string[], + private startTs: number, + private limit: number, + private aggregationType: AggregationType, + private timeWindow: number, + private interval: number, + private stateData: boolean, + private utils: UtilsService) { + this.tsKeyNames.forEach((key) => { + this.dataBuffer[key] = []; + }); + if (this.stateData) { + this.lastPrevKvPairData = {}; + } + switch (this.aggregationType) { + case AggregationType.MIN: + this.aggFunction = min; + break; + case AggregationType.MAX: + this.aggFunction = max; + break; + case AggregationType.AVG: + this.aggFunction = avg; + break; + case AggregationType.SUM: + this.aggFunction = sum; + break; + case AggregationType.COUNT: + this.aggFunction = count; + break; + case AggregationType.NONE: + this.aggFunction = none; + break; + default: + this.aggFunction = avg; + } + } + + public reset(startTs: number, timeWindow: number, interval: number) { + if (this.intervalTimeoutHandle) { + clearTimeout(this.intervalTimeoutHandle); + this.intervalTimeoutHandle = null; + } + this.intervalScheduledTime = this.utils.currentPerfTime(); + this.startTs = startTs; + this.timeWindow = timeWindow; + this.interval = interval; + this.endTs = this.startTs + this.timeWindow; + this.elapsed = 0; + this.aggregationTimeout = Math.max(this.interval, 1000); + this.resetPending = true; + this.intervalTimeoutHandle = setTimeout(this.onInterval.bind(this), this.aggregationTimeout); + } + + public destroy() { + if (this.intervalTimeoutHandle) { + clearTimeout(this.intervalTimeoutHandle); + this.intervalTimeoutHandle = null; + } + this.aggregationMap = null; + } + + public onData(data: SubscriptionDataHolder, update: boolean, history: boolean, detectChanges: boolean) { + if (!this.dataReceived || this.resetPending) { + let updateIntervalScheduledTime = true; + if (!this.dataReceived) { + this.elapsed = 0; + this.dataReceived = true; + this.endTs = this.startTs + this.timeWindow; + } + if (this.resetPending) { + this.resetPending = false; + updateIntervalScheduledTime = false; + } + if (update) { + this.aggregationMap = {}; + this.updateAggregatedData(data.data); + } else { + this.aggregationMap = this.processAggregatedData(data.data); + } + if (updateIntervalScheduledTime) { + this.intervalScheduledTime = this.utils.currentPerfTime(); + } + this.onInterval(history, detectChanges); + } else { + this.updateAggregatedData(data.data); + if (history) { + this.intervalScheduledTime = this.utils.currentPerfTime(); + this.onInterval(history, detectChanges); + } + } + } + + private onInterval(history?: boolean, detectChanges?: boolean) { + const now = this.utils.currentPerfTime(); + this.elapsed += now - this.intervalScheduledTime; + this.intervalScheduledTime = now; + if (this.intervalTimeoutHandle) { + clearTimeout(this.intervalTimeoutHandle); + this.intervalTimeoutHandle = null; + } + if (!history) { + const delta = Math.floor(this.elapsed / this.interval); + if (delta || !this.data) { + this.startTs += delta * this.interval; + this.endTs += delta * this.interval; + this.data = this.updateData(); + this.elapsed = this.elapsed - delta * this.interval; + } + } else { + this.data = this.updateData(); + } + if (this.onDataCb) { + this.onDataCb(this.data, detectChanges); + } + if (!history) { + this.intervalTimeoutHandle = setTimeout(this.onInterval.bind(this), this.aggregationTimeout); + } + } + + private updateData(): SubscriptionData { + this.tsKeyNames.forEach((key) => { + this.dataBuffer[key] = []; + }); + for (const key of Object.keys(this.aggregationMap)) { + const aggKeyData = this.aggregationMap[key]; + let keyData = this.dataBuffer[key]; + aggKeyData.forEach((aggData, aggTimestamp) => { + if (aggTimestamp <= this.startTs) { + if (this.stateData && + (!this.lastPrevKvPairData[key] || this.lastPrevKvPairData[key][0] < aggTimestamp)) { + this.lastPrevKvPairData[key] = [aggTimestamp, aggData.aggValue]; + } + aggKeyData.delete(aggTimestamp); + } else if (aggTimestamp <= this.endTs) { + const kvPair: [number, any] = [aggTimestamp, aggData.aggValue]; + keyData.push(kvPair); + } + }); + keyData.sort((set1, set2) => set1[0] - set2[0]); + if (this.stateData) { + this.updateStateBounds(keyData, deepClone(this.lastPrevKvPairData[key])); + } + if (keyData.length > this.limit) { + keyData = keyData.slice(keyData.length - this.limit); + } + this.dataBuffer[key] = keyData; + } + return this.dataBuffer; + } + + private updateStateBounds(keyData: [number, any][], lastPrevKvPair: [number, any]) { + if (lastPrevKvPair) { + lastPrevKvPair[0] = this.startTs; + } + let firstKvPair; + if (!keyData.length) { + if (lastPrevKvPair) { + firstKvPair = lastPrevKvPair; + keyData.push(firstKvPair); + } + } else { + firstKvPair = keyData[0]; + } + if (firstKvPair && firstKvPair[0] > this.startTs) { + if (lastPrevKvPair) { + keyData.unshift(lastPrevKvPair); + } + } + if (keyData.length) { + let lastKvPair = keyData[keyData.length - 1]; + if (lastKvPair[0] < this.endTs) { + lastKvPair = deepClone(lastKvPair); + lastKvPair[0] = this.endTs; + keyData.push(lastKvPair); + } + } + } + + private processAggregatedData(data: SubscriptionData): AggregationMap { + const isCount = this.aggregationType === AggregationType.COUNT; + const aggregationMap: AggregationMap = {}; + for (const key of Object.keys(data)) { + let aggKeyData = aggregationMap[key]; + if (!aggKeyData) { + aggKeyData = new Map(); + aggregationMap[key] = aggKeyData; + } + const keyData = data[key]; + keyData.forEach((kvPair) => { + const timestamp = kvPair[0]; + const value = this.convertValue(kvPair[1]); + const aggKey = timestamp; + const aggData = { + count: isCount ? value : 1, + sum: value, + aggValue: value + }; + aggKeyData.set(aggKey, aggData); + }); + } + return aggregationMap; + } + + private updateAggregatedData(data: SubscriptionData) { + const isCount = this.aggregationType === AggregationType.COUNT; + for (const key of Object.keys(data)) { + let aggKeyData = this.aggregationMap[key]; + if (!aggKeyData) { + aggKeyData = new Map(); + this.aggregationMap[key] = aggKeyData; + } + const keyData = data[key]; + keyData.forEach((kvPair) => { + const timestamp = kvPair[0]; + const value = this.convertValue(kvPair[1]); + const aggTimestamp = this.noAggregation ? timestamp : (this.startTs + + Math.floor((timestamp - this.startTs) / this.interval) * this.interval + this.interval / 2); + let aggData = aggKeyData.get(aggTimestamp); + if (!aggData) { + aggData = { + count: 1, + sum: value, + aggValue: isCount ? 1 : value + }; + aggKeyData.set(aggTimestamp, aggData); + } else { + this.aggFunction(aggData, value); + } + }); + } + } + + private isNumeric(val: any): boolean { + return (val - parseFloat( val ) + 1) >= 0; + } + + private convertValue(val: string): any { + if (!this.noAggregation || val && this.isNumeric(val)) { + return Number(val); + } else { + return val; + } + } + +} + diff --git a/ui-ngx/src/app/core/api/datasource-subcription.ts b/ui-ngx/src/app/core/api/datasource-subcription.ts new file mode 100644 index 0000000000..982215a50b --- /dev/null +++ b/ui-ngx/src/app/core/api/datasource-subcription.ts @@ -0,0 +1,666 @@ +/// +/// Copyright © 2016-2019 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 { DataSet, DataSetHolder, DatasourceType, widgetType } from '@shared/models/widget.models'; +import { + AttributesSubscriptionCmd, + DataKeyType, + GetHistoryCmd, + SubscriptionData, + SubscriptionDataHolder, + SubscriptionUpdateMsg, + TelemetryService, + TelemetrySubscriber, + TimeseriesSubscriptionCmd +} from '@shared/models/telemetry/telemetry.models'; +import { DatasourceListener } from './datasource.service'; +import { AggregationType, SubscriptionTimewindow, YEAR } from '@shared/models/time/time.models'; +import { deepClone, isDefined, isObject, isDefinedAndNotNull } from '@core/utils'; +import { UtilsService } from '@core/services/utils.service'; +import { EntityType } from '@shared/models/entity-type.models'; +import { DataAggregator } from '@core/api/data-aggregator'; +import Timeout = NodeJS.Timeout; + +declare type DataKeyFunction = (time: number, prevValue: any) => any; + +declare type DataKeyPostFunction = (time: number, value: any, prevValue: any, timePrev: number, prevOrigValue: any) => any; + +export interface SubscriptionDataKey { + name: string; + type: DataKeyType; + funcBody: string; + func?: DataKeyFunction; + postFuncBody: string; + postFunc?: DataKeyPostFunction; + index?: number; + key?: string; + lastUpdateTime?: number; +} + +export interface DatasourceSubscriptionOptions { + datasourceType: DatasourceType; + dataKeys: Array; + type: widgetType; + entityType?: EntityType; + entityId?: string; + subscriptionTimewindow?: SubscriptionTimewindow; +} + +export class DatasourceSubscription { + + private listeners: Array = []; + private datasourceType: DatasourceType = this.datasourceSubscriptionOptions.datasourceType; + + private history = this.datasourceSubscriptionOptions.subscriptionTimewindow && + isObject(this.datasourceSubscriptionOptions.subscriptionTimewindow.fixedWindow); + + private realtime = this.datasourceSubscriptionOptions.subscriptionTimewindow && + isDefinedAndNotNull(this.datasourceSubscriptionOptions.subscriptionTimewindow.realtimeWindowMs); + + private subscribers = new Array(); + + private dataAggregator: DataAggregator; + + private dataKeys: {[key: string]: Array | SubscriptionDataKey} = {}; + private datasourceData: {[key: string]: DataSetHolder} = {}; + private datasourceOrigData: {[key: string]: DataSetHolder} = {}; + + private frequency: number; + private tickScheduledTime = 0; + private tickElapsed = 0; + private timer: Timeout; + + constructor(private datasourceSubscriptionOptions: DatasourceSubscriptionOptions, + private telemetryService: TelemetryService, + private utils: UtilsService) { + this.initializeSubscription(); + } + + private initializeSubscription() { + for (let i = 0; i < this.datasourceSubscriptionOptions.dataKeys.length; i++) { + const dataKey = deepClone(this.datasourceSubscriptionOptions.dataKeys[i]); + dataKey.index = i; + if (this.datasourceType === DatasourceType.function) { + if (!dataKey.func) { + dataKey.func = new Function('time', 'prevValue', dataKey.funcBody) as DataKeyFunction; + } + } else { + if (dataKey.postFuncBody && !dataKey.postFunc) { + dataKey.postFunc = new Function('time', 'value', 'prevValue', 'timePrev', 'prevOrigValue', + dataKey.postFuncBody) as DataKeyPostFunction; + } + } + let key: string; + if (this.datasourceType === DatasourceType.entity || this.datasourceSubscriptionOptions.type === widgetType.timeseries) { + if (this.datasourceType === DatasourceType.function) { + key = `${dataKey.name}_${dataKey.index}_${dataKey.type}`; + } else { + key = `${dataKey.name}_${dataKey.type}`; + } + let dataKeysList = this.dataKeys[key] as Array; + if (!dataKeysList) { + dataKeysList = []; + this.dataKeys[key] = dataKeysList; + } + const index = dataKeysList.push(dataKey) - 1; + this.datasourceData[key + '_' + index] = { + data: [] + }; + } else { + key = String(this.utils.objectHashCode(dataKey)); + this.datasourceData[key] = { + data: [] + }; + this.dataKeys[key] = dataKey; + } + dataKey.key = key; + } + this.datasourceOrigData = deepClone(this.datasourceData); + if (this.datasourceType === DatasourceType.function) { + this.frequency = 1000; + if (this.datasourceSubscriptionOptions.type === widgetType.timeseries) { + this.frequency = Math.min(this.datasourceSubscriptionOptions.subscriptionTimewindow.aggregation.interval, 5000); + } + } + } + + public addListener(listener: DatasourceListener) { + this.listeners.push(listener); + if (this.history) { + this.start(); + } + } + + public hasListeners(): boolean { + return this.listeners.length > 0; + } + + public removeListener(listener: DatasourceListener) { + this.listeners.splice(this.listeners.indexOf(listener), 1); + } + + public syncListener(listener: DatasourceListener) { + let key: string; + let dataKey: SubscriptionDataKey; + if (this.datasourceType === DatasourceType.entity || this.datasourceSubscriptionOptions.type === widgetType.timeseries) { + for (key of Object.keys(this.dataKeys)) { + const dataKeysList = this.dataKeys[key] as Array; + for (let i = 0; i < dataKeysList.length; i++) { + dataKey = dataKeysList[i]; + const datasourceKey = `${key}_${i}`; + listener.dataUpdated(this.datasourceData[datasourceKey], + listener.datasourceIndex, + dataKey.index, false); + } + } + } else { + for (key of Object.keys(this.dataKeys)) { + dataKey = this.dataKeys[key] as SubscriptionDataKey; + listener.dataUpdated(this.datasourceData[key], + listener.datasourceIndex, + dataKey.index, false); + } + } + } + + public unsubscribe() { + if (this.timer) { + clearTimeout(this.timer); + this.timer = null; + } + if (this.datasourceType === DatasourceType.entity) { + this.subscribers.forEach( + (subscriber) => { + subscriber.unsubscribe(); + } + ); + this.subscribers.length = 0; + } + if (this.dataAggregator) { + this.dataAggregator.destroy(); + this.dataAggregator = null; + } + } + + public start() { + if (this.history && !this.hasListeners()) { + return; + } + let subsTw = this.datasourceSubscriptionOptions.subscriptionTimewindow; + const tsKeyNames: string[] = []; + const attrKeyNames: string[] = []; + let dataKey: SubscriptionDataKey; + if (this.datasourceType === DatasourceType.entity) { + + let tsKeys = ''; + let attrKeys = ''; + + for (const key of Object.keys(this.dataKeys)) { + const dataKeysList = this.dataKeys[key] as Array; + dataKey = dataKeysList[0]; + if (dataKey.type === DataKeyType.timeseries) { + tsKeyNames.push(dataKey.name); + } else if (dataKey.type === DataKeyType.attribute) { + attrKeyNames.push(dataKey.name); + } + } + tsKeys = tsKeyNames.join(','); + attrKeys = attrKeyNames.join(','); + if (tsKeys.length > 0) { + if (this.history) { + const historyCommand = new GetHistoryCmd(); + historyCommand.entityType = this.datasourceSubscriptionOptions.entityType; + historyCommand.entityId = this.datasourceSubscriptionOptions.entityId; + historyCommand.keys = tsKeys; + historyCommand.startTs = subsTw.fixedWindow.startTimeMs; + historyCommand.endTs = subsTw.fixedWindow.endTimeMs; + historyCommand.interval = subsTw.aggregation.interval; + historyCommand.limit = subsTw.aggregation.limit; + historyCommand.agg = subsTw.aggregation.type; + + const subscriber = new TelemetrySubscriber(this.telemetryService); + subscriber.subscriptionCommands.push(historyCommand); + + let firstStateHistoryCommand: GetHistoryCmd; + if (subsTw.aggregation.stateData) { + firstStateHistoryCommand = this.createFirstStateHistoryCommand(subsTw.fixedWindow.startTimeMs, tsKeys); + subscriber.subscriptionCommands.push(firstStateHistoryCommand); + } + let data: SubscriptionUpdateMsg; + let firstStateData: SubscriptionUpdateMsg; + + subscriber.data$.subscribe( + (subscriptionUpdate) => { + if (subsTw.aggregation.stateData && firstStateHistoryCommand + && firstStateHistoryCommand.cmdId === subscriptionUpdate.subscriptionId) { + if (data) { + this.onStateHistoryData(subscriptionUpdate, data, subsTw.aggregation.limit, + subsTw.fixedWindow.startTimeMs, subsTw.fixedWindow.endTimeMs, + (newData) => { + this.onData(newData.data, DataKeyType.timeseries, true); + } + ); + } else { + firstStateData = data; + } + } else { + if (subsTw.aggregation.stateData) { + if (firstStateData) { + this.onStateHistoryData(firstStateData, subscriptionUpdate, subsTw.aggregation.limit, + subsTw.fixedWindow.startTimeMs, subsTw.fixedWindow.endTimeMs, + (newData) => { + this.onData(newData.data, DataKeyType.timeseries, true); + }); + } else { + data = subscriptionUpdate; + } + } else { + for (const key of Object.keys(data.data)) { + const keyData = data.data[key]; + keyData.sort((set1, set2) => set1[0] - set2[0]); + } + this.onData(data.data, DataKeyType.timeseries, true); + } + } + } + ); + subscriber.subscribe(); + this.subscribers.push(subscriber); + } else { + const subscriptionCommand = new TimeseriesSubscriptionCmd(); + subscriptionCommand.entityType = this.datasourceSubscriptionOptions.entityType; + subscriptionCommand.entityId = this.datasourceSubscriptionOptions.entityId; + subscriptionCommand.keys = tsKeys; + + const subscriber = new TelemetrySubscriber(this.telemetryService); + subscriber.subscriptionCommands.push(subscriptionCommand); + + if (this.datasourceSubscriptionOptions.type === widgetType.timeseries) { + this.updateRealtimeSubscriptionCommand(subscriptionCommand, subsTw); + + let firstStateSubscriptionCommand: GetHistoryCmd; + if (subsTw.aggregation.stateData) { + firstStateSubscriptionCommand = this.createFirstStateHistoryCommand(subsTw.startTs, tsKeys); + subscriber.subscriptionCommands.push(firstStateSubscriptionCommand); + } + this.dataAggregator = this.createRealtimeDataAggregator(subsTw, tsKeyNames, DataKeyType.timeseries); + + let data: SubscriptionUpdateMsg; + let firstStateData: SubscriptionUpdateMsg; + let stateDataReceived: boolean; + + subscriber.data$.subscribe( + (subscriptionUpdate) => { + if (subsTw.aggregation.stateData && + firstStateSubscriptionCommand && firstStateSubscriptionCommand.cmdId === subscriptionUpdate.subscriptionId) { + if (data) { + this.onStateHistoryData(subscriptionUpdate, data, subsTw.aggregation.limit, + subsTw.startTs, subsTw.startTs + subsTw.aggregation.timeWindow, + (newData) => { + this.dataAggregator.onData(newData, false, false, true); + }); + stateDataReceived = true; + } else { + firstStateData = data; + } + } else { + if (subsTw.aggregation.stateData && !stateDataReceived) { + if (firstStateData) { + this.onStateHistoryData(firstStateData, subscriptionUpdate, subsTw.aggregation.limit, + subsTw.startTs, subsTw.startTs + subsTw.aggregation.timeWindow, + (newData) => { + this.dataAggregator.onData(newData, false, false, true); + }); + stateDataReceived = true; + } else { + data = subscriptionUpdate; + } + } else { + this.dataAggregator.onData(subscriptionUpdate, false, false, true); + } + } + } + ); + subscriber.reconnect$.subscribe(() => { + let newSubsTw: SubscriptionTimewindow = null; + this.listeners.forEach((listener) => { + if (!newSubsTw) { + newSubsTw = listener.updateRealtimeSubscription(); + } else { + listener.setRealtimeSubscription(newSubsTw); + } + }); + subsTw = newSubsTw; + firstStateData = null; + data = null; + stateDataReceived = false; + this.updateRealtimeSubscriptionCommand(subscriptionCommand, subsTw); + if (subsTw.aggregation.stateData) { + this.updateFirstStateHistoryCommand(firstStateSubscriptionCommand, subsTw.startTs); + } + this.dataAggregator.reset(newSubsTw.startTs, newSubsTw.aggregation.timeWindow, newSubsTw.aggregation.interval); + }); + } else { + subscriber.data$.subscribe( + (subscriptionUpdate) => { + if (subscriptionUpdate.data) { + this.onData(subscriptionUpdate.data, DataKeyType.timeseries, true); + } + } + ); + } + + subscriber.subscribe(); + this.subscribers.push(subscriber); + + } + } + + if (attrKeys.length) { + const attrsSubscriptionCommand = new AttributesSubscriptionCmd(); + attrsSubscriptionCommand.entityType = this.datasourceSubscriptionOptions.entityType; + attrsSubscriptionCommand.entityId = this.datasourceSubscriptionOptions.entityId; + attrsSubscriptionCommand.keys = attrKeys; + + const subscriber = new TelemetrySubscriber(this.telemetryService); + subscriber.subscriptionCommands.push(attrsSubscriptionCommand); + subscriber.data$.subscribe( + (subscriptionUpdate) => { + if (subscriptionUpdate.data) { + this.onData(subscriptionUpdate.data, DataKeyType.attribute, true); + } + } + ); + + subscriber.subscribe(); + this.subscribers.push(subscriber); + } + } else if (this.datasourceType === DatasourceType.function) { + if (this.datasourceSubscriptionOptions.type === widgetType.timeseries) { + for (const key of Object.keys(this.dataKeys)) { + const dataKeysList = this.dataKeys[key] as Array; + dataKeysList.forEach((subscriptionDataKey) => { + tsKeyNames.push(`${subscriptionDataKey.name}_${subscriptionDataKey.index}`); + }); + } + this.dataAggregator = this.createRealtimeDataAggregator(subsTw, tsKeyNames, DataKeyType.function); + } + this.tickScheduledTime = this.utils.currentPerfTime(); + if (this.history) { + this.onTick(true); + } else { + this.timer = setTimeout(this.onTick.bind(this, true), 0); + } + } + } + + private createFirstStateHistoryCommand(startTs: number, tsKeys: string): GetHistoryCmd { + const command = new GetHistoryCmd(); + command.entityType = this.datasourceSubscriptionOptions.entityType; + command.entityId = this.datasourceSubscriptionOptions.entityId; + command.keys = tsKeys; + command.startTs = startTs - YEAR; + command.endTs = startTs; + command.interval = 1000; + command.limit = 1; + command.agg = AggregationType.NONE; + return command; + } + + private updateFirstStateHistoryCommand(stateHistoryCommand: GetHistoryCmd, startTs: number) { + stateHistoryCommand.startTs = startTs - YEAR; + stateHistoryCommand.endTs = startTs; + } + + private onStateHistoryData(firstStateData: SubscriptionUpdateMsg, data: SubscriptionUpdateMsg, + limit: number, startTs: number, endTs: number, onData: (data: SubscriptionUpdateMsg) => void) { + for (const key of Object.keys(data.data)) { + const keyData = data.data[key]; + keyData.sort((set1, set2) => set1[0] - set2[0]); + if (keyData.length < limit) { + let firstStateKeyData = firstStateData.data[key]; + if (firstStateKeyData.length) { + const firstStateDataTsKv = firstStateKeyData[0]; + firstStateDataTsKv[0] = startTs; + firstStateKeyData = [ + [ startTs, firstStateKeyData[0][1] ] + ]; + keyData.unshift(firstStateDataTsKv); + } + } + if (keyData.length) { + const lastTsKv = deepClone(keyData[keyData.length - 1]); + lastTsKv[0] = endTs; + keyData.push(lastTsKv); + } + } + onData(data); + } + + private createRealtimeDataAggregator(subsTw: SubscriptionTimewindow, + tsKeyNames: Array, dataKeyType: DataKeyType): DataAggregator { + return new DataAggregator( + (data, detectChanges) => { + this.onData(data, dataKeyType, detectChanges); + }, + tsKeyNames, + subsTw.startTs, + subsTw.aggregation.limit, + subsTw.aggregation.type, + subsTw.aggregation.timeWindow, + subsTw.aggregation.interval, + subsTw.aggregation.stateData, + this.utils + ); + } + + private updateRealtimeSubscriptionCommand(subscriptionCommand: TimeseriesSubscriptionCmd, subsTw: SubscriptionTimewindow) { + subscriptionCommand.startTs = subsTw.startTs; + subscriptionCommand.timeWindow = subsTw.aggregation.timeWindow; + subscriptionCommand.interval = subsTw.aggregation.interval; + subscriptionCommand.limit = subsTw.aggregation.limit; + subscriptionCommand.agg = subsTw.aggregation.type; + } + + private generateSeries(dataKey: SubscriptionDataKey, index: number, startTime: number, endTime: number): [number, any][] { + const data: [number, any][] = []; + let prevSeries: [number, any]; + const datasourceDataKey = `${dataKey.key}_${index}`; + const datasourceKeyData = this.datasourceData[datasourceDataKey].data; + if (datasourceKeyData.length > 0) { + prevSeries = datasourceKeyData[datasourceKeyData.length - 1]; + } else { + prevSeries = [0, 0]; + } + for (let time = startTime; time <= endTime && (this.timer || this.history); time += this.frequency) { + const value = dataKey.func(time, prevSeries[1]); + const series: [number, any] = [time, value]; + data.push(series); + prevSeries = series; + } + if (data.length > 0) { + dataKey.lastUpdateTime = data[data.length - 1][0]; + } + return data; + } + + private generateLatest(dataKey: SubscriptionDataKey, detectChanges: boolean) { + let prevSeries: [number, any]; + const datasourceKeyData = this.datasourceData[dataKey.key].data; + if (datasourceKeyData.length > 0) { + prevSeries = datasourceKeyData[datasourceKeyData.length - 1]; + } else { + prevSeries = [0, 0]; + } + const time = Date.now(); + const value = dataKey.func(time, prevSeries[1]); + const series: [number, any] = [time, value]; + this.datasourceData[dataKey.key].data = [series]; + this.listeners.forEach( + (listener) => { + listener.dataUpdated(this.datasourceData[dataKey.key], + listener.datasourceIndex, + dataKey.index, detectChanges); + } + ); + } + + private onTick(detectChanges: boolean) { + const now = this.utils.currentPerfTime(); + this.tickElapsed += now - this.tickScheduledTime; + this.tickScheduledTime = now; + + if (this.timer) { + clearTimeout(this.timer); + } + let key: string; + if (this.datasourceSubscriptionOptions.type === widgetType.timeseries) { + let startTime: number; + let endTime: number; + let delta: number; + const generatedData: SubscriptionDataHolder = { + data: {} + }; + if (!this.history) { + delta = Math.floor(this.tickElapsed / this.frequency); + } + const deltaElapsed = this.history ? this.frequency : delta * this.frequency; + this.tickElapsed = this.tickElapsed - deltaElapsed; + for (key of Object.keys(this.dataKeys)) { + const dataKeyList = this.dataKeys[key] as Array; + for (let index = 0; index < dataKeyList.length && (this.timer || this.history); index ++) { + const dataKey = dataKeyList[index]; + if (!startTime) { + if (this.realtime) { + if (dataKey.lastUpdateTime) { + startTime = dataKey.lastUpdateTime + this.frequency; + endTime = dataKey.lastUpdateTime + deltaElapsed; + } else { + startTime = this.datasourceSubscriptionOptions.subscriptionTimewindow.startTs; + endTime = startTime + this.datasourceSubscriptionOptions.subscriptionTimewindow.realtimeWindowMs + this.frequency; + if (this.datasourceSubscriptionOptions.subscriptionTimewindow.aggregation.type === AggregationType.NONE) { + const time = endTime - this.frequency * this.datasourceSubscriptionOptions.subscriptionTimewindow.aggregation.limit; + startTime = Math.max(time, startTime); + } + } + } else { + startTime = this.datasourceSubscriptionOptions.subscriptionTimewindow.fixedWindow.startTimeMs; + endTime = this.datasourceSubscriptionOptions.subscriptionTimewindow.fixedWindow.endTimeMs; + } + } + const data = this.generateSeries(dataKey, index, startTime, endTime); + generatedData.data[`${dataKey.name}_${dataKey.index}`] = data; + } + } + if (this.dataAggregator) { + this.dataAggregator.onData(generatedData, true, this.history, detectChanges); + } + } else if (this.datasourceSubscriptionOptions.type === widgetType.latest) { + for (key of Object.keys(this.dataKeys)) { + this.generateLatest(this.dataKeys[key] as SubscriptionDataKey, detectChanges); + } + } + + if (!this.history) { + this.timer = setTimeout(this.onTick.bind(this, true), this.frequency); + } + } + + private onData(sourceData: SubscriptionData, type: DataKeyType, detectChanges: boolean) { + for (const keyName of Object.keys(sourceData)) { + const keyData = sourceData[keyName]; + const key = `${keyName}_${type}`; + const dataKeyList = this.dataKeys[key] as Array; + for (let keyIndex = 0; dataKeyList && keyIndex < dataKeyList.length; keyIndex++) { + const datasourceKey = `${key}_${keyIndex}`; + if (this.datasourceData[datasourceKey].data) { + const dataKey = dataKeyList[keyIndex]; + const data: DataSet = []; + let prevSeries: [number, any]; + let prevOrigSeries: [number, any]; + let datasourceKeyData: DataSet; + let datasourceOrigKeyData: DataSet; + let update = false; + if (this.realtime) { + datasourceKeyData = []; + datasourceOrigKeyData = []; + } else { + datasourceKeyData = this.datasourceData[datasourceKey].data; + datasourceOrigKeyData = this.datasourceOrigData[datasourceKey].data; + } + if (datasourceKeyData.length > 0) { + prevSeries = datasourceKeyData[datasourceKeyData.length - 1]; + prevOrigSeries = datasourceOrigKeyData[datasourceOrigKeyData.length - 1]; + } else { + prevSeries = [0, 0]; + prevOrigSeries = [0, 0]; + } + this.datasourceOrigData[datasourceKey].data = []; + if (this.datasourceSubscriptionOptions.type === widgetType.timeseries) { + keyData.forEach((keySeries) => { + let series = keySeries; + const time = series[0]; + this.datasourceOrigData[datasourceKey].data.push(series); + let value = this.convertValue(series[1]); + if (dataKey.postFunc) { + value = dataKey.postFunc(time, value, prevSeries[1], prevOrigSeries[0], prevOrigSeries[1]); + } + prevOrigSeries = series; + series = [time, value]; + data.push(series); + prevSeries = series; + }); + update = true; + } else if (this.datasourceSubscriptionOptions.type === widgetType.latest) { + if (keyData.length > 0) { + let series = keyData[0]; + const time = series[0]; + this.datasourceOrigData[datasourceKey].data.push(series); + let value = this.convertValue(series[1]); + if (dataKey.postFunc) { + value = dataKey.postFunc(time, value, prevSeries[1], prevOrigSeries[0], prevOrigSeries[1]); + } + series = [time, value]; + data.push(series); + } + update = true; + } + if (update) { + this.datasourceData[datasourceKey].data = data; + this.listeners.forEach((listener) => { + listener.dataUpdated(this.datasourceData[datasourceKey], + listener.datasourceIndex, + dataKey.index, detectChanges); + }); + } + } + } + } + } + + private isNumeric(val: any): boolean { + return (val - parseFloat( val ) + 1) >= 0; + } + + private convertValue(val: string): any { + if (val && this.isNumeric(val)) { + return Number(val); + } else { + return val; + } + } + +} diff --git a/ui-ngx/src/app/core/api/datasource.service.ts b/ui-ngx/src/app/core/api/datasource.service.ts index 279466d386..6214f5aeeb 100644 --- a/ui-ngx/src/app/core/api/datasource.service.ts +++ b/ui-ngx/src/app/core/api/datasource.service.ts @@ -18,10 +18,26 @@ import { Injectable } from '@angular/core'; import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service'; import { UtilsService } from '@core/services/utils.service'; import { EntityType } from '@app/shared/models/entity-type.models'; +import { DataSetHolder, Datasource, DatasourceType, widgetType } from '@shared/models/widget.models'; +import { SubscriptionTimewindow } from '@shared/models/time/time.models'; +import { + DatasourceSubscription, + DatasourceSubscriptionOptions, + SubscriptionDataKey +} from '@core/api/datasource-subcription'; +import { deepClone } from '@core/utils'; export interface DatasourceListener { + subscriptionType: widgetType; + subscriptionTimewindow: SubscriptionTimewindow; + datasource: Datasource; entityType: EntityType; entityId: string; + datasourceIndex: number; + dataUpdated: (data: DataSetHolder, datasourceIndex: number, dataKeyIndex: number, detectChanges: boolean) => void; + updateRealtimeSubscription: () => SubscriptionTimewindow; + setRealtimeSubscription: (subscriptionTimewindow: SubscriptionTimewindow) => void; + datasourceSubscriptionKey?: number; } @Injectable({ @@ -29,14 +45,65 @@ export interface DatasourceListener { }) export class DatasourceService { + private subscriptions: {[datasourceSubscriptionKey: string]: DatasourceSubscription} = {}; + constructor(private telemetryService: TelemetryWebsocketService, private utils: UtilsService) {} public subscribeToDatasource(listener: DatasourceListener) { - // TODO: + const datasource = listener.datasource; + if (datasource.type === DatasourceType.entity && (!listener.entityId || !listener.entityType)) { + return; + } + const subscriptionDataKeys: Array = []; + datasource.dataKeys.forEach((dataKey) => { + const subscriptionDataKey: SubscriptionDataKey = { + name: dataKey.name, + type: dataKey.type, + funcBody: dataKey.funcBody, + postFuncBody: dataKey.postFuncBody + }; + subscriptionDataKeys.push(subscriptionDataKey); + }); + + const datasourceSubscriptionOptions: DatasourceSubscriptionOptions = { + datasourceType: datasource.type, + dataKeys: subscriptionDataKeys, + type: listener.subscriptionType + }; + + if (listener.subscriptionType === widgetType.timeseries) { + datasourceSubscriptionOptions.subscriptionTimewindow = deepClone(listener.subscriptionTimewindow); + } + if (datasourceSubscriptionOptions.datasourceType === DatasourceType.entity) { + datasourceSubscriptionOptions.entityType = listener.entityType; + datasourceSubscriptionOptions.entityId = listener.entityId; + } + listener.datasourceSubscriptionKey = this.utils.objectHashCode(datasourceSubscriptionOptions); + let subscription: DatasourceSubscription; + if (this.subscriptions[listener.datasourceSubscriptionKey]) { + subscription = this.subscriptions[listener.datasourceSubscriptionKey]; + subscription.syncListener(listener); + } else { + subscription = new DatasourceSubscription(datasourceSubscriptionOptions, + this.telemetryService, this.utils); + this.subscriptions[listener.datasourceSubscriptionKey] = subscription; + subscription.start(); + } + subscription.addListener(listener); } public unsubscribeFromDatasource(listener: DatasourceListener) { - // TODO: + if (listener.datasourceSubscriptionKey) { + const subscription = this.subscriptions[listener.datasourceSubscriptionKey]; + if (subscription) { + subscription.removeListener(listener); + if (!subscription.hasListeners()) { + subscription.unsubscribe(); + delete this.subscriptions[listener.datasourceSubscriptionKey]; + } + } + listener.datasourceSubscriptionKey = null; + } } } diff --git a/ui-ngx/src/app/core/api/widget-api.models.ts b/ui-ngx/src/app/core/api/widget-api.models.ts index 2b52a73494..9afaa2053d 100644 --- a/ui-ngx/src/app/core/api/widget-api.models.ts +++ b/ui-ngx/src/app/core/api/widget-api.models.ts @@ -28,14 +28,15 @@ import { TimeService } from '../services/time.service'; import { DeviceService } from '../http/device.service'; import { AlarmService } from '../http/alarm.service'; import { UtilsService } from '@core/services/utils.service'; -import { Timewindow } from '@shared/models/time/time.models'; +import { Timewindow, WidgetTimewindow } from '@shared/models/time/time.models'; import { EntityType } from '@shared/models/entity-type.models'; import { AlarmSearchStatus } from '@shared/models/alarm.models'; import { HttpErrorResponse } from '@angular/common/http'; import { DatasourceService } from '@core/api/datasource.service'; +import { RafService } from '@core/services/raf.service'; export interface TimewindowFunctions { - onUpdateTimewindow: (startTimeMs: number, endTimeMs: number, interval: number) => void; + onUpdateTimewindow: (startTimeMs: number, endTimeMs: number, interval?: number) => void; onResetTimewindow: () => void; } @@ -128,6 +129,7 @@ export interface WidgetSubscriptionContext { alarmService: AlarmService; datasourceService: DatasourceService; utils: UtilsService; + raf: RafService; widgetUtils: IWidgetUtils; dashboardTimewindowApi: TimewindowFunctions; getServerTimeDiff: () => Observable; @@ -137,10 +139,10 @@ export interface WidgetSubscriptionContext { } export interface WidgetSubscriptionCallbacks { - onDataUpdated?: (subscription: IWidgetSubscription) => void; + onDataUpdated?: (subscription: IWidgetSubscription, detectChanges: boolean) => void; onDataUpdateError?: (subscription: IWidgetSubscription, e: any) => void; dataLoading?: (subscription: IWidgetSubscription) => void; - legendDataUpdated?: (subscription: IWidgetSubscription) => void; + legendDataUpdated?: (subscription: IWidgetSubscription, detectChanges: boolean) => void; timeWindowUpdated?: (subscription: IWidgetSubscription, timeWindowConfig: Timewindow) => void; rpcStateChanged?: (subscription: IWidgetSubscription) => void; onRpcSuccess?: (subscription: IWidgetSubscription) => void; @@ -184,7 +186,8 @@ export interface IWidgetSubscription { datasources?: Array; data?: Array; hiddenData?: Array<{data: DataSet}>; - timeWindow?: Timewindow; + timeWindowConfig?: Timewindow; + timeWindow?: WidgetTimewindow; alarmSource?: Datasource; alarmSearchStatus?: AlarmSearchStatus; @@ -202,7 +205,11 @@ export interface IWidgetSubscription { onAliasesChanged(aliasIds: Array): boolean; - onUpdateTimewindow(startTimeMs: number, endTimeMs: number, interval: number): void; + onDashboardTimewindowChanged(dashboardTimewindow: Timewindow): void; + + updateDataVisibility(index: number): void; + + onUpdateTimewindow(startTimeMs: number, endTimeMs: number, interval?: number): void; onResetTimewindow(): void; updateTimewindowConfig(newTimewindow: Timewindow): void; diff --git a/ui-ngx/src/app/core/api/widget-subscription.ts b/ui-ngx/src/app/core/api/widget-subscription.ts index b4cd76d0c0..9d1ebd3b46 100644 --- a/ui-ngx/src/app/core/api/widget-subscription.ts +++ b/ui-ngx/src/app/core/api/widget-subscription.ts @@ -23,8 +23,10 @@ import { } from '@core/api/widget-api.models'; import { DataSet, + DataSetHolder, Datasource, DatasourceData, + DatasourceType, LegendConfig, LegendData, LegendKey, @@ -32,7 +34,13 @@ import { widgetType } from '@app/shared/models/widget.models'; import { HttpErrorResponse } from '@angular/common/http'; -import { Timewindow } from '@app/shared/models/time/time.models'; +import { + createSubscriptionTimewindow, + SubscriptionTimewindow, + Timewindow, + toHistoryTimewindow, + WidgetTimewindow +} from '@app/shared/models/time/time.models'; import { Observable, of, ReplaySubject, Subject } from 'rxjs'; import { CancelAnimationFrame } from '@core/services/raf.service'; import { EntityType } from '@shared/models/entity-type.models'; @@ -40,6 +48,7 @@ import { AlarmInfo, AlarmSearchStatus } from '@shared/models/alarm.models'; import { deepClone, isDefined } from '@core/utils'; import { AlarmSourceListener } from '@core/http/alarm.service'; import { DatasourceListener } from '@core/api/datasource.service'; +import * as deepEqual from 'deep-equal'; export class WidgetSubscription implements IWidgetSubscription { @@ -48,16 +57,16 @@ export class WidgetSubscription implements IWidgetSubscription { type: widgetType; callbacks: WidgetSubscriptionCallbacks; - timeWindow: Timewindow; + timeWindow: WidgetTimewindow; originalTimewindow: Timewindow; timeWindowConfig: Timewindow; - subscriptionTimewindow: Timewindow; + subscriptionTimewindow: SubscriptionTimewindow; useDashboardTimewindow: boolean; data: Array; datasources: Array; datasourceListeners: Array; - hiddenData: Array<{ data: DataSet }>; + hiddenData: Array; legendData: LegendData; legendConfig: LegendConfig; caulculateLegendData: boolean; @@ -323,17 +332,95 @@ export class WidgetSubscription implements IWidgetSubscription { } } + private resetData() { + for (let i = 0; i < this.data.length; i++) { + this.data[i].data = []; + this.hiddenData[i].data = []; + if (this.displayLegend) { + this.legendData.data[i].min = null; + this.legendData.data[i].max = null; + this.legendData.data[i].avg = null; + this.legendData.data[i].total = null; + this.legendData.data[i].hidden = false; + } + } + this.onDataUpdated(); + } + getFirstEntityInfo(): EntityInfo { return undefined; } + onAliasesChanged(aliasIds: Array): boolean { + return false; + } + + private onDataUpdated(detectChanges?: boolean) { + if (this.cafs.dataUpdated) { + this.cafs.dataUpdated(); + this.cafs.dataUpdated = null; + } + this.cafs.dataUpdated = this.ctx.raf.raf(() => { + try { + this.callbacks.onDataUpdated(this, detectChanges); + } catch (e) { + this.callbacks.onDataUpdateError(this, e); + } + }); + } + + onDashboardTimewindowChanged(newDashboardTimewindow: Timewindow): void { + if (this.type === widgetType.timeseries || this.type === widgetType.alarm) { + if (this.useDashboardTimewindow) { + if (!deepEqual(this.timeWindowConfig, newDashboardTimewindow) && newDashboardTimewindow) { + this.timeWindowConfig = deepClone(newDashboardTimewindow); + this.update(); + } + } + } + } + + updateDataVisibility(index: number): void { + if (this.displayLegend) { + const hidden = this.legendData.keys[index].dataKey.hidden; + if (hidden) { + this.hiddenData[index].data = this.data[index].data; + this.data[index].data = []; + } else { + this.data[index].data = this.hiddenData[index].data; + this.hiddenData[index].data = []; + } + this.onDataUpdated(); + } + } + updateTimewindowConfig(newTimewindow: Timewindow): void { } onResetTimewindow(): void { + if (this.useDashboardTimewindow) { + this.ctx.dashboardTimewindowApi.onResetTimewindow(); + } else { + if (this.originalTimewindow) { + this.timeWindowConfig = deepClone(this.originalTimewindow); + this.originalTimewindow = null; + this.callbacks.timeWindowUpdated(this, this.timeWindowConfig); + this.update(); + } + } } - onUpdateTimewindow(startTimeMs: number, endTimeMs: number, interval: number): void { + onUpdateTimewindow(startTimeMs: number, endTimeMs: number, interval?: number): void { + if (this.useDashboardTimewindow) { + this.ctx.dashboardTimewindowApi.onUpdateTimewindow(startTimeMs, endTimeMs); + } else { + if (!this.originalTimewindow) { + this.originalTimewindow = deepClone(this.timeWindowConfig); + } + this.timeWindowConfig = toHistoryTimewindow(this.timeWindowConfig, startTimeMs, endTimeMs, interval, this.ctx.timeService); + this.callbacks.timeWindowUpdated(this, this.timeWindowConfig); + this.update(); + } } sendOneWayCommand(method: string, params?: any, timeout?: number): Observable { @@ -347,12 +434,109 @@ export class WidgetSubscription implements IWidgetSubscription { clearRpcError(): void { } + update() { + this.unsubscribe(); + this.subscribe(); + } + subscribe(): void { + if (this.cafs.subscribe) { + this.cafs.subscribe(); + this.cafs.subscribe = null; + } + this.cafs.subscribe = this.ctx.raf.raf(() => { + this.doSubscribe(); + }); + } + + private doSubscribe() { + if (this.type === widgetType.rpc) { + return; + } + if (this.type === widgetType.alarm) { + this.alarmsSubscribe(); + } else { + this.notifyDataLoading(); + if (this.type === widgetType.timeseries && this.timeWindowConfig) { + this.updateRealtimeSubscription(); + if (this.subscriptionTimewindow.fixedWindow) { + this.onDataUpdated(); + } + } + let index = 0; + this.datasources.forEach((datasource) => { + const listener: DatasourceListener = { + subscriptionType: this.type, + subscriptionTimewindow: this.subscriptionTimewindow, + datasource, + entityType: datasource.entityType, + entityId: datasource.entityId, + dataUpdated: this.dataUpdated.bind(this), + updateRealtimeSubscription: () => { + this.subscriptionTimewindow = this.updateRealtimeSubscription(); + return this.subscriptionTimewindow; + }, + setRealtimeSubscription: (subscriptionTimewindow) => { + this.updateRealtimeSubscription(deepClone(subscriptionTimewindow)); + }, + datasourceIndex: index + }; + for (let a = 0; a < datasource.dataKeys.length; a++) { + this.data[index + a].data = []; + } + index += datasource.dataKeys.length; + this.datasourceListeners.push(listener); + + if (datasource.dataKeys.length) { + this.ctx.datasourceService.subscribeToDatasource(listener); + } + let forceUpdate = false; + if (datasource.unresolvedStateEntity || + !datasource.dataKeys.length || + (datasource.type === DatasourceType.entity && !datasource.entityId) + ) { + forceUpdate = true; + } + if (forceUpdate) { + this.notifyDataLoaded(); + this.onDataUpdated(); + } + }); + } + } + + private alarmsSubscribe() { + // TODO: + } + + + unsubscribe() { + if (this.type !== widgetType.rpc) { + if (this.type === widgetType.alarm) { + this.alarmsUnsubscribe(); + } else { + this.datasourceListeners.forEach((listener) => { + this.ctx.datasourceService.unsubscribeFromDatasource(listener); + }); + this.datasourceListeners.length = 0; + this.resetData(); + } + } + } + + private alarmsUnsubscribe() { // TODO: - this.notifyDataLoaded(); } destroy(): void { + this.unsubscribe(); + for (const cafId of Object.keys(this.cafs)) { + if (this.cafs[cafId]) { + this.cafs[cafId](); + this.cafs[cafId] = null; + } + } + // TODO: } private notifyDataLoading() { @@ -365,10 +549,89 @@ export class WidgetSubscription implements IWidgetSubscription { this.callbacks.dataLoading(this); } - onAliasesChanged(aliasIds: Array): boolean { - return false; + private updateTimewindow() { + this.timeWindow.interval = this.subscriptionTimewindow.aggregation.interval || 1000; + if (this.subscriptionTimewindow.realtimeWindowMs) { + this.timeWindow.maxTime = Date.now() + this.timeWindow.stDiff; + this.timeWindow.minTime = this.timeWindow.maxTime - this.subscriptionTimewindow.realtimeWindowMs; + } else if (this.subscriptionTimewindow.fixedWindow) { + this.timeWindow.maxTime = this.subscriptionTimewindow.fixedWindow.endTimeMs; + this.timeWindow.minTime = this.subscriptionTimewindow.fixedWindow.startTimeMs; + } + } + + private updateRealtimeSubscription(subscriptionTimewindow?: SubscriptionTimewindow) { + if (subscriptionTimewindow) { + this.subscriptionTimewindow = subscriptionTimewindow; + } else { + this.subscriptionTimewindow = + createSubscriptionTimewindow(this.timeWindowConfig, this.timeWindow.stDiff, + this.stateData, this.ctx.timeService); + } + this.updateTimewindow(); + return this.subscriptionTimewindow; + } + + private dataUpdated(sourceData: DataSetHolder, datasourceIndex: number, dataKeyIndex: number, detectChanges: boolean) { + for (let x = 0; x < this.datasourceListeners.length; x++) { + this.datasources[x].dataReceived = this.datasources[x].dataReceived === true; + if (this.datasourceListeners[x].datasourceIndex === datasourceIndex && sourceData.data.length > 0) { + this.datasources[x].dataReceived = true; + } + } + this.notifyDataLoaded(); + let update = true; + let currentData: DataSetHolder; + if (this.displayLegend && this.legendData.keys[datasourceIndex + dataKeyIndex].dataKey.hidden) { + currentData = this.hiddenData[datasourceIndex + dataKeyIndex]; + } else { + currentData = this.data[datasourceIndex + dataKeyIndex]; + } + if (this.type === widgetType.latest) { + const prevData = currentData.data; + if (!sourceData.data.length) { + update = false; + } else if (prevData && prevData[0] && prevData[0].length > 1 && sourceData.data.length > 0) { + const prevTs = prevData[0][0]; + const prevValue = prevData[0][1]; + if (prevTs === sourceData.data[0][0] && prevValue === sourceData.data[0][1]) { + update = false; + } + } + } + if (update) { + if (this.subscriptionTimewindow && this.subscriptionTimewindow.realtimeWindowMs) { + this.updateTimewindow(); + } + currentData.data = sourceData.data; + if (this.caulculateLegendData) { + this.updateLegend(datasourceIndex + dataKeyIndex, sourceData.data, detectChanges); + } + this.onDataUpdated(detectChanges); + } + } + + private updateLegend(dataIndex: number, data: DataSet, detectChanges: boolean) { + const dataKey = this.legendData.keys[dataIndex].dataKey; + const decimals = isDefined(dataKey.decimals) ? dataKey.decimals : this.decimals; + const units = dataKey.units && dataKey.units.length ? dataKey.units : this.units; + const legendKeyData = this.legendData.data[dataIndex]; + if (this.legendConfig.showMin) { + legendKeyData.min = this.ctx.widgetUtils.formatValue(calculateMin(data), decimals, units); + } + if (this.legendConfig.showMax) { + legendKeyData.max = this.ctx.widgetUtils.formatValue(calculateMax(data), decimals, units); + } + if (this.legendConfig.showAvg) { + legendKeyData.avg = this.ctx.widgetUtils.formatValue(calculateAvg(data), decimals, units); + } + if (this.legendConfig.showTotal) { + legendKeyData.total = this.ctx.widgetUtils.formatValue(calculateTotal(data), decimals, units); + } + this.callbacks.legendDataUpdated(this, detectChanges !== false); } + private loadStDiff(): Observable { const loadSubject = new ReplaySubject(1); if (this.ctx.getServerTimeDiff && this.timeWindow) { @@ -394,3 +657,47 @@ export class WidgetSubscription implements IWidgetSubscription { return loadSubject.asObservable(); } } + +function calculateMin(data: DataSet): number { + if (data.length > 0) { + let result = Number(data[0][1]); + for (let i = 1; i < data.length; i++) { + result = Math.min(result, Number(data[i][1])); + } + return result; + } else { + return null; + } +} + +function calculateMax(data: DataSet): number { + if (data.length > 0) { + let result = Number(data[0][1]); + for (let i = 1; i < data.length; i++) { + result = Math.max(result, Number(data[i][1])); + } + return result; + } else { + return null; + } +} + +function calculateAvg(data: DataSet): number { + if (data.length > 0) { + return calculateTotal(data) / data.length; + } else { + return null; + } +} + +function calculateTotal(data: DataSet): number { + if (data.length > 0) { + let result = 0; + data.forEach((dataRow) => { + result += Number(dataRow[1]); + }); + return result; + } else { + return null; + } +} diff --git a/ui-ngx/src/app/core/services/utils.service.ts b/ui-ngx/src/app/core/services/utils.service.ts index 272b5ce3a7..6622051e8d 100644 --- a/ui-ngx/src/app/core/services/utils.service.ts +++ b/ui-ngx/src/app/core/services/utils.service.ts @@ -54,6 +54,32 @@ export class UtilsService { } } + public hashCode(str: string): number { + let hash = 0; + let i: number; + let char: number; + if (str.length === 0) { + return hash; + } + for (i = 0; i < str.length; i++) { + char = str.charCodeAt(i); + // tslint:disable-next-line:no-bitwise + hash = ((hash << 5) - hash) + char; + // tslint:disable-next-line:no-bitwise + hash = hash & hash; // Convert to 32bit integer + } + return hash; + } + + public objectHashCode(obj: any): number { + let hash = 0; + if (obj) { + const str = JSON.stringify(obj); + hash = this.hashCode(str); + } + return hash; + } + public processWidgetException(exception: any): ExceptionData { const data = this.parseException(exception, -5); if (this.widgetEditMode) { @@ -203,4 +229,9 @@ export class UtilsService { }); } + public currentPerfTime(): number { + return this.window.performance && this.window.performance.now ? + this.window.performance.now() : Date.now(); + } + } diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index b5022eb7cb..8cf2cdc59b 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -79,10 +79,22 @@ export function isDefined(value: any): boolean { return typeof value !== 'undefined'; } +export function isDefinedAndNotNull(value: any): boolean { + return typeof value !== 'undefined' && value !== null; +} + export function isFunction(value: any): boolean { return typeof value === 'function'; } +export function isObject(value: any): boolean { + return value !== null && typeof value === 'object'; +} + +export function isNumber(value: any): boolean { + return typeof value === 'number'; +} + export function objToBase64(obj: any): string { const json = JSON.stringify(obj); const encoded = utf8Encode(json); @@ -295,10 +307,24 @@ function utf8ToBytes(input: string, units?: number): number[] { return bytes; } -export function deepClone(obj: T): T { - if (obj) { - return JSON.parse(JSON.stringify(obj)); - } else { - return obj; +export function deepClone(target: T): T { + if (target === null) { + return target; + } + if (target instanceof Date) { + return new Date(target.getTime()) as any; + } + if (target instanceof Array) { + const cp = [] as any[]; + (target as any[]).forEach((v) => { cp.push(v); }); + return cp.map((n: any) => deepClone(n)) as any; + } + if (typeof target === 'object' && target !== {}) { + const cp = { ...(target as { [key: string]: any }) } as { [key: string]: any }; + Object.keys(cp).forEach(k => { + cp[k] = deepClone(cp[k]); + }); + return cp as T; } + return target; } diff --git a/ui-ngx/src/app/core/ws/telemetry-websocket.service.ts b/ui-ngx/src/app/core/ws/telemetry-websocket.service.ts index 3eee1d4f02..89b50cf0a0 100644 --- a/ui-ngx/src/app/core/ws/telemetry-websocket.service.ts +++ b/ui-ngx/src/app/core/ws/telemetry-websocket.service.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Inject, Injectable } from '@angular/core'; +import { Inject, Injectable, NgZone } from '@angular/core'; import { AttributesSubscriptionCmd, GetHistoryCmd, @@ -66,6 +66,7 @@ export class TelemetryWebsocketService implements TelemetryService { constructor(private store: Store, private authService: AuthService, + private ngZone: NgZone, @Inject(WINDOW) private window: Window) { this.store.pipe(select(selectIsAuthenticated)).subscribe( (authenticated: boolean) => { @@ -222,7 +223,9 @@ export class TelemetryWebsocketService implements TelemetryService { ); this.dataStream.subscribe((message) => { - this.onMessage(message as SubscriptionUpdateMsg); + this.ngZone.runOutsideAngular(() => { + this.onMessage(message as SubscriptionUpdateMsg); + }); }, (error) => { this.onError(error); diff --git a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts index f20bf2beba..bf11051bec 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts @@ -39,7 +39,7 @@ import { IDashboardComponent, WidgetsData } from '../../models/dashboard-component.models'; -import { merge, Observable } from 'rxjs'; +import { merge, Observable, ReplaySubject, Subject } from 'rxjs'; import { map, share, tap } from 'rxjs/operators'; import { WidgetLayout } from '@shared/models/dashboard.models'; import { DialogService } from '@core/services/dialog.service'; @@ -117,6 +117,10 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo @Input() dashboardTimewindow: Timewindow; + dashboardTimewindowChangedSubject: Subject = new ReplaySubject(); + + dashboardTimewindowChanged = this.dashboardTimewindowChangedSubject.asObservable(); + originalDashboardTimewindow: Timewindow; gridsterOpts: GridsterConfig; @@ -262,18 +266,20 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo ngAfterViewInit(): void { } - onUpdateTimewindow(startTimeMs: number, endTimeMs: number, interval: number): void { + onUpdateTimewindow(startTimeMs: number, endTimeMs: number, interval?: number): void { if (!this.originalDashboardTimewindow) { this.originalDashboardTimewindow = deepClone(this.dashboardTimewindow); } this.dashboardTimewindow = toHistoryTimewindow(this.dashboardTimewindow, startTimeMs, endTimeMs, interval, this.timeService); + this.dashboardTimewindowChangedSubject.next(this.dashboardTimewindow); } onResetTimewindow(): void { if (this.originalDashboardTimewindow) { this.dashboardTimewindow = deepClone(this.originalDashboardTimewindow); this.originalDashboardTimewindow = null; + this.dashboardTimewindowChangedSubject.next(this.dashboardTimewindow); } } 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 42f17f6c41..d3eb695f44 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 @@ -158,7 +158,7 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn if (this.entitiesTableConfig.useTimePageLink) { this.timewindow = historyInterval(24 * 60 * 60 * 1000); - const currentTime = new Date().getTime(); + const currentTime = Date.now(); this.pageLink = new TimePageLink(10, 0, null, sortOrder, currentTime - this.timewindow.history.timewindowMs, currentTime); } else { @@ -216,7 +216,7 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn if (this.entitiesTableConfig.useTimePageLink) { const timePageLink = this.pageLink as TimePageLink; if (this.timewindow.history.timewindowMs) { - const currentTime = new Date().getTime(); + const currentTime = Date.now(); timePageLink.startTime = currentTime - this.timewindow.history.timewindowMs; timePageLink.endTime = currentTime; } else { diff --git a/ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts index 47ddfc3912..48ece2ac81 100644 --- a/ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts @@ -21,6 +21,7 @@ import { AppState } from '@core/core.state'; import { WidgetContext, IDynamicWidgetComponent } from '@home/models/widget-component.models'; import { ExceptionData } from '@shared/models/error.models'; import { HttpErrorResponse } from '@angular/common/http'; +import { RafService } from '@core/services/raf.service'; export class DynamicWidgetComponent extends PageComponent implements IDynamicWidgetComponent, OnInit, OnDestroy { @@ -37,7 +38,8 @@ export class DynamicWidgetComponent extends PageComponent implements IDynamicWid [key: string]: any; - constructor(protected store: Store) { + constructor(public raf: RafService, + protected store: Store) { super(store); } diff --git a/ui-ngx/src/app/modules/home/components/widget/legend.component.scss b/ui-ngx/src/app/modules/home/components/widget/legend.component.scss index 8df6fc5077..a30061421b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/legend.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/legend.component.scss @@ -29,10 +29,14 @@ padding: 0 10px 1px 0; color: rgb(255, 110, 64); white-space: nowrap; + font-size: 12px; } } .tb-legend-keys { + td { + font-size: 12px; + } td.tb-legend-label, td.tb-legend-value { padding: 2px 10px; diff --git a/ui-ngx/src/app/modules/home/components/widget/legend.component.ts b/ui-ngx/src/app/modules/home/components/widget/legend.component.ts index aab873e419..455fe58d73 100644 --- a/ui-ngx/src/app/modules/home/components/widget/legend.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/legend.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, Input, OnInit } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { LegendConfig, LegendData, LegendDirection, LegendPosition } from '@shared/models/widget.models'; @Component({ @@ -30,6 +30,9 @@ export class LegendComponent implements OnInit { @Input() legendData: LegendData; + @Output() + legendKeyHiddenChange = new EventEmitter(); + displayHeader: boolean; isHorizontal: boolean; @@ -50,6 +53,7 @@ export class LegendComponent implements OnInit { toggleHideData(index: number) { this.legendData.keys[index].dataKey.hidden = !this.legendData.keys[index].dataKey.hidden; + this.legendKeyHiddenChange.emit(index); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts new file mode 100644 index 0000000000..767b7109b5 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts @@ -0,0 +1,592 @@ +/// +/// Copyright © 2016-2019 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 { JsonSettingsSchema, DataKey, DatasourceData } from '@shared/models/widget.models'; + +export declare type ChartType = 'line' | 'pie' | 'bar' | 'state' | 'graph'; + +export declare type TbFlotSettings = TbFlotBaseSettings & TbFlotGraphSettings & TbFlotBarSettings & TbFlotPieSettings; + +export declare type TooltipValueFormatFunction = (value: any) => string; + +export declare type TbFlotTicksFormatterFunction = (t: number, a?: TbFlotPlotAxis) => string; + +export interface TbFlotSeries extends DatasourceData, JQueryPlotSeriesOptions { + dataKey: TbFlotDataKey; + yaxisIndex?: number; + yaxis?: number; +} + +export interface TbFlotDataKey extends DataKey { + settings?: TbFlotKeySettings; + tooltipValueFormatFunction?: TooltipValueFormatFunction; +} + +export interface TbFlotPlotAxis extends JQueryPlotAxis, TbFlotAxisOptions { + options: TbFlotAxisOptions; +} + +export interface TbFlotAxisOptions extends JQueryPlotAxisOptions { + tickUnits?: string; + hidden?: boolean; + keysInfo?: Array<{hidden: boolean}>; + ticksFormatterFunction?: TbFlotTicksFormatterFunction; +} + +export interface TbFlotPlotDataSeries extends JQueryPlotDataSeries { + dataKey?: TbFlotDataKey; + percent?: number; +} + +export interface TbFlotPlotItem extends jquery.flot.item { + series: TbFlotPlotDataSeries; +} + +export interface TbFlotHoverInfo { + seriesHover: Array; + time?: any; +} + +export interface TbFlotSeriesHoverInfo { + hoverIndex: number; + units: string; + decimals: number; + label: string; + color: string; + index: number; + tooltipValueFormatFunction: TooltipValueFormatFunction; + value: any; + time: any; + distance: number; +} + +export interface TbFlotGridSettings { + color: string; + backgroundColor: string; + tickColor: string; + outlineWidth: number; + verticalLines: boolean; + horizontalLines: boolean; + minBorderMargin: number; + margin: number; +} + +export interface TbFlotXAxisSettings { + showLabels: boolean; + title: string; + color: boolean; +} + +export interface TbFlotYAxisSettings { + min: number; + max: number; + showLabels: boolean; + title: string; + color: string; + ticksFormatter: string; + tickDecimals: number; + tickSize: number; +} + +export interface TbFlotBaseSettings { + stack: boolean; + shadowSize: number; + fontColor: string; + fontSize: number; + tooltipIndividual: boolean; + tooltipCumulative: boolean; + tooltipValueFormatter: string; + grid: TbFlotGridSettings; + xaxis: TbFlotXAxisSettings; + yaxis: TbFlotYAxisSettings; +} + +export interface TbFlotGraphSettings extends TbFlotBaseSettings { + smoothLines: boolean; +} + +export interface TbFlotBarSettings extends TbFlotBaseSettings { + defaultBarWidth: number; +} + +export interface TbFlotPieSettings { + radius: number; + innerRadius: number; + tilt: number; + animatedPie: boolean; + stroke: { + color: string; + width: number; + }; + showLabels: boolean; + fontColor: string; + fontSize: number; +} + +export declare type TbFlotYAxisPosition = 'left' | 'right'; + +export interface TbFlotKeySettings { + showLines: boolean; + fillLines: boolean; + showPoints: boolean; + lineWidth: number; + tooltipValueFormatter: string; + showSeparateAxis: boolean; + axisMin: number; + axisMax: number; + axisTitle: string; + axisTickDecimals: number; + axisTickSize: number; + axisPosition: TbFlotYAxisPosition; + axisTicksFormatter: string; +} + +export function flotSettingsSchema(chartType: ChartType): JsonSettingsSchema { + + const schema: JsonSettingsSchema = { + schema: { + type: 'object', + title: 'Settings', + properties: { + } + } + }; + + const properties: any = schema.schema.properties; + properties.stack = { + title: 'Stacking', + type: 'boolean', + default: false + }; + if (chartType === 'graph') { + properties.smoothLines = { + title: 'Display smooth (curved) lines', + type: 'boolean', + default: false + }; + } + if (chartType === 'bar') { + properties.defaultBarWidth = { + title: 'Default bar width for non-aggregated data (milliseconds)', + type: 'number', + default: 600 + }; + } + properties.shadowSize = { + title: 'Shadow size', + type: 'number', + default: 4 + }; + properties.fontColor = { + title: 'Font color', + type: 'string', + default: '#545454' + }; + properties.fontSize = { + title: 'Font size', + type: 'number', + default: 10 + }; + properties.tooltipIndividual = { + title: 'Hover individual points', + type: 'boolean', + default: false + }; + properties.tooltipCumulative = { + title: 'Show cumulative values in stacking mode', + type: 'boolean', + default: false + }; + properties.tooltipValueFormatter = { + title: 'Tooltip value format function, f(value)', + type: 'string', + default: '' + }; + + properties.grid = { + title: 'Grid settings', + type: 'object', + properties: { + color: { + title: 'Primary color', + type: 'string', + default: '#545454' + }, + backgroundColor: { + title: 'Background color', + type: 'string', + default: null + }, + tickColor: { + title: 'Ticks color', + type: 'string', + default: '#DDDDDD' + }, + outlineWidth: { + title: 'Grid outline/border width (px)', + type: 'number', + default: 1 + }, + verticalLines: { + title: 'Show vertical lines', + type: 'boolean', + default: true + }, + horizontalLines: { + title: 'Show horizontal lines', + type: 'boolean', + default: true + } + } + }; + + properties.xaxis = { + title: 'X axis settings', + type: 'object', + properties: { + showLabels: { + title: 'Show labels', + type: 'boolean', + default: true + }, + title: { + title: 'Axis title', + type: 'string', + default: null + }, + color: { + title: 'Ticks color', + type: 'string', + default: null + } + } + }; + + properties.yaxis = { + title: 'Y axis settings', + type: 'object', + properties: { + min: { + title: 'Minimum value on the scale', + type: 'number', + default: null + }, + max: { + title: 'Maximum value on the scale', + type: 'number', + default: null + }, + showLabels: { + title: 'Show labels', + type: 'boolean', + default: true + }, + title: { + title: 'Axis title', + type: 'string', + default: null + }, + color: { + title: 'Ticks color', + type: 'string', + default: null + }, + ticksFormatter: { + title: 'Ticks formatter function, f(value)', + type: 'string', + default: '' + }, + tickDecimals: { + title: 'The number of decimals to display', + type: 'number', + default: 0 + }, + tickSize: { + title: 'Step size between ticks', + type: 'number', + default: null + } + } + }; + + schema.schema.required = []; + schema.form = ['stack']; + if (chartType === 'graph') { + schema.form.push('smoothLines'); + } + if (chartType === 'bar') { + schema.form.push('defaultBarWidth'); + } + schema.form.push('shadowSize'); + schema.form.push({ + key: 'fontColor', + type: 'color' + }); + schema.form.push('fontSize'); + schema.form.push('tooltipIndividual'); + schema.form.push('tooltipCumulative'); + schema.form.push({ + key: 'tooltipValueFormatter', + type: 'javascript' + }); + schema.form.push({ + key: 'grid', + items: [ + { + key: 'grid.color', + type: 'color' + }, + { + key: 'grid.backgroundColor', + type: 'color' + }, + { + key: 'grid.tickColor', + type: 'color' + }, + 'grid.outlineWidth', + 'grid.verticalLines', + 'grid.horizontalLines' + ] + }); + schema.form.push({ + key: 'xaxis', + items: [ + 'xaxis.showLabels', + 'xaxis.title', + { + key: 'xaxis.color', + type: 'color' + } + ] + }); + schema.form.push({ + key: 'yaxis', + items: [ + 'yaxis.min', + 'yaxis.max', + 'yaxis.tickDecimals', + 'yaxis.tickSize', + 'yaxis.showLabels', + 'yaxis.title', + { + key: 'yaxis.color', + type: 'color' + }, + { + key: 'yaxis.ticksFormatter', + type: 'javascript' + } + ] + }); + return schema; +} + +export function flotPieSettingsSchema(): JsonSettingsSchema { + return { + schema: { + type: 'object', + title: 'Settings', + properties: { + radius: { + title: 'Radius', + type: 'number', + default: 1 + }, + innerRadius: { + title: 'Inner radius', + type: 'number', + default: 0 + }, + tilt: { + title: 'Tilt', + type: 'number', + default: 1 + }, + animatedPie: { + title: 'Enable pie animation (experimental)', + type: 'boolean', + default: false + }, + stroke: { + title: 'Stroke', + type: 'object', + properties: { + color: { + title: 'Color', + type: 'string', + default: '' + }, + width: { + title: 'Width (pixels)', + type: 'number', + default: 0 + } + } + }, + showLabels: { + title: 'Show labels', + type: 'boolean', + default: false + }, + fontColor: { + title: 'Font color', + type: 'string', + default: '#545454' + }, + fontSize: { + title: 'Font size', + type: 'number', + default: 10 + } + }, + required: [] + }, + form: [ + 'radius', + 'innerRadius', + 'animatedPie', + 'tilt', + { + key: 'stroke', + items: [ + { + key: 'stroke.color', + type: 'color' + }, + 'stroke.width' + ] + }, + 'showLabels', + { + key: 'fontColor', + type: 'color' + }, + 'fontSize' + ] + }; +} + +export function flotDatakeySettingsSchema(defaultShowLines: boolean): JsonSettingsSchema { + return { + schema: { + type: 'object', + title: 'DataKeySettings', + properties: { + showLines: { + title: 'Show lines', + type: 'boolean', + default: defaultShowLines + }, + fillLines: { + title: 'Fill lines', + type: 'boolean', + default: false + }, + showPoints: { + title: 'Show points', + type: 'boolean', + default: false + }, + lineWidth: { + title: 'Line width', + type: 'number', + default: null + }, + tooltipValueFormatter: { + title: 'Tooltip value format function, f(value)', + type: 'string', + default: '' + }, + showSeparateAxis: { + title: 'Show separate axis', + type: 'boolean', + default: false + }, + axisMin: { + title: 'Minimum value on the axis scale', + type: 'number', + default: null + }, + axisMax: { + title: 'Maximum value on the axis scale', + type: 'number', + default: null + }, + axisTitle: { + title: 'Axis title', + type: 'string', + default: '' + }, + axisTickDecimals: { + title: 'Axis tick number of digits after floating point', + type: 'number', + default: null + }, + axisTickSize: { + title: 'Axis step size between ticks', + type: 'number', + default: null + }, + axisPosition: { + title: 'Axis position', + type: 'string', + default: 'left' + }, + axisTicksFormatter: { + title: 'Ticks formatter function, f(value)', + type: 'string', + default: '' + } + }, + required: ['showLines', 'fillLines', 'showPoints'] + }, + form: [ + 'showLines', + 'fillLines', + 'showPoints', + { + key: 'tooltipValueFormatter', + type: 'javascript' + }, + 'showSeparateAxis', + 'axisMin', + 'axisMax', + 'axisTitle', + 'axisTickDecimals', + 'axisTickSize', + { + key: 'axisPosition', + type: 'rc-select', + multiple: false, + items: [ + { + value: 'left', + label: 'Left' + }, + { + value: 'right', + label: 'Right' + } + ] + }, + { + key: 'axisTicksFormatter', + type: 'javascript' + } + ] + }; +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts new file mode 100644 index 0000000000..8f8096f04e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts @@ -0,0 +1,1074 @@ +/// +/// Copyright © 2016-2019 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 { WidgetContext } from '@home/models/widget-component.models'; +import { deepClone, isDefined, isNumber, isUndefined } from '@app/core/utils'; +import { IWidgetSubscription } from '@core/api/widget-api.models'; +import { DatasourceData, JsonSettingsSchema } from '@app/shared/models/widget.models'; +import { + ChartType, + flotDatakeySettingsSchema, + flotPieSettingsSchema, + flotSettingsSchema, + TbFlotAxisOptions, + TbFlotHoverInfo, + TbFlotKeySettings, + TbFlotPlotAxis, + TbFlotPlotDataSeries, + TbFlotPlotItem, + TbFlotSeries, + TbFlotSeriesHoverInfo, + TbFlotSettings, + TbFlotTicksFormatterFunction, + TooltipValueFormatFunction +} from './flot-widget.models'; +import * as moment from 'moment'; +import * as tinycolor from 'tinycolor2'; +import { AggregationType } from '@shared/models/time/time.models'; +import { CancelAnimationFrame } from '@core/services/raf.service'; +import Timeout = NodeJS.Timeout; + +export class TbFlot { + + private settings: TbFlotSettings; + + private tooltip: JQuery; + + private yAxisTickFormatter: TbFlotTicksFormatterFunction; + private ticksFormatterFunction: TbFlotTicksFormatterFunction; + private yaxis: TbFlotAxisOptions; + private yaxes: Array; + + private options: JQueryPlotOptions; + private subscription: IWidgetSubscription; + private $element: JQuery; + + private trackUnits: string; + private trackDecimals: number; + private tooltipIndividual: boolean; + private tooltipCumulative: boolean; + + private defaultBarWidth: number; + + private plotInited = false; + private plot: JQueryPlot; + + private createPlotTimeoutHandle: Timeout; + private updateTimeoutHandle: Timeout; + private resizeTimeoutHandle: Timeout; + + private mouseEventsEnabled: boolean; + private isMouseInteraction = false; + private flotHoverHandler = this.onFlotHover.bind(this); + private flotSelectHandler = this.onFlotSelect.bind(this); + private dblclickHandler = this.onFlotDblClick.bind(this); + private mousedownHandler = this.onFlotMouseDown.bind(this); + private mouseupHandler = this.onFlotMouseUp.bind(this); + private mouseleaveHandler = this.onFlotMouseLeave.bind(this); + + private animatedPie: boolean; + private pieDataAnimationDuration: number; + private pieData: DatasourceData[]; + private pieRenderedData: any[]; + private pieTargetData: any[]; + private pieAnimationStartTime: number; + private pieAnimationLastTime: number; + private pieAnimationCaf: CancelAnimationFrame; + + static get pieSettingsSchema(): JsonSettingsSchema { + return flotPieSettingsSchema(); + } + + static get pieDatakeySettingsSchema(): JsonSettingsSchema { + return {}; + } + + static settingsSchema(chartType: ChartType): JsonSettingsSchema { + return flotSettingsSchema(chartType); + } + + static datakeySettingsSchema(defaultShowLines: boolean): JsonSettingsSchema { + return flotDatakeySettingsSchema(defaultShowLines); + } + + constructor(private ctx: WidgetContext, private chartType: ChartType) { + this.chartType = this.chartType || 'line'; + this.settings = ctx.settings as TbFlotSettings; + this.tooltip = $('#flot-series-tooltip'); + if (this.tooltip.length === 0) { + this.tooltip = this.createTooltipElement(); + } + + this.trackDecimals = ctx.decimals; + this.trackUnits = ctx.units; + this.tooltipIndividual = this.chartType === 'pie' || (isDefined(this.settings.tooltipIndividual) + ? this.settings.tooltipIndividual : false); + this.tooltipCumulative = isDefined(this.settings.tooltipCumulative) ? this.settings.tooltipCumulative : false; + + const font = { + color: this.settings.fontColor || '#545454', + size: this.settings.fontSize || 10, + family: 'Roboto' + }; + + this.options = { + title: null, + subtitile: null, + shadowSize: isDefined(this.settings.shadowSize) ? this.settings.shadowSize : 4, + HtmlText: false, + grid: { + hoverable: true, + mouseActiveRadius: 10, + autoHighlight: this.tooltipIndividual === true + }, + selection : { mode : ctx.isMobile ? null : 'x' }, + legend : { + show: false + } + }; + + if (this.chartType === 'line' || this.chartType === 'bar' || this.chartType === 'state') { + this.options.xaxis = { + mode: 'time', + timezone: 'browser', + font: deepClone(font), + labelFont: deepClone(font) + }; + this.yaxis = { + font: deepClone(font), + labelFont: deepClone(font) + }; + if (this.settings.xaxis) { + if (this.settings.xaxis.showLabels === false) { + this.options.xaxis.tickFormatter = () => { + return ''; + }; + } + this.options.xaxis.font.color = this.settings.xaxis.color || this.options.xaxis.font.color; + this.options.xaxis.label = this.settings.xaxis.title || null; + this.options.xaxis.labelFont.color = this.options.xaxis.font.color; + this.options.xaxis.labelFont.size = this.options.xaxis.font.size + 2; + this.options.xaxis.labelFont.weight = 'bold'; + } + + this.yAxisTickFormatter = this.formatYAxisTicks.bind(this); + + this.yaxis.tickFormatter = this.yAxisTickFormatter; + + if (this.settings.yaxis) { + this.yaxis.font.color = this.settings.yaxis.color || this.yaxis.font.color; + this.yaxis.min = isDefined(this.settings.yaxis.min) ? this.settings.yaxis.min : null; + this.yaxis.max = isDefined(this.settings.yaxis.max) ? this.settings.yaxis.max : null; + this.yaxis.label = this.settings.yaxis.title || null; + this.yaxis.labelFont.color = this.yaxis.font.color; + this.yaxis.labelFont.size = this.yaxis.font.size + 2; + this.yaxis.labelFont.weight = 'bold'; + if (isNumber(this.settings.yaxis.tickSize)) { + this.yaxis.tickSize = this.settings.yaxis.tickSize; + } else { + this.yaxis.tickSize = null; + } + if (isNumber(this.settings.yaxis.tickDecimals)) { + this.yaxis.tickDecimals = this.settings.yaxis.tickDecimals; + } else { + this.yaxis.tickDecimals = null; + } + if (this.settings.yaxis.ticksFormatter && this.settings.yaxis.ticksFormatter.length) { + try { + this.yaxis.ticksFormatterFunction = new Function('value', + this.settings.yaxis.ticksFormatter) as TbFlotTicksFormatterFunction; + } catch (e) { + this.yaxis.ticksFormatterFunction = null; + } + } + } + + this.options.grid.borderWidth = 1; + this.options.grid.color = this.settings.fontColor || '#545454'; + + if (this.settings.grid) { + this.options.grid.color = this.settings.grid.color || '#545454'; + this.options.grid.backgroundColor = this.settings.grid.backgroundColor || null; + this.options.grid.tickColor = this.settings.grid.tickColor || '#DDDDDD'; + this. options.grid.borderWidth = isDefined(this.settings.grid.outlineWidth) ? + this.settings.grid.outlineWidth : 1; + if (this.settings.grid.verticalLines === false) { + this.options.xaxis.tickLength = 0; + } + if (this.settings.grid.horizontalLines === false) { + this.yaxis.tickLength = 0; + } + if (isDefined(this.settings.grid.margin)) { + this.options.grid.margin = this.settings.grid.margin; + } + if (isDefined(this.settings.grid.minBorderMargin)) { + this.options.grid.minBorderMargin = this.settings.grid.minBorderMargin; + } + } + + this.options.crosshair = { + mode: 'x' + }; + + this.options.series = { + stack: this.settings.stack === true + }; + + if (this.chartType === 'line' && this.settings.smoothLines) { + this.options.series.curvedLines = { + active: true, + monotonicFit: true + }; + } + + if (this.chartType === 'bar') { + this.options.series.lines = { + show: false, + fill: false, + steps: false + }; + this.options.series.bars = { + show: true, + lineWidth: 0, + fill: 0.9 + }; + this.defaultBarWidth = this.settings.defaultBarWidth || 600; + } + + if (this.chartType === 'state') { + this.options.series.lines = { + steps: true, + show: true + }; + } + } else if (this.chartType === 'pie') { + this.options.series = { + pie: { + show: true, + label: { + show: this.settings.showLabels === true + }, + radius: this.settings.radius || 1, + innerRadius: this.settings.innerRadius || 0, + stroke: { + color: '#fff', + width: 0 + }, + tilt: this.settings.tilt || 1, + shadow: { + left: 5, + top: 15, + alpha: 0.02 + } + } + }; + if (this.settings.stroke) { + this.options.series.pie.stroke.color = this.settings.stroke.color || '#fff'; + this.options.series.pie.stroke.width = this.settings.stroke.width || 0; + } + + if (this.options.series.pie.label.show) { + this.options.series.pie.label.formatter = (label, series) => { + return `
${series.dataKey.label}
${Math.round(series.percent)}%
`; + }; + this.options.series.pie.label.radius = 3 / 4; + this.options.series.pie.label.background = { + opacity: 0.8 + }; + } + + // Experimental + this.animatedPie = this.settings.animatedPie === true; + + } + + if (this.ctx.defaultSubscription) { + this.init(this.ctx.$container, this.ctx.defaultSubscription); + } + } + + + private init($element: JQuery, subscription: IWidgetSubscription) { + this.subscription = subscription; + this.$element = $element; + const colors: string[] = []; + this.yaxes = []; + const yaxesMap: {[units: string]: TbFlotAxisOptions} = {}; + + let tooltipValueFormatFunction: TooltipValueFormatFunction = null; + if (this.settings.tooltipValueFormatter && this.settings.tooltipValueFormatter.length) { + try { + tooltipValueFormatFunction = new Function('value', this.settings.tooltipValueFormatter) as TooltipValueFormatFunction; + } catch (e) { + tooltipValueFormatFunction = null; + } + } + + for (let i = 0; i < this.subscription.data.length; i++) { + const series = this.subscription.data[i] as TbFlotSeries; + colors.push(series.dataKey.color); + const keySettings = series.dataKey.settings; + series.dataKey.tooltipValueFormatFunction = tooltipValueFormatFunction; + if (keySettings.tooltipValueFormatter && keySettings.tooltipValueFormatter.length) { + try { + series.dataKey.tooltipValueFormatFunction = new Function('value', + keySettings.tooltipValueFormatter) as TooltipValueFormatFunction; + } catch (e) { + series.dataKey.tooltipValueFormatFunction = tooltipValueFormatFunction; + } + } + series.lines = { + fill: keySettings.fillLines === true + }; + if (this.chartType === 'line' || this.chartType === 'state') { + series.lines.show = keySettings.showLines !== false; + } else { + series.lines.show = keySettings.showLines === true; + } + if (isDefined(keySettings.lineWidth) && keySettings.lineWidth !== null) { + series.lines.lineWidth = keySettings.lineWidth; + } + series.points = { + show: false, + radius: 8 + }; + if (keySettings.showPoints === true) { + series.points.show = true; + series.points.lineWidth = 5; + series.points.radius = 3; + } + if (this.chartType === 'line' && this.settings.smoothLines && !series.points.show) { + series.curvedLines = { + apply: true + }; + } + + const lineColor = tinycolor(series.dataKey.color); + lineColor.setAlpha(.75); + + series.highlightColor = lineColor.toRgbString(); + + if (this.yaxis) { + const units = series.dataKey.units && series.dataKey.units.length ? series.dataKey.units : this.trackUnits; + let yaxis: TbFlotAxisOptions; + if (keySettings.showSeparateAxis) { + yaxis = this.createYAxis(keySettings, units); + this.yaxes.push(yaxis); + } else { + yaxis = yaxesMap[units]; + if (!yaxis) { + yaxis = this.createYAxis(keySettings, units); + yaxesMap[units] = yaxis; + this.yaxes.push(yaxis); + } + } + series.yaxisIndex = this.yaxes.indexOf(yaxis); + series.yaxis = series.yaxisIndex + 1; + yaxis.keysInfo[i] = {hidden: false}; + yaxis.hidden = false; + } + } + this.options.colors = colors; + this.options.yaxes = deepClone(this.yaxes); + if (this.chartType === 'line' || this.chartType === 'bar' || this.chartType === 'state') { + if (this.chartType === 'bar') { + if (this.subscription.timeWindowConfig.aggregation && + this.subscription.timeWindowConfig.aggregation.type === AggregationType.NONE) { + this.options.series.bars.barWidth = this.defaultBarWidth; + } else { + this.options.series.bars.barWidth = this.subscription.timeWindow.interval * 0.6; + } + } + this.options.xaxis.min = this.subscription.timeWindow.minTime; + this.options.xaxis.max = this.subscription.timeWindow.maxTime; + } + + this.checkMouseEvents(); + + if (this.plot) { + this.plot.destroy(); + } + if (this.chartType === 'pie' && this.animatedPie) { + this.pieDataAnimationDuration = 250; + this.pieData = deepClone(this.subscription.data); + this.pieRenderedData = []; + this.pieTargetData = []; + for (let i = 0; i < this.subscription.data.length; i++) { + this.pieTargetData[i] = (this.subscription.data[i].data && this.subscription.data[i].data[0]) + ? this.subscription.data[i].data[0][1] : 0; + } + this.pieDataRendered(); + } + this.plotInited = true; + this.createPlot(); + } + + public update() { + if (this.updateTimeoutHandle) { + clearTimeout(this.updateTimeoutHandle); + this.updateTimeoutHandle = null; + } + if (this.subscription) { + if (!this.isMouseInteraction && this.plot) { + if (this.chartType === 'line' || this.chartType === 'bar' || this.chartType === 'state') { + + let axisVisibilityChanged = false; + if (this.yaxis) { + for (let i = 0; i < this.subscription.data.length; i++) { + const series = this.subscription.data[i] as TbFlotSeries; + const yaxisIndex = series.yaxisIndex; + if (this.yaxes[yaxisIndex].keysInfo[i].hidden !== series.dataKey.hidden) { + this.yaxes[yaxisIndex].keysInfo[i].hidden = series.dataKey.hidden; + axisVisibilityChanged = true; + } + } + if (axisVisibilityChanged) { + this.options.yaxes.length = 0; + this.yaxes.forEach((yaxis) => { + let hidden = true; + yaxis.keysInfo.forEach((info) => { + if (info) { + hidden = hidden && info.hidden; + } + }); + yaxis.hidden = hidden; + let newIndex = 1; + if (!yaxis.hidden) { + this.options.yaxes.push(yaxis); + newIndex = this.options.yaxes.length; + } + for (let k = 0; k < yaxis.keysInfo.length; k++) { + if (yaxis.keysInfo[k]) { + (this.subscription.data[k] as TbFlotSeries).yaxis = newIndex; + } + } + + }); + this.options.yaxis = { + show: this.options.yaxes.length ? true : false + }; + } + } + + this.options.xaxis.min = this.subscription.timeWindow.minTime; + this.options.xaxis.max = this.subscription.timeWindow.maxTime; + if (this.chartType === 'bar') { + if (this.subscription.timeWindowConfig.aggregation && + this.subscription.timeWindowConfig.aggregation.type === AggregationType.NONE) { + this.options.series.bars.barWidth = this.defaultBarWidth; + } else { + this.options.series.bars.barWidth = this.subscription.timeWindow.interval * 0.6; + } + } + + if (axisVisibilityChanged) { + this.redrawPlot(); + } else { + this.plot.getOptions().xaxes[0].min = this.subscription.timeWindow.minTime; + this.plot.getOptions().xaxes[0].max = this.subscription.timeWindow.maxTime; + if (this.chartType === 'bar') { + if (this.subscription.timeWindowConfig.aggregation && + this.subscription.timeWindowConfig.aggregation.type === AggregationType.NONE) { + this.plot.getOptions().series.bars.barWidth = this.defaultBarWidth; + } else { + this.plot.getOptions().series.bars.barWidth = this.subscription.timeWindow.interval * 0.6; + } + } + this.updateData(); + } + } else if (this.chartType === 'pie') { + if (this.animatedPie) { + this.nextPieDataAnimation(true); + } else { + this.updateData(); + } + } + } else if (this.isMouseInteraction && this.plot) { + this.updateTimeoutHandle = setTimeout(this.update.bind(this), 30); + } + } + } + + public resize() { + if (this.resizeTimeoutHandle) { + clearTimeout(this.resizeTimeoutHandle); + this.resizeTimeoutHandle = null; + } + if (this.plot && this.plotInited) { + const width = this.$element.width(); + const height = this.$element.height(); + if (width && height) { + this.plot.resize(); + if (this.chartType !== 'pie') { + this.plot.setupGrid(); + } + this.plot.draw(); + } else { + this.resizeTimeoutHandle = setTimeout(this.resize.bind(this), 30); + } + } + } + + public checkMouseEvents() { + const enabled = !this.ctx.isMobile && !this.ctx.isEdit; + if (isUndefined(this.mouseEventsEnabled) || this.mouseEventsEnabled !== enabled) { + this.mouseEventsEnabled = enabled; + if (this.$element) { + if (enabled) { + this.enableMouseEvents(); + } else { + this.disableMouseEvents(); + } + this.redrawPlot(); + } + } + } + + public destroy() { + this.cleanup(); + if (this.plot) { + this.plot.destroy(); + this.plot = null; + this.plotInited = false; + } + } + + private cleanup() { + if (this.updateTimeoutHandle) { + clearTimeout(this.updateTimeoutHandle); + this.updateTimeoutHandle = null; + } + if (this.createPlotTimeoutHandle) { + clearTimeout(this.createPlotTimeoutHandle); + this.createPlotTimeoutHandle = null; + } + if (this.resizeTimeoutHandle) { + clearTimeout(this.resizeTimeoutHandle); + this.resizeTimeoutHandle = null; + } + } + + private createPlot() { + if (this.createPlotTimeoutHandle) { + clearTimeout(this.createPlotTimeoutHandle); + this.createPlotTimeoutHandle = null; + } + if (this.plotInited && !this.plot) { + const width = this.$element.width(); + const height = this.$element.height(); + if (width && height) { + if (this.chartType === 'pie' && this.animatedPie) { + this.plot = $.plot(this.$element, this.pieData, this.options) as JQueryPlot; + } else { + this.plot = $.plot(this.$element, this.subscription.data, this.options) as JQueryPlot; + } + } else { + this.createPlotTimeoutHandle = setTimeout(this.createPlot.bind(this), 30); + } + } + } + + private updateData() { + this.plot.setData(this.subscription.data); + if (this.chartType !== 'pie') { + this.plot.setupGrid(); + } + this.plot.draw(); + } + + private redrawPlot() { + if (this.plot && this.plotInited) { + this.plot.destroy(); + this.plot = null; + this.createPlot(); + } + } + + private createYAxis(keySettings: TbFlotKeySettings, units: string): TbFlotAxisOptions { + const yaxis = deepClone(this.yaxis); + let tickDecimals: number; + let tickSize: number; + const label = keySettings.axisTitle && keySettings.axisTitle.length ? keySettings.axisTitle : yaxis.label; + if (isNumber(keySettings.axisTickDecimals)) { + tickDecimals = keySettings.axisTickDecimals; + } else { + tickDecimals = yaxis.tickDecimals; + } + if (isNumber(keySettings.axisTickSize)) { + tickSize = keySettings.axisTickSize; + } else { + tickSize = yaxis.tickSize; + } + const position = keySettings.axisPosition && keySettings.axisPosition.length ? keySettings.axisPosition : 'left'; + const min = isDefined(keySettings.axisMin) ? keySettings.axisMin : yaxis.min; + const max = isDefined(keySettings.axisMax) ? keySettings.axisMax : yaxis.max; + yaxis.label = label; + yaxis.min = min; + yaxis.max = max; + yaxis.tickUnits = units; + yaxis.tickDecimals = tickDecimals; + yaxis.tickSize = tickSize; + if (position === 'right' && tickSize === null) { + yaxis.alignTicksWithAxis = 1; + } else { + yaxis.alignTicksWithAxis = null; + } + yaxis.position = position; + + yaxis.keysInfo = []; + + if (keySettings.axisTicksFormatter && keySettings.axisTicksFormatter.length) { + try { + yaxis.ticksFormatterFunction = new Function('value', keySettings.axisTicksFormatter) as TbFlotTicksFormatterFunction; + } catch (e) { + yaxis.ticksFormatterFunction = this.yaxis.ticksFormatterFunction; + } + } + return yaxis; + } + + private seriesInfoDiv(label: string, color: string, value: any, + units: string, trackDecimals: number, active: boolean, + percent: number, valueFormatFunction: TooltipValueFormatFunction): JQuery { + const divElement = $('
'); + divElement.css({ + display: 'flex', + alignItems: 'center', + justifyContent: 'flex-start' + }); + const lineSpan = $(''); + lineSpan.css({ + backgroundColor: color, + width: '20px', + height: '3px', + display: 'inline-block', + verticalAlign: 'middle', + marginRight: '5px' + }); + divElement.append(lineSpan); + const labelSpan = $(`${label}:`); + labelSpan.css({ + marginRight: '10px' + }); + if (active) { + labelSpan.css({ + color: '#FFF', + fontWeight: '700' + }); + } + divElement.append(labelSpan); + let valueContent: string; + if (valueFormatFunction) { + valueContent = valueFormatFunction(value); + } else { + valueContent = this.ctx.utils.formatValue(value, trackDecimals, units); + } + if (isNumber(percent)) { + valueContent += ' (' + Math.round(percent) + ' %)'; + } + const valueSpan = $(`${valueContent}`); + valueSpan.css({ + marginLeft: 'auto', + fontWeight: '700' + }); + if (active) { + valueSpan.css({ + color: '#FFF' + }); + } + divElement.append(valueSpan); + return divElement; + } + + private seriesInfoDivFromInfo(seriesHoverInfo: TbFlotSeriesHoverInfo, seriesIndex: number): string { + const units = seriesHoverInfo.units && seriesHoverInfo.units.length ? seriesHoverInfo.units : this.trackUnits; + const decimals = isDefined(seriesHoverInfo.decimals) ? seriesHoverInfo.decimals : this.trackDecimals; + const divElement = this.seriesInfoDiv(seriesHoverInfo.label, seriesHoverInfo.color, + seriesHoverInfo.value, units, decimals, seriesHoverInfo.index === seriesIndex, null, seriesHoverInfo.tooltipValueFormatFunction); + return divElement.prop('outerHTML'); + } + + private createTooltipElement(): JQuery { + const tooltip = $('
'); + tooltip.css({ + fontSize: '12px', + fontFamily: 'Roboto', + fontWeight: '300', + lineHeight: '18px', + opacity: '1', + backgroundColor: 'rgba(0,0,0,0.7)', + color: '#D9DADB', + position: 'absolute', + display: 'none', + zIndex: '1100', + padding: '4px 10px', + borderRadius: '4px' + }).appendTo('body'); + return tooltip; + } + + private formatPieTooltip(item: TbFlotPlotItem): string { + const units = item.series.dataKey.units && item.series.dataKey.units.length ? item.series.dataKey.units : this.trackUnits; + const decimals = isDefined(item.series.dataKey.decimals) ? item.series.dataKey.decimals : this.trackDecimals; + const divElement = this.seriesInfoDiv(item.series.dataKey.label, item.series.dataKey.color, + item.datapoint[1][0][1], units, decimals, true, item.series.percent, item.series.dataKey.tooltipValueFormatFunction); + return divElement.prop('outerHTML'); + } + + private formatChartTooltip(hoverInfo: TbFlotHoverInfo, seriesIndex: number): string { + let content = ''; + const timestamp = parseInt(hoverInfo.time, 10); + const date = moment(timestamp).format('YYYY-MM-DD HH:mm:ss'); + const dateDiv = $(`
${date}
`); + dateDiv.css({ + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + padding: '4px', + fontWeight: '700' + }); + content += dateDiv.prop('outerHTML'); + if (this.tooltipIndividual) { + const found = hoverInfo.seriesHover.find((seriesHover) => { + return seriesHover.index === seriesIndex; + }); + if (found) { + content += this.seriesInfoDivFromInfo(found, seriesIndex); + } + } else { + const seriesDiv = $('
'); + seriesDiv.css({ + display: 'flex', + flexDirection: 'row' + }); + const maxRows = 15; + const columns = Math.ceil(hoverInfo.seriesHover.length / maxRows); + let columnsContent = ''; + for (let c = 0; c < columns; c++) { + const columnDiv = $('
'); + columnDiv.css({ + display: 'flex', + flexDirection: 'column' + }); + let columnContent = ''; + for (let i = c * maxRows; i < (c + 1) * maxRows; i++) { + if (i === hoverInfo.seriesHover.length) { + break; + } + const seriesHoverInfo = hoverInfo.seriesHover[i]; + columnContent += this.seriesInfoDivFromInfo(seriesHoverInfo, seriesIndex); + } + columnDiv.html(columnContent); + if (c > 0) { + columnsContent += ''; + } + columnsContent += columnDiv.prop('outerHTML'); + } + seriesDiv.html(columnsContent); + content += seriesDiv.prop('outerHTML'); + } + return content; + } + + private formatYAxisTicks(value: number, axis?: TbFlotPlotAxis): string { + if (this.settings.yaxis && this.settings.yaxis.showLabels === false) { + return ''; + } + if (axis.options.ticksFormatterFunction) { + return axis.options.ticksFormatterFunction(value); + } + const factor = axis.options.tickDecimals ? Math.pow(10, axis.options.tickDecimals) : 1; + let formatted = '' + Math.round(value * factor) / factor; + if (isDefined(axis.options.tickDecimals) && axis.options.tickDecimals !== null) { + const decimal = formatted.indexOf('.'); + const precision = decimal === -1 ? 0 : formatted.length - decimal - 1; + if (precision < axis.options.tickDecimals) { + formatted = (precision ? formatted : formatted + '.') + ('' + factor).substr(1, axis.options.tickDecimals - precision); + } + } + if (axis.options.tickUnits) { + formatted += ' ' + axis.options.tickUnits; + } + return formatted; + } + + private enableMouseEvents() { + this.$element.css('pointer-events', ''); + this.$element.addClass('mouse-events'); + this.options.selection = { mode : 'x' }; + this.$element.bind('plothover', this.flotHoverHandler); + this.$element.bind('plotselected', this.flotSelectHandler); + this.$element.bind('dblclick', this.dblclickHandler); + this.$element.bind('mousedown', this.mousedownHandler); + this.$element.bind('mouseup', this.mouseupHandler); + this.$element.bind('mouseleave', this.mouseleaveHandler); + } + + private disableMouseEvents() { + this.$element.css('pointer-events', 'none'); + this.$element.removeClass('mouse-events'); + this.options.selection = { mode : null }; + this.$element.unbind('plothover', this.flotHoverHandler); + this.$element.unbind('plotselected', this.flotSelectHandler); + this.$element.unbind('dblclick', this.dblclickHandler); + this.$element.unbind('mousedown', this.mousedownHandler); + this.$element.unbind('mouseup', this.mouseupHandler); + this.$element.unbind('mouseleave', this.mouseleaveHandler); + } + + private onFlotHover(e: any, pos: JQueryPlotPoint, item: TbFlotPlotItem) { + if (!this.plot) { + return; + } + if (!this.tooltipIndividual || item) { + const multipleModeTooltip = !this.tooltipIndividual; + if (multipleModeTooltip) { + this.plot.unhighlight(); + } + const pageX = pos.pageX; + const pageY = pos.pageY; + + let tooltipHtml; + let hoverInfo: TbFlotHoverInfo; + + if (this.chartType === 'pie') { + tooltipHtml = this.formatPieTooltip(item); + } else { + hoverInfo = this.getHoverInfo(this.plot.getData(), pos); + if (isNumber(hoverInfo.time)) { + hoverInfo.seriesHover.sort((a, b) => { + return b.value - a.value; + }); + tooltipHtml = this.formatChartTooltip(hoverInfo, item ? item.seriesIndex : -1); + } + } + if (tooltipHtml) { + this.tooltip.html(tooltipHtml) + .css({top: 0, left: 0}) + .fadeIn(200); + + const windowWidth = $( window ).width(); + const windowHeight = $( window ).height(); + const tooltipWidth = this.tooltip.width(); + const tooltipHeight = this.tooltip.height(); + let left = pageX + 5; + let top = pageY + 5; + if (windowWidth - pageX < tooltipWidth + 50) { + left = pageX - tooltipWidth - 10; + } + if (windowHeight - pageY < tooltipHeight + 20) { + top = pageY - tooltipHeight - 10; + } + this.tooltip.css({ + top, + left + }); + if (multipleModeTooltip) { + hoverInfo.seriesHover.forEach((seriesHoverInfo) => { + this.plot.highlight(seriesHoverInfo.index, seriesHoverInfo.hoverIndex); + }); + } + } + } else { + this.tooltip.stop(true); + this.tooltip.hide(); + this.plot.unhighlight(); + } + } + + private onFlotSelect(e: any, ranges: JQueryPlotSelectionRanges) { + if (!this.plot) { + return; + } + this.plot.clearSelection(); + this.subscription.onUpdateTimewindow(ranges.xaxis.from, ranges.xaxis.to); + } + + private onFlotDblClick() { + this.subscription.onResetTimewindow(); + } + + private onFlotMouseDown() { + this.isMouseInteraction = true; + } + + private onFlotMouseUp() { + this.isMouseInteraction = false; + } + + private onFlotMouseLeave() { + if (!this.tooltip) { + return; + } + this.tooltip.stop(true); + this.tooltip.hide(); + if (this.plot) { + this.plot.unhighlight(); + } + this.isMouseInteraction = false; + } + + private getHoverInfo(seriesList: TbFlotPlotDataSeries[], pos: JQueryPlotPoint): TbFlotHoverInfo { + let i: number; + let series: TbFlotPlotDataSeries; + let hoverIndex: number; + let hoverDistance: number; + let minDistance: number; + let pointTime: any; + let minTime: any; + let value: any; + let lastValue: any; + const results: TbFlotHoverInfo = { + seriesHover: [] + }; + for (i = 0; i < seriesList.length; i++) { + series = seriesList[i]; + hoverIndex = this.findHoverIndexFromData(pos.x, series); + if (series.data[hoverIndex] && series.data[hoverIndex][0]) { + hoverDistance = pos.x - series.data[hoverIndex][0]; + pointTime = series.data[hoverIndex][0]; + + if (!minDistance + || (hoverDistance >= 0 && (hoverDistance < minDistance || minDistance < 0)) + || (hoverDistance < 0 && hoverDistance > minDistance)) { + minDistance = hoverDistance; + minTime = pointTime; + } + if (series.stack) { + if (this.tooltipIndividual || !this.tooltipCumulative) { + value = series.data[hoverIndex][1]; + } else { + lastValue += series.data[hoverIndex][1]; + value = lastValue; + } + } else { + value = series.data[hoverIndex][1]; + } + if (series.stack || (series.curvedLines && series.curvedLines.apply)) { + hoverIndex = this.findHoverIndexFromDataPoints(pos.x, series, hoverIndex); + } + results.seriesHover.push({ + value, + hoverIndex, + color: series.dataKey.color, + label: series.dataKey.label, + units: series.dataKey.units, + decimals: series.dataKey.decimals, + tooltipValueFormatFunction: series.dataKey.tooltipValueFormatFunction, + time: pointTime, + distance: hoverDistance, + index: i + }); + } + } + results.time = minTime; + return results; + } + + private findHoverIndexFromData(posX: number, series: TbFlotPlotDataSeries): number { + let lower = 0; + let upper = series.data.length - 1; + let middle: number; + const index: number = null; + while (index === null) { + if (lower > upper) { + return Math.max(upper, 0); + } + middle = Math.floor((lower + upper) / 2); + if (series.data[middle][0] === posX) { + return middle; + } else if (series.data[middle][0] < posX) { + lower = middle + 1; + } else { + upper = middle - 1; + } + } + } + + private findHoverIndexFromDataPoints(posX: number, series: TbFlotPlotDataSeries, last: number): number { + const ps = series.datapoints.pointsize; + const initial = last * ps; + const len = series.datapoints.points.length; + let j: number; + for (j = initial; j < len; j += ps) { + if ((!series.lines.steps && series.datapoints.points[initial] != null && series.datapoints.points[j] == null) + || series.datapoints.points[j] > posX) { + return Math.max(j - ps, 0) / ps; + } + } + return j / ps - 1; + } + + pieDataRendered() { + for (let i = 0; i < this.pieTargetData.length; i++) { + const value = this.pieTargetData[i] ? this.pieTargetData[i] : 0; + this.pieRenderedData[i] = value; + if (!this.pieData[i].data[0]) { + this.pieData[i].data[0] = [0, 0]; + } + this.pieData[i].data[0][1] = value; + } + } + + nextPieDataAnimation(start) { + if (start) { + this.finishPieDataAnimation(); + this.pieAnimationStartTime = this.pieAnimationLastTime = Date.now(); + for (let i = 0; i < this.subscription.data.length; i++) { + this.pieTargetData[i] = (this.subscription.data[i].data && this.subscription.data[i].data[0]) + ? this.subscription.data[i].data[0][1] : 0; + } + } + if (this.pieAnimationCaf) { + this.pieAnimationCaf(); + this.pieAnimationCaf = null; + } + this.pieAnimationCaf = this.ctx.$scope.raf.raf(this.onPieDataAnimation.bind(this)); + } + + onPieDataAnimation() { + const time = Date.now(); + const elapsed = time - this.pieAnimationLastTime; // this.pieAnimationStartTime; + const progress = (time - this.pieAnimationStartTime) / this.pieDataAnimationDuration; + if (progress >= 1) { + this.finishPieDataAnimation(); + } else { + if (elapsed >= 40) { + for (let i = 0; i < this.pieTargetData.length; i++) { + const prevValue = this.pieRenderedData[i]; + const targetValue = this.pieTargetData[i]; + const value = prevValue + (targetValue - prevValue) * progress; + if (!this.pieData[i].data[0]) { + this.pieData[i].data[0] = [0, 0]; + } + this.pieData[i].data[0][1] = value; + } + this.plot.setData(this.pieData); + this.plot.draw(); + this.pieAnimationLastTime = time; + } + this.nextPieDataAnimation(false); + } + } + + private finishPieDataAnimation() { + this.pieDataRendered(); + this.plot.setData(this.pieData); + this.plot.draw(); + } + +} 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 764fea65e8..aa7682bf23 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 @@ -36,6 +36,7 @@ import { WidgetComponentsModule } from '@home/components/widget/widget-component import { WINDOW } from '@core/services/window.service'; import * as tinycolor from 'tinycolor2'; +import { TbFlot } from './lib/flot-widget'; // declare var jQuery: any; @@ -63,6 +64,8 @@ export class WidgetComponentService { this.window.tinycolor = tinycolor; // @ts-ignore this.window.cssjs = cssjs; + // @ts-ignore + this.window.TbFlot = TbFlot; this.cssParser.testMode = false; this.init(); diff --git a/ui-ngx/src/app/modules/home/components/widget/widget.component.html b/ui-ngx/src/app/modules/home/components/widget/widget.component.html index 4d071ba34d..03a58007b0 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/widget.component.html @@ -16,11 +16,21 @@ -->
- + +
- + +
Widget Error: {{ widgetErrorData.name + ": " + widgetErrorData.message}} 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 44d56727c4..961ef5a99d 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 @@ -28,7 +28,8 @@ import { SimpleChanges, ViewChild, ViewContainerRef, - ViewEncapsulation + ViewEncapsulation, + ChangeDetectorRef } from '@angular/core'; import { DashboardWidget, IDashboardComponent } from '@home/models/dashboard-component.models'; import { @@ -157,7 +158,8 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI private dashboardService: DashboardService, private datasourceService: DatasourceService, private utils: UtilsService, - private raf: RafService) { + private raf: RafService, + private cd: ChangeDetectorRef) { super(store); } @@ -290,7 +292,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI } }; this.widgetContext.utils = { - formatValue: this.formatValue + formatValue: this.formatValue.bind(this) }; this.widgetContext.actionsApi = { actionDescriptorsBySourceId, @@ -325,6 +327,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI alarmService: this.alarmService, datasourceService: this.datasourceService, utils: this.utils, + raf: this.raf, widgetUtils: this.widgetContext.utils, dashboardTimewindowApi: { onResetTimewindow: this.dashboard.onResetTimewindow.bind(this.dashboard), @@ -407,6 +410,13 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI } } + public onLegendKeyHiddenChange(index: number) { + for (const id of Object.keys(this.widgetContext.subscriptions)) { + const subscription = this.widgetContext.subscriptions[id]; + subscription.updateDataVisibility(index); + } + } + private loadFromWidgetInfo() { const widgetNamespace = `widget-type-${(this.widget.isSystemType ? 'sys-' : '')}${this.widget.bundleAlias}-${this.widget.typeAlias}`; const elem = this.elementRef.nativeElement; @@ -464,11 +474,17 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI } if (!this.widgetContext.inited && this.isReady()) { this.widgetContext.inited = true; - try { - this.widgetTypeInstance.onInit(); - } catch (e) { - this.handleWidgetException(e); + if (this.cafs.init) { + this.cafs.init(); + this.cafs.init = null; } + this.cafs.init = this.raf.raf(() => { + try { + this.widgetTypeInstance.onInit(); + } catch (e) { + this.handleWidgetException(e); + } + }); if (!this.typeParameters.useCustomDatasources && this.widgetContext.defaultSubscription) { this.widgetContext.defaultSubscription.subscribe(); } @@ -570,6 +586,15 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI } )); + this.rxSubscriptions.push(this.dashboard.dashboardTimewindowChanged.subscribe( + (dashboardTimewindow) => { + for (const id of Object.keys(this.widgetContext.subscriptions)) { + const subscription = this.widgetContext.subscriptions[id]; + subscription.onDashboardTimewindowChanged(dashboardTimewindow); + } + } + )); + this.configureDynamicWidgetComponent(); if (!this.typeParameters.useCustomDatasources) { // this.cre @@ -722,12 +747,17 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI dataLoading: (subscription) => { if (this.loadingData !== subscription.loadingData) { this.loadingData = subscription.loadingData; + this.cd.detectChanges(); } }, - legendDataUpdated: (subscription) => { + legendDataUpdated: (subscription, detectChanges) => { + if (detectChanges) { + this.cd.detectChanges(); + } }, timeWindowUpdated: (subscription, timeWindowConfig) => { this.widget.config.timewindow = timeWindowConfig; + this.cd.detectChanges(); } }; 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 c1a1dc8cea..d1be047ae4 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 @@ -47,9 +47,10 @@ export interface IDashboardComponent { isMobileSize: boolean; autofillHeight: boolean; dashboardTimewindow: Timewindow; + dashboardTimewindowChanged: Observable; aliasController: IAliasController; stateController: IStateController; - onUpdateTimewindow(startTimeMs: number, endTimeMs: number, interval: number): void; + onUpdateTimewindow(startTimeMs: number, endTimeMs: number, interval?: number): void; onResetTimewindow(): 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 089ee571f4..cb8c8605ea 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 @@ -29,7 +29,7 @@ import { WidgetTypeDescriptor, WidgetTypeParameters } from '@shared/models/widget.models'; -import { Timewindow } from '@shared/models/time/time.models'; +import { Timewindow, WidgetTimewindow } from '@shared/models/time/time.models'; import { EntityInfo, IAliasController, @@ -43,6 +43,7 @@ import { } from '@core/api/widget-api.models'; import { ComponentFactory } from '@angular/core'; import { HttpErrorResponse } from '@angular/common/http'; +import { RafService } from '@core/services/raf.service'; export interface IWidgetAction { name: string; @@ -93,7 +94,7 @@ export interface WidgetContext { datasources?: Array; data?: Array; hiddenData?: Array<{data: DataSet}>; - timeWindow?: Timewindow; + timeWindow?: WidgetTimewindow; } export interface IDynamicWidgetComponent { @@ -103,6 +104,7 @@ export interface IDynamicWidgetComponent { rpcEnabled: boolean; rpcErrorText: string; rpcRejection: HttpErrorResponse; + raf: RafService; [key: string]: any; } diff --git a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts index 49e650cdcd..baacc18006 100644 --- a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts +++ b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts @@ -158,18 +158,25 @@ export class TelemetryPluginCmdsWrapper { } } -export interface SubscriptionUpdateMsg { +export interface SubscriptionData { + [key: string]: [number, any][]; +} + +export interface SubscriptionDataHolder { + data: SubscriptionData; +} + +export interface SubscriptionUpdateMsg extends SubscriptionDataHolder { subscriptionId: number; errorCode: number; errorMsg: string; - data: {[key: string]: [number, string][]}; } export class SubscriptionUpdate implements SubscriptionUpdateMsg { subscriptionId: number; errorCode: number; errorMsg: string; - data: {[key: string]: [number, string][]}; + data: SubscriptionData; constructor(msg: SubscriptionUpdateMsg) { this.subscriptionId = msg.subscriptionId; diff --git a/ui-ngx/src/app/shared/models/time/time.models.ts b/ui-ngx/src/app/shared/models/time/time.models.ts index 98af5fbf15..fec2262e0e 100644 --- a/ui-ngx/src/app/shared/models/time/time.models.ts +++ b/ui-ngx/src/app/shared/models/time/time.models.ts @@ -21,6 +21,7 @@ export const SECOND = 1000; export const MINUTE = 60 * SECOND; export const HOUR = 60 * MINUTE; export const DAY = 24 * HOUR; +export const YEAR = DAY * 365; export enum TimewindowType { REALTIME, @@ -68,6 +69,7 @@ export const aggregationTranslations = new Map( ); export interface Aggregation { + interval?: number; type: AggregationType; limit: number; } @@ -78,6 +80,25 @@ export interface Timewindow { realtime?: IntervalWindow; history?: HistoryWindow; aggregation?: Aggregation; +} + +export interface SubscriptionAggregation extends Aggregation { + interval?: number; + timeWindow?: number; + stateData?: boolean; +} + +export interface SubscriptionTimewindow { + startTs?: number; + realtimeWindowMs?: number; + fixedWindow?: FixedWindow; + aggregation?: SubscriptionAggregation; +} + +export interface WidgetTimewindow { + minTime?: number; + maxTime?: number; + interval?: number; stDiff?: number; } @@ -91,7 +112,7 @@ export function historyInterval(timewindowMs: number): Timewindow { } export function defaultTimewindow(timeService: TimeService): Timewindow { - const currentTime = new Date().getTime(); + const currentTime = Date.now(); const timewindow: Timewindow = { displayValue: '', selectedTab: TimewindowType.REALTIME, @@ -183,6 +204,67 @@ export function toHistoryTimewindow(timewindow: Timewindow, startTimeMs: number, return historyTimewindow; } +export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: number, stateData: boolean, + timeService: TimeService): SubscriptionTimewindow { + const subscriptionTimewindow: SubscriptionTimewindow = { + fixedWindow: null, + realtimeWindowMs: null, + aggregation: { + interval: SECOND, + limit: timeService.getMaxDatapointsLimit(), + type: AggregationType.AVG + } + }; + let aggTimewindow = 0; + if (stateData) { + subscriptionTimewindow.aggregation.type = AggregationType.NONE; + subscriptionTimewindow.aggregation.stateData = true; + } + if (isDefined(timewindow.aggregation) && !stateData) { + subscriptionTimewindow.aggregation = { + type: timewindow.aggregation.type || AggregationType.AVG, + limit: timewindow.aggregation.limit || timeService.getMaxDatapointsLimit() + }; + } + if (isDefined(timewindow.realtime)) { + subscriptionTimewindow.realtimeWindowMs = timewindow.realtime.timewindowMs; + subscriptionTimewindow.aggregation.interval = + timeService.boundIntervalToTimewindow(subscriptionTimewindow.realtimeWindowMs, timewindow.realtime.interval, + subscriptionTimewindow.aggregation.type); + subscriptionTimewindow.startTs = Date.now() + stDiff - subscriptionTimewindow.realtimeWindowMs; + const startDiff = subscriptionTimewindow.startTs % subscriptionTimewindow.aggregation.interval; + aggTimewindow = subscriptionTimewindow.realtimeWindowMs; + if (startDiff) { + subscriptionTimewindow.startTs -= startDiff; + aggTimewindow += subscriptionTimewindow.aggregation.interval; + } + } else if (isDefined(timewindow.history)) { + if (isDefined(timewindow.history.timewindowMs)) { + const currentTime = Date.now(); + subscriptionTimewindow.fixedWindow = { + startTimeMs: currentTime - timewindow.history.timewindowMs, + endTimeMs: currentTime + }; + aggTimewindow = timewindow.history.timewindowMs; + } else { + subscriptionTimewindow.fixedWindow = { + startTimeMs: timewindow.history.fixedTimewindow.startTimeMs, + endTimeMs: timewindow.history.fixedTimewindow.endTimeMs + }; + aggTimewindow = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs; + } + subscriptionTimewindow.startTs = subscriptionTimewindow.fixedWindow.startTimeMs; + subscriptionTimewindow.aggregation.interval = + timeService.boundIntervalToTimewindow(aggTimewindow, timewindow.history.interval, subscriptionTimewindow.aggregation.type); + } + const aggregation = subscriptionTimewindow.aggregation; + aggregation.timeWindow = aggTimewindow; + if (aggregation.type !== AggregationType.NONE) { + aggregation.limit = Math.ceil(aggTimewindow / subscriptionTimewindow.aggregation.interval); + } + return subscriptionTimewindow; +} + export function cloneSelectedTimewindow(timewindow: Timewindow): Timewindow { const cloned: Timewindow = {}; if (isDefined(timewindow.selectedTab)) { diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index b3e911b298..f57e0e52e4 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -221,16 +221,21 @@ export interface Datasource { entityId?: string; entityName?: string; entityAliasId?: string; + unresolvedStateEntity?: boolean; + dataReceived?: boolean; [key: string]: any; // TODO: } export type DataSet = [number, any][]; -export interface DatasourceData { +export interface DataSetHolder { + data: DataSet; +} + +export interface DatasourceData extends DataSetHolder { datasource: Datasource; dataKey: DataKey; - data: DataSet; } export interface LegendKey { @@ -239,10 +244,10 @@ export interface LegendKey { } export interface LegendKeyData { - min: number; - max: number; - avg: number; - total: number; + min: string; + max: string; + avg: string; + total: string; hidden: boolean; } @@ -340,3 +345,13 @@ export interface Widget { col: number; config: WidgetConfig; } + +export interface JsonSettingsSchema { + schema?: { + type: string; + title: string; + properties: {[key: string]: any}; + required?: string[]; + }; + form?: any[]; +} diff --git a/ui-ngx/src/styles.scss b/ui-ngx/src/styles.scss index 1b5d25bafe..e99c4270ee 100644 --- a/ui-ngx/src/styles.scss +++ b/ui-ngx/src/styles.scss @@ -209,6 +209,10 @@ label { } } +.tb-noselect { + user-select: none; +} + div { &.tb-small { font-size: 14px; diff --git a/ui-ngx/src/tsconfig.app.json b/ui-ngx/src/tsconfig.app.json index f74d460ff1..44125beebb 100644 --- a/ui-ngx/src/tsconfig.app.json +++ b/ui-ngx/src/tsconfig.app.json @@ -2,7 +2,7 @@ "extends": "../tsconfig.json", "compilerOptions": { "outDir": "../out-tsc/app", - "types": ["node", "jquery"] + "types": ["node", "jquery", "flot", "tinycolor2"] }, "exclude": [ "test.ts", diff --git a/ui-ngx/src/typings/jquery.flot.typings.d.ts b/ui-ngx/src/typings/jquery.flot.typings.d.ts new file mode 100644 index 0000000000..2c84383241 --- /dev/null +++ b/ui-ngx/src/typings/jquery.flot.typings.d.ts @@ -0,0 +1,128 @@ +/// +/// Copyright © 2016-2019 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. +/// + + +interface JQueryPlot extends jquery.flot.plot { + destroy(): void; + highlight(series: jquery.flot.dataSeries | number, datapoint: jquery.flot.item | number): void; + clearSelection(): void; +} + +interface JQueryPlotPoint extends jquery.flot.point { + pageX: number; + pageY: number; +} + +interface JQueryPlotDataSeries extends jquery.flot.dataSeries, JQueryPlotSeriesOptions { + datapoints?: jquery.flot.datapoints; +} + +interface JQueryPlotOptions extends jquery.flot.plotOptions { + title?: string; + subtitile?: string; + shadowSize?: number; + HtmlText?: boolean; + selection?: JQueryPlotSelection; + xaxis?: JQueryPlotAxisOptions; + series?: JQueryPlotSeriesOptions; + crosshair?: JQueryPlotCrosshairOptions; +} + +interface JQueryPlotAxisOptions extends jquery.flot.axisOptions { + label?: string; + labelFont?: any; +} + +interface JQueryPlotAxis extends jquery.flot.axis, JQueryPlotAxisOptions { + options: JQueryPlotAxisOptions; +} + +interface JQueryPlotSeriesOptions extends jquery.flot.seriesOptions { + stack?: boolean; + curvedLines?: JQueryPlotCurvedLinesOptions; + pie?: JQueryPlotPieOptions; +} + +declare type JQueryPlotCrosshairMode = 'x' | 'y' | 'xy' | null; + +interface JQueryPlotCrosshairOptions { + mode?: JQueryPlotCrosshairMode; + color?: string; + lineWidth?: number; +} + +interface JQueryPlotCurvedLinesOptions { + active?: boolean; + apply?: boolean; + monotonicFit?: boolean; + tension?: number; + nrSplinePoints?: number; + legacyOverride?: any; +} + +interface JQueryPlotPieOptions { + show?: boolean; + radius?: any; + innerRadius?: any; + startAngle?: number; + tilt?: number; + offset?: { + top?: number; + left?: number; + }; + stroke?: { + color?: string; + width?: number; + }; + shadow?: { + top?: number; + left?: number; + alpha?: number; + }; + label?: { + show?: boolean; + formatter?: (label: string, slice?: any) => string; + radius?: any; + background?: { + color?: string; + opacity?: number; + }; + threshold?: number; + }; + combine?: { + threshold?: number; + color?: string; + label?: string; + }; + highlight?: number; +} + +declare type JQueryPlotSelectionMode = 'x' | 'y' | 'xy' | null; +declare type JQueryPlotSelectionShape = 'round' | 'mitter' | 'bevel'; + +interface JQueryPlotSelection { + mode?: JQueryPlotSelectionMode; + color?: string; + shape?: JQueryPlotSelectionShape; + minSize?: number; +} + +interface JQueryPlotSelectionRanges { + [axis: string]: { + from: number; + to: number; + }; +} diff --git a/ui-ngx/src/typings.d.ts b/ui-ngx/src/typings/jquery.typings.d.ts similarity index 100% rename from ui-ngx/src/typings.d.ts rename to ui-ngx/src/typings/jquery.typings.d.ts diff --git a/ui-ngx/tsconfig.json b/ui-ngx/tsconfig.json index 4d36a53659..02a68043b0 100644 --- a/ui-ngx/tsconfig.json +++ b/ui-ngx/tsconfig.json @@ -13,7 +13,8 @@ "target": "es5", "typeRoots": [ "node_modules/@types", - "src/typings.d.ts" + "src/typings/jquery.typings.d.ts", + "src/typings/jquery.flot.typings.d.ts" ], "paths": { "@app/*": ["src/app/*"], From d29a8731cedb7a0bc827c86a5baff8be04ca6025 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 13 Sep 2019 13:10:24 +0300 Subject: [PATCH 034/133] Implement local timewindow handling. --- .../src/app/core/api/widget-subscription.ts | 16 +++++++--------- .../dashboard/dashboard.component.html | 3 ++- .../components/widget/widget.component.ts | 14 ++++++++++---- ui-ngx/src/polyfills.ts | 2 ++ ui-ngx/src/zone-flags.ts | 19 +++++++++++++++++++ 5 files changed, 40 insertions(+), 14 deletions(-) create mode 100644 ui-ngx/src/zone-flags.ts diff --git a/ui-ngx/src/app/core/api/widget-subscription.ts b/ui-ngx/src/app/core/api/widget-subscription.ts index 9d1ebd3b46..5545b22ef7 100644 --- a/ui-ngx/src/app/core/api/widget-subscription.ts +++ b/ui-ngx/src/app/core/api/widget-subscription.ts @@ -321,14 +321,6 @@ export class WidgetSubscription implements IWidgetSubscription { }); if (this.displayLegend) { this.legendData.keys = this.legendData.keys.sort((key1, key2) => key1.dataKey.label.localeCompare(key2.dataKey.label)); - // TODO: - } - if (this.type === widgetType.timeseries) { - if (this.useDashboardTimewindow) { - // TODO: - } else { - // TODO: - } } } @@ -395,6 +387,10 @@ export class WidgetSubscription implements IWidgetSubscription { } updateTimewindowConfig(newTimewindow: Timewindow): void { + if (!this.useDashboardTimewindow) { + this.timeWindowConfig = newTimewindow; + this.update(); + } } onResetTimewindow(): void { @@ -424,14 +420,17 @@ export class WidgetSubscription implements IWidgetSubscription { } sendOneWayCommand(method: string, params?: any, timeout?: number): Observable { + // TODO: return undefined; } sendTwoWayCommand(method: string, params?: any, timeout?: number): Observable { + // TODO: return undefined; } clearRpcError(): void { + // TODO: } update() { @@ -536,7 +535,6 @@ export class WidgetSubscription implements IWidgetSubscription { this.cafs[cafId] = null; } } - // TODO: } private notifyDataLoading() { diff --git a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html index 775ee94c05..a352d2da46 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html @@ -50,6 +50,7 @@ {{widget.title}} @@ -98,7 +99,7 @@ + + {{displayValue}} + + diff --git a/ui-ngx/src/app/modules/home/components/alias/aliases-entity-select.component.scss b/ui-ngx/src/app/modules/home/components/alias/aliases-entity-select.component.scss new file mode 100644 index 0000000000..393de63842 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alias/aliases-entity-select.component.scss @@ -0,0 +1,38 @@ +/** + * Copyright © 2016-2019 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 { + min-width: 52px; + + section.tb-aliases-entity-select { + min-height: 32px; + padding: 0 6px; + + @media #{$mat-lt-md} { + padding: 0; + } + + span { + max-width: 200px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + pointer-events: all; + cursor: pointer; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/alias/aliases-entity-select.component.ts b/ui-ngx/src/app/modules/home/components/alias/aliases-entity-select.component.ts new file mode 100644 index 0000000000..cc9b6b91ea --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alias/aliases-entity-select.component.ts @@ -0,0 +1,178 @@ +/// +/// Copyright © 2016-2019 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, Input, OnDestroy, OnInit, ViewChild, ViewContainerRef } from '@angular/core'; +import { TooltipPosition } from '@angular/material'; +import { IAliasController } from '@core/api/widget-api.models'; +import { CdkOverlayOrigin, ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; +import { TranslateService } from '@ngx-translate/core'; +import { Subscription } from 'rxjs'; +import { BreakpointObserver } from '@angular/cdk/layout'; +import { DOCUMENT } from '@angular/common'; +import { WINDOW } from '@core/services/window.service'; +import { ComponentPortal, PortalInjector } from '@angular/cdk/portal'; +import { + ALIASES_ENTITY_SELECT_PANEL_DATA, + AliasesEntitySelectPanelComponent, + AliasesEntitySelectPanelData +} from './aliases-entity-select-panel.component'; + +@Component({ + selector: 'tb-aliases-entity-select', + templateUrl: './aliases-entity-select.component.html', + styleUrls: ['./aliases-entity-select.component.scss'] +}) +export class AliasesEntitySelectComponent implements OnInit, OnDestroy { + + @Input() + aliasController: IAliasController; + + @Input() + tooltipPosition: TooltipPosition = 'above'; + + @Input() disabled: boolean; + + @ViewChild('aliasEntitySelectPanelOrigin', {static: false}) aliasEntitySelectPanelOrigin: CdkOverlayOrigin; + + displayValue: string; + + private rxSubscriptions = new Array(); + + constructor(private translate: TranslateService, + private overlay: Overlay, + private breakpointObserver: BreakpointObserver, + private viewContainerRef: ViewContainerRef, + @Inject(DOCUMENT) private document: Document, + @Inject(WINDOW) private window: Window) { + } + + ngOnInit(): void { + this.rxSubscriptions.push(this.aliasController.entityAliasesChanged.subscribe( + () => { + this.updateDisplayValue(); + } + )); + this.rxSubscriptions.push(this.aliasController.entityAliasResolved.subscribe( + () => { + this.updateDisplayValue(); + } + )); + } + + ngOnDestroy(): void { + this.rxSubscriptions.forEach((subscription) => { + subscription.unsubscribe(); + }); + this.rxSubscriptions.length = 0; + } + + openEditMode() { + if (this.disabled) { + return; + } + const panelHeight = this.breakpointObserver.isMatched('min-height: 350px') ? 250 : 150; + const panelWidth = 300; + const position = this.overlay.position(); + const config = new OverlayConfig({ + panelClass: 'tb-aliases-entity-select-panel', + backdropClass: 'cdk-overlay-transparent-backdrop', + hasBackdrop: true, + }); + const el = this.aliasEntitySelectPanelOrigin.elementRef.nativeElement; + const offset = el.getBoundingClientRect(); + const scrollTop = this.window.pageYOffset || this.document.documentElement.scrollTop || this.document.body.scrollTop || 0; + const scrollLeft = this.window.pageXOffset || this.document.documentElement.scrollLeft || this.document.body.scrollLeft || 0; + const bottomY = offset.bottom - scrollTop; + const leftX = offset.left - scrollLeft; + let originX; + let originY; + let overlayX; + let overlayY; + const wHeight = this.document.documentElement.clientHeight; + const wWidth = this.document.documentElement.clientWidth; + if (bottomY + panelHeight > wHeight) { + originY = 'top'; + overlayY = 'bottom'; + } else { + originY = 'bottom'; + overlayY = 'top'; + } + if (leftX + panelWidth > wWidth) { + originX = 'end'; + overlayX = 'end'; + } else { + originX = 'start'; + overlayX = 'start'; + } + const connectedPosition: ConnectedPosition = { + originX, + originY, + overlayX, + overlayY + }; + config.positionStrategy = position.flexibleConnectedTo(this.aliasEntitySelectPanelOrigin.elementRef) + .withPositions([connectedPosition]); + const overlayRef = this.overlay.create(config); + overlayRef.backdropClick().subscribe(() => { + overlayRef.dispose(); + }); + + const injector = this._createAliasesEntitySelectPanelInjector( + overlayRef, + { + aliasController: this.aliasController + } + ); + overlayRef.attach(new ComponentPortal(AliasesEntitySelectPanelComponent, this.viewContainerRef, injector)); + } + + private _createAliasesEntitySelectPanelInjector(overlayRef: OverlayRef, data: AliasesEntitySelectPanelData): PortalInjector { + const injectionTokens = new WeakMap([ + [ALIASES_ENTITY_SELECT_PANEL_DATA, data], + [OverlayRef, overlayRef] + ]); + return new PortalInjector(this.viewContainerRef.injector, injectionTokens); + } + + private updateDisplayValue() { + let displayValue; + let singleValue = true; + let currentAliasId; + const entityAliases = this.aliasController.getEntityAliases(); + for (const aliasId of Object.keys(entityAliases)) { + const entityAlias = entityAliases[aliasId]; + if (!entityAlias.filter.resolveMultiple) { + const resolvedAlias = this.aliasController.getInstantAliasInfo(aliasId); + if (resolvedAlias && resolvedAlias.currentEntity) { + if (!currentAliasId) { + currentAliasId = aliasId; + } else { + singleValue = false; + break; + } + } + } + } + if (singleValue && currentAliasId) { + const aliasInfo = this.aliasController.getInstantAliasInfo(currentAliasId); + displayValue = aliasInfo.currentEntity.name; + } else { + displayValue = this.translate.instant('entity.entities'); + } + this.displayValue = displayValue; + } + +} diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index d306bc6a26..2f15f9d253 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -38,6 +38,8 @@ import { DashboardComponent } from '@home/components/dashboard/dashboard.compone import { WidgetComponent } from '@home/components/widget/widget.component'; import { WidgetComponentService } from './widget/widget-component.service'; import { LegendComponent } from '@home/components/widget/legend.component'; +import { AliasesEntitySelectPanelComponent } from '@home/components/alias/aliases-entity-select-panel.component'; +import { AliasesEntitySelectComponent } from '@home/components/alias/aliases-entity-select.component'; @NgModule({ entryComponents: [ @@ -48,7 +50,8 @@ import { LegendComponent } from '@home/components/widget/legend.component'; AlarmTableHeaderComponent, AlarmDetailsDialogComponent, AddAttributeDialogComponent, - EditAttributeValuePanelComponent + EditAttributeValuePanelComponent, + AliasesEntitySelectPanelComponent ], declarations: [ @@ -69,6 +72,8 @@ import { LegendComponent } from '@home/components/widget/legend.component'; AttributeTableComponent, AddAttributeDialogComponent, EditAttributeValuePanelComponent, + AliasesEntitySelectPanelComponent, + AliasesEntitySelectComponent, DashboardComponent, WidgetComponent, LegendComponent @@ -89,6 +94,7 @@ import { LegendComponent } from '@home/components/widget/legend.component'; AlarmTableComponent, AlarmDetailsDialogComponent, AttributeTableComponent, + AliasesEntitySelectComponent, DashboardComponent, WidgetComponent, LegendComponent 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 e3d8bd9c1c..0097c56238 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 @@ -61,10 +61,9 @@ import { WidgetTypeInstance } from '@home/models/widget-component.models'; import { - EntityInfo, IWidgetSubscription, StateObject, - StateParams, + StateParams, SubscriptionEntityInfo, SubscriptionInfo, WidgetSubscriptionContext, WidgetSubscriptionOptions @@ -1065,7 +1064,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI this.store.dispatch(new ActionNotificationShow({message: messageToShow, type: 'error'})); } - private getActiveEntityInfo(): EntityInfo { + private getActiveEntityInfo(): SubscriptionEntityInfo { let entityInfo = this.widgetContext.activeEntityInfo; if (!entityInfo) { for (const id of Object.keys(this.widgetContext.subscriptions)) { diff --git a/ui-ngx/src/app/modules/home/home.component.html b/ui-ngx/src/app/modules/home/home.component.html index c4d387464e..b29f051677 100644 --- a/ui-ngx/src/app/modules/home/home.component.html +++ b/ui-ngx/src/app/modules/home/home.component.html @@ -38,7 +38,7 @@ -
+
diff --git a/ui-ngx/src/app/modules/home/home.component.ts b/ui-ngx/src/app/modules/home/home.component.ts index 4d83abb163..dde0ba0a27 100644 --- a/ui-ngx/src/app/modules/home/home.component.ts +++ b/ui-ngx/src/app/modules/home/home.component.ts @@ -40,6 +40,8 @@ import { MatSidenav } from '@angular/material'; }) export class HomeComponent extends PageComponent implements OnInit { + activeComponent: any; + sidenavMode = 'side'; sidenavOpened = true; 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 cb8c8605ea..35517d5209 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 @@ -31,12 +31,11 @@ import { } from '@shared/models/widget.models'; import { Timewindow, WidgetTimewindow } from '@shared/models/time/time.models'; import { - EntityInfo, IAliasController, IStateController, IWidgetSubscription, IWidgetUtils, - RpcApi, + RpcApi, SubscriptionEntityInfo, TimewindowFunctions, WidgetActionsApi, WidgetSubscriptionApi @@ -85,7 +84,7 @@ export interface WidgetContext { actionsApi?: WidgetActionsApi; stateController?: IStateController; aliasController?: IAliasController; - activeEntityInfo?: EntityInfo; + activeEntityInfo?: SubscriptionEntityInfo; widgetTitleTemplate?: string; widgetTitle?: string; customHeaderActions?: Array; diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.html b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.html new file mode 100644 index 0000000000..e2956320ba --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.html @@ -0,0 +1,157 @@ + +
+
+ +
+
+ + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+
+
+

{{ dashboard.title }}

+ + dashboard.title + + +
+
+
+ TODO: MAIN LAYOUT tb-dashboard-layout +
+ + + TODO: RIGHT LAYOUT tb-dashboard-layout + + +
+ + + +
+ +
diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.scss b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.scss new file mode 100644 index 0000000000..4b927b1eeb --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.scss @@ -0,0 +1,137 @@ +/** + * Copyright © 2016-2019 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"; + +$toolbar-height: 50px !default; +$fullscreen-toolbar-height: 64px !default; +$mobile-toolbar-height: 84px !default; + +tb-dashboard-page { + display: flex; + width: 100%; + height: 100%; +} + +div.tb-dashboard-page { + &.mat-content { + background-color: #eee; + } + section.tb-dashboard-title { + position: absolute; + top: 0; + left: 20px; + mat-form-field { + .mat-form-field-infix { + width: 100%; + } + } + input.tb-dashboard-title { + height: 38px; + font-size: 2rem; + font-weight: 500; + letter-spacing: .005em; + } + } + div.tb-padded { + top: 60px; + } + + section.tb-padded { + top: 60px; + } + + div.tb-shrinked { + width: 40%; + } + + section.tb-dashboard-toolbar { + position: absolute; + top: 0; + left: 0; + z-index: 13; + pointer-events: none; + + &.tb-dashboard-toolbar-opened { + right: 0; + // transition: right .3s cubic-bezier(.55, 0, .55, .2); + } + + &.tb-dashboard-toolbar-closed { + right: 18px; + transition: right .3s cubic-bezier(.55, 0, .55, .2) .2s; + } + } + + .tb-dashboard-container { + &.tb-dashboard-toolbar-opened { + &.is-fullscreen { + margin-top: $mobile-toolbar-height; + + @media #{$mat-gt-sm} { + margin-top: $fullscreen-toolbar-height; + } + } + + &:not(.is-fullscreen) { + margin-top: $mobile-toolbar-height; + + @media #{$mat-gt-sm} { + margin-top: $toolbar-height; + } + + transition: margin-top .3s cubic-bezier(.55, 0, .55, .2); + } + } + + &.tb-dashboard-toolbar-closed { + margin-top: 0; + + transition: margin-top .3s cubic-bezier(.55, 0, .55, .2) .2s; + } + + .tb-dashboard-layouts { + /*md-backdrop { + z-index: 1; + }*/ + #tb-right-layout { + mat-sidenav { + z-index: 1; + } + } + } + } + + section.tb-powered-by-footer { + position: absolute; + right: 25px; + bottom: 5px; + z-index: 30; + pointer-events: none; + + span { + font-size: 12px; + + a { + font-weight: 700; + text-decoration: none; + pointer-events: all; + border: none; + } + } + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.ts new file mode 100644 index 0000000000..73c81a2347 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.ts @@ -0,0 +1,438 @@ +/// +/// Copyright © 2016-2019 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, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { ActivatedRoute, Router } from '@angular/router'; +import { UtilsService } from '@core/services/utils.service'; +import { AuthService } from '@core/auth/auth.service'; +import { Dashboard, DashboardConfiguration, WidgetLayout } from '@app/shared/models/dashboard.models'; +import { WINDOW } from '@core/services/window.service'; +import { WindowMessage } from '@shared/models/window-message.model'; +import { deepClone, isDefined } from '@app/core/utils'; +import { + DashboardContext, + DashboardPageLayoutContext, + DashboardPageLayouts, + DashboardPageScope +} from './dashboard-page.models'; +import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; +import { MediaBreakpoints } from '@shared/models/constants'; +import { AuthUser } from '@shared/models/user.model'; +import { getCurrentAuthUser } from '@core/auth/auth.selectors'; +import { Widget } from '@app/shared/models/widget.models'; +import { environment as env } from '@env/environment'; +import { Authority } from '@shared/models/authority.enum'; +import { DialogService } from '@core/services/dialog.service'; +import { EntityService } from '@core/http/entity.service'; +import { AliasController } from '@core/api/alias-controller'; +import { Subscription } from 'rxjs'; + +@Component({ + selector: 'tb-dashboard-page', + templateUrl: './dashboard-page.component.html', + styleUrls: ['./dashboard-page.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class DashboardPageComponent extends PageComponent implements OnDestroy { + + authUser: AuthUser = getCurrentAuthUser(this.store); + + dashboard: Dashboard; + dashboardConfiguration: DashboardConfiguration; + + prevDashboard: Dashboard; + + iframeMode = this.utils.iframeMode; + widgetEditMode: boolean; + singlePageMode: boolean; + forceFullscreen = this.authService.forceFullscreen; + + isFullscreen = false; + isEdit = false; + isEditingWidget = false; + isMobile = !this.breakpointObserver.isMatched(MediaBreakpoints['gt-sm']); + forceDashboardMobileMode = false; + isAddingWidget = false; + + isToolbarOpened = false; + isRightLayoutOpened = false; + + editingWidget: Widget = null; + editingWidgetLayout: WidgetLayout = null; + editingWidgetOriginal: Widget = null; + editingWidgetLayoutOriginal: WidgetLayout = null; + editingWidgetSubtitle: string = null; + editingLayoutCtx: DashboardPageLayoutContext = null; + + thingsboardVersion: string = env.tbVersion; + + currentDashboardId: string; + currentCustomerId: string; + currentDashboardScope: DashboardPageScope; + + layouts: DashboardPageLayouts = { + main: { + show: false, + layoutCtx: { + id: 'main', + widgets: [], + widgetLayouts: {}, + gridSettings: {}, + ignoreLoading: false + } + }, + right: { + show: false, + layoutCtx: { + id: 'right', + widgets: [], + widgetLayouts: {}, + gridSettings: {}, + ignoreLoading: false + } + } + }; + + dashboardCtx: DashboardContext = { + dashboard: null, + dashboardTimewindow: null, + state: null, + stateController: { + openRightLayout: this.openRightLayout.bind(this) + }, + aliasController: null + }; + + private rxSubscriptions = new Array(); + + get toolbarOpened(): boolean { + return !this.widgetEditMode && + (this.toolbarAlwaysOpen() || this.isToolbarOpened || this.isEdit || this.showRightLayoutSwitch()); + } + set toolbarOpened(toolbarOpened: boolean) { + } + + get rightLayoutOpened(): boolean { + return !this.isMobile || this.isRightLayoutOpened; + } + set rightLayoutOpened(rightLayoutOpened: boolean) { + } + + constructor(protected store: Store, + @Inject(WINDOW) private window: Window, + private breakpointObserver: BreakpointObserver, + private route: ActivatedRoute, + private router: Router, + private utils: UtilsService, + private authService: AuthService, + private entityService: EntityService, + private dialogService: DialogService) { + super(store); + + this.rxSubscriptions.push(this.route.data.subscribe( + (data) => { + this.init(data); + } + )); + + this.rxSubscriptions.push(this.breakpointObserver + .observe(MediaBreakpoints['gt-sm']) + .subscribe((state: BreakpointState) => { + this.isMobile = !state.matches; + } + )); + } + + private init(data: any) { + + this.reset(); + + this.currentDashboardId = this.route.snapshot.params.dashboardId; + + if (this.route.snapshot.params.customerId) { + this.currentCustomerId = this.route.snapshot.params.customerId; + this.currentDashboardScope = 'customer'; + } else { + this.currentDashboardScope = this.authUser.authority === Authority.TENANT_ADMIN ? 'tenant' : 'customer'; + this.currentCustomerId = this.authUser.customerId; + } + + this.dashboard = data.dashboard; + this.dashboardConfiguration = this.dashboard.configuration; + this.widgetEditMode = data.widgetEditMode; + this.singlePageMode = data.singlePageMode; + + this.dashboardCtx.dashboard = this.dashboard; + this.dashboardCtx.dashboardTimewindow = this.dashboardConfiguration.timewindow; + this.dashboardCtx.aliasController = new AliasController(this.utils, + this.entityService, + this.dashboardCtx.stateController, + this.dashboardConfiguration.entityAliases); + + if (this.widgetEditMode) { + const message: WindowMessage = { + type: 'widgetEditModeInited' + }; + this.window.parent.postMessage(JSON.stringify(message), '*'); + } + } + + private reset() { + this.dashboard = null; + this.dashboardConfiguration = null; + this.prevDashboard = null; + + this.widgetEditMode = false; + this.singlePageMode = false; + + this.isFullscreen = false; + this.isEdit = false; + this.isEditingWidget = false; + this.forceDashboardMobileMode = false; + this.isAddingWidget = false; + + this.isToolbarOpened = false; + this.isRightLayoutOpened = false; + + this.editingWidget = null; + this.editingWidgetLayout = null; + this.editingWidgetOriginal = null; + this.editingWidgetLayoutOriginal = null; + this.editingWidgetSubtitle = null; + this.editingLayoutCtx = null; + + this.currentDashboardId = null; + this.currentCustomerId = null; + this.currentDashboardScope = null; + } + + ngOnDestroy(): void { + this.rxSubscriptions.forEach((subscription) => { + subscription.unsubscribe(); + }); + this.rxSubscriptions.length = 0; + } + + public openToolbar() { + this.isToolbarOpened = true; + } + + public closeToolbar() { + this.isToolbarOpened = false; + } + + public showCloseToolbar() { + return !this.toolbarAlwaysOpen() && !this.isEdit && !this.showRightLayoutSwitch(); + } + + public hideFullscreenButton(): boolean { + return this.widgetEditMode || this.iframeMode || this.forceFullscreen || this.singlePageMode; + } + + public toolbarAlwaysOpen(): boolean { + if (this.dashboard.configuration.settings && + isDefined(this.dashboard.configuration.settings.toolbarAlwaysOpen)) { + return this.dashboard.configuration.settings.toolbarAlwaysOpen; + } else { + return true; + } + } + + public displayTitle(): boolean { + if (this.dashboard.configuration.settings && + isDefined(this.dashboard.configuration.settings.showTitle)) { + return this.dashboard.configuration.settings.showTitle; + } else { + return false; + } + } + + public displayExport(): boolean { + if (this.dashboard.configuration.settings && + isDefined(this.dashboard.configuration.settings.showDashboardExport)) { + return this.dashboard.configuration.settings.showDashboardExport; + } else { + return true; + } + } + + public displayDashboardTimewindow(): boolean { + if (this.dashboard.configuration.settings && + isDefined(this.dashboard.configuration.settings.showDashboardTimewindow)) { + return this.dashboard.configuration.settings.showDashboardTimewindow; + } else { + return true; + } + } + + public displayDashboardsSelect(): boolean { + if (this.dashboard.configuration.settings && + isDefined(this.dashboard.configuration.settings.showDashboardsSelect)) { + return this.dashboard.configuration.settings.showDashboardsSelect; + } else { + return true; + } + } + + public displayEntitiesSelect(): boolean { + if (this.dashboard.configuration.settings && + isDefined(this.dashboard.configuration.settings.showEntitiesSelect)) { + return this.dashboard.configuration.settings.showEntitiesSelect; + } else { + return true; + } + } + + public showRightLayoutSwitch(): boolean { + return this.isMobile && this.layouts.right.show; + } + + public toggleLayouts() { + this.isRightLayoutOpened = !this.isRightLayoutOpened; + } + + public openRightLayout() { + this.isRightLayoutOpened = true; + } + + public mainLayoutWidth(): string { + if (this.isEditingWidget && this.editingLayoutCtx.id === 'main') { + return '100%'; + } else { + return this.layouts.right.show && !this.isMobile ? '50%' : '100%'; + } + } + + public mainLayoutHeight(): string { + if (!this.isEditingWidget || this.editingLayoutCtx.id === 'main') { + return '100%'; + } else { + return '0px'; + } + } + + public rightLayoutWidth(): string { + if (this.isEditingWidget && this.editingLayoutCtx.id === 'right') { + return '100%'; + } else { + return this.isMobile ? '100%' : '50%'; + } + } + + public rightLayoutHeight(): string { + if (!this.isEditingWidget || this.editingLayoutCtx.id === 'right') { + return '100%'; + } else { + return '0px'; + } + } + + public isPublicUser(): boolean { + return this.authUser.isPublic; + } + + public isTenantAdmin(): boolean { + return this.authUser.authority === Authority.TENANT_ADMIN; + } + + public isSystemAdmin(): boolean { + return this.authUser.authority === Authority.SYS_ADMIN; + } + + public exportDashboard($event: Event) { + if ($event) { + $event.stopPropagation(); + } + // TODO: + this.dialogService.todo(); + } + + public openEntityAliases($event: Event) { + if ($event) { + $event.stopPropagation(); + } + // TODO: + this.dialogService.todo(); + } + + public openDashboardSettings($event: Event) { + if ($event) { + $event.stopPropagation(); + } + // TODO: + this.dialogService.todo(); + } + + public currentDashboardIdChanged(dashboardId: string) { + if (!this.widgetEditMode) { + if (this.currentDashboardScope === 'customer' && this.authUser.authority === Authority.TENANT_ADMIN) { + this.router.navigateByUrl(`customers/${this.currentCustomerId}/dashboards/${dashboardId}`); + } else { + if (this.singlePageMode) { + this.router.navigateByUrl(`dashboard/${dashboardId}`); + } else { + this.router.navigateByUrl(`dashboards/${dashboardId}`); + } + } + } + } + + public toggleDashboardEditMode() { + this.setEditMode(!this.isEdit, true); + } + + private setEditMode(isEdit: boolean, revert: boolean) { + this.isEdit = isEdit; + if (this.isEdit) { + // TODO: + // this.dashboardCtx.stateController.preserveState(); + this.prevDashboard = deepClone(this.dashboard); + } else { + if (this.widgetEditMode) { + if (revert) { + this.dashboard = this.prevDashboard; + } + } else { + this.resetHighlight(); + if (revert) { + this.dashboard = this.prevDashboard; + this.dashboardConfiguration = this.dashboard.configuration; + this.dashboardCtx.dashboardTimewindow = this.dashboardConfiguration.timewindow; + this.entityAliasesUpdated(); + } else { + this.dashboard.configuration.timewindow = this.dashboardCtx.dashboardTimewindow; + } + } + } + } + + private resetHighlight() { + for (const l of Object.keys(this.layouts)) { + if (this.layouts[l].layoutCtx) { + if (this.layouts[l].layoutCtx.ctrl) { + this.layouts[l].layoutCtx.ctrl.resetHighlight(); + } + } + } + } + + private entityAliasesUpdated() { + this.dashboardCtx.aliasController.updateEntityAliases(this.dashboard.configuration.entityAliases); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.models.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.models.ts new file mode 100644 index 0000000000..c08000c4e2 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.models.ts @@ -0,0 +1,48 @@ +/// +/// Copyright © 2016-2019 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 { DashboardLayoutId, GridSettings, WidgetLayout, Dashboard } from '@app/shared/models/dashboard.models'; +import { Widget } from '@app/shared/models/widget.models'; +import { Timewindow } from '@shared/models/time/time.models'; +import { IAliasController, IStateController } from '@core/api/widget-api.models'; + +export declare type DashboardPageScope = 'tenant' | 'customer'; + +export interface DashboardContext { + state: string; + dashboard: Dashboard; + dashboardTimewindow: Timewindow; + aliasController: IAliasController; + stateController: IStateController; +} + +export interface DashboardPageLayoutContext { + id: DashboardLayoutId; + widgets: Array; + widgetLayouts: {[id: string]: WidgetLayout}; + gridSettings: GridSettings; + ignoreLoading: boolean; +} + +export interface DashboardPageLayout { + show: boolean; + layoutCtx: DashboardPageLayoutContext; +} + +export interface DashboardPageLayouts { + main: DashboardPageLayout; + right: DashboardPageLayout; +} diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-routing.module.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-routing.module.ts index ee52db4849..0d1e1c6db1 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-routing.module.ts @@ -14,12 +14,39 @@ /// limitations under the License. /// -import {NgModule} from '@angular/core'; -import {RouterModule, Routes} from '@angular/router'; +import { Injectable, NgModule } from '@angular/core'; +import { ActivatedRouteSnapshot, Resolve, RouterModule, Routes } from '@angular/router'; import {EntitiesTableComponent} from '../../components/entity/entities-table.component'; import {Authority} from '@shared/models/authority.enum'; import {DashboardsTableConfigResolver} from './dashboards-table-config.resolver'; +import { DashboardPageComponent } from '@home/pages/dashboard/dashboard-page.component'; +import { BreadCrumbConfig, BreadCrumbLabelFunction } from '@shared/components/breadcrumb'; +import { widgetTypesBreadcumbLabelFunction } from '@home/pages/widget/widget-library-routing.module'; +import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; +import { WidgetService } from '@core/http/widget.service'; +import { Observable } from 'rxjs'; +import { Dashboard } from '@app/shared/models/dashboard.models'; +import { DashboardService } from '@core/http/dashboard.service'; +import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; +import { map } from 'rxjs/operators'; + +@Injectable() +export class DashboardResolver implements Resolve { + + constructor(private dashboardService: DashboardService, + private dashboardUtils: DashboardUtilsService) { + } + + resolve(route: ActivatedRouteSnapshot): Observable { + const dashboardId = route.params.dashboardId; + return this.dashboardService.getDashboard(dashboardId).pipe( + map((dashboard) => this.dashboardUtils.validateAndUpdateDashboard(dashboard)) + ); + } +} + +export const dashboardBreadcumbLabelFunction: BreadCrumbLabelFunction = ((route, translate, component) => component.dashboard.title); const routes: Routes = [ { @@ -42,6 +69,22 @@ const routes: Routes = [ resolve: { entitiesTableConfig: DashboardsTableConfigResolver } + }, + { + path: ':dashboardId', + component: DashboardPageComponent, + data: { + breadcrumb: { + labelFunction: dashboardBreadcumbLabelFunction, + icon: 'dashboard' + } as BreadCrumbConfig, + auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], + title: 'dashboard.dashboard', + widgetEditMode: false + }, + resolve: { + dashboard: DashboardResolver + } } ] } @@ -51,7 +94,8 @@ const routes: Routes = [ imports: [RouterModule.forChild(routes)], exports: [RouterModule], providers: [ - DashboardsTableConfigResolver + DashboardsTableConfigResolver, + DashboardResolver ] }) export class DashboardRoutingModule { } diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-toolbar.component.html b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-toolbar.component.html new file mode 100644 index 0000000000..50291df2a3 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-toolbar.component.html @@ -0,0 +1,35 @@ + + + + + + + + + + + diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-toolbar.component.scss b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-toolbar.component.scss new file mode 100644 index 0000000000..f6a2f6b105 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-toolbar.component.scss @@ -0,0 +1,185 @@ +/** + * Copyright © 2016-2019 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"; + +$toolbar-height: 50px !default; +$fullscreen-toolbar-height: 64px !default; +$mobile-toolbar-height: 80px !default; +$half-mobile-toolbar-height: 40px !default; +$mobile-toolbar-height-total: 84px !default; + +tb-dashboard-toolbar { + mat-fab-toolbar { + mat-fab-trigger { + .mat-button { + &.mat-fab { + width: 36px; + height: 36px; + margin: 4px 0 0 4px; + line-height: 36px; + .mat-button-wrapper { + width: 36px; + height: 36px; + padding: 0 6px; + box-sizing: border-box; + } + mat-icon { + display: block; + position: absolute; + top: 25%; + width: 18px; + min-width: 18px; + height: 18px; + min-height: 18px; + margin: 0; + line-height: 18px; + } + } + } + } + + &.is-fullscreen { + + .mat-fab-toolbar-wrapper { + height: $mobile-toolbar-height-total; + + @media #{$mat-gt-sm} { + height: $fullscreen-toolbar-height; + } + + mat-toolbar { + height: $mobile-toolbar-height; + min-height: $mobile-toolbar-height; + .mat-toolbar-tools { + height: $mobile-toolbar-height; + min-height: $mobile-toolbar-height; + } + @media #{$mat-gt-sm} { + height: $fullscreen-toolbar-height; + min-height: $fullscreen-toolbar-height; + .mat-toolbar-tools { + height: $fullscreen-toolbar-height; + min-height: $fullscreen-toolbar-height; + } + } + } + } + } + + .mat-fab-toolbar-wrapper { + height: $mobile-toolbar-height-total; + + @media #{$mat-gt-sm} { + height: $toolbar-height; + } + + mat-toolbar { + height: $mobile-toolbar-height; + min-height: $mobile-toolbar-height; + .mat-toolbar-tools { + height: $mobile-toolbar-height; + min-height: $mobile-toolbar-height; + } + + @media #{$mat-gt-sm} { + height: $toolbar-height; + min-height: $toolbar-height; + .mat-toolbar-tools { + height: $toolbar-height; + min-height: $toolbar-height; + } + } + + mat-fab-actions { + margin-top: 0; + font-size: 16px; + + @media #{$mat-lt-md} { + height: $mobile-toolbar-height; + max-height: $mobile-toolbar-height; + } + + .close-action { + margin-right: -18px; + } + + .mat-fab-action-item { + width: 100%; + height: $mobile-toolbar-height; + + @media #{$mat-gt-sm} { + height: 46px; + } + + .tb-dashboard-action-panels { + height: $mobile-toolbar-height; + + @media #{$mat-gt-sm} { + height: 46px; + } + + .tb-dashboard-action-panel { + min-width: 0; + height: $half-mobile-toolbar-height; + + @media #{$mat-lt-md} { + mat-menu{ + margin: 0; + } + } + + @media #{$mat-gt-sm} { + height: 46px; + } + + > div { + height: $half-mobile-toolbar-height; + + @media #{$mat-gt-sm} { + height: 46px; + } + } + + mat-select { + margin: 0; + pointer-events: all; + } + + tb-states-component { + pointer-events: all; + } + + button.mat-icon-button:not(.tb-mat-32) { + min-width: 40px; + + @media #{$mat-lt-md} { + min-width: 28px; + padding: 2px; + margin: 0; + .mat-button-wrapper { + display: block; + line-height: 24px; + } + } + } + } + } + } + } + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-toolbar.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-toolbar.component.ts new file mode 100644 index 0000000000..bee1c2838f --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-toolbar.component.ts @@ -0,0 +1,47 @@ +/// +/// Copyright © 2016-2019 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, OnDestroy, OnInit, ViewEncapsulation, Input, Output, EventEmitter } from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; + +@Component({ + selector: 'tb-dashboard-toolbar', + templateUrl: './dashboard-toolbar.component.html', + styleUrls: ['./dashboard-toolbar.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class DashboardToolbarComponent implements OnInit { + + @Input() + toolbarOpened: boolean; + + @Input() + forceFullscreen: boolean; + + @Output() + triggerClick = new EventEmitter(); + + constructor() { + } + + ngOnInit(): void { + } + + onTriggerClick() { + this.triggerClick.emit(); + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard.module.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard.module.ts index 4b66eb7a7e..5ab82cd49a 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard.module.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard.module.ts @@ -14,16 +14,18 @@ /// limitations under the License. /// -import {NgModule} from '@angular/core'; -import {CommonModule} from '@angular/common'; -import {SharedModule} from '@shared/shared.module'; -import {HomeDialogsModule} from '../../dialogs/home-dialogs.module'; -import {DashboardFormComponent} from '@modules/home/pages/dashboard/dashboard-form.component'; -import {ManageDashboardCustomersDialogComponent} from '@modules/home/pages/dashboard/manage-dashboard-customers-dialog.component'; -import {DashboardRoutingModule} from './dashboard-routing.module'; -import {MakeDashboardPublicDialogComponent} from '@modules/home/pages/dashboard/make-dashboard-public-dialog.component'; -import {HomeComponentsModule} from '@modules/home/components/home-components.module'; +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from '@shared/shared.module'; +import { HomeDialogsModule } from '../../dialogs/home-dialogs.module'; +import { DashboardFormComponent } from '@modules/home/pages/dashboard/dashboard-form.component'; +import { ManageDashboardCustomersDialogComponent } from '@modules/home/pages/dashboard/manage-dashboard-customers-dialog.component'; +import { DashboardRoutingModule } from './dashboard-routing.module'; +import { MakeDashboardPublicDialogComponent } from '@modules/home/pages/dashboard/make-dashboard-public-dialog.component'; +import { HomeComponentsModule } from '@modules/home/components/home-components.module'; import { DashboardTabsComponent } from '@home/pages/dashboard/dashboard-tabs.component'; +import { DashboardPageComponent } from '@home/pages/dashboard/dashboard-page.component'; +import { DashboardToolbarComponent } from './dashboard-toolbar.component'; @NgModule({ entryComponents: [ @@ -36,7 +38,9 @@ import { DashboardTabsComponent } from '@home/pages/dashboard/dashboard-tabs.com DashboardFormComponent, DashboardTabsComponent, ManageDashboardCustomersDialogComponent, - MakeDashboardPublicDialogComponent + MakeDashboardPublicDialogComponent, + DashboardToolbarComponent, + DashboardPageComponent ], imports: [ CommonModule, diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts index c29628003e..93335e7aa2 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts @@ -303,9 +303,11 @@ export class DashboardsTableConfigResolver implements Resolve +
+ +

widget.select-widget-type

+ + +
+ + +
+
+
+
+ +
+
+
+
+ + +
+
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 new file mode 100644 index 0000000000..60ba753e8f --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/widget/select-widget-type-dialog.component.scss @@ -0,0 +1,50 @@ +/** + * Copyright © 2016-2019 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 ::ng-deep { + button.tb-card-button { + width: 100%; + height: 100%; + max-width: 240px; + .mat-button-wrapper { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + mat-icon { + margin: auto; + } + span { + height: 18px; + min-height: 18px; + max-height: 18px; + padding: 0 0 20px 0; + margin: auto; + font-size: 18px; + font-weight: 400; + line-height: 18px; + white-space: normal; + } + } + &.mat-raised-button.mat-primary { + .mat-ripple-element { + opacity: 0.3; + background-color: rgba(255, 255, 255, 0.3); + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/widget/select-widget-type-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/widget/select-widget-type-dialog.component.ts new file mode 100644 index 0000000000..6e854cb96a --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/widget/select-widget-type-dialog.component.ts @@ -0,0 +1,52 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { MatDialogRef } from '@angular/material'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { Router } from '@angular/router'; +import { widgetType, widgetTypesData } from '@shared/models/widget.models'; + +@Component({ + selector: 'tb-select-widget-type-dialog', + templateUrl: './select-widget-type-dialog.component.html', + styleUrls: ['./select-widget-type-dialog.component.scss'] +}) +export class SelectWidgetTypeDialogComponent extends + DialogComponent { + + widgetTypes = widgetType; + + allWidgetTypes = Object.keys(widgetType); + + widgetTypesDataMap = widgetTypesData; + + constructor(protected store: Store, + protected router: Router, + public dialogRef: MatDialogRef) { + super(store, router, dialogRef); + } + + cancel(): void { + this.dialogRef.close(null); + } + + typeSelected(type: widgetType) { + this.dialogRef.close(type); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html new file mode 100644 index 0000000000..224490a5a4 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html @@ -0,0 +1,255 @@ + + +
+
+ + + + + + + + + {{ widgetTypesDataMap.get(type).name | translate }} + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+
+
+ + + + +
+
+ +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+
+
+
+ + +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+ +
+
+ + +
+
+
+
+
+
+
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 new file mode 100644 index 0000000000..220b388c39 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.scss @@ -0,0 +1,186 @@ +/** + * Copyright © 2016-2019 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. + */ + +$edit-toolbar-height: 40px !default; + +tb-widget-editor { + flex: 1; + display: flex; + flex-direction: column; +} + + +.tb-editor { + .tb-split { + box-sizing: border-box; + overflow-x: hidden; + overflow-y: auto; + } + + mat-form-field.resource-field { + max-height: 40px; + margin: 10px 0px 0px 0px; + .mat-form-field-wrapper { + padding-bottom: 0; + .mat-form-field-flex { + max-height: 40px; + .mat-form-field-infix { + border: 0; + } + } + .mat-form-field-underline { + bottom: 0; + } + } + } + + .ace_editor { + font-size: 14px !important; + } + + .tb-content { + border: 1px solid #c0c0c0; + } + + .gutter { + background-color: transparent; + + background-repeat: no-repeat; + background-position: 50%; + + &.gutter-horizontal { + cursor: col-resize; + background-image: url("../../../../../assets/split.js/grips/vertical.png"); + } + + &.gutter-vertical { + cursor: row-resize; + background-image: url("../../../../../assets/split.js/grips/horizontal.png"); + } + } + + .tb-split.tb-split-horizontal, + .gutter.gutter-horizontal { + float: left; + height: 100%; + } + + .tb-split.tb-split-vertical { + display: flex; + + .tb-split.tb-content { + height: 100%; + } + } +} + +.tb-split-vertical { + mat-tab-group { + .mat-tab-body-wrapper { + height: calc(100% - 49px); + + mat-tab-body { + height: 100%; + + & > div { + height: 100%; + } + } + } + } +} + +div.tb-editor-area-title-panel { + position: absolute; + top: 5px; + right: 20px; + z-index: 5; + font-size: .8rem; + font-weight: 500; + + label { + padding: 4px; + color: #00acc1; + text-transform: uppercase; + background: rgba(220, 220, 220, .35); + border-radius: 5px; + &:not(:last-child) { + margin-right: 4px; + } + } + + button.mat-button, button.mat-icon-button, button.mat-icon-button.tb-mat-32 { + align-items: center; + vertical-align: middle; + min-width: 32px; + min-height: 15px; + padding: 4px; + margin: 0; + font-size: .8rem; + line-height: 15px; + color: #7b7b7b; + background: rgba(220, 220, 220, .35); + &:not(:last-child) { + margin-right: 4px; + } + } +} + +.tb-resize-container { + position: relative; + width: 100%; + height: 100%; + overflow-y: auto; + + .ace_editor { + height: 100%; + } +} + +mat-toolbar.tb-edit-toolbar { + + min-height: $edit-toolbar-height !important; + max-height: $edit-toolbar-height !important; + + button.mat-button { + min-width: 65px; + min-height: 30px; + font-size: 12px; + line-height: 30px; + + mat-icon { + font-size: 20px; + } + + span { + padding-right: 6px; + } + } + + mat-form-field { + input { + font-size: 1.2rem; + font-weight: 400; + letter-spacing: .005em; + } + div.mat-form-field-infix { + padding-bottom: 5px; + } + &.tb-widget-title { + min-width: 250px; + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts new file mode 100644 index 0000000000..ef07407bd3 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts @@ -0,0 +1,626 @@ +/// +/// Copyright © 2016-2019 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, ElementRef, Inject, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core'; +import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { WidgetService } from '@core/http/widget.service'; +import { WidgetInfo } from '@home/models/widget-component.models'; +import { WidgetConfig, widgetType, WidgetType, widgetTypesData, Widget } from '@shared/models/widget.models'; +import { ActivatedRoute } from '@angular/router'; +import { deepClone } from '@core/utils'; +import { HasDirtyFlag } from '@core/guards/confirm-on-exit.guard'; +import { AuthUser } from '@shared/models/user.model'; +import { getCurrentAuthUser } from '@core/auth/auth.selectors'; +import { Authority } from '@shared/models/authority.enum'; +import { NULL_UUID } from '@shared/models/id/has-uuid'; +import { Hotkey, HotkeysService } from 'angular2-hotkeys'; +import { TranslateService } from '@ngx-translate/core'; +import { getCurrentIsLoading } from '@app/core/interceptors/load.selectors'; +import * as ace from 'ace-builds'; +import { css_beautify, html_beautify } from 'js-beautify'; +import { CancelAnimationFrame, RafService } from '@core/services/raf.service'; +import { WINDOW } from '@core/services/window.service'; +import { WindowMessage } from '@shared/models/window-message.model'; +import { ExceptionData } from '@shared/models/error.models'; +import Timeout = NodeJS.Timeout; +import { ActionNotificationHide, ActionNotificationShow } from '@core/notification/notification.actions'; + +@Component({ + selector: 'tb-widget-editor', + templateUrl: './widget-editor.component.html', + styleUrls: ['./widget-editor.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class WidgetEditorComponent extends PageComponent implements OnInit, OnDestroy, HasDirtyFlag { + + @ViewChild('topPanel', {static: true}) + topPanelElmRef: ElementRef; + + @ViewChild('topLeftPanel', {static: true}) + topLeftPanelElmRef: ElementRef; + + @ViewChild('topRightPanel', {static: true}) + topRightPanelElmRef: ElementRef; + + @ViewChild('bottomPanel', {static: true}) + bottomPanelElmRef: ElementRef; + + @ViewChild('javascriptPanel', {static: true}) + javascriptPanelElmRef: ElementRef; + + @ViewChild('framePanel', {static: true}) + framePanelElmRef: ElementRef; + + @ViewChild('htmlInput', {static: true}) + htmlInputElmRef: ElementRef; + + @ViewChild('cssInput', {static: true}) + cssInputElmRef: ElementRef; + + @ViewChild('settingsJsonInput', {static: true}) + settingsJsonInputElmRef: ElementRef; + + @ViewChild('dataKeySettingsJsonInput', {static: true}) + dataKeySettingsJsonInputElmRef: ElementRef; + + @ViewChild('javascriptInput', {static: true}) + javascriptInputElmRef: ElementRef; + + @ViewChild('widgetIFrame', {static: true}) + widgetIFrameElmRef: ElementRef; + + iframe: JQuery; + + widgetTypes = widgetType; + allWidgetTypes = Object.keys(widgetType); + widgetTypesDataMap = widgetTypesData; + + authUser: AuthUser; + + isReadOnly: boolean; + + widgetsBundle: WidgetsBundle; + widgetType: WidgetType; + widget: WidgetInfo; + origWidget: WidgetInfo; + + isDirty = false; + + fullscreen = false; + htmlFullscreen = false; + cssFullscreen = false; + jsonSettingsFullscreen = false; + jsonDataKeySettingsFullscreen = false; + javascriptFullscreen = false; + iFrameFullscreen = false; + + aceEditors: ace.Ace.Editor[] = []; + editorsResizeCafs: {[editorId: string]: CancelAnimationFrame} = {}; + htmlEditor: ace.Ace.Editor; + cssEditor: ace.Ace.Editor; + jsonSettingsEditor: ace.Ace.Editor; + dataKeyJsonSettingsEditor: ace.Ace.Editor; + jsEditor: ace.Ace.Editor; + aceResizeListeners: { element: any, resizeListener: any }[] = []; + + onWindowMessageListener = this.onWindowMessage.bind(this); + + iframeWidgetEditModeInited = false; + saveWidgetPending = false; + saveWidgetAsPending = false; + + gotError = false; + errorMarkers: number[] = []; + errorAnnotationId = -1; + + saveWidgetTimeout: Timeout; + + constructor(protected store: Store, + @Inject(WINDOW) private window: Window, + private route: ActivatedRoute, + private widgetService: WidgetService, + private hotkeysService: HotkeysService, + private translate: TranslateService, + private raf: RafService) { + super(store); + + this.authUser = getCurrentAuthUser(store); + + this.widgetsBundle = this.route.snapshot.data.widgetsBundle; + if (this.authUser.authority === Authority.TENANT_ADMIN) { + this.isReadOnly = !this.widgetsBundle || this.widgetsBundle.tenantId.id === NULL_UUID; + } else { + this.isReadOnly = this.authUser.authority !== Authority.SYS_ADMIN; + } + this.widgetType = this.route.snapshot.data.widgetEditorData.widgetType; + this.widget = this.route.snapshot.data.widgetEditorData.widget; + if (this.widgetType) { + const config = JSON.parse(this.widget.defaultConfig); + this.widget.defaultConfig = JSON.stringify(config); + } + this.origWidget = deepClone(this.widget); + if (!this.widgetType) { + this.isDirty = true; + } + } + + ngOnInit(): void { + this.initHotKeys(); + this.initSplitLayout(); + this.initAceEditors(); + this.iframe = $(this.widgetIFrameElmRef.nativeElement); + this.window.addEventListener('message', this.onWindowMessageListener); + this.iframe.attr('data-widget', JSON.stringify(this.widget)); + this.iframe.attr('src', '/widget-editor'); + } + + ngOnDestroy(): void { + this.window.removeEventListener('message', this.onWindowMessageListener); + this.aceResizeListeners.forEach((resizeListener) => { + // @ts-ignore + removeResizeListener(resizeListener.element, resizeListener.resizeListener); + }); + } + + private initHotKeys(): void { + this.hotkeysService.add( + new Hotkey('ctrl+q', (event: KeyboardEvent) => { + if (!getCurrentIsLoading(this.store) && !this.undoDisabled()) { + event.preventDefault(); + this.undoWidget(); + } + return false; + }, ['INPUT', 'SELECT', 'TEXTAREA'], + this.translate.instant('widget.undo')) + ); + this.hotkeysService.add( + new Hotkey('ctrl+s', (event: KeyboardEvent) => { + if (!getCurrentIsLoading(this.store) && !this.saveDisabled()) { + event.preventDefault(); + this.saveWidget(); + } + return false; + }, ['INPUT', 'SELECT', 'TEXTAREA'], + this.translate.instant('widget.save')) + ); + this.hotkeysService.add( + new Hotkey('shift+ctrl+s', (event: KeyboardEvent) => { + if (!getCurrentIsLoading(this.store) && !this.saveAsDisabled()) { + event.preventDefault(); + this.saveWidgetAs(); + } + return false; + }, ['INPUT', 'SELECT', 'TEXTAREA'], + this.translate.instant('widget.saveAs')) + ); + this.hotkeysService.add( + new Hotkey('shift+ctrl+f', (event: KeyboardEvent) => { + event.preventDefault(); + this.fullscreen = !this.fullscreen; + return false; + }, ['INPUT', 'SELECT', 'TEXTAREA'], + this.translate.instant('widget.toggle-fullscreen')) + ); + this.hotkeysService.add( + new Hotkey('ctrl+enter', (event: KeyboardEvent) => { + event.preventDefault(); + this.applyWidgetScript(); + return false; + }, ['INPUT', 'SELECT', 'TEXTAREA'], + this.translate.instant('widget.run')) + ); + } + + private initSplitLayout() { + Split([this.topPanelElmRef.nativeElement, this.bottomPanelElmRef.nativeElement], { + sizes: [35, 65], + gutterSize: 8, + cursor: 'row-resize', + direction: 'vertical' + }); + Split([this.topLeftPanelElmRef.nativeElement, this.topRightPanelElmRef.nativeElement], { + sizes: [50, 50], + gutterSize: 8, + cursor: 'col-resize' + }); + Split([this.javascriptPanelElmRef.nativeElement, this.framePanelElmRef.nativeElement], { + sizes: [50, 50], + gutterSize: 8, + cursor: 'col-resize' + }); + } + + private initAceEditors() { + this.htmlEditor = this.createAceEditor(this.htmlInputElmRef, 'html'); + this.htmlEditor.on('input', () => { + const editorValue = this.htmlEditor.getValue(); + if (this.widget.templateHtml !== editorValue) { + this.widget.templateHtml = editorValue; + this.isDirty = true; + } + }); + this.cssEditor = this.createAceEditor(this.cssInputElmRef, 'css'); + this.cssEditor.on('input', () => { + const editorValue = this.cssEditor.getValue(); + if (this.widget.templateCss !== editorValue) { + this.widget.templateCss = editorValue; + this.isDirty = true; + } + }); + this.jsonSettingsEditor = this.createAceEditor(this.settingsJsonInputElmRef, 'json'); + this.jsonSettingsEditor.on('input', () => { + const editorValue = this.jsonSettingsEditor.getValue(); + if (this.widget.settingsSchema !== editorValue) { + this.widget.settingsSchema = editorValue; + this.isDirty = true; + } + }); + this.dataKeyJsonSettingsEditor = this.createAceEditor(this.dataKeySettingsJsonInputElmRef, 'json'); + this.dataKeyJsonSettingsEditor.on('input', () => { + const editorValue = this.dataKeyJsonSettingsEditor.getValue(); + if (this.widget.dataKeySettingsSchema !== editorValue) { + this.widget.dataKeySettingsSchema = editorValue; + this.isDirty = true; + } + }); + this.jsEditor = this.createAceEditor(this.javascriptInputElmRef, 'javascript'); + this.jsEditor.on('input', () => { + const editorValue = this.jsEditor.getValue(); + if (this.widget.controllerScript !== editorValue) { + this.widget.controllerScript = editorValue; + this.isDirty = true; + } + }); + this.jsEditor.on('change', () => { + this.cleanupJsErrors(); + }); + this.setAceEditorValues(); + } + + private setAceEditorValues() { + this.htmlEditor.setValue(this.widget.templateHtml ? this.widget.templateHtml : '', -1); + this.cssEditor.setValue(this.widget.templateCss ? this.widget.templateCss : '', -1); + this.jsonSettingsEditor.setValue(this.widget.settingsSchema ? this.widget.settingsSchema : '', -1); + this.dataKeyJsonSettingsEditor.setValue(this.widget.dataKeySettingsSchema ? this.widget.dataKeySettingsSchema : '', -1); + this.jsEditor.setValue(this.widget.controllerScript ? this.widget.controllerScript : '', -1); + } + + private createAceEditor(editorElementRef: ElementRef, mode: string): ace.Ace.Editor { + const editorElement = editorElementRef.nativeElement; + let editorOptions: Partial = { + mode: `ace/mode/${mode}`, + showGutter: true, + showPrintMargin: true + }; + const advancedOptions = { + enableSnippets: true, + enableBasicAutocompletion: true, + enableLiveAutocompletion: true + }; + editorOptions = {...editorOptions, ...advancedOptions}; + const aceEditor = ace.edit(editorElement, editorOptions); + aceEditor.session.setUseWrapMode(true); + this.aceEditors.push(aceEditor); + + const resizeListener = this.onAceEditorResize.bind(this, aceEditor); + + // @ts-ignore + addResizeListener(editorElement, resizeListener); + this.aceResizeListeners.push({element: editorElement, resizeListener}); + return aceEditor; + } + + private onAceEditorResize(aceEditor: ace.Ace.Editor) { + if (this.editorsResizeCafs[aceEditor.id]) { + this.editorsResizeCafs[aceEditor.id](); + delete this.editorsResizeCafs[aceEditor.id]; + } + this.editorsResizeCafs[aceEditor.id] = this.raf.raf(() => { + aceEditor.resize(); + aceEditor.renderer.updateFull(); + }); + } + + private onWindowMessage(event: MessageEvent) { + let message: WindowMessage; + if (event.data) { + try { + message = JSON.parse(event.data); + } catch (e) {} + } + if (message) { + switch (message.type) { + case 'widgetException': + this.onWidgetException(message.data); + break; + case 'widgetEditModeInited': + this.onWidgetEditModeInited(); + break; + case 'widgetEditUpdated': + this.onWidgetEditUpdated(message.data); + break; + } + } + } + + private onWidgetEditModeInited() { + this.iframeWidgetEditModeInited = true; + if (this.saveWidgetPending || this.saveWidgetAsPending) { + if (!this.saveWidgetTimeout) { + this.saveWidgetTimeout = setTimeout(() => { + if (!this.gotError) { + if (this.saveWidgetPending) { + this.commitSaveWidget(); + } else if (this.saveWidgetAsPending) { + this.commitSaveWidgetAs(); + } + } else { + this.store.dispatch(new ActionNotificationShow( + {message: this.translate.instant('widget.unable-to-save-widget-error'), type: 'error'})); + this.saveWidgetPending = false; + this.saveWidgetAsPending = false; + } + this.saveWidgetTimeout = undefined; + }, 1500); + } + } + } + + private onWidgetEditUpdated(widget: Widget) { + this.widget.sizeX = widget.sizeX / 2; + this.widget.sizeY = widget.sizeY / 2; + this.widget.defaultConfig = JSON.stringify(widget.config); + this.iframe.attr('data-widget', JSON.stringify(this.widget)); + this.isDirty = true; + } + + private onWidgetException(details: ExceptionData) { + if (!this.gotError) { + this.gotError = true; + let errorInfo = 'Error:'; + if (details.name) { + errorInfo += ' ' + details.name + ':'; + } + if (details.message) { + errorInfo += ' ' + details.message; + } + if (details.lineNumber) { + errorInfo += '
Line ' + details.lineNumber; + if (details.columnNumber) { + errorInfo += ' column ' + details.columnNumber; + } + errorInfo += ' of script.'; + } + if (!this.saveWidgetPending && !this.saveWidgetAsPending) { + this.store.dispatch(new ActionNotificationShow( + {message: errorInfo, type: 'error', target: 'javascriptPanel'})); + } + if (details.lineNumber) { + const line = details.lineNumber - 1; + let column = 0; + if (details.columnNumber) { + column = details.columnNumber; + } + const errorMarkerId = this.jsEditor.session.addMarker(new ace.Range(line, 0, line, Infinity), + 'ace_active-line', 'screenLine'); + this.errorMarkers.push(errorMarkerId); + const annotations = this.jsEditor.session.getAnnotations(); + const errorAnnotation: ace.Ace.Annotation = { + row: line, + column, + text: details.message, + type: 'error' + }; + this.errorAnnotationId = annotations.push(errorAnnotation) - 1; + this.jsEditor.session.setAnnotations(annotations); + } + } + } + + private cleanupJsErrors() { + this.store.dispatch(new ActionNotificationHide({})); + this.errorMarkers.forEach((errorMarker) => { + this.jsEditor.session.removeMarker(errorMarker); + }); + this.errorMarkers.length = 0; + if (this.errorAnnotationId > -1) { + const annotations = this.jsEditor.session.getAnnotations(); + annotations.splice(this.errorAnnotationId, 1); + this.jsEditor.session.setAnnotations(annotations); + this.errorAnnotationId = -1; + } + } + + private commitSaveWidget() { + // TODO: + this.saveWidgetPending = false; + } + + private commitSaveWidgetAs() { + // TODO: + this.saveWidgetAsPending = false; + } + + applyWidgetScript(): void { + this.cleanupJsErrors(); + this.gotError = false; + this.iframeWidgetEditModeInited = false; + const config: WidgetConfig = JSON.parse(this.widget.defaultConfig); + config.title = this.widget.widgetName; + this.widget.defaultConfig = JSON.stringify(config); + this.iframe.attr('data-widget', JSON.stringify(this.widget)); + this.iframe[0].contentWindow.location.reload(true); + } + + undoWidget(): void { + this.widget = deepClone(this.origWidget); + this.setAceEditorValues(); + this.isDirty = false; + this.applyWidgetScript(); + } + + saveWidget(): void { + if (!this.widget.widgetName) { + this.store.dispatch(new ActionNotificationShow( + {message: this.translate.instant('widget.missing-widget-title-error'), type: 'error'})); + } else { + this.saveWidgetPending = true; + this.applyWidgetScript(); + } + } + + saveWidgetAs(): void { + this.saveWidgetAsPending = true; + this.applyWidgetScript(); + } + + undoDisabled(): boolean { + return !this.isDirty + || !this.iframeWidgetEditModeInited + || this.saveWidgetPending + || this.saveWidgetAsPending; + } + + saveDisabled(): boolean { + return this.isReadOnly + || !this.isDirty + || !this.iframeWidgetEditModeInited + || this.saveWidgetPending + || this.saveWidgetAsPending; + } + + saveAsDisabled(): boolean { + return !this.iframeWidgetEditModeInited + || this.saveWidgetPending + || this.saveWidgetAsPending; + } + + beautifyCss(): void { + const res = css_beautify(this.widget.templateCss, {indent_size: 4}); + if (this.widget.templateCss !== res) { + this.isDirty = true; + this.widget.templateCss = res; + this.cssEditor.setValue(this.widget.templateCss ? this.widget.templateCss : '', -1); + } + } + + beautifyHtml(): void { + const res = html_beautify(this.widget.templateHtml, {indent_size: 4, wrap_line_length: 60}); + if (this.widget.templateHtml !== res) { + this.isDirty = true; + this.widget.templateHtml = res; + this.htmlEditor.setValue(this.widget.templateHtml ? this.widget.templateHtml : '', -1); + } + } + + beautifyJson(): void { + const res = js_beautify(this.widget.settingsSchema, {indent_size: 4}); + if (this.widget.settingsSchema !== res) { + this.isDirty = true; + this.widget.settingsSchema = res; + this.jsonSettingsEditor.setValue(this.widget.settingsSchema ? this.widget.settingsSchema : '', -1); + } + } + + beautifyDataKeyJson(): void { + const res = js_beautify(this.widget.dataKeySettingsSchema, {indent_size: 4}); + if (this.widget.dataKeySettingsSchema !== res) { + this.isDirty = true; + this.widget.dataKeySettingsSchema = res; + this.dataKeyJsonSettingsEditor.setValue(this.widget.dataKeySettingsSchema ? this.widget.dataKeySettingsSchema : '', -1); + } + } + + beautifyJs(): void { + const res = js_beautify(this.widget.controllerScript, {indent_size: 4, wrap_line_length: 60}); + if (this.widget.controllerScript !== res) { + this.isDirty = true; + this.widget.controllerScript = res; + this.jsEditor.setValue(this.widget.controllerScript ? this.widget.controllerScript : '', -1); + } + } + + removeResource(index: number) { + if (index > -1) { + if (this.widget.resources.splice(index, 1).length > 0) { + this.isDirty = true; + } + } + } + + addResource() { + this.widget.resources.push({url: ''}); + this.isDirty = true; + } + + widetTypeChanged() { + const config: WidgetConfig = JSON.parse(this.widget.defaultConfig); + if (this.widget.type !== widgetType.rpc && + this.widget.type !== widgetType.alarm) { + if (config.targetDeviceAliases) { + delete config.targetDeviceAliases; + } + if (config.alarmSource) { + delete config.alarmSource; + } + if (!config.datasources) { + config.datasources = []; + } + if (!config.timewindow) { + config.timewindow = { + realtime: { + timewindowMs: 60000 + } + }; + } + } else if (this.widget.type === widgetType.rpc) { + if (config.datasources) { + delete config.datasources; + } + if (config.alarmSource) { + delete config.alarmSource; + } + if (config.timewindow) { + delete config.timewindow; + } + if (!config.targetDeviceAliases) { + config.targetDeviceAliases = []; + } + } else { // alarm + if (config.datasources) { + delete config.datasources; + } + if (config.targetDeviceAliases) { + delete config.targetDeviceAliases; + } + if (!config.alarmSource) { + config.alarmSource = {}; + } + if (!config.timewindow) { + config.timewindow = { + realtime: { + timewindowMs: 24 * 60 * 60 * 1000 + } + }; + } + } + this.widget.defaultConfig = JSON.stringify(config); + this.isDirty = true; + } +} diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts b/ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts index 98d8fcb797..5f452b346f 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts @@ -17,20 +17,24 @@ import { Injectable, NgModule } from '@angular/core'; import { ActivatedRouteSnapshot, Resolve, RouterModule, Routes } from '@angular/router'; -import {EntitiesTableComponent} from '../../components/entity/entities-table.component'; -import {Authority} from '@shared/models/authority.enum'; -import {RuleChainsTableConfigResolver} from '@modules/home/pages/rulechain/rulechains-table-config.resolver'; -import {WidgetsBundlesTableConfigResolver} from '@modules/home/pages/widget/widgets-bundles-table-config.resolver'; +import { EntitiesTableComponent } from '../../components/entity/entities-table.component'; +import { Authority } from '@shared/models/authority.enum'; +import { WidgetsBundlesTableConfigResolver } from '@modules/home/pages/widget/widgets-bundles-table-config.resolver'; import { WidgetLibraryComponent } from '@home/pages/widget/widget-library.component'; import { BreadCrumbConfig, BreadCrumbLabelFunction } from '@shared/components/breadcrumb'; -import { User } from '@shared/models/user.model'; -import { Store } from '@ngrx/store'; -import { AppState } from '@core/core.state'; -import { UserService } from '@core/http/user.service'; import { Observable } from 'rxjs'; -import { getCurrentAuthUser } from '@core/auth/auth.selectors'; import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; import { WidgetService } from '@core/http/widget.service'; +import { WidgetEditorComponent } from '@home/pages/widget/widget-editor.component'; +import { map } from 'rxjs/operators'; +import { toWidgetInfo, WidgetInfo } from '@home/models/widget-component.models'; +import { widgetType, WidgetType } from '@app/shared/models/widget.models'; +import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard'; + +export interface WidgetEditorData { + widgetType: WidgetType; + widget: WidgetInfo; +} @Injectable() export class WidgetsBundleResolver implements Resolve { @@ -39,13 +43,61 @@ export class WidgetsBundleResolver implements Resolve { } resolve(route: ActivatedRouteSnapshot): Observable { - const widgetsBundleId = route.params.widgetsBundleId; + let widgetsBundleId = route.params.widgetsBundleId; + if (!widgetsBundleId) { + widgetsBundleId = route.parent.params.widgetsBundleId; + } return this.widgetsService.getWidgetsBundle(widgetsBundleId); } } +@Injectable() +export class WidgetEditorDataResolver implements Resolve { + + constructor(private widgetsService: WidgetService) { + } + + resolve(route: ActivatedRouteSnapshot): Observable { + const widgetTypeId = route.params.widgetTypeId; + return this.widgetsService.getWidgetTypeById(widgetTypeId).pipe( + map((result) => { + return { + widgetType: result, + widget: toWidgetInfo(result) + }; + }) + ); + } +} + +@Injectable() +export class WidgetEditorAddDataResolver implements Resolve { + + constructor(private widgetsService: WidgetService) { + } + + resolve(route: ActivatedRouteSnapshot): Observable { + let widgetTypeParam = route.params.widgetType as widgetType; + if (!widgetTypeParam) { + widgetTypeParam = widgetType.timeseries; + } + return this.widgetsService.getWidgetTemplate(widgetTypeParam).pipe( + map((widget) => { + widget.widgetName = null; + return { + widgetType: null, + widget + }; + }) + ); + } +} + export const widgetTypesBreadcumbLabelFunction: BreadCrumbLabelFunction = ((route, translate) => route.data.widgetsBundle.title); +export const widgetEditorBreadcumbLabelFunction: BreadCrumbLabelFunction = + ((route, translate, component) => component ? (component as WidgetEditorComponent).widget.widgetName : ''); + export const routes: Routes = [ { path: 'widgets-bundles', @@ -69,10 +121,7 @@ export const routes: Routes = [ }, { path: ':widgetsBundleId/widgetTypes', - component: WidgetLibraryComponent, data: { - auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN], - title: 'widget.widget-library', breadcrumb: { labelFunction: widgetTypesBreadcumbLabelFunction, icon: 'now_widgets' @@ -80,7 +129,49 @@ export const routes: Routes = [ }, resolve: { widgetsBundle: WidgetsBundleResolver - } + }, + children: [ + { + path: '', + component: WidgetLibraryComponent, + data: { + auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN], + title: 'widget.widget-library' + } + }, + { + path: ':widgetTypeId', + component: WidgetEditorComponent, + canDeactivate: [ConfirmOnExitGuard], + data: { + auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN], + title: 'widget.editor', + breadcrumb: { + labelFunction: widgetEditorBreadcumbLabelFunction, + icon: 'insert_chart' + } as BreadCrumbConfig + }, + resolve: { + widgetEditorData: WidgetEditorDataResolver + } + }, + { + path: 'add/:widgetType', + component: WidgetEditorComponent, + canDeactivate: [ConfirmOnExitGuard], + data: { + auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN], + title: 'widget.editor', + breadcrumb: { + labelFunction: widgetEditorBreadcumbLabelFunction, + icon: 'insert_chart' + } as BreadCrumbConfig + }, + resolve: { + widgetEditorData: WidgetEditorAddDataResolver + } + } + ] } ] } @@ -91,7 +182,9 @@ export const routes: Routes = [ exports: [RouterModule], providers: [ WidgetsBundlesTableConfigResolver, - WidgetsBundleResolver + WidgetsBundleResolver, + WidgetEditorDataResolver, + WidgetEditorAddDataResolver ] }) export class WidgetLibraryRoutingModule { } diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.ts b/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.ts index 3400d56514..9f1f2e39b9 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.ts +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.ts @@ -21,7 +21,7 @@ import { PageComponent } from '@shared/components/page.component'; import { AuthUser } from '@shared/models/user.model'; import { getCurrentAuthUser } from '@core/auth/auth.selectors'; import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; -import { ActivatedRoute } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { Authority } from '@shared/models/authority.enum'; import { NULL_UUID } from '@shared/models/id/has-uuid'; import { Observable } from 'rxjs'; @@ -34,6 +34,13 @@ import { DashboardCallbacks, WidgetsData } from '@home/models/dashboard-componen import { IAliasController } from '@app/core/api/widget-api.models'; import { toWidgetInfo } from '@home/models/widget-component.models'; import { DummyAliasController } from '@core/api/alias-controller'; +import { + DeviceCredentialsDialogComponent, + DeviceCredentialsDialogData +} from '@home/pages/device/device-credentials-dialog.component'; +import { DeviceCredentials } from '@shared/models/device.models'; +import { MatDialog } from '@angular/material/dialog'; +import { SelectWidgetTypeDialogComponent } from '@home/pages/widget/select-widget-type-dialog.component'; @Component({ selector: 'tb-widget-library', @@ -83,8 +90,10 @@ export class WidgetLibraryComponent extends PageComponent implements OnInit { constructor(protected store: Store, private route: ActivatedRoute, + private router: Router, private widgetService: WidgetService, - private dialogService: DialogService) { + private dialogService: DialogService, + private dialog: MatDialog) { super(store); this.authUser = getCurrentAuthUser(store); @@ -163,33 +172,43 @@ export class WidgetLibraryComponent extends PageComponent implements OnInit { } importWidgetType($event: Event): void { - if (event) { - event.stopPropagation(); + if ($event) { + $event.stopPropagation(); } this.dialogService.todo(); } openWidgetType($event: Event, widget?: Widget): void { - if (event) { - event.stopPropagation(); + if ($event) { + $event.stopPropagation(); } if (widget) { - this.dialogService.todo(); + this.router.navigate([widget.typeId.id], {relativeTo: this.route}); } else { - this.dialogService.todo(); + this.dialog.open(SelectWidgetTypeDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'] + }).afterClosed().subscribe( + (type) => { + if (type) { + this.router.navigate(['add', type], {relativeTo: this.route}); + } + } + ); } } exportWidgetType($event: Event, widget: Widget): void { - if (event) { - event.stopPropagation(); + if ($event) { + $event.stopPropagation(); } this.dialogService.todo(); } removeWidgetType($event: Event, widget: Widget): void { - if (event) { - event.stopPropagation(); + if ($event) { + $event.stopPropagation(); } this.dialogService.todo(); } diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-library.module.ts b/ui-ngx/src/app/modules/home/pages/widget/widget-library.module.ts index e0a1234fae..25ff3a9b78 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widget-library.module.ts +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-library.module.ts @@ -21,14 +21,19 @@ import {WidgetsBundleComponent} from '@modules/home/pages/widget/widgets-bundle. import {WidgetLibraryRoutingModule} from '@modules/home/pages/widget/widget-library-routing.module'; import {HomeComponentsModule} from '@modules/home/components/home-components.module'; import { WidgetLibraryComponent } from './widget-library.component'; +import { WidgetEditorComponent } from '@home/pages/widget/widget-editor.component'; +import { SelectWidgetTypeDialogComponent } from '@home/pages/widget/select-widget-type-dialog.component'; @NgModule({ entryComponents: [ - WidgetsBundleComponent + WidgetsBundleComponent, + SelectWidgetTypeDialogComponent ], declarations: [ WidgetsBundleComponent, - WidgetLibraryComponent + WidgetLibraryComponent, + WidgetEditorComponent, + SelectWidgetTypeDialogComponent ], imports: [ CommonModule, diff --git a/ui-ngx/src/app/shared/components/breadcrumb.component.html b/ui-ngx/src/app/shared/components/breadcrumb.component.html index 7f895270b4..35643ba4fd 100644 --- a/ui-ngx/src/app/shared/components/breadcrumb.component.html +++ b/ui-ngx/src/app/shared/components/breadcrumb.component.html @@ -17,7 +17,7 @@ -->

- {{ breadcrumb.ignoreTranslate ? breadcrumb.label : (breadcrumb.label | translate) }} + {{ breadcrumb.ignoreTranslate ? (breadcrumb.labelFunction ? breadcrumb.labelFunction() : breadcrumb.label) : (breadcrumb.label | translate) }}

@@ -26,7 +26,7 @@ {{ breadcrumb.icon }} - {{ breadcrumb.ignoreTranslate ? breadcrumb.label : (breadcrumb.label | translate) }} + {{ breadcrumb.ignoreTranslate ? (breadcrumb.labelFunction ? breadcrumb.labelFunction() : breadcrumb.label) : (breadcrumb.label | translate) }} @@ -34,7 +34,7 @@ {{ breadcrumb.icon }} - {{ breadcrumb.ignoreTranslate ? breadcrumb.label : (breadcrumb.label | translate) }} + {{ breadcrumb.ignoreTranslate ? (breadcrumb.labelFunction ? breadcrumb.labelFunction() : 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 1ea092d637..57c99d4340 100644 --- a/ui-ngx/src/app/shared/components/breadcrumb.component.ts +++ b/ui-ngx/src/app/shared/components/breadcrumb.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Component, Input, OnDestroy, OnInit } from '@angular/core'; import { BehaviorSubject, Subject } from 'rxjs'; import { BreadCrumb, BreadCrumbConfig } from './breadcrumb'; import { ActivatedRoute, ActivatedRouteSnapshot, NavigationEnd, Router } from '@angular/router'; @@ -28,6 +28,13 @@ import { TranslateService } from '@ngx-translate/core'; }) export class BreadcrumbComponent implements OnInit, OnDestroy { + activeComponentValue: any; + + @Input() + set activeComponent(activeComponent: any) { + this.activeComponentValue = activeComponent; + } + breadcrumbs$: Subject> = new BehaviorSubject>(this.buildBreadCrumbs(this.activatedRoute.snapshot)); routerEventsSubscription = this.router.events.pipe( @@ -61,9 +68,12 @@ export class BreadcrumbComponent implements OnInit, OnDestroy { const breadcrumbConfig = route.routeConfig.data.breadcrumb as BreadCrumbConfig; if (breadcrumbConfig && !breadcrumbConfig.skip) { let label; + let labelFunction; let ignoreTranslate; if (breadcrumbConfig.labelFunction) { - label = breadcrumbConfig.labelFunction(route, this.translate); + labelFunction = () => { + return breadcrumbConfig.labelFunction(route, this.translate, this.activeComponentValue); + }; ignoreTranslate = true; } else { label = breadcrumbConfig.label || 'home.home'; @@ -71,10 +81,11 @@ export class BreadcrumbComponent implements OnInit, OnDestroy { } const icon = breadcrumbConfig.icon || 'home'; const isMdiIcon = icon.startsWith('mdi:'); - const link = [ '/' + route.url.join('') ]; + const link = [ route.pathFromRoot.map(v => v.url.map(segment => segment.toString()).join('/')).join('/') ]; const queryParams = route.queryParams; const breadcrumb = { label, + labelFunction, ignoreTranslate, icon, isMdiIcon, @@ -89,5 +100,4 @@ export class BreadcrumbComponent implements OnInit, OnDestroy { } return newBreadcrumbs; } - } diff --git a/ui-ngx/src/app/shared/components/breadcrumb.ts b/ui-ngx/src/app/shared/components/breadcrumb.ts index 5db7b7b4c4..e7071ed2ca 100644 --- a/ui-ngx/src/app/shared/components/breadcrumb.ts +++ b/ui-ngx/src/app/shared/components/breadcrumb.ts @@ -19,6 +19,7 @@ import { TranslateService } from '@ngx-translate/core'; export interface BreadCrumb { label: string; + labelFunction?: () => string; ignoreTranslate: boolean; icon: string; isMdiIcon: boolean; @@ -26,7 +27,7 @@ export interface BreadCrumb { queryParams: Params; } -export type BreadCrumbLabelFunction = (route: ActivatedRouteSnapshot, translate: TranslateService) => string; +export type BreadCrumbLabelFunction = (route: ActivatedRouteSnapshot, translate: TranslateService, component: any) => string; export interface BreadCrumbConfig { labelFunction: BreadCrumbLabelFunction; diff --git a/ui-ngx/src/app/shared/components/circular-progress.directive.ts b/ui-ngx/src/app/shared/components/circular-progress.directive.ts new file mode 100644 index 0000000000..38166e629f --- /dev/null +++ b/ui-ngx/src/app/shared/components/circular-progress.directive.ts @@ -0,0 +1,83 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Directive, ElementRef, ViewContainerRef, ComponentFactoryResolver, ComponentRef, Input } from '@angular/core'; +import { Overlay } from '@angular/cdk/overlay'; +import { MatProgressBar, MatSpinner } from '@angular/material'; + +@Directive({ + selector: '[tb-circular-progress]' +}) +export class CircularProgressDirective { + + showProgressValue = false; + + children: JQuery; + + cssWidth: any; + + @Input('tb-circular-progress') + set showProgress(showProgress: boolean) { + if (this.showProgressValue !== showProgress) { + const element = this.elementRef.nativeElement; + this.showProgressValue = showProgress; + this.spinnerRef.instance._elementRef.nativeElement.style.display = showProgress ? 'block' : 'none'; + if (showProgress) { + this.cssWidth = $(element).prop('style').width; + if (!this.cssWidth) { + $(element).css('width', ''); + const width = $(element).prop('offsetWidth'); + $(element).css('width', width + 'px'); + } + this.children = $(element).children(); + $(element).empty(); + $(element).append($(this.spinnerRef.instance._elementRef.nativeElement)); + } else { + $(element).empty(); + $(element).append(this.children); + if (this.cssWidth) { + $(element).css('width', this.cssWidth); + } else { + $(element).css('width', ''); + } + } + } + } + + spinnerRef: ComponentRef; + + constructor(private elementRef: ElementRef, + private componentFactoryResolver: ComponentFactoryResolver, + private viewContainerRef: ViewContainerRef) { + this.createCircularProgress(); + } + + createCircularProgress() { + this.elementRef.nativeElement.style.position = 'relative'; + const factory = this.componentFactoryResolver.resolveComponentFactory(MatSpinner); + this.spinnerRef = this.viewContainerRef.createComponent(factory, 0); + this.spinnerRef.instance.mode = 'indeterminate'; + this.spinnerRef.instance.diameter = 20; + const el = this.spinnerRef.instance._elementRef.nativeElement; + el.style.margin = 'auto'; + el.style.position = 'absolute'; + el.style.left = '0'; + el.style.right = '0'; + el.style.top = '0'; + el.style.bottom = '0'; + el.style.display = 'none'; + } +} diff --git a/ui-ngx/src/app/shared/components/dashboard-select-panel.component.html b/ui-ngx/src/app/shared/components/dashboard-select-panel.component.html new file mode 100644 index 0000000000..6787d4b85c --- /dev/null +++ b/ui-ngx/src/app/shared/components/dashboard-select-panel.component.html @@ -0,0 +1,28 @@ + +
+ + {{'dashboard.select-dashboard' | translate}} + + + {{dashboard.title}} + + + +
diff --git a/ui-ngx/src/app/shared/components/dashboard-select-panel.component.scss b/ui-ngx/src/app/shared/components/dashboard-select-panel.component.scss new file mode 100644 index 0000000000..628af4eeed --- /dev/null +++ b/ui-ngx/src/app/shared/components/dashboard-select-panel.component.scss @@ -0,0 +1,55 @@ +/** + * Copyright © 2016-2019 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 { + min-width: 300px; + max-width: 320px; + max-height: 150px; + overflow-x: hidden; + overflow-y: auto; + background: #fff; + border-radius: 4px; + box-shadow: + 0 7px 8px -4px rgba(0, 0, 0, .2), + 0 13px 19px 2px rgba(0, 0, 0, .14), + 0 5px 24px 4px rgba(0, 0, 0, .12); + + @media (min-height: 350px) { + max-height: 250px; + } + + @media #{$mat-gt-xs} { + max-width: 100%; + } + + .mat-content { + background-color: #fff; + } +} + +:host ::ng-deep { + mat-form-field { + .mat-form-field-infix { + width: 100%; + mat-select { + .mat-select-value { + max-width: 100%; + } + } + } + } +} diff --git a/ui-ngx/src/app/shared/components/dashboard-select-panel.component.ts b/ui-ngx/src/app/shared/components/dashboard-select-panel.component.ts new file mode 100644 index 0000000000..ff9de24bc7 --- /dev/null +++ b/ui-ngx/src/app/shared/components/dashboard-select-panel.component.ts @@ -0,0 +1,48 @@ +/// +/// Copyright © 2016-2019 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, InjectionToken } from '@angular/core'; +import { Observable } from 'rxjs'; +import { DashboardInfo } from '../models/dashboard.models'; + +export const DASHBOARD_SELECT_PANEL_DATA = new InjectionToken('DashboardSelectPanelData'); + +export interface DashboardSelectPanelData { + dashboards$: Observable>; + dashboardId: string; + onDashboardSelected: (dashboardId: string) => void; +} + +@Component({ + selector: 'tb-dashboard-select-panel', + templateUrl: './dashboard-select-panel.component.html', + styleUrls: ['./dashboard-select-panel.component.scss'] +}) +export class DashboardSelectPanelComponent { + + dashboards$: Observable>; + dashboardId: string; + + constructor(@Inject(DASHBOARD_SELECT_PANEL_DATA) + private data: DashboardSelectPanelData) { + this.dashboards$ = this.data.dashboards$; + this.dashboardId = this.data.dashboardId; + } + + public dashboardSelected(dashboardId: string) { + this.data.onDashboardSelected(dashboardId); + } +} diff --git a/ui-ngx/src/app/shared/components/dashboard-select.component.html b/ui-ngx/src/app/shared/components/dashboard-select.component.html new file mode 100644 index 0000000000..40e80cbdd7 --- /dev/null +++ b/ui-ngx/src/app/shared/components/dashboard-select.component.html @@ -0,0 +1,35 @@ + + + + {{dashboard.title}} + + +
+ +
diff --git a/ui-ngx/src/app/shared/components/dashboard-select.component.scss b/ui-ngx/src/app/shared/components/dashboard-select.component.scss new file mode 100644 index 0000000000..bb0233663b --- /dev/null +++ b/ui-ngx/src/app/shared/components/dashboard-select.component.scss @@ -0,0 +1,40 @@ +/** + * Copyright © 2016-2019 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 { + min-width: 52px; + + mat-select { + max-width: 300px; + pointer-events: all; + } + + .tb-dashboard-select { + min-height: 32px; + + span { + pointer-events: all; + cursor: pointer; + } + } +} + +:host ::ng-deep { + mat-select { + .mat-select-value { + max-width: 282px; + } + } +} diff --git a/ui-ngx/src/app/shared/components/dashboard-select.component.ts b/ui-ngx/src/app/shared/components/dashboard-select.component.ts new file mode 100644 index 0000000000..b9d56ac080 --- /dev/null +++ b/ui-ngx/src/app/shared/components/dashboard-select.component.ts @@ -0,0 +1,214 @@ +/// +/// Copyright © 2016-2019 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, Inject, Input, OnInit, ViewChild, ViewContainerRef } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { Observable, of } from 'rxjs'; +import { PageLink } from '@shared/models/page/page-link'; +import { map, share } from 'rxjs/operators'; +import { emptyPageData, PageData } from '@shared/models/page/page-data'; +import { DashboardInfo } from '@app/shared/models/dashboard.models'; +import { DashboardService } from '@core/http/dashboard.service'; +import { Store } from '@ngrx/store'; +import { AppState } from '@app/core/core.state'; +import { getCurrentAuthUser } from '@app/core/auth/auth.selectors'; +import { Authority } from '@shared/models/authority.enum'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { TooltipPosition } from '@angular/material/tooltip'; +import { CdkOverlayOrigin, ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; +import { BreakpointObserver } from '@angular/cdk/layout'; +import { DOCUMENT } from '@angular/common'; +import { WINDOW } from '@core/services/window.service'; +import { ComponentPortal, PortalInjector } from '@angular/cdk/portal'; +import { + DASHBOARD_SELECT_PANEL_DATA, + DashboardSelectPanelComponent, + DashboardSelectPanelData +} from './dashboard-select-panel.component'; + +@Component({ + selector: 'tb-dashboard-select', + templateUrl: './dashboard-select.component.html', + styleUrls: ['./dashboard-select.component.scss'], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => DashboardSelectComponent), + multi: true + }] +}) +export class DashboardSelectComponent implements ControlValueAccessor, OnInit { + + @Input() + dashboardsScope: 'customer' | 'tenant'; + + @Input() + customerId: string; + + @Input() + tooltipPosition: TooltipPosition = 'above'; + + private requiredValue: boolean; + get required(): boolean { + return this.requiredValue; + } + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + @Input() + disabled: boolean; + + dashboards$: Observable>; + + dashboardId: string | null; + + @ViewChild('dashboardSelectPanelOrigin', {static: false}) dashboardSelectPanelOrigin: CdkOverlayOrigin; + + private propagateChange = (v: any) => { }; + + constructor(private store: Store, + private dashboardService: DashboardService, + private overlay: Overlay, + private breakpointObserver: BreakpointObserver, + private viewContainerRef: ViewContainerRef, + @Inject(DOCUMENT) private document: Document, + @Inject(WINDOW) private window: Window) { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + ngOnInit() { + + const pageLink = new PageLink(100); + + this.dashboards$ = this.getDashboards(pageLink).pipe( + map((pageData) => pageData.data), + share() + ); + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + writeValue(value: string | null): void { + this.dashboardId = value; + } + + dashboardIdChanged() { + this.updateView(); + } + + openDashboardSelectPanel() { + if (this.disabled) { + return; + } + const panelHeight = this.breakpointObserver.isMatched('min-height: 350px') ? 250 : 150; + const panelWidth = 300; + const position = this.overlay.position(); + const config = new OverlayConfig({ + panelClass: 'tb-dashboard-select-panel', + backdropClass: 'cdk-overlay-transparent-backdrop', + hasBackdrop: true, + }); + const el = this.dashboardSelectPanelOrigin.elementRef.nativeElement; + const offset = el.getBoundingClientRect(); + const scrollTop = this.window.pageYOffset || this.document.documentElement.scrollTop || this.document.body.scrollTop || 0; + const scrollLeft = this.window.pageXOffset || this.document.documentElement.scrollLeft || this.document.body.scrollLeft || 0; + const bottomY = offset.bottom - scrollTop; + const leftX = offset.left - scrollLeft; + let originX; + let originY; + let overlayX; + let overlayY; + const wHeight = this.document.documentElement.clientHeight; + const wWidth = this.document.documentElement.clientWidth; + if (bottomY + panelHeight > wHeight) { + originY = 'top'; + overlayY = 'bottom'; + } else { + originY = 'bottom'; + overlayY = 'top'; + } + if (leftX + panelWidth > wWidth) { + originX = 'end'; + overlayX = 'end'; + } else { + originX = 'start'; + overlayX = 'start'; + } + const connectedPosition: ConnectedPosition = { + originX, + originY, + overlayX, + overlayY + }; + config.positionStrategy = position.flexibleConnectedTo(this.dashboardSelectPanelOrigin.elementRef) + .withPositions([connectedPosition]); + const overlayRef = this.overlay.create(config); + overlayRef.backdropClick().subscribe(() => { + overlayRef.dispose(); + }); + + const injector = this._createDashboardSelectPanelInjector( + overlayRef, + { + dashboards$: this.dashboards$, + dashboardId: this.dashboardId, + onDashboardSelected: (dashboardId) => { + overlayRef.dispose(); + this.dashboardId = dashboardId; + this.updateView(); + } + } + ); + overlayRef.attach(new ComponentPortal(DashboardSelectPanelComponent, this.viewContainerRef, injector)); + } + + private _createDashboardSelectPanelInjector(overlayRef: OverlayRef, data: DashboardSelectPanelData): PortalInjector { + const injectionTokens = new WeakMap([ + [DASHBOARD_SELECT_PANEL_DATA, data], + [OverlayRef, overlayRef] + ]); + return new PortalInjector(this.viewContainerRef.injector, injectionTokens); + } + + private updateView() { + this.propagateChange(this.dashboardId); + } + + private getDashboards(pageLink: PageLink): Observable> { + let dashboardsObservable: Observable>; + const authUser = getCurrentAuthUser(this.store); + if (this.dashboardsScope === 'customer' || authUser.authority === Authority.CUSTOMER_USER) { + if (this.customerId) { + dashboardsObservable = this.dashboardService.getCustomerDashboards(this.customerId, pageLink, false, true); + } else { + dashboardsObservable = of(emptyPageData()); + } + } else { + dashboardsObservable = this.dashboardService.getTenantDashboards(pageLink, false, true); + } + return dashboardsObservable; + } + +} diff --git a/ui-ngx/src/app/shared/components/fab-toolbar.component.html b/ui-ngx/src/app/shared/components/fab-toolbar.component.html new file mode 100644 index 0000000000..384bec4dc1 --- /dev/null +++ b/ui-ngx/src/app/shared/components/fab-toolbar.component.html @@ -0,0 +1,22 @@ + +
+
+ +
+
diff --git a/ui-ngx/src/app/shared/components/fab-toolbar.component.scss b/ui-ngx/src/app/shared/components/fab-toolbar.component.scss new file mode 100644 index 0000000000..443ff107e0 --- /dev/null +++ b/ui-ngx/src/app/shared/components/fab-toolbar.component.scss @@ -0,0 +1,187 @@ +/** + * Copyright © 2016-2019 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. + */ +$font-size: 10px !default; +@function rem($multiplier) { + @return $multiplier * $font-size; +} + +$button-fab-width: rem(5.600) !default; +$button-fab-height: rem(5.600) !default; +$button-fab-padding: rem(1.60) !default; +$icon-button-margin: rem(0.600) !default; +$z-index-fab: 20 !default; + +$swift-ease-in-duration: 0.3s !default; +$swift-ease-in-timing-function: cubic-bezier(0.55, 0, 0.55, 0.2) !default; +$swift-ease-in: all $swift-ease-in-duration $swift-ease-in-timing-function !default; + +@mixin rtl-prop($ltr-prop, $rtl-prop, $value, $reset-value) { + #{$ltr-prop}: $value; + [dir=rtl] & { + #{$ltr-prop}: $reset-value; + #{$rtl-prop}: $value; + } +} + +@mixin fab-position($spot, $top: auto, $right: auto, $bottom: auto, $left: auto) { + &.mat-fab-#{$spot} { + top: $top; + right: $right; + bottom: $bottom; + left: $left; + position: absolute; + } +} + +@mixin fab-all-positions() { + @include fab-position(bottom-right, auto, ($button-fab-width - $button-fab-padding)/2, ($button-fab-height - $button-fab-padding)/2, auto); + @include fab-position(bottom-left, auto, auto, ($button-fab-height - $button-fab-padding)/2, ($button-fab-width - $button-fab-padding)/2); + @include fab-position(top-right, ($button-fab-height - $button-fab-padding)/2, ($button-fab-width - $button-fab-padding)/2, auto, auto); + @include fab-position(top-left, ($button-fab-height - $button-fab-padding)/2, auto, auto, ($button-fab-width - $button-fab-padding)/2); +} + +mat-fab-toolbar { + $icon-delay: 200ms; + @include fab-all-positions(); + display: block; + + .mat-fab-toolbar-wrapper { + display: block; + position: relative; + overflow: hidden; + + height: $button-fab-width + ($icon-button-margin * 2); + } + + mat-fab-trigger { + position: absolute; + z-index: $z-index-fab; + + button { + overflow: visible !important; + opacity: .5; + } + + .mat-fab-toolbar-background { + display: block; + position: absolute; + z-index: $z-index-fab + 1; + opacity: 1; + } + + mat-icon { + position: relative; + z-index: $z-index-fab + 2; + + opacity: 1; + + } + + } + + &.mat-left { + mat-fab-trigger { + @include rtl-prop(right, left, 0, auto); + } + + .mat-toolbar-tools { + flex-direction: row-reverse; + + > .mat-button:first-child { + @include rtl-prop(margin-right, margin-left, 0.6rem, auto) + } + + > .mat-button:first-child { + @include rtl-prop(margin-left, margin-right, -0.8rem, auto); + } + + + > .mat-button:last-child { + @include rtl-prop(margin-right, margin-left, 8px, auto); + } + + } + } + + &.mat-right { + mat-fab-trigger { + @include rtl-prop(left, right, 0, auto); + } + + .mat-toolbar-tools { + flex-direction: row; + } + } + + mat-toolbar { + padding: 0 !important; + background-color: transparent !important; + pointer-events: none; + position: relative; + z-index: $z-index-fab + 3; + + .mat-toolbar-tools { + padding: 0 20px !important; + margin-top: 3px; + } + + .mat-fab-action-item { + opacity: 0; + transform: scale(0); + } + } + + &.mat-is-open { + mat-fab-trigger > button { + box-shadow: none; + opacity: 1; + + mat-icon { + opacity: 0; + } + } + + .mat-fab-action-item { + opacity: 1; + transform: scale(1); + } + } + + &.mat-animation { + mat-fab-trigger { + button { + transition: opacity .3s cubic-bezier(.55, 0, .55, .2) .2s; + } + .mat-fab-toolbar-background { + transition: $swift-ease-in; + } + mat-icon { + transition: all $icon-delay ease-in; + } + } + mat-toolbar { + .mat-fab-action-item { + transition: $swift-ease-in; + transition-duration: $swift-ease-in-duration / 2; + } + } + &.mat-is-open { + mat-fab-trigger > button { + transition: opacity .3s cubic-bezier(.55, 0, .55, .2); + } + } + } +} diff --git a/ui-ngx/src/app/shared/components/fab-toolbar.component.ts b/ui-ngx/src/app/shared/components/fab-toolbar.component.ts new file mode 100644 index 0000000000..92447f36b4 --- /dev/null +++ b/ui-ngx/src/app/shared/components/fab-toolbar.component.ts @@ -0,0 +1,188 @@ +/// +/// Copyright © 2016-2019 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, + Input, + OnChanges, + OnInit, + Renderer2, + ViewEncapsulation, + SimpleChanges, + Inject, AfterViewInit, AfterViewChecked, Directive, OnDestroy +} from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { WINDOW } from '@core/services/window.service'; +import { mixinColor, CanColorCtor } from '@angular/material'; + +export declare type FabToolbarDirection = 'left' | 'right'; + +class MatFabToolbarBase { + // tslint:disable-next-line:variable-name + constructor(public _elementRef: ElementRef) {} +} +const MatFabToolbarMixinBase: CanColorCtor & typeof MatFabToolbarBase = mixinColor(MatFabToolbarBase); + +@Directive({ + selector: 'mat-fab-trigger' +}) +export class FabTriggerDirective { + + constructor(private el: ElementRef) { + } + +} + +@Directive({ + selector: 'mat-fab-actions' +}) +export class FabActionsDirective implements OnInit { + + constructor(private el: ElementRef) { + } + + ngOnInit(): void { + const element = $(this.el.nativeElement); + const children = element.children(); + children.wrap('
'); + } + +} + +@Component({ + selector: 'mat-fab-toolbar', + templateUrl: './fab-toolbar.component.html', + styleUrls: ['./fab-toolbar.component.scss'], + inputs: ['color'], + encapsulation: ViewEncapsulation.None +}) +export class FabToolbarComponent extends MatFabToolbarMixinBase implements OnInit, OnDestroy, AfterViewInit, OnChanges { + + @Input() + isOpen: boolean; + + @Input() + direction: FabToolbarDirection; + + fabToolbarResizeListener = this.onFabToolbarResize.bind(this); + + constructor(private el: ElementRef, + @Inject(WINDOW) private window: Window) { + super(el); + } + + ngOnInit(): void { + const element = $(this.el.nativeElement); + element.addClass('mat-fab-toolbar'); + element.find('mat-fab-trigger').find('button') + .prepend('
'); + element.addClass(`mat-${this.direction}`); + // @ts-ignore + addResizeListener(this.el.nativeElement, this.fabToolbarResizeListener); + } + + ngOnDestroy(): void { + // @ts-ignore + removeResizeListener(this.el.nativeElement, this.fabToolbarResizeListener); + } + + ngAfterViewInit(): void { + this.triggerOpenClose(true); + } + + ngOnChanges(changes: SimpleChanges): void { + for (const propName of Object.keys(changes)) { + const change = changes[propName]; + if (!change.firstChange && change.currentValue !== change.previousValue) { + if (propName === 'isOpen') { + this.triggerOpenClose(); + } + } + } + } + + private onFabToolbarResize() { + if (this.isOpen) { + this.triggerOpenClose(true); + } + } + + private triggerOpenClose(disableAnimation?: boolean): void { + const el = this.el.nativeElement; + const element = $(this.el.nativeElement); + if (disableAnimation) { + element.removeClass('mat-animation'); + } else { + element.addClass('mat-animation'); + } + const backgroundElement: HTMLElement = el.querySelector('.mat-fab-toolbar-background'); + const triggerElement: HTMLElement = el.querySelector('mat-fab-trigger button'); + const toolbarElement: HTMLElement = el.querySelector('mat-toolbar'); + const iconElement: HTMLElement = el.querySelector('mat-fab-trigger button mat-icon'); + const actions = element.find('mat-fab-actions').children(); + if (triggerElement && backgroundElement) { + const width = el.offsetWidth; + const height = el.offsetHeight; + const scale = 2 * (width / triggerElement.offsetWidth); + + backgroundElement.style.borderRadius = width + 'px'; + + if (this.isOpen) { + element.addClass('mat-is-open'); + toolbarElement.style.pointerEvents = 'inherit'; + + backgroundElement.style.width = triggerElement.offsetWidth + 'px'; + backgroundElement.style.height = triggerElement.offsetHeight + 'px'; + backgroundElement.style.transform = 'scale(' + scale + ')'; + + backgroundElement.style.transitionDelay = '0ms'; + if (iconElement) { + iconElement.style.transitionDelay = disableAnimation ? '0ms' : '.3s'; + } + + actions.each((index, action) => { + action.style.transitionDelay = disableAnimation ? '0ms' : ((actions.length - index) * 25 + 'ms'); + }); + + } else { + element.removeClass('mat-is-open'); + toolbarElement.style.pointerEvents = 'none'; + + backgroundElement.style.transform = 'scale(1)'; + + backgroundElement.style.top = '0'; + + if (element.hasClass('mat-right')) { + backgroundElement.style.left = '0'; + backgroundElement.style.right = null; + } + + if (element.hasClass('mat-left')) { + backgroundElement.style.right = '0'; + backgroundElement.style.left = null; + } + + backgroundElement.style.transitionDelay = disableAnimation ? '0ms' : '200ms'; + + actions.each((index, action) => { + action.style.transitionDelay = (disableAnimation ? 0 : 200) + (index * 25) + 'ms'; + }); + } + } + } + +} diff --git a/ui-ngx/src/app/shared/models/alias.models.ts b/ui-ngx/src/app/shared/models/alias.models.ts new file mode 100644 index 0000000000..d6d9a58c20 --- /dev/null +++ b/ui-ngx/src/app/shared/models/alias.models.ts @@ -0,0 +1,70 @@ +/// +/// Copyright © 2016-2019 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 { EntityType } from '@shared/models/entity-type.models'; + +export enum AliasFilterType { + singleEntity = 'singleEntity', + entityList = 'entityList', + entityName = 'entityName', + stateEntity = 'stateEntity', + assetType = 'assetType', + deviceType = 'deviceType', + entityViewType = 'entityViewType', + relationsQuery = 'relationsQuery', + assetSearchQuery = 'assetSearchQuery', + deviceSearchQuery = 'deviceSearchQuery', + entityViewSearchQuery = 'entityViewSearchQuery' +} + +export const aliasFilterTypeTranslationMap = new Map( + [ + [ AliasFilterType.singleEntity, 'alias.filter-type-single-entity' ], + [ AliasFilterType.entityList, 'alias.filter-type-entity-list' ], + [ AliasFilterType.entityName, 'alias.filter-type-entity-name' ], + [ AliasFilterType.stateEntity, 'alias.filter-type-state-entity' ], + [ AliasFilterType.assetType, 'alias.filter-type-asset-type' ], + [ AliasFilterType.deviceType, 'alias.filter-type-device-type' ], + [ AliasFilterType.entityViewType, 'alias.filter-type-entity-view-type' ], + [ AliasFilterType.relationsQuery, 'alias.filter-type-relations-query' ], + [ AliasFilterType.assetSearchQuery, 'alias.filter-type-asset-search-query' ], + [ AliasFilterType.deviceSearchQuery, 'alias.filter-type-device-search-query' ], + [ AliasFilterType.entityViewSearchQuery, 'alias.filter-type-entity-view-search-query' ] + ] +); + +export interface EntityAliasFilter { + type: AliasFilterType; + entityType: EntityType; + resolveMultiple: boolean; + entityList?: string[]; + entityNameFilter?: string; + [key: string]: any; + // TODO: + +} + +export interface EntityAlias { + id: string; + alias: string; + filter: EntityAliasFilter; + [key: string]: any; + // TODO: +} + +export interface EntityAliases { + [id: string]: EntityAlias; +} diff --git a/ui-ngx/src/app/shared/models/dashboard.models.ts b/ui-ngx/src/app/shared/models/dashboard.models.ts index 5de95e2a1f..e2cf08a6e2 100644 --- a/ui-ngx/src/app/shared/models/dashboard.models.ts +++ b/ui-ngx/src/app/shared/models/dashboard.models.ts @@ -14,22 +14,26 @@ /// limitations under the License. /// -import {BaseData} from '@shared/models/base-data'; -import {DashboardId} from '@shared/models/id/dashboard-id'; -import {TenantId} from '@shared/models/id/tenant-id'; -import {ShortCustomerInfo} from '@shared/models/customer.model'; +import { BaseData } from '@shared/models/base-data'; +import { DashboardId } from '@shared/models/id/dashboard-id'; +import { TenantId } from '@shared/models/id/tenant-id'; +import { ShortCustomerInfo } from '@shared/models/customer.model'; +import { Widget } from './widget.models'; +import { Timewindow } from '@shared/models/time/time.models'; +import { EntityType } from '@shared/models/entity-type.models'; +import { EntityAlias, EntityAliases } from './alias.models'; export interface DashboardInfo extends BaseData { - tenantId: TenantId; - title: string; - assignedCustomers: Array; + tenantId?: TenantId; + title?: string; + assignedCustomers?: Array; } export interface WidgetLayout { sizeX: number; sizeY: number; - mobileHeight: number; - mobileOrder: number; + mobileHeight?: number; + mobileOrder?: number; col: number; row: number; } @@ -38,13 +42,59 @@ export interface WidgetLayouts { [id: string]: WidgetLayout; } +export interface GridSettings { + backgroundColor?: string; + color?: string; + columns?: number; + margins?: [number, number]; + backgroundSizeMode?: string; + [key: string]: any; + // TODO: +} + +export interface DashboardLayout { + widgets: WidgetLayouts; + gridSettings: GridSettings; +} + +export declare type DashboardLayoutId = 'main' | 'right'; + +export interface DashboardStateLayouts { + main?: DashboardLayout; + right?: DashboardLayout; +} + +export interface DashboardState { + name: string; + root: boolean; + layouts: DashboardStateLayouts; +} + +export declare type StateControllerId = 'entity' | 'default' | string; + +export interface DashboardSettings { + stateControllerId?: StateControllerId; + showTitle?: boolean; + showDashboardsSelect?: boolean; + showEntitiesSelect?: boolean; + showDashboardTimewindow?: boolean; + showDashboardExport?: boolean; + toolbarAlwaysOpen?: boolean; + titleColor?: string; +} + export interface DashboardConfiguration { + timewindow?: Timewindow; + settings?: DashboardSettings; + widgets?: {[id: string]: Widget } | Widget[]; + states?: {[id: string]: DashboardState }; + entityAliases?: EntityAliases; [key: string]: any; // TODO: } export interface Dashboard extends DashboardInfo { - configuration: DashboardConfiguration; + configuration?: DashboardConfiguration; } export function isPublicDashboard(dashboard: DashboardInfo): boolean { diff --git a/ui-ngx/src/app/shared/models/entity.models.ts b/ui-ngx/src/app/shared/models/entity.models.ts new file mode 100644 index 0000000000..b1349986fa --- /dev/null +++ b/ui-ngx/src/app/shared/models/entity.models.ts @@ -0,0 +1,28 @@ +/// +/// Copyright © 2016-2019 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 { BaseData } from '@shared/models/base-data'; +import { EntityType } from '@shared/models/entity-type.models'; +import { EntityId } from '@shared/models/id/entity-id'; + +export interface EntityInfo { + origEntity?: BaseData; + name?: string; + label?: string; + entityType?: EntityType; + id?: string; + entityDescription?: string; +} diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index f57e0e52e4..d94fead1e5 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -38,6 +38,8 @@ export interface WidgetTypeTemplate { export interface WidgetTypeData { name: string; + icon: string; + isMdiIcon?: boolean; template: WidgetTypeTemplate; } @@ -47,6 +49,7 @@ export const widgetTypesData = new Map( widgetType.timeseries, { name: 'widget.timeseries', + icon: 'timeline', template: { bundleAlias: 'charts', alias: 'basic_timeseries' @@ -57,6 +60,7 @@ export const widgetTypesData = new Map( widgetType.latest, { name: 'widget.latest-values', + icon: 'track_changes', template: { bundleAlias: 'cards', alias: 'attributes_card' @@ -67,6 +71,8 @@ export const widgetTypesData = new Map( widgetType.rpc, { name: 'widget.rpc', + icon: 'mdi:developer-board', + isMdiIcon: true, template: { bundleAlias: 'gpio_widgets', alias: 'basic_gpio_control' @@ -77,6 +83,7 @@ export const widgetTypesData = new Map( widgetType.alarm, { name: 'widget.alarm', + icon: 'error', template: { bundleAlias: 'alarm_widgets', alias: 'alarms_table' @@ -87,6 +94,7 @@ export const widgetTypesData = new Map( widgetType.static, { name: 'widget.static', + icon: 'font_download', template: { bundleAlias: 'cards', alias: 'html_card' @@ -214,7 +222,7 @@ export enum DatasourceType { } export interface Datasource { - type: DatasourceType; + type?: DatasourceType | any; name?: string; dataKeys?: Array; entityType?: EntityType; @@ -333,7 +341,7 @@ export interface WidgetConfig { export interface Widget { id?: string; - typeId: WidgetTypeId; + typeId?: WidgetTypeId; isSystemType: boolean; bundleAlias: string; typeAlias: string; diff --git a/ui-ngx/src/app/shared/models/window-message.model.ts b/ui-ngx/src/app/shared/models/window-message.model.ts index adb53b2c42..9889214ab6 100644 --- a/ui-ngx/src/app/shared/models/window-message.model.ts +++ b/ui-ngx/src/app/shared/models/window-message.model.ts @@ -14,9 +14,9 @@ /// limitations under the License. /// -export type WindowMessageType = 'widgetException'; +export type WindowMessageType = 'widgetException' | 'widgetEditModeInited' | 'widgetEditUpdated'; export interface WindowMessage { type: WindowMessageType; - data: any; + data?: any; } diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index 051d17c936..4a074919b8 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -14,12 +14,12 @@ /// limitations under the License. /// -import {NgModule} from '@angular/core'; -import {CommonModule, DatePipe} from '@angular/common'; -import {FooterComponent} from './components/footer.component'; -import {LogoComponent} from './components/logo.component'; -import {TbSnackBarComponent, ToastDirective} from './components/toast.directive'; -import {BreadcrumbComponent} from '@app/shared/components/breadcrumb.component'; +import { NgModule } from '@angular/core'; +import { CommonModule, DatePipe } from '@angular/common'; +import { FooterComponent } from './components/footer.component'; +import { LogoComponent } from './components/logo.component'; +import { TbSnackBarComponent, ToastDirective } from './components/toast.directive'; +import { BreadcrumbComponent } from '@app/shared/components/breadcrumb.component'; import { MatAutocompleteModule, @@ -51,43 +51,49 @@ import { MatToolbarModule, MatTooltipModule } from '@angular/material'; -import {MatDatetimepickerModule, MatNativeDatetimeModule} from '@mat-datetimepicker/core'; -import {GridsterModule} from 'angular-gridster2'; -import {FlexLayoutModule} from '@angular/flex-layout'; -import {FormsModule, ReactiveFormsModule} from '@angular/forms'; -import {RouterModule} from '@angular/router'; -import {ShareModule as ShareButtonsModule} from '@ngx-share/core'; -import {UserMenuComponent} from '@shared/components/user-menu.component'; -import {NospacePipe} from './pipe/nospace.pipe'; -import {TranslateModule} from '@ngx-translate/core'; -import {TbCheckboxComponent} from '@shared/components/tb-checkbox.component'; -import {HelpComponent} from '@shared/components/help.component'; -import {TbAnchorComponent} from '@shared/components/tb-anchor.component'; -import {MillisecondsToTimeStringPipe} from '@shared/pipe/milliseconds-to-time-string.pipe'; -import {TimewindowComponent} from '@shared/components/time/timewindow.component'; -import {OverlayModule} from '@angular/cdk/overlay'; -import {TimewindowPanelComponent} from '@shared/components/time/timewindow-panel.component'; -import {TimeintervalComponent} from '@shared/components/time/timeinterval.component'; -import {DatetimePeriodComponent} from '@shared/components/time/datetime-period.component'; -import {EnumToArrayPipe} from '@shared/pipe/enum-to-array.pipe'; -import {ClipboardModule} from 'ngx-clipboard'; +import { MatDatetimepickerModule, MatNativeDatetimeModule } from '@mat-datetimepicker/core'; +import { GridsterModule } from 'angular-gridster2'; +import { FlexLayoutModule } from '@angular/flex-layout'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { RouterModule } from '@angular/router'; +import { ShareModule as ShareButtonsModule } from '@ngx-share/core'; +import { HotkeyModule } from 'angular2-hotkeys'; +import { UserMenuComponent } from '@shared/components/user-menu.component'; +import { NospacePipe } from './pipe/nospace.pipe'; +import { TranslateModule } from '@ngx-translate/core'; +import { TbCheckboxComponent } from '@shared/components/tb-checkbox.component'; +import { HelpComponent } from '@shared/components/help.component'; +import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; +import { MillisecondsToTimeStringPipe } from '@shared/pipe/milliseconds-to-time-string.pipe'; +import { TimewindowComponent } from '@shared/components/time/timewindow.component'; +import { OverlayModule } from '@angular/cdk/overlay'; +import { TimewindowPanelComponent } from '@shared/components/time/timewindow-panel.component'; +import { TimeintervalComponent } from '@shared/components/time/timeinterval.component'; +import { DatetimePeriodComponent } from '@shared/components/time/datetime-period.component'; +import { EnumToArrayPipe } from '@shared/pipe/enum-to-array.pipe'; +import { ClipboardModule } from 'ngx-clipboard'; import { ValueInputComponent } from '@shared/components/value-input.component'; -import {FullscreenDirective} from '@shared/components/fullscreen.directive'; -import {HighlightPipe} from '@shared/pipe/highlight.pipe'; -import {DashboardAutocompleteComponent} from '@shared/components/dashboard-autocomplete.component'; -import {EntitySubTypeAutocompleteComponent} from '@shared/components/entity/entity-subtype-autocomplete.component'; -import {EntitySubTypeSelectComponent} from './components/entity/entity-subtype-select.component'; -import {EntityAutocompleteComponent} from './components/entity/entity-autocomplete.component'; -import {EntityListComponent} from '@shared/components/entity/entity-list.component'; -import {EntityTypeSelectComponent} from './components/entity/entity-type-select.component'; -import {EntitySelectComponent} from './components/entity/entity-select.component'; -import {DatetimeComponent} from '@shared/components/time/datetime.component'; -import {EntityKeysListComponent} from './components/entity/entity-keys-list.component'; -import {SocialSharePanelComponent} from './components/socialshare-panel.component'; +import { FullscreenDirective } from '@shared/components/fullscreen.directive'; +import { HighlightPipe } from '@shared/pipe/highlight.pipe'; +import { DashboardAutocompleteComponent } from '@shared/components/dashboard-autocomplete.component'; +import { EntitySubTypeAutocompleteComponent } from '@shared/components/entity/entity-subtype-autocomplete.component'; +import { EntitySubTypeSelectComponent } from './components/entity/entity-subtype-select.component'; +import { EntityAutocompleteComponent } from './components/entity/entity-autocomplete.component'; +import { EntityListComponent } from '@shared/components/entity/entity-list.component'; +import { EntityTypeSelectComponent } from './components/entity/entity-type-select.component'; +import { EntitySelectComponent } from './components/entity/entity-select.component'; +import { DatetimeComponent } from '@shared/components/time/datetime.component'; +import { EntityKeysListComponent } from './components/entity/entity-keys-list.component'; +import { SocialSharePanelComponent } from './components/socialshare-panel.component'; import { RelationTypeAutocompleteComponent } from '@shared/components/relation/relation-type-autocomplete.component'; import { EntityListSelectComponent } from './components/entity/entity-list-select.component'; import { JsonObjectEditComponent } from './components/json-object-edit.component'; import { FooterFabButtonsComponent } from '@shared/components/footer-fab-buttons.component'; +import { CircularProgressDirective } from './components/circular-progress.directive'; +import { MatSpinner } from '@angular/material/progress-spinner'; +import { FabToolbarComponent, FabActionsDirective, FabTriggerDirective } from './components/fab-toolbar.component'; +import { DashboardSelectPanelComponent } from '@shared/components/dashboard-select-panel.component'; +import { DashboardSelectComponent } from '@shared/components/dashboard-select.component'; @NgModule({ providers: [ @@ -100,6 +106,8 @@ import { FooterFabButtonsComponent } from '@shared/components/footer-fab-buttons TbSnackBarComponent, TbAnchorComponent, TimewindowPanelComponent, + DashboardSelectPanelComponent, + MatSpinner ], declarations: [ FooterComponent, @@ -107,6 +115,7 @@ import { FooterFabButtonsComponent } from '@shared/components/footer-fab-buttons FooterFabButtonsComponent, ToastDirective, FullscreenDirective, + CircularProgressDirective, TbAnchorComponent, HelpComponent, TbCheckboxComponent, @@ -116,6 +125,8 @@ import { FooterFabButtonsComponent } from '@shared/components/footer-fab-buttons TimewindowComponent, TimewindowPanelComponent, TimeintervalComponent, + DashboardSelectComponent, + DashboardSelectPanelComponent, DatetimePeriodComponent, DatetimeComponent, ValueInputComponent, @@ -131,6 +142,9 @@ import { FooterFabButtonsComponent } from '@shared/components/footer-fab-buttons RelationTypeAutocompleteComponent, SocialSharePanelComponent, JsonObjectEditComponent, + FabTriggerDirective, + FabActionsDirective, + FabToolbarComponent, NospacePipe, MillisecondsToTimeStringPipe, EnumToArrayPipe, @@ -176,7 +190,8 @@ import { FooterFabButtonsComponent } from '@shared/components/footer-fab-buttons FormsModule, ReactiveFormsModule, OverlayModule, - ShareButtonsModule + ShareButtonsModule, + HotkeyModule ], exports: [ FooterComponent, @@ -184,6 +199,7 @@ import { FooterFabButtonsComponent } from '@shared/components/footer-fab-buttons FooterFabButtonsComponent, ToastDirective, FullscreenDirective, + CircularProgressDirective, TbAnchorComponent, HelpComponent, TbCheckboxComponent, @@ -192,6 +208,7 @@ import { FooterFabButtonsComponent } from '@shared/components/footer-fab-buttons TimewindowComponent, TimewindowPanelComponent, TimeintervalComponent, + DashboardSelectComponent, DatetimePeriodComponent, DatetimeComponent, DashboardAutocompleteComponent, @@ -206,6 +223,9 @@ import { FooterFabButtonsComponent } from '@shared/components/footer-fab-buttons RelationTypeAutocompleteComponent, SocialSharePanelComponent, JsonObjectEditComponent, + FabTriggerDirective, + FabActionsDirective, + FabToolbarComponent, ValueInputComponent, MatButtonModule, MatCheckboxModule, @@ -244,6 +264,7 @@ import { FooterFabButtonsComponent } from '@shared/components/footer-fab-buttons ReactiveFormsModule, OverlayModule, ShareButtonsModule, + HotkeyModule, NospacePipe, MillisecondsToTimeStringPipe, EnumToArrayPipe, diff --git a/ui-ngx/src/assets/split.js/grips/horizontal.png b/ui-ngx/src/assets/split.js/grips/horizontal.png new file mode 100644 index 0000000000000000000000000000000000000000..cbe7da77ddab697a99d363f39ed93184ef505ccc GIT binary patch literal 104 zcmeAS@N?(olHy`uVBq!ia0vp^azM<=$P6Si-k;kEq}T#{LR{Soe9oLX^NH#28lVVc zNswPKgTu2MX+VyEr;B4qMO?B4Gm8Kdi$Dy+{7RmK2db14fN~6;u6{1-oD!M<_c0o- literal 0 HcmV?d00001 diff --git a/ui-ngx/src/assets/split.js/grips/vertical.png b/ui-ngx/src/assets/split.js/grips/vertical.png new file mode 100644 index 0000000000000000000000000000000000000000..0ac8fa1e03fd46c25f1b22ad9eac95b825edbf91 GIT binary patch literal 91 zcmeAS@N?(olHy`uVBq!ia0vp^tUxTs!3HFs)Lq#Nq?9~e978mMlP!v$ov9aKV`7t- p&A3{jqq%P*ci$|_EVVde#<~+X&uyMGX&+EEgQu&X%Q~loCIHDl8R-B3 literal 0 HcmV?d00001 diff --git a/ui-ngx/src/styles.scss b/ui-ngx/src/styles.scss index e99c4270ee..d3583f98e7 100644 --- a/ui-ngx/src/styles.scss +++ b/ui-ngx/src/styles.scss @@ -169,6 +169,25 @@ section.tb-header-buttons { } } +section.tb-footer-buttons { + position: fixed; + right: 20px; + bottom: 20px; + z-index: 30; + pointer-events: none; + + .tb-btn-footer { + margin: 6px 8px; + position: relative !important; + display: inline-block !important; + animation: tbMoveFromBottomFade .3s ease both; + &.tb-hide { + animation: tbMoveToBottomFade .3s ease both; + } + } +} + + .tb-details-buttons { button { margin: 6px 8px; @@ -293,7 +312,7 @@ pre.tb-highlight { } .tb-fullscreen-parent { - background: #fff; + background: #eee; } mat-label { diff --git a/ui-ngx/src/theme.scss b/ui-ngx/src/theme.scss index ed82d0f56b..eb7ce76033 100644 --- a/ui-ngx/src/theme.scss +++ b/ui-ngx/src/theme.scss @@ -166,9 +166,52 @@ $tb-dark-theme: get-tb-dark-theme( $tb-accent ); +@mixin mat-fab-toolbar-theme($theme) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + mat-fab-toolbar { + .mat-fab-toolbar-background { + background: mat-color($background, app-bar); + color: mat-color($foreground, text); + } + &.mat-primary { + .mat-fab-toolbar-background { + @include _mat-toolbar-color($primary); + } + } + &.mat-accent { + .mat-fab-toolbar-background { + @include _mat-toolbar-color($accent); + } + } + &.mat-warn { + .mat-fab-toolbar-background { + @include _mat-toolbar-color($warn); + } + } + } +} + +@mixin tb-components-theme($theme) { + $primary: map-get($theme, primary); + + mat-toolbar{ + &.mat-hue-3 { + background-color: mat-color($primary, 'A100'); + } + } + + @include mat-fab-toolbar-theme($tb-theme); +} + .tb-default { @include angular-material-theme($tb-theme); @include mat-datetimepicker-theme($tb-theme); + @include tb-components-theme($tb-theme); } .tb-dark { @@ -388,11 +431,13 @@ $tb-dark-theme: get-tb-dark-theme( width: 32px; height: 32px; line-height: 32px; + padding: 0 !important; } &.tb-mat-96 { width: 96px; height: 96px; line-height: 96px; + padding: 0 !important; } } @@ -549,6 +594,27 @@ $tb-dark-theme: get-tb-dark-theme( right: 0; } + .tb-progress-cover { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 6; + background-color: #eee; + opacity: 1; + } + + .mat-button.tb-fullscreen-button-style, + .tb-fullscreen-button-style { + background: #ccc; + opacity: .85; + + mat-icon { + color: #666; + } + } + span.no-data-found { position: relative; display: flex; diff --git a/ui-ngx/src/tsconfig.app.json b/ui-ngx/src/tsconfig.app.json index 44125beebb..33398f7584 100644 --- a/ui-ngx/src/tsconfig.app.json +++ b/ui-ngx/src/tsconfig.app.json @@ -2,7 +2,7 @@ "extends": "../tsconfig.json", "compilerOptions": { "outDir": "../out-tsc/app", - "types": ["node", "jquery", "flot", "tinycolor2"] + "types": ["node", "jquery", "flot", "tinycolor2", "js-beautify"] }, "exclude": [ "test.ts", diff --git a/ui-ngx/src/typings/split.js.typings.d.ts b/ui-ngx/src/typings/split.js.typings.d.ts new file mode 100644 index 0000000000..5c999f3126 --- /dev/null +++ b/ui-ngx/src/typings/split.js.typings.d.ts @@ -0,0 +1,39 @@ +/// +/// Copyright © 2016-2019 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. +/// + +interface SplitOptions { + sizes?: number[]; + minSize?: number[] | number; + gutterSize?: number; + snapOffset?: number; + direction?: 'horizontal' | 'vertical'; + cursor?: 'col-resize' | 'row-resize'; + gutter?: (index: number, direction: string) => HTMLElement; + elementStyle?: (dimension: string, elementSize: number, gutterSize: number) => any; + gutterStyle?: (dimension: string, gutterSize: number) => any; + onDrag?: () => void; + onDragStart?: () => void; + onDragEnd?: () => void; +} + +interface SplitObject { + setSizes: (sizes: number[]) => void; + getSizes: () => number[]; + collapse: (index: number) => void; + destroy: () => void; +} + +declare function Split(elements: HTMLElement | string[], options?: SplitOptions): SplitObject; diff --git a/ui-ngx/tsconfig.json b/ui-ngx/tsconfig.json index 02a68043b0..d1e3fa083a 100644 --- a/ui-ngx/tsconfig.json +++ b/ui-ngx/tsconfig.json @@ -14,7 +14,8 @@ "typeRoots": [ "node_modules/@types", "src/typings/jquery.typings.d.ts", - "src/typings/jquery.flot.typings.d.ts" + "src/typings/jquery.flot.typings.d.ts", + "src/typings/split.js.typings.d.ts" ], "paths": { "@app/*": ["src/app/*"], From b60b3144a0854ec0cdf75fbb96492c2deefa42e9 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 20 Sep 2019 20:30:43 +0300 Subject: [PATCH 036/133] State Controllers and Dashboard Layouts. --- ui-ngx/src/app/core/api/alias-controller.ts | 6 + ui-ngx/src/app/core/api/widget-api.models.ts | 20 +- .../core/services/dashboard-utils.service.ts | 42 ++- ui-ngx/src/app/core/services/utils.service.ts | 18 +- .../translate/missing-translate-handler.ts | 5 +- .../dashboard/dashboard.component.html | 2 +- .../dashboard/dashboard.component.ts | 76 ++--- .../widget/widget-component.service.ts | 2 +- .../home/models/dashboard-component.models.ts | 69 ++++ .../home/models/widget-component.models.ts | 2 +- .../pages/customer/customer-routing.module.ts | 40 ++- .../dashboard/dashboard-page.component.html | 47 ++- .../dashboard/dashboard-page.component.ts | 132 +++++++- .../pages/dashboard/dashboard-page.models.ts | 18 +- .../home/pages/dashboard/dashboard.module.ts | 6 +- .../layout/dashboard-layout.component.html | 65 ++++ .../layout/dashboard-layout.component.scss | 23 ++ .../layout/dashboard-layout.component.ts | 80 +++++ .../pages/dashboard/layout/layout.models.ts | 20 ++ .../default-state-controller.component.html | 23 ++ .../default-state-controller.component.scss | 20 ++ .../default-state-controller.component.ts | 255 ++++++++++++++ .../entity-state-controller.component.html | 36 ++ .../entity-state-controller.component.scss | 45 +++ .../entity-state-controller.component.ts | 316 ++++++++++++++++++ .../states/state-controller.component.ts | 173 ++++++++++ .../states/state-controller.models.ts | 30 ++ .../states/states-component.directive.ts | 122 +++++++ .../states/states-controller.module.ts | 56 ++++ .../states/states-controller.service.ts | 64 ++++ .../widget/widget-library-routing.module.ts | 73 +++- .../widget/widget-library.component.html | 3 +- .../pages/widget/widget-library.component.ts | 64 +--- .../footer-fab-buttons.component.html | 2 +- .../footer-fab-buttons.component.scss | 18 +- .../footer-fab-buttons.component.ts | 10 + .../src/app/shared/models/dashboard.models.ts | 17 +- 37 files changed, 1830 insertions(+), 170 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/pages/dashboard/layout/dashboard-layout.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/dashboard/layout/dashboard-layout.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/dashboard/layout/dashboard-layout.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/dashboard/layout/layout.models.ts create mode 100644 ui-ngx/src/app/modules/home/pages/dashboard/states/default-state-controller.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/dashboard/states/default-state-controller.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/dashboard/states/default-state-controller.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/dashboard/states/entity-state-controller.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/dashboard/states/entity-state-controller.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/dashboard/states/entity-state-controller.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/dashboard/states/state-controller.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/dashboard/states/state-controller.models.ts create mode 100644 ui-ngx/src/app/modules/home/pages/dashboard/states/states-component.directive.ts create mode 100644 ui-ngx/src/app/modules/home/pages/dashboard/states/states-controller.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/dashboard/states/states-controller.service.ts diff --git a/ui-ngx/src/app/core/api/alias-controller.ts b/ui-ngx/src/app/core/api/alias-controller.ts index 8f16ee5209..056efc83cf 100644 --- a/ui-ngx/src/app/core/api/alias-controller.ts +++ b/ui-ngx/src/app/core/api/alias-controller.ts @@ -58,6 +58,9 @@ export class DummyAliasController implements IAliasController { updateEntityAliases(entityAliases: EntityAliases) { } + dashboardStateChanged() { + } + } export class AliasController implements IAliasController { @@ -111,4 +114,7 @@ export class AliasController implements IAliasController { updateEntityAliases(entityAliases: EntityAliases) { } + dashboardStateChanged() { + } + } diff --git a/ui-ngx/src/app/core/api/widget-api.models.ts b/ui-ngx/src/app/core/api/widget-api.models.ts index 9301857659..934315e678 100644 --- a/ui-ngx/src/app/core/api/widget-api.models.ts +++ b/ui-ngx/src/app/core/api/widget-api.models.ts @@ -86,6 +86,7 @@ export interface IAliasController { getEntityAliases(): EntityAliases; updateCurrentAliasEntity(aliasId: string, currentEntity: EntityInfo); updateEntityAliases(entityAliases: EntityAliases); + dashboardStateChanged(); [key: string]: any | null; // TODO: } @@ -103,12 +104,19 @@ export interface StateParams { } export interface IStateController { - getStateParams?: () => StateParams; - openState?: (id: string, params?: StateParams, openRightLayout?: boolean) => void; - updateState?: (id?: string, params?: StateParams, openRightLayout?: boolean) => void; - openRightLayout: () => void; - preserveState?: () => void; - // TODO: + getStateParams(): StateParams; + getStateParamsByStateId(stateId: string): StateParams; + openState(id: string, params?: StateParams, openRightLayout?: boolean): void; + updateState(id?: string, params?: StateParams, openRightLayout?: boolean): void; + resetState(): void; + openRightLayout(): void; + preserveState(): void; + cleanupPreservedStates(): void; + navigatePrevState(index: number): void; + getStateId(): string; + getStateIndex(): number; + getStateIdAtIndex(index: number): string; + getEntityId(entityParamName: string): EntityId; } export interface SubscriptionInfo { 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 30fdb3aded..cafeaccd2f 100644 --- a/ui-ngx/src/app/core/services/dashboard-utils.service.ts +++ b/ui-ngx/src/app/core/services/dashboard-utils.service.ts @@ -22,7 +22,9 @@ import { DashboardLayout, DashboardStateLayouts, DashboardState, - DashboardConfiguration + DashboardConfiguration, + DashboardLayoutInfo, + DashboardLayoutsInfo } from '@shared/models/dashboard.models'; import { isUndefined, isDefined, isString } from '@core/utils'; import { DatasourceType, Widget, Datasource } from '@app/shared/models/widget.models'; @@ -238,6 +240,44 @@ export class DashboardUtilsService { }; } + public getRootStateId(states: {[id: string]: DashboardState }): string { + for (const stateId of Object.keys(states)) { + const state = states[stateId]; + if (state.root) { + return stateId; + } + } + return Object.keys(states)[0]; + } + + public getStateLayoutsData(dashboard: Dashboard, targetState: string): DashboardLayoutsInfo { + const dashboardConfiguration = dashboard.configuration; + const states = dashboardConfiguration.states; + const state = states[targetState]; + if (state) { + const allWidgets = dashboardConfiguration.widgets; + const result: DashboardLayoutsInfo = {}; + for (const l of Object.keys(state.layouts)) { + const layout: DashboardLayout = state.layouts[l]; + if (layout) { + result[l] = { + widgets: [], + widgetLayouts: {}, + gridSettings: {} + } as DashboardLayoutInfo; + for (const id of Object.keys(layout.widgets)) { + result[l].widgets.push(allWidgets[id]); + } + result[l].widgetLayouts = layout.widgets; + result[l].gridSettings = layout.gridSettings; + } + } + return result; + } else { + return null; + } + } + private validateAndUpdateEntityAliases(configuration: DashboardConfiguration, datasourcesByAliasId: {[aliasId: string]: Array}, targetDevicesByAliasId: {[aliasId: string]: Array>}): DashboardConfiguration { diff --git a/ui-ngx/src/app/core/services/utils.service.ts b/ui-ngx/src/app/core/services/utils.service.ts index b323baf2b5..1e33f84625 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 @@ import { Inject, Injectable } from '@angular/core'; import { WINDOW } from '@core/services/window.service'; import { ExceptionData } from '@app/shared/models/error.models'; -import { isUndefined, isDefined } from '@core/utils'; +import { isUndefined, isDefined, deepClone } from '@core/utils'; import { WindowMessage } from '@shared/models/window-message.model'; import { TranslateService } from '@ngx-translate/core'; import { customTranslationsPrefix } from '@app/shared/models/constants'; @@ -28,6 +28,8 @@ import { alarmFields } from '@shared/models/alarm.models'; import { materialColors } from '@app/shared/models/material.models'; import { WidgetInfo } from '@home/models/widget-component.models'; +const varsRegex = /\$\{([^}]*)\}/g; + @Injectable({ providedIn: 'root' }) @@ -144,6 +146,20 @@ export class UtilsService { return result; } + public insertVariable(pattern: string, name: string, value: any): string { + let result = deepClone(pattern); + let match = varsRegex.exec(pattern); + while (match !== null) { + const variable = match[0]; + const variableName = match[1]; + if (variableName === name) { + result = result.split(variable).join(value); + } + match = varsRegex.exec(pattern); + } + return result; + } + public guid(): string { function s4(): string { return Math.floor((1 + Math.random()) * 0x10000) diff --git a/ui-ngx/src/app/core/translate/missing-translate-handler.ts b/ui-ngx/src/app/core/translate/missing-translate-handler.ts index 71cf578b2b..08da9e71a7 100644 --- a/ui-ngx/src/app/core/translate/missing-translate-handler.ts +++ b/ui-ngx/src/app/core/translate/missing-translate-handler.ts @@ -15,9 +15,12 @@ /// import {MissingTranslationHandler, MissingTranslationHandlerParams} from '@ngx-translate/core'; +import { customTranslationsPrefix } from '@app/shared/models/constants'; export class TbMissingTranslationHandler implements MissingTranslationHandler { handle(params: MissingTranslationHandlerParams) { - console.warn('Translation for ' + params.key + ' doesn\'t exist'); + if (params.key && !params.key.startsWith(customTranslationsPrefix)) { + console.warn('Translation for ' + params.key + ' doesn\'t exist'); + } } } diff --git a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html index a352d2da46..467ce77fb5 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html @@ -26,7 +26,7 @@ (contextmenu)="openDashboardContextMenu($event)">
- +
; + widgets: Array; + + @Input() + widgetLayouts: WidgetLayouts; @Input() callbacks: DashboardCallbacks; @@ -125,8 +130,6 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo gridsterOpts: GridsterConfig; - dashboardLoading = true; - highlightedMode = false; highlightedWidget: DashboardWidget = null; selectedWidget: DashboardWidget = null; @@ -138,9 +141,9 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo @ViewChildren(GridsterItemComponent) gridsterItems: QueryList; - widgets$: Observable>; + dashboardLoading = true; - widgets: Array; + dashboardWidgets = new DashboardWidgets(this); constructor(protected store: Store, private timeService: TimeService, @@ -172,25 +175,26 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo defaultItemRows: 6, resizable: {enabled: this.isEdit}, draggable: {enabled: this.isEdit}, - itemChangeCallback: item => this.sortWidgets(this.widgets) + itemChangeCallback: item => this.dashboardWidgets.sortWidgets() }; this.updateMobileOpts(); - this.loadDashboard(); - this.breakpointObserver .observe(MediaBreakpoints['gt-sm']).subscribe( () => { this.updateMobileOpts(); } ); + + this.updateWidgets(); } ngOnChanges(changes: SimpleChanges): void { let updateMobileOpts = false; let updateLayoutOpts = false; let updateEditingOpts = false; + let updateWidgets = false; for (const propName of Object.keys(changes)) { const change = changes[propName]; if (!change.firstChange && change.currentValue !== change.previousValue) { @@ -200,9 +204,14 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo updateLayoutOpts = true; } else if (propName === 'isEdit') { updateEditingOpts = true; + } else if (['widgets', 'widgetLayouts'].includes(propName)) { + updateWidgets = true; } } } + if (updateWidgets) { + this.updateWidgets(); + } if (updateMobileOpts) { this.updateMobileOpts(); } @@ -217,50 +226,9 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo } } - loadDashboard() { - this.widgets$ = this.widgetsData.pipe( - map(widgetsData => { - const dashboardWidgets = new Array(); - let maxRows = this.gridsterOpts.maxRows; - widgetsData.widgets.forEach( - (widget) => { - let widgetLayout: WidgetLayout; - if (widgetsData.widgetLayouts && widget.id) { - widgetLayout = widgetsData.widgetLayouts[widget.id]; - } - const dashboardWidget = new DashboardWidget(this, widget, widgetLayout); - const bottom = dashboardWidget.y + dashboardWidget.rows; - maxRows = Math.max(maxRows, bottom); - dashboardWidgets.push(dashboardWidget); - } - ); - this.sortWidgets(dashboardWidgets); - this.gridsterOpts.maxRows = maxRows; - return dashboardWidgets; - }), - tap((widgets) => { - this.widgets = widgets; - this.dashboardLoading = false; - }) - ); - } - - reload() { - this.loadDashboard(); - } - - sortWidgets(widgets?: Array) { - if (widgets) { - widgets.sort((widget1, widget2) => { - const row1 = widget1.widgetOrder; - const row2 = widget2.widgetOrder; - let res = row1 - row2; - if (res === 0) { - res = widget1.x - widget2.x; - } - return res; - }); - } + private updateWidgets() { + this.dashboardWidgets.setWidgets(this.widgets, this.widgetLayouts); + this.dashboardLoading = false; } ngAfterViewInit(): void { 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 aa7682bf23..7271dff81a 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 @@ -119,7 +119,7 @@ export class WidgetComponentService { } else { fetchQueue = new Array>(); this.widgetsInfoFetchQueue.set(key, fetchQueue); - this.widgetService.getWidgetType(bundleAlias, widgetTypeAlias, isSystem).subscribe( + this.widgetService.getWidgetType(bundleAlias, widgetTypeAlias, isSystem, true, false).subscribe( (widgetType) => { this.loadWidget(widgetType, bundleAlias, isSystem, widgetInfoSubject); }, 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 d1be047ae4..66d5a340fd 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 @@ -43,6 +43,7 @@ export interface DashboardCallbacks { export interface IDashboardComponent { gridsterOpts: GridsterConfig; gridster: GridsterComponent; + dashboardWidgets: DashboardWidgets; mobileAutofillHeight: boolean; isMobileSize: boolean; autofillHeight: boolean; @@ -54,6 +55,74 @@ export interface IDashboardComponent { onResetTimewindow(): void; } +export class DashboardWidgets implements Iterable { + + dashboardWidgets: Array = []; + + [Symbol.iterator](): Iterator { + return this.dashboardWidgets[Symbol.iterator](); + } + + constructor(private dashboard: IDashboardComponent) { + } + + setWidgets(widgets: Array, widgetLayouts: WidgetLayouts) { + let maxRows = this.dashboard.gridsterOpts.maxRows; + this.dashboardWidgets.length = 0; + widgets.forEach((widget) => { + let widgetLayout: WidgetLayout; + if (widgetLayouts && widget.id) { + widgetLayout = widgetLayouts[widget.id]; + } + const dashboardWidget = new DashboardWidget(this.dashboard, widget, widgetLayout); + const bottom = dashboardWidget.y + dashboardWidget.rows; + maxRows = Math.max(maxRows, bottom); + this.dashboardWidgets.push(dashboardWidget); + }); + this.sortWidgets(); + this.dashboard.gridsterOpts.maxRows = maxRows; + } + + addWidget(widget: Widget, widgetLayout: WidgetLayout) { + const dashboardWidget = new DashboardWidget(this.dashboard, widget, widgetLayout); + let maxRows = this.dashboard.gridsterOpts.maxRows; + const bottom = dashboardWidget.y + dashboardWidget.rows; + maxRows = Math.max(maxRows, bottom); + this.dashboardWidgets.push(dashboardWidget); + this.sortWidgets(); + this.dashboard.gridsterOpts.maxRows = maxRows; + } + + removeWidget(widget: Widget): boolean { + const index = this.dashboardWidgets.findIndex((dashboardWidget) => dashboardWidget.widget === widget); + if (index > -1) { + this.dashboardWidgets.splice(index, 1); + let maxRows = this.dashboard.gridsterOpts.maxRows; + this.dashboardWidgets.forEach((dashboardWidget) => { + const bottom = dashboardWidget.y + dashboardWidget.rows; + maxRows = Math.max(maxRows, bottom); + }); + this.sortWidgets(); + this.dashboard.gridsterOpts.maxRows = maxRows; + return true; + } + return false; + } + + sortWidgets() { + this.dashboardWidgets.sort((widget1, widget2) => { + const row1 = widget1.widgetOrder; + const row2 = widget2.widgetOrder; + let res = row1 - row2; + if (res === 0) { + res = widget1.x - widget2.x; + } + return res; + }); + } + +} + export class DashboardWidget implements GridsterItem { isFullscreen = false; 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 35517d5209..5071cd8509 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 @@ -123,7 +123,7 @@ export const MissingWidgetType: WidgetInfo = { sizeY: 6, resources: [], templateHtml: '
' + - '
widget.widget-type-not-found
' + + '
' + '
', templateCss: '', controllerScript: 'self.onInit = function() {}', diff --git a/ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts b/ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts index 10a0e0f4eb..2ee13e5160 100644 --- a/ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts @@ -24,6 +24,9 @@ import {CustomersTableConfigResolver} from './customers-table-config.resolver'; import {DevicesTableConfigResolver} from '@modules/home/pages/device/devices-table-config.resolver'; import {AssetsTableConfigResolver} from '../asset/assets-table-config.resolver'; import {DashboardsTableConfigResolver} from '@modules/home/pages/dashboard/dashboards-table-config.resolver'; +import { DashboardPageComponent } from '@home/pages/dashboard/dashboard-page.component'; +import { BreadCrumbConfig } from '@shared/components/breadcrumb'; +import { dashboardBreadcumbLabelFunction, DashboardResolver } from '@home/pages/dashboard/dashboard-routing.module'; const routes: Routes = [ { @@ -95,19 +98,42 @@ const routes: Routes = [ }, { path: ':customerId/dashboards', - component: EntitiesTableComponent, data: { - auth: [Authority.TENANT_ADMIN], - title: 'customer.assets', - dashboardsType: 'customer', breadcrumb: { label: 'customer.dashboards', icon: 'dashboard' } }, - resolve: { - entitiesTableConfig: DashboardsTableConfigResolver - } + children: [ + { + path: '', + component: EntitiesTableComponent, + data: { + auth: [Authority.TENANT_ADMIN], + title: 'customer.dashboards', + dashboardsType: 'customer' + }, + resolve: { + entitiesTableConfig: DashboardsTableConfigResolver + } + }, + { + path: ':dashboardId', + component: DashboardPageComponent, + data: { + breadcrumb: { + labelFunction: dashboardBreadcumbLabelFunction, + icon: 'dashboard' + } as BreadCrumbConfig, + auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], + title: 'customer.dashboard', + widgetEditMode: false + }, + resolve: { + dashboard: DashboardResolver + } + } + ] } ] } diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.html b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.html index e2956320ba..65530c30a7 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.html +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.html @@ -85,7 +85,31 @@ fxLayoutAlign.gt-sm="end center" fxLayoutAlign="space-between center" fxLayoutGap="12px"> - +
+ + +
+ +
@@ -110,7 +134,13 @@ id="tb-main-layout" [ngStyle]="{width: mainLayoutWidth(), height: mainLayoutHeight()}"> - TODO: MAIN LAYOUT tb-dashboard-layout + +
@@ -123,14 +153,23 @@ position="end" [mode]="isMobile ? 'over' : 'side'" [(opened)]="rightLayoutOpened"> - TODO: RIGHT LAYOUT tb-dashboard-layout + +
+ + +
diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/layout/dashboard-layout.component.scss b/ui-ngx/src/app/modules/home/pages/dashboard/layout/dashboard-layout.component.scss new file mode 100644 index 0000000000..00358e9b31 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/layout/dashboard-layout.component.scss @@ -0,0 +1,23 @@ +/** + * Copyright © 2016-2019 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 { + button.tb-add-new-widget { + padding-right: 12px; + font-size: 24px; + border-style: dashed; + border-width: 2px; + } +} diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/layout/dashboard-layout.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/layout/dashboard-layout.component.ts new file mode 100644 index 0000000000..8c0ba8671b --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/layout/dashboard-layout.component.ts @@ -0,0 +1,80 @@ +/// +/// Copyright © 2016-2019 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, OnDestroy, OnInit, Input, ChangeDetectorRef, ViewChild } from '@angular/core'; +import { StateControllerComponent } from '@home/pages/dashboard/states/state-controller.component'; +import { ILayoutController } from '@home/pages/dashboard/layout/layout.models'; +import { DashboardContext, DashboardPageLayoutContext } from '@home/pages/dashboard/dashboard-page.models'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { Widget } from '@shared/models/widget.models'; +import { WidgetLayouts } from '@shared/models/dashboard.models'; +import { GridsterComponent } from 'angular-gridster2'; +import { IDashboardComponent } from '@home/models/dashboard-component.models'; + +@Component({ + selector: 'tb-dashboard-layout', + templateUrl: './dashboard-layout.component.html', + styleUrls: ['./dashboard-layout.component.scss'] +}) +export class DashboardLayoutComponent extends PageComponent implements ILayoutController, OnInit, OnDestroy { + + layoutCtxValue: DashboardPageLayoutContext; + + @Input() + set layoutCtx(val: DashboardPageLayoutContext) { + this.layoutCtxValue = val; + if (this.layoutCtxValue) { + this.layoutCtxValue.ctrl = this; + } + } + get layoutCtx(): DashboardPageLayoutContext { + return this.layoutCtxValue; + } + + @Input() + dashboardCtx: DashboardContext; + + @Input() + isEdit: boolean; + + @Input() + isMobile: boolean; + + @Input() + widgetEditMode: boolean; + + @ViewChild('dashboard', {static: true}) dashboard: IDashboardComponent; + + constructor(protected store: Store, + private cd: ChangeDetectorRef) { + super(store); + } + + ngOnInit(): void { + } + + ngOnDestroy(): void { + } + + reload() { + } + + setResizing(layoutVisibilityChanged: boolean) { + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/layout/layout.models.ts b/ui-ngx/src/app/modules/home/pages/dashboard/layout/layout.models.ts new file mode 100644 index 0000000000..86b224fe0a --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/layout/layout.models.ts @@ -0,0 +1,20 @@ +/// +/// Copyright © 2016-2019 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 interface ILayoutController { + reload(); + setResizing(layoutVisibilityChanged: boolean); +} diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/default-state-controller.component.html b/ui-ngx/src/app/modules/home/pages/dashboard/states/default-state-controller.component.html new file mode 100644 index 0000000000..b86d4bb530 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/default-state-controller.component.html @@ -0,0 +1,23 @@ + + + + {{getStateName(stateKv.key, stateKv.value)}} + + diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/default-state-controller.component.scss b/ui-ngx/src/app/modules/home/pages/dashboard/states/default-state-controller.component.scss new file mode 100644 index 0000000000..691099ef0b --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/default-state-controller.component.scss @@ -0,0 +1,20 @@ +/** + * Copyright © 2016-2019 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 { + mat-select.default-state-controller { + margin: 0; + } +} diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/default-state-controller.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/states/default-state-controller.component.ts new file mode 100644 index 0000000000..4a6b2bcdf7 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/default-state-controller.component.ts @@ -0,0 +1,255 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + Component, + OnInit, + ViewEncapsulation, + Input, + OnDestroy, + OnChanges, + SimpleChanges, + NgZone +} from '@angular/core'; +import { IStateController, StateParams, StateObject } from '@core/api/widget-api.models'; +import { ActivatedRoute, Router } from '@angular/router'; +import { Observable, Subscription, of } from 'rxjs'; +import { IDashboardController } from '@home/pages/dashboard/dashboard-page.models'; +import { DashboardState } from '@shared/models/dashboard.models'; +import { IStateControllerComponent, StateControllerState } from './state-controller.models'; +import { StateControllerComponent } from './state-controller.component'; +import { StatesControllerService } from '@home/pages/dashboard/states/states-controller.service'; +import { EntityId } from '@app/shared/models/id/entity-id'; +import { UtilsService } from '@core/services/utils.service'; +import { base64toObj, objToBase64 } from '@app/core/utils'; +import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; +import { EntityService } from '@core/http/entity.service'; +import { EntityType } from '@shared/models/entity-type.models'; +import { map } from 'rxjs/operators'; + +@Component({ + selector: 'tb-default-state-controller', + templateUrl: './default-state-controller.component.html', + styleUrls: ['./default-state-controller.component.scss'] +}) +export class DefaultStateControllerComponent extends StateControllerComponent implements OnInit, OnDestroy { + + constructor(protected router: Router, + protected route: ActivatedRoute, + protected statesControllerService: StatesControllerService, + private utils: UtilsService, + private entityService: EntityService, + private dashboardUtils: DashboardUtilsService) { + super(router, route, statesControllerService); + } + + ngOnInit(): void { + super.ngOnInit(); + } + + ngOnDestroy(): void { + super.ngOnDestroy(); + } + + protected init() { + if (this.preservedState) { + this.stateObject = this.preservedState; + setTimeout(() => { + this.gotoState(this.stateObject[0].id, true); + }, 1); + } else { + const initialState = this.currentState; + this.stateObject = this.parseState(initialState); + setTimeout(() => { + this.gotoState(this.stateObject[0].id, false); + }, 1); + } + } + + protected onMobileChanged() { + } + + protected onStateIdChanged() { + } + + protected onStatesChanged() { + } + + protected onStateChanged() { + this.stateObject = this.parseState(this.currentState); + this.gotoState(this.stateObject[0].id, true); + } + + protected stateControllerId(): string { + return 'default'; + } + + public getStateParams(): StateParams { + if (this.stateObject && this.stateObject.length) { + return this.stateObject[this.stateObject.length - 1].params; + } else { + return {}; + } + } + + public openState(id: string, params?: StateParams, openRightLayout?: boolean): void { + if (this.states && this.states[id]) { + if (!params) { + params = {}; + } + const newState: StateObject = { + id, + params + }; + this.stateObject[0] = newState; + this.gotoState(this.stateObject[0].id, true, openRightLayout); + } + } + + public updateState(id: string, params?: StateParams, openRightLayout?: boolean): void { + if (!id) { + id = this.getStateId(); + } + if (this.states && this.states[id]) { + if (!params) { + params = {}; + } + const newState: StateObject = { + id, + params + }; + this.stateObject[0] = newState; + this.gotoState(this.stateObject[0].id, true, openRightLayout); + } + } + + public getEntityId(entityParamName: string): EntityId { + return null; + } + + public getStateId(): string { + if (this.stateObject && this.stateObject.length) { + return this.stateObject[this.stateObject.length - 1].id; + } else { + return ''; + } + } + + public getStateIdAtIndex(index: number): string { + if (this.stateObject && this.stateObject[index]) { + return this.stateObject[index].id; + } else { + return ''; + } + } + + public getStateIndex(): number { + if (this.stateObject && this.stateObject.length) { + return this.stateObject.length - 1; + } else { + return -1; + } + } + + public getStateParamsByStateId(stateId: string): StateParams { + const stateObj = this.getStateObjById(stateId); + if (stateObj) { + return stateObj.params; + } else { + return null; + } + } + + public navigatePrevState(index: number): void { + if (index < this.stateObject.length - 1) { + this.stateObject.splice(index + 1, this.stateObject.length - index - 1); + this.gotoState(this.stateObject[this.stateObject.length - 1].id, true); + } + } + + public resetState(): void { + const rootStateId = this.dashboardUtils.getRootStateId(this.states); + this.stateObject = [ { id: rootStateId, params: {} } ]; + this.gotoState(rootStateId, true); + } + + public getStateName(id: string, state: DashboardState): string { + return this.utils.customTranslation(state.name, id); + } + + public displayStateSelection(): boolean { + return this.states && Object.keys(this.states).length > 1; + } + + public selectedStateIdChanged() { + this.gotoState(this.stateObject[0].id, true); + } + + private parseState(stateBase64: string): StateControllerState { + let result: StateControllerState; + if (stateBase64) { + try { + result = base64toObj(stateBase64); + } catch (e) { + result = [ { id: null, params: {} } ]; + } + } + if (!result) { + result = []; + } + if (!result.length) { + result[0] = { id: null, params: {} }; + } else if (result.length > 1) { + const newResult = []; + newResult.push(result[result.length - 1]); + result = newResult; + } + const rootStateId = this.dashboardUtils.getRootStateId(this.states); + if (!result[0].id) { + result[0].id = rootStateId; + } + if (!this.states[result[0].id]) { + result[0].id = rootStateId; + } + let i = result.length; + while (i--) { + if (!result[i].id || !this.states[result[i].id]) { + result.splice(i, 1); + } + } + return result; + } + + private gotoState(stateId: string, update: boolean, openRightLayout?: boolean) { + if (this.dashboardCtrl.dashboardCtx.state !== stateId) { + this.dashboardCtrl.openDashboardState(stateId, openRightLayout); + if (update) { + this.updateLocation(); + } + } + } + + private updateLocation() { + if (this.stateObject[0].id) { + const newState = objToBase64(this.stateObject); + this.updateStateParam(newState); + } + } + + private getStateObjById(id: string): StateObject { + return this.stateObject.find((stateObj) => stateObj.id === id); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/entity-state-controller.component.html b/ui-ngx/src/app/modules/home/pages/dashboard/states/entity-state-controller.component.html new file mode 100644 index 0000000000..e490340470 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/entity-state-controller.component.html @@ -0,0 +1,36 @@ + + +
+
+ + {{getStateName(i)}} + > + +
+ + + {{getStateName(i)}} + + +
+ + + diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/entity-state-controller.component.scss b/ui-ngx/src/app/modules/home/pages/dashboard/states/entity-state-controller.component.scss new file mode 100644 index 0000000000..4cbec02a67 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/entity-state-controller.component.scss @@ -0,0 +1,45 @@ +/** + * Copyright © 2016-2019 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 { + .entity-state-controller { + .state-divider { + padding-right: 15px; + padding-left: 15px; + overflow: hidden; + font-size: 18px; + text-overflow: ellipsis; + white-space: nowrap; + pointer-events: none; + } + + .state-entry { + overflow: hidden; + font-size: 18px; + text-overflow: ellipsis; + white-space: nowrap; + outline: none; + } + + mat-select { + margin: 0; + + .mat-select-value-text { + font-size: 18px; + font-weight: 700; + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/entity-state-controller.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/states/entity-state-controller.component.ts new file mode 100644 index 0000000000..4c87aaf2df --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/entity-state-controller.component.ts @@ -0,0 +1,316 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + Component, + OnInit, + ViewEncapsulation, + Input, + OnDestroy, + OnChanges, + SimpleChanges, + NgZone +} from '@angular/core'; +import { IStateController, StateParams, StateObject } from '@core/api/widget-api.models'; +import { ActivatedRoute, Router } from '@angular/router'; +import { Observable, Subscription, of } from 'rxjs'; +import { IDashboardController } from '@home/pages/dashboard/dashboard-page.models'; +import { DashboardState } from '@shared/models/dashboard.models'; +import { IStateControllerComponent, StateControllerState } from './state-controller.models'; +import { StateControllerComponent } from './state-controller.component'; +import { StatesControllerService } from '@home/pages/dashboard/states/states-controller.service'; +import { EntityId } from '@app/shared/models/id/entity-id'; +import { UtilsService } from '@core/services/utils.service'; +import { base64toObj, objToBase64 } from '@app/core/utils'; +import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; +import { EntityService } from '@core/http/entity.service'; +import { EntityType } from '@shared/models/entity-type.models'; +import { map } from 'rxjs/operators'; + +@Component({ + selector: 'tb-entity-state-controller', + templateUrl: './entity-state-controller.component.html', + styleUrls: ['./entity-state-controller.component.scss'] +}) +export class EntityStateControllerComponent extends StateControllerComponent implements OnInit, OnDestroy { + + private selectedStateIndex = -1; + + constructor(protected router: Router, + protected route: ActivatedRoute, + protected statesControllerService: StatesControllerService, + private utils: UtilsService, + private entityService: EntityService, + private dashboardUtils: DashboardUtilsService) { + super(router, route, statesControllerService); + } + + ngOnInit(): void { + super.ngOnInit(); + } + + ngOnDestroy(): void { + super.ngOnDestroy(); + } + + protected init() { + if (this.preservedState) { + this.stateObject = this.preservedState; + this.selectedStateIndex = this.stateObject.length - 1; + setTimeout(() => { + this.gotoState(this.stateObject[this.stateObject.length - 1].id, true); + }, 1); + } else { + const initialState = this.currentState; + this.stateObject = this.parseState(initialState); + this.selectedStateIndex = this.stateObject.length - 1; + setTimeout(() => { + this.gotoState(this.stateObject[this.stateObject.length - 1].id, false); + }, 1); + } + } + + protected onMobileChanged() { + } + + protected onStateIdChanged() { + } + + protected onStatesChanged() { + } + + protected onStateChanged() { + this.stateObject = this.parseState(this.currentState); + this.selectedStateIndex = this.stateObject.length - 1; + this.gotoState(this.stateObject[this.stateObject.length - 1].id, true); + } + + protected stateControllerId(): string { + return 'entity'; + } + + public getStateParams(): StateParams { + if (this.stateObject && this.stateObject.length) { + return this.stateObject[this.stateObject.length - 1].params; + } else { + return {}; + } + } + + public openState(id: string, params?: StateParams, openRightLayout?: boolean): void { + if (this.states && this.states[id]) { + this.resolveEntity(params).subscribe( + (entityName) => { + params.entityName = entityName; + const newState: StateObject = { + id, + params + }; + this.stateObject.push(newState); + this.selectedStateIndex = this.stateObject.length - 1; + this.gotoState(this.stateObject[this.stateObject.length - 1].id, true, openRightLayout); + } + ); + } + } + + public updateState(id: string, params?: StateParams, openRightLayout?: boolean): void { + if (!id) { + id = this.getStateId(); + } + if (this.states && this.states[id]) { + this.resolveEntity(params).subscribe( + (entityName) => { + params.entityName = entityName; + const newState: StateObject = { + id, + params + }; + this.stateObject[this.stateObject.length - 1] = newState; + this.gotoState(this.stateObject[this.stateObject.length - 1].id, true, openRightLayout); + } + ); + } + } + + public getEntityId(entityParamName: string): EntityId { + const stateParams = this.getStateParams(); + if (!entityParamName || !entityParamName.length) { + return stateParams.entityId; + } else if (stateParams[entityParamName]) { + return stateParams[entityParamName].entityId; + } + return null; + } + + public getStateId(): string { + if (this.stateObject && this.stateObject.length) { + return this.stateObject[this.stateObject.length - 1].id; + } else { + return ''; + } + } + + public getStateIdAtIndex(index: number): string { + if (this.stateObject && this.stateObject[index]) { + return this.stateObject[index].id; + } else { + return ''; + } + } + + public getStateIndex(): number { + if (this.stateObject && this.stateObject.length) { + return this.stateObject.length - 1; + } else { + return -1; + } + } + + public getStateParamsByStateId(stateId: string): StateParams { + const stateObj = this.getStateObjById(stateId); + if (stateObj) { + return stateObj.params; + } else { + return null; + } + } + + public navigatePrevState(index: number): void { + if (index < this.stateObject.length - 1) { + this.stateObject.splice(index + 1, this.stateObject.length - index - 1); + this.selectedStateIndex = this.stateObject.length - 1; + this.gotoState(this.stateObject[this.stateObject.length - 1].id, true); + } + } + + public resetState(): void { + const rootStateId = this.dashboardUtils.getRootStateId(this.states); + this.stateObject = [ { id: rootStateId, params: {} } ]; + this.gotoState(rootStateId, true); + } + + public getStateName(index: number): string { + let result = ''; + if (this.stateObject[index]) { + let stateName = this.states[this.stateObject[index].id].name; + stateName = this.utils.customTranslation(stateName, stateName); + const params = this.stateObject[index].params; + const entityName = params && params.entityName ? params.entityName : ''; + result = this.utils.insertVariable(stateName, 'entityName', entityName); + for (const prop of Object.keys(params)) { + if (params[prop] && params[prop].entityName) { + result = this.utils.insertVariable(result, prop + ':entityName', params[prop].entityName); + } + } + } + return result; + } + + public selectedStateIndexChanged() { + this.navigatePrevState(this.selectedStateIndex); + } + + private parseState(stateBase64: string): StateControllerState { + let result: StateControllerState; + if (stateBase64) { + try { + result = base64toObj(stateBase64); + } catch (e) { + result = [ { id: null, params: {} } ]; + } + } + if (!result) { + result = []; + } + if (!result.length) { + result[0] = { id: null, params: {} }; + } + const rootStateId = this.dashboardUtils.getRootStateId(this.states); + if (!result[0].id) { + result[0].id = rootStateId; + } + if (!this.states[result[0].id]) { + result[0].id = rootStateId; + } + let i = result.length; + while (i--) { + if (!result[i].id || !this.states[result[i].id]) { + result.splice(i, 1); + } + } + return result; + } + + private gotoState(stateId: string, update: boolean, openRightLayout?: boolean) { + this.dashboardCtrl.openDashboardState(stateId, openRightLayout); + if (update) { + this.updateLocation(); + } + } + + private updateLocation() { + if (this.stateObject[this.stateObject.length - 1].id) { + let newState; + if (this.isDefaultState()) { + newState = null; + } else { + newState = objToBase64(this.stateObject); + } + this.updateStateParam(newState); + } + } + + private isDefaultState(): boolean { + if (this.stateObject.length === 1) { + const state = this.stateObject[0]; + const rootStateId = this.dashboardUtils.getRootStateId(this.states); + if (state.id === rootStateId && (!state.params || this.isEmpty(state.params))) { + return true; + } + } + return false; + } + + private isEmpty(obj: any): boolean { + for (const key of Object.keys(obj)) { + return !Object.prototype.hasOwnProperty.call(obj, key); + } + return true; + } + + private resolveEntity(params: StateParams): Observable { + if (params && params.targetEntityParamName) { + params = params[params.targetEntityParamName]; + } + if (params && params.entityId && params.entityId.id && params.entityId.entityType) { + if (params.entityName && params.entityName.length) { + return of(params.entityName); + } else { + return this.entityService.getEntity(params.entityId.entityType as EntityType, + params.entityId.id, true, true).pipe( + map((entity) => entity.name) + ); + } + } else { + return of(''); + } + } + + private getStateObjById(id: string): StateObject { + return this.stateObject.find((stateObj) => stateObj.id === id); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/state-controller.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/states/state-controller.component.ts new file mode 100644 index 0000000000..4ef1e83d83 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/state-controller.component.ts @@ -0,0 +1,173 @@ +/// +/// Copyright © 2016-2019 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 { IStateControllerComponent, StateControllerState } from '@home/pages/dashboard/states/state-controller.models'; +import { IDashboardController } from '../dashboard-page.models'; +import { DashboardState } from '@app/shared/models/dashboard.models'; +import { Subscription } from 'rxjs'; +import { OnDestroy, OnInit } from '@angular/core'; +import { ActivatedRoute, Router, Params } from '@angular/router'; +import { StatesControllerService } from '@home/pages/dashboard/states/states-controller.service'; +import { EntityId } from '@app/shared/models/id/entity-id'; +import { StateParams } from '@app/core/api/widget-api.models'; + +export abstract class StateControllerComponent implements IStateControllerComponent, OnInit, OnDestroy { + + stateObject: StateControllerState = []; + dashboardCtrl: IDashboardController; + preservedState: any; + + isMobileValue: boolean; + set isMobile(val: boolean) { + if (this.isMobileValue !== val) { + this.isMobileValue = val; + if (this.inited) { + this.onMobileChanged(); + } + } + } + get isMobile(): boolean { + return this.isMobileValue; + } + + stateValue: string; + set state(val: string) { + if (this.stateValue !== val) { + this.stateValue = val; + if (this.inited) { + this.onStateIdChanged(); + } + } + } + get state(): string { + return this.stateValue; + } + + dashboardIdValue: string; + set dashboardId(val: string) { + if (this.dashboardIdValue !== val) { + this.dashboardIdValue = val; + if (this.inited) { + this.init(); + } + } + } + get dashboardId(): string { + return this.dashboardIdValue; + } + + statesValue: { [id: string]: DashboardState }; + set states(val: { [id: string]: DashboardState }) { + if (this.statesValue !== val) { + this.statesValue = val; + if (this.inited) { + this.onStatesChanged(); + } + } + } + get states(): { [id: string]: DashboardState } { + return this.statesValue; + } + + currentState: string; + + private rxSubscriptions = new Array(); + + private inited = false; + + constructor(protected router: Router, + protected route: ActivatedRoute, + protected statesControllerService: StatesControllerService) { + } + + ngOnInit(): void { + this.rxSubscriptions.push(this.route.queryParamMap.subscribe((paramMap) => { + const newState = paramMap.get('state'); + if (this.currentState !== newState) { + this.currentState = newState; + if (this.inited) { + this.onStateChanged(); + } + } + })); + this.init(); + this.inited = true; + } + + ngOnDestroy(): void { + this.rxSubscriptions.forEach((subscription) => { + subscription.unsubscribe(); + }); + this.rxSubscriptions.length = 0; + } + + protected updateStateParam(newState: string) { + this.currentState = newState; + const queryParams: Params = { state: this.currentState }; + this.router.navigate( + [], + { + relativeTo: this.route, + queryParams, + queryParamsHandling: 'merge', + }); + } + + public openRightLayout(): void { + this.dashboardCtrl.openRightLayout(); + } + + public preserveState() { + this.statesControllerService.preserveStateControllerState(this.stateControllerId(), this.stateObject); + } + + public cleanupPreservedStates() { + this.statesControllerService.cleanupPreservedStates(); + } + + protected abstract init(); + + protected abstract onMobileChanged(); + + protected abstract onStateIdChanged(); + + protected abstract onStatesChanged(); + + protected abstract onStateChanged(); + + protected abstract stateControllerId(): string; + + public abstract getEntityId(entityParamName: string): EntityId; + + public abstract getStateId(): string; + + public abstract getStateIdAtIndex(index: number): string; + + public abstract getStateIndex(): number; + + public abstract getStateParams(): StateParams; + + public abstract getStateParamsByStateId(stateId: string): StateParams; + + public abstract navigatePrevState(index: number): void; + + public abstract openState(id: string, params?: StateParams, openRightLayout?: boolean): void; + + public abstract resetState(): void; + + public abstract updateState(id?: string, params?: StateParams, openRightLayout?: boolean): void; + +} diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/state-controller.models.ts b/ui-ngx/src/app/modules/home/pages/dashboard/states/state-controller.models.ts new file mode 100644 index 0000000000..2fa0fef100 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/state-controller.models.ts @@ -0,0 +1,30 @@ +/// +/// Copyright © 2016-2019 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 { IStateController, StateObject } from '@core/api/widget-api.models'; +import { IDashboardController } from '@home/pages/dashboard/dashboard-page.models'; +import { DashboardState } from '@shared/models/dashboard.models'; + +export declare type StateControllerState = StateObject[]; + +export interface IStateControllerComponent extends IStateController { + state: string; + isMobile: boolean; + dashboardCtrl: IDashboardController; + states: {[id: string]: DashboardState }; + dashboardId: string; + preservedState: any; +} diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/states-component.directive.ts b/ui-ngx/src/app/modules/home/pages/dashboard/states/states-component.directive.ts new file mode 100644 index 0000000000..48970e36ae --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/states-component.directive.ts @@ -0,0 +1,122 @@ +/// +/// Copyright © 2016-2019 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 { + ComponentRef, + Directive, + ElementRef, + Input, + OnChanges, + OnInit, + OnDestroy, + SimpleChanges, + ViewContainerRef, + ChangeDetectorRef +} from '@angular/core'; +import { DashboardPageComponent } from '@home/pages/dashboard/dashboard-page.component'; +import { DashboardState } from '@shared/models/dashboard.models'; +import { IDashboardController } from '@home/pages/dashboard/dashboard-page.models'; +import { StatesControllerService } from '@home/pages/dashboard/states/states-controller.service'; +import { IStateController } from '@core/api/widget-api.models'; +import { IStateControllerComponent } from '@home/pages/dashboard/states/state-controller.models'; + +@Directive({ + selector: 'tb-states-component' +}) +export class StatesComponentDirective implements OnInit, OnDestroy, OnChanges { + + @Input() + statesControllerId: string; + + @Input() + dashboardCtrl: IDashboardController; + + @Input() + dashboardId: string; + + @Input() + states: {[id: string]: DashboardState }; + + @Input() + state: string; + + @Input() + isMobile: boolean; + + stateControllerComponentRef: ComponentRef; + stateControllerComponent: IStateControllerComponent; + + constructor(private viewContainerRef: ViewContainerRef, + private statesControllerService: StatesControllerService) { + } + + ngOnInit(): void { + this.init(); + } + + ngOnDestroy(): void { + this.destroy(); + } + + ngOnChanges(changes: SimpleChanges): void { + for (const propName of Object.keys(changes)) { + const change = changes[propName]; + if (!change.firstChange && change.currentValue !== change.previousValue) { + if (propName === 'statesControllerId') { + this.reInit(); + } else if (propName === 'states') { + this.stateControllerComponent.states = this.states; + } else if (propName === 'dashboardId') { + this.stateControllerComponent.dashboardId = this.dashboardId; + } else if (propName === 'isMobile') { + this.stateControllerComponent.isMobile = this.isMobile; + } else if (propName === 'state') { + this.stateControllerComponent.state = this.state; + } + } + } + } + + private reInit() { + this.destroy(); + this.init(); + } + + private init() { + this.viewContainerRef.clear(); + let stateControllerData = this.statesControllerService.getStateController(this.statesControllerId); + if (!stateControllerData) { + stateControllerData = this.statesControllerService.getStateController('default'); + } + const preservedState = this.statesControllerService.withdrawStateControllerState(this.statesControllerId); + const stateControllerFactory = stateControllerData.factory; + this.stateControllerComponentRef = this.viewContainerRef.createComponent(stateControllerFactory); + this.stateControllerComponent = this.stateControllerComponentRef.instance; + this.dashboardCtrl.dashboardCtx.stateController = this.stateControllerComponent; + this.stateControllerComponent.preservedState = preservedState; + this.stateControllerComponent.dashboardCtrl = this.dashboardCtrl; + this.stateControllerComponent.state = this.state; + this.stateControllerComponent.isMobile = this.isMobile; + this.stateControllerComponent.states = this.states; + this.stateControllerComponent.dashboardId = this.dashboardId; + } + + private destroy() { + if (this.stateControllerComponentRef) { + this.stateControllerComponentRef.destroy(); + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/states-controller.module.ts b/ui-ngx/src/app/modules/home/pages/dashboard/states/states-controller.module.ts new file mode 100644 index 0000000000..55063eea2c --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/states-controller.module.ts @@ -0,0 +1,56 @@ +/// +/// Copyright © 2016-2019 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 { CommonModule } from '@angular/common'; +import { SharedModule } from '@shared/shared.module'; +import { HomeComponentsModule } from '@modules/home/components/home-components.module'; +import { StatesControllerService } from './states-controller.service'; +import { EntityStateControllerComponent } from './entity-state-controller.component'; +import { StatesComponentDirective } from './states-component.directive'; +import { HomeDialogsModule } from '@app/modules/home/dialogs/home-dialogs.module'; +import { DefaultStateControllerComponent } from '@home/pages/dashboard/states/default-state-controller.component'; + +@NgModule({ + entryComponents: [ + DefaultStateControllerComponent, + EntityStateControllerComponent + ], + declarations: [ + StatesComponentDirective, + DefaultStateControllerComponent, + EntityStateControllerComponent + ], + imports: [ + CommonModule, + SharedModule, + HomeComponentsModule, + HomeDialogsModule + ], + exports: [ + StatesComponentDirective + ], + providers: [ + StatesControllerService + ] +}) +export class StatesControllerModule { + + constructor(private statesControllerService: StatesControllerService) { + this.statesControllerService.registerStatesController('default', DefaultStateControllerComponent); + this.statesControllerService.registerStatesController('entity', EntityStateControllerComponent); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/states-controller.service.ts b/ui-ngx/src/app/modules/home/pages/dashboard/states/states-controller.service.ts new file mode 100644 index 0000000000..e99224342d --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/states-controller.service.ts @@ -0,0 +1,64 @@ +/// +/// Copyright © 2016-2019 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 { ComponentFactory, ComponentFactoryResolver, Injectable, Type } from '@angular/core'; +import { deepClone } from '@core/utils'; +import { IStateControllerComponent } from '@home/pages/dashboard/states/state-controller.models'; + +export interface StateControllerData { + factory: ComponentFactory; + state?: any; +} + +@Injectable() +export class StatesControllerService { + + statesControllers: {[stateControllerId: string]: StateControllerData} = {}; + + constructor(private componentFactoryResolver: ComponentFactoryResolver) { + } + + public registerStatesController(stateControllerId: string, stateControllerComponent: Type): void { + const componentFactory = this.componentFactoryResolver.resolveComponentFactory(stateControllerComponent); + this.statesControllers[stateControllerId] = { + factory: componentFactory + }; + } + + public getStateControllers(): {[stateControllerId: string]: StateControllerData} { + return this.statesControllers; + } + + public getStateController(stateControllerId: string): StateControllerData { + return this.statesControllers[stateControllerId]; + } + + public preserveStateControllerState(stateControllerId: string, state: any) { + this.statesControllers[stateControllerId].state = deepClone(state); + } + + public withdrawStateControllerState(stateControllerId: string): any { + const state = this.statesControllers[stateControllerId].state; + this.statesControllers[stateControllerId].state = null; + return state; + } + + public cleanupPreservedStates() { + for (const stateControllerId of Object.keys(this.statesControllers)) { + this.statesControllers[stateControllerId].state = null; + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts b/ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts index 5f452b346f..966c3feee7 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts @@ -26,10 +26,12 @@ import { Observable } from 'rxjs'; import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; import { WidgetService } from '@core/http/widget.service'; import { WidgetEditorComponent } from '@home/pages/widget/widget-editor.component'; -import { map } from 'rxjs/operators'; +import { map, share } from 'rxjs/operators'; import { toWidgetInfo, WidgetInfo } from '@home/models/widget-component.models'; -import { widgetType, WidgetType } from '@app/shared/models/widget.models'; +import { Widget, widgetType, WidgetType } from '@app/shared/models/widget.models'; import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard'; +import { WidgetsData } from '@home/models/dashboard-component.models'; +import { NULL_UUID } from '@shared/models/id/has-uuid'; export interface WidgetEditorData { widgetType: WidgetType; @@ -51,6 +53,69 @@ export class WidgetsBundleResolver implements Resolve { } } +@Injectable() +export class WidgetsTypesDataResolver implements Resolve { + + constructor(private widgetsService: WidgetService) { + } + + resolve(route: ActivatedRouteSnapshot): Observable { + const widgetsBundle: WidgetsBundle = route.parent.data.widgetsBundle; + const bundleAlias = widgetsBundle.alias; + const isSystem = widgetsBundle.tenantId.id === NULL_UUID; + return this.widgetsService.getBundleWidgetTypes(bundleAlias, + isSystem).pipe( + map((types) => { + types = types.sort((a, b) => { + let result = widgetType[b.descriptor.type].localeCompare(widgetType[a.descriptor.type]); + if (result === 0) { + result = b.createdTime - a.createdTime; + } + return result; + }); + const widgetTypes = new Array(types.length); + let top = 0; + const lastTop = [0, 0, 0]; + let col = 0; + let column = 0; + types.forEach((type) => { + const widgetTypeInfo = toWidgetInfo(type); + const sizeX = 8; + const sizeY = Math.floor(widgetTypeInfo.sizeY); + const widget: Widget = { + typeId: type.id, + isSystemType: isSystem, + bundleAlias, + typeAlias: widgetTypeInfo.alias, + type: widgetTypeInfo.type, + title: widgetTypeInfo.widgetName, + sizeX, + sizeY, + row: top, + col, + config: JSON.parse(widgetTypeInfo.defaultConfig) + }; + + widget.config.title = widgetTypeInfo.widgetName; + + widgetTypes.push(widget); + top += sizeY; + if (top > lastTop[column] + 10) { + lastTop[column] = top; + column++; + if (column > 2) { + column = 0; + } + top = lastTop[column]; + col = column * 8; + } + }); + return { widgets: widgetTypes }; + } + )); + } +} + @Injectable() export class WidgetEditorDataResolver implements Resolve { @@ -137,6 +202,9 @@ export const routes: Routes = [ data: { auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN], title: 'widget.widget-library' + }, + resolve: { + widgetsData: WidgetsTypesDataResolver } }, { @@ -183,6 +251,7 @@ export const routes: Routes = [ providers: [ WidgetsBundlesTableConfigResolver, WidgetsBundleResolver, + WidgetsTypesDataResolver, WidgetEditorDataResolver, WidgetEditorAddDataResolver ] diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.html b/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.html index ebb93167ed..7d23708ed0 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.html +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.html @@ -28,7 +28,8 @@ class="mat-headline tb-absolute-fill">widgets-bundle.empty >; + widgetsData: WidgetsData; footerFabButtons: FooterFabButtons = { fabTogglerName: 'widget.add-widget-type', @@ -84,8 +84,6 @@ export class WidgetLibraryComponent extends PageComponent implements OnInit { onRemoveWidget: this.removeWidgetType.bind(this) }; - widgetsData: Observable; - aliasController: IAliasController = new DummyAliasController(); constructor(protected store: Store, @@ -98,70 +96,12 @@ export class WidgetLibraryComponent extends PageComponent implements OnInit { this.authUser = getCurrentAuthUser(store); this.widgetsBundle = this.route.snapshot.data.widgetsBundle; + this.widgetsData = this.route.snapshot.data.widgetsData; if (this.authUser.authority === Authority.TENANT_ADMIN) { this.isReadOnly = !this.widgetsBundle || this.widgetsBundle.tenantId.id === NULL_UUID; } else { this.isReadOnly = this.authUser.authority !== Authority.SYS_ADMIN; } - this.loadWidgetTypes(); - this.widgetsData = this.widgetTypes$.pipe( - map(widgets => ({ widgets }))); - } - - loadWidgetTypes() { - const bundleAlias = this.widgetsBundle.alias; - const isSystem = this.widgetsBundle.tenantId.id === NULL_UUID; - this.widgetTypes$ = this.widgetService.getBundleWidgetTypes(bundleAlias, - isSystem).pipe( - map((types) => { - types = types.sort((a, b) => { - let result = widgetType[b.descriptor.type].localeCompare(widgetType[a.descriptor.type]); - if (result === 0) { - result = b.createdTime - a.createdTime; - } - return result; - }); - const widgetTypes = new Array(types.length); - let top = 0; - const lastTop = [0, 0, 0]; - let col = 0; - let column = 0; - types.forEach((type) => { - const widgetTypeInfo = toWidgetInfo(type); - const sizeX = 8; - const sizeY = Math.floor(widgetTypeInfo.sizeY); - const widget: Widget = { - typeId: type.id, - isSystemType: isSystem, - bundleAlias, - typeAlias: widgetTypeInfo.alias, - type: widgetTypeInfo.type, - title: widgetTypeInfo.widgetName, - sizeX, - sizeY, - row: top, - col, - config: JSON.parse(widgetTypeInfo.defaultConfig) - }; - - widget.config.title = widgetTypeInfo.widgetName; - - widgetTypes.push(widget); - top += sizeY; - if (top > lastTop[column] + 10) { - lastTop[column] = top; - column++; - if (column > 2) { - column = 0; - } - top = lastTop[column]; - col = column * 8; - } - }); - return widgetTypes; - } - ), - share()); } ngOnInit(): void { diff --git a/ui-ngx/src/app/shared/components/footer-fab-buttons.component.html b/ui-ngx/src/app/shared/components/footer-fab-buttons.component.html index 5d98611cc6..4e0eb00477 100644 --- a/ui-ngx/src/app/shared/components/footer-fab-buttons.component.html +++ b/ui-ngx/src/app/shared/components/footer-fab-buttons.component.html @@ -15,7 +15,7 @@ limitations under the License. --> -
- + + + +
+
+
+
+ + +
+
+
+
); diff --git a/ui-ngx/src/app/shared/components/json-form/react/json-form-utils.ts b/ui-ngx/src/app/shared/components/json-form/react/json-form-utils.ts index df58a1918c..d849cd8860 100644 --- a/ui-ngx/src/app/shared/components/json-form/react/json-form-utils.ts +++ b/ui-ngx/src/app/shared/components/json-form/react/json-form-utils.ts @@ -544,11 +544,13 @@ function traverseSchema(schema: JsonSchemaData, fn: (prop: any, path: string[]) const traverse = ($schema: JsonSchemaData, $fn: (prop: any, path: string[]) => any, $path: string[]) => { $fn($schema, $path); - for (const k of Object.keys($schema.properties)) { - if ($schema.properties.hasOwnProperty(k)) { - const currentPath = $path.slice(); - currentPath.push(k); - traverse($schema.properties[k], $fn, currentPath); + if ($schema.properties) { + for (const k of Object.keys($schema.properties)) { + if ($schema.properties.hasOwnProperty(k)) { + const currentPath = $path.slice(); + currentPath.push(k); + traverse($schema.properties[k], $fn, currentPath); + } } } if (!ignoreArrays && $schema.items) { diff --git a/ui-ngx/src/app/shared/components/json-form/react/json-form.models.ts b/ui-ngx/src/app/shared/components/json-form/react/json-form.models.ts index a90dc47392..f53d52177e 100644 --- a/ui-ngx/src/app/shared/components/json-form/react/json-form.models.ts +++ b/ui-ngx/src/app/shared/components/json-form/react/json-form.models.ts @@ -18,12 +18,13 @@ import { isUndefined, isDefined, isString } from '@app/core/utils'; import * as equal from 'deep-equal'; import ObjectPath from 'objectpath'; import * as React from 'react'; +import * as tinycolor from 'tinycolor2'; export interface SchemaValidationResult { valid: boolean; error?: { message?: string; - } + }; } export interface FormOption { @@ -47,6 +48,11 @@ export interface GroupInfo { GroupTitle: string; } +export type onChangeFn = (key: (string | number)[], val: any) => void; +export type OnColorClickFn = (key: (string | number)[], val: tinycolor.ColorFormats.RGBA, + colorSelectedFn: (color: tinycolor.ColorFormats.RGBA) => void) => void; +export type onToggleFullscreenFn = (element: HTMLElement, fullscreenFinishFn?: () => void) => void; + export interface JsonFormProps { model?: any; schema?: any; @@ -55,9 +61,9 @@ export interface JsonFormProps { isFullscreen: boolean; ignore?: {[key: string]: boolean}; option: FormOption; - onModelChange?: (key: (string | number)[], val: any) => void; - onColorClick?: (event: MouseEvent, key: string, val: string) => void; - onToggleFullscreen?: () => void; + onModelChange?: onChangeFn; + onColorClick?: OnColorClickFn; + onToggleFullscreen?: onToggleFullscreenFn; mapper?: {[type: string]: any}; } @@ -98,22 +104,24 @@ export interface JsonFormData { [key: string]: any; } +export type ComponentBuilderFn = (form: JsonFormData, + model: any, + index: number, + onChange: onChangeFn, + onColorClick: OnColorClickFn, + onToggleFullscreen: onToggleFullscreenFn, + mapper: {[type: string]: any}) => JSX.Element; + export interface JsonFormFieldProps { value: any; model: any; form: JsonFormData; - builder: (form: JsonFormData, - model: any, - index: number, - onChange: (key: (string | number)[], val: any) => void, - onColorClick: (event: MouseEvent, key: string, val: string) => void, - onToggleFullscreen: () => void, - mapper: {[type: string]: any}) => JSX.Element; + builder: ComponentBuilderFn; mapper?: {[type: string]: any}; - onChange?: (key: (string | number)[], val: any) => void; - onColorClick?: (event: MouseEvent, key: string, val: string) => void; + onChange?: onChangeFn; + onColorClick?: OnColorClickFn; onChangeValidate?: (e: any) => void; - onToggleFullscreen?: () => void; + onToggleFullscreen?: onToggleFullscreenFn; valid?: boolean; error?: string; options?: { diff --git a/ui-ngx/src/app/shared/components/json-form/react/json-form.scss b/ui-ngx/src/app/shared/components/json-form/react/json-form.scss index 09676a37fc..ae6c9d5597 100644 --- a/ui-ngx/src/app/shared/components/json-form/react/json-form.scss +++ b/ui-ngx/src/app/shared/components/json-form/react/json-form.scss @@ -21,47 +21,24 @@ $swift-ease-out-timing-function: cubic-bezier(.25, .8, .25, 1) !default; $input-label-float-offset: 6px !default; $input-label-float-scale: .75 !default; -.tb-json-form { - &.tb-fullscreen { - [name="ReactSchemaForm"] { - .SchemaForm { - &.SchemaFormFullscreen { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - - > div:not(.fullscreen-form-field) { - display: none !important; - } - - > div.fullscreen-form-field { - position: relative; - width: 100%; - height: 100%; - } - } - } +$previewSize: 100px !default; - > div { - > section { - margin: 0 !important; - box-shadow: none !important; - - .SchemaGroupname { - display: none !important; - } +.tb-json-form { - > div { - padding: 0 !important; - } - } - } + &.tb-fullscreen { + background: #fff; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + > div.fullscreen-form-field { + position: relative; + width: 100%; + height: 100%; } } - .json-form-error { position: relative; bottom: -5px; @@ -74,6 +51,7 @@ $input-label-float-scale: .75 !default; .tb-container { position: relative; + box-sizing: border-box; padding: 10px 0; margin-top: 32px; } @@ -144,6 +122,7 @@ $input-label-float-scale: .75 !default; .tb-head-label { color: rgba(0, 0, 0, .54); + padding-bottom: 15px; } .SchemaGroupname { @@ -154,4 +133,130 @@ $input-label-float-scale: .75 !default; .invisible { display: none; } + + span.tb-toggle-icon { + padding-top: 12px; + padding-bottom: 12px; + } + + .tb-button-toggle .tb-toggle-icon { + display: inline-block; + width: 15px; + margin: auto 0 auto auto; + background-size: 100% auto; + + transition: transform .3s, ease-in-out; + } + + .tb-button-toggle .tb-toggle-icon.tb-toggled { + transform: rotateZ(180deg); + } + + .fullscreen-form-field { + .json-form-ace-editor { + height: calc(100% - 60px); + } + } + + .json-form-ace-editor { + position: relative; + height: 100%; + border: 1px solid #c0c0c0; + + .title-panel { + position: absolute; + top: 10px; + right: 20px; + z-index: 5; + font-size: .8rem; + font-weight: 500; + + label { + padding: 4px; + color: #00acc1; + text-transform: uppercase; + background: rgba(220, 220, 220, .35); + border-radius: 5px; + } + + button.tidy-button { + background: rgba(220, 220, 220, .35) !important; + + span { + padding: 0 !important; + font-size: 12px !important; + } + } + } + } + + .tb-image-select-container { + position: relative; + width: 100%; + height: $previewSize; + } + + .tb-image-preview { + width: auto; + max-width: $previewSize; + height: auto; + max-height: $previewSize; + } + + .tb-image-preview-container { + position: relative; + float: left; + width: $previewSize; + height: $previewSize; + margin-right: 12px; + vertical-align: top; + border: solid 1px; + + div { + width: 100%; + font-size: 18px; + text-align: center; + } + + div, .tb-image-preview { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } + } + + .tb-dropzone { + outline: none; + position: relative; + height: $previewSize; + padding: 0 8px; + overflow: hidden; + vertical-align: top; + border: dashed 2px; + + div { + position: absolute; + top: 50%; + left: 50%; + width: 100%; + font-size: 24px; + text-align: center; + transform: translate(-50%, -50%); + } + } + + .tb-image-clear-container { + position: relative; + float: right; + width: 48px; + height: $previewSize; + } + + .tb-image-clear-btn { + position: absolute !important; + top: 50%; + transform: translate(0%, -50%) !important; + } + } diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index 63965492a6..02837ea5f9 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -283,11 +283,6 @@ export interface LegendData { data: Array; } -export interface WidgetConfigSettings { - [key: string]: any; - // TODO: -} - export enum WidgetActionType { openDashboardState = 'openDashboardState', updateDashboardState = 'updateDashboardState', @@ -347,7 +342,7 @@ export interface WidgetConfig { units?: string; decimals?: number; actions?: {[actionSourceId: string]: Array}; - settings?: WidgetConfigSettings; + settings?: any; alarmSource?: Datasource; alarmSearchStatus?: AlarmSearchStatus; alarmsPollingInterval?: number; diff --git a/ui-ngx/src/scss/mixins.scss b/ui-ngx/src/scss/mixins.scss index 21f5f58cdc..1cde9ee21a 100644 --- a/ui-ngx/src/scss/mixins.scss +++ b/ui-ngx/src/scss/mixins.scss @@ -39,3 +39,12 @@ margin: auto; } } + +@mixin tb-checkered-bg() { + background-color: #fff; + background-image: + linear-gradient(45deg, #ddd 25%, transparent 25%, transparent 75%, #ddd 75%, #ddd), + linear-gradient(45deg, #ddd 25%, transparent 25%, transparent 75%, #ddd 75%, #ddd); + background-position: 0 0, 4px 4px; + background-size: 8px 8px; +} diff --git a/ui-ngx/src/styles.scss b/ui-ngx/src/styles.scss index 6cbfe8675f..2b96834b38 100644 --- a/ui-ngx/src/styles.scss +++ b/ui-ngx/src/styles.scss @@ -852,4 +852,25 @@ mat-label { } } } + + .tb-color-preview { + cursor: pointer; + box-sizing: border-box; + position: relative; + width: 24px; + min-width: 24px; + height: 24px; + overflow: hidden; + content: ""; + border: 2px solid #fff; + border-radius: 50%; + box-shadow: 0 3px 1px -2px rgba(0, 0, 0, .14), 0 2px 2px 0 rgba(0, 0, 0, .098), 0 1px 5px 0 rgba(0, 0, 0, .084); + + @include tb-checkered-bg(); + + .tb-color-result { + width: 100%; + height: 100%; + } + } } From c3ebc2f20d4dc1e03418c80d278a2cbd34460918 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 24 Oct 2019 19:52:19 +0300 Subject: [PATCH 044/133] Widget Configuration: Widget Actions. --- ui-ngx/package-lock.json | 6 +- .../src/app/core/api/widget-subscription.ts | 43 +- .../src/app/core/services/dialog.service.ts | 15 + ui-ngx/src/app/core/services/utils.service.ts | 39 +- .../dashboard/dashboard.component.html | 2 +- .../dashboard/dashboard.component.scss | 1 + .../dashboard/dashboard.component.ts | 35 +- .../components/details-panel.component.ts | 2 +- .../home/components/home-components.module.ts | 25 +- ...custom-action-pretty-editor.component.html | 53 +++ ...custom-action-pretty-editor.component.scss | 107 +++++ .../custom-action-pretty-editor.component.ts | 138 ++++++ ...ction-pretty-resources-tabs.component.html | 100 +++++ ...ction-pretty-resources-tabs.component.scss | 79 ++++ ...-action-pretty-resources-tabs.component.ts | 211 ++++++++++ .../widget/action/custom-sample-css.raw | 118 ++++++ .../widget/action/custom-sample-html.raw | 304 ++++++++++++++ .../widget/action/custom-sample-js.raw | 397 ++++++++++++++++++ .../manage-widget-actions.component.html | 118 ++++++ .../manage-widget-actions.component.models.ts | 167 ++++++++ .../manage-widget-actions.component.scss | 43 ++ .../action/manage-widget-actions.component.ts | 316 ++++++++++++++ .../widget-action-dialog.component.html | 163 +++++++ .../action/widget-action-dialog.component.ts | 295 +++++++++++++ .../components/widget/data-keys.component.ts | 4 +- .../widget/legend-config-panel.component.html | 57 +++ .../widget/legend-config-panel.component.scss | 32 ++ .../widget/legend-config-panel.component.ts | 118 ++++++ .../widget/legend-config.component.html | 22 + .../widget/legend-config.component.ts | 198 +++++++++ .../widget/widget-config.component.html | 188 ++++++++- .../widget/widget-config.component.models.ts | 3 +- .../widget/widget-config.component.scss | 10 + .../widget/widget-config.component.ts | 311 ++++++++++---- .../components/widget/widget.component.ts | 43 +- .../home/models/dashboard-component.models.ts | 4 +- .../home/models/widget-component.models.ts | 16 +- .../dashboard/dashboard-page.component.ts | 32 +- .../pages/dashboard/dashboard-page.models.ts | 2 + .../dashboard/edit-widget.component.html | 1 + .../pages/dashboard/edit-widget.component.ts | 2 +- .../layout/dashboard-layout.component.ts | 4 +- .../default-state-controller.component.ts | 2 +- .../entity-state-controller.component.ts | 2 +- .../dashboard-autocomplete.component.ts | 3 +- .../material-icons-dialog.component.html | 78 ++++ .../material-icons-dialog.component.scss | 39 ++ .../dialog/material-icons-dialog.component.ts | 108 +++++ .../footer-fab-buttons.component.ts | 2 +- .../shared/components/fullscreen.directive.ts | 10 +- .../shared/components/js-func.component.html | 3 +- .../shared/components/js-func.component.scss | 13 +- .../shared/components/js-func.component.ts | 49 ++- .../json-form/json-form.component.ts | 1 + .../json-object-edit.component.html | 2 +- .../components/json-object-edit.component.ts | 4 +- .../material-icon-select.component.html | 24 ++ .../material-icon-select.component.scss | 23 + .../material-icon-select.component.ts | 125 ++++++ .../components/time/timewindow.component.html | 4 +- .../components/time/timewindow.component.ts | 1 + ui-ngx/src/app/shared/models/widget.models.ts | 29 +- ui-ngx/src/app/shared/shared.module.ts | 9 +- .../assets/locale/locale.constant-en_US.json | 1 + ui-ngx/src/styles.scss | 9 +- ui-ngx/src/typings/rawloader.typings.d.ts | 20 + ui-ngx/tsconfig.json | 1 + 67 files changed, 4197 insertions(+), 189 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-editor.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-editor.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-editor.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-resources-tabs.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-resources-tabs.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-resources-tabs.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/action/custom-sample-css.raw create mode 100644 ui-ngx/src/app/modules/home/components/widget/action/custom-sample-html.raw create mode 100644 ui-ngx/src/app/modules/home/components/widget/action/custom-sample-js.raw create mode 100644 ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.models.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/legend-config-panel.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/legend-config-panel.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/legend-config-panel.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/legend-config.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/legend-config.component.ts create mode 100644 ui-ngx/src/app/shared/components/dialog/material-icons-dialog.component.html create mode 100644 ui-ngx/src/app/shared/components/dialog/material-icons-dialog.component.scss create mode 100644 ui-ngx/src/app/shared/components/dialog/material-icons-dialog.component.ts create mode 100644 ui-ngx/src/app/shared/components/material-icon-select.component.html create mode 100644 ui-ngx/src/app/shared/components/material-icon-select.component.scss create mode 100644 ui-ngx/src/app/shared/components/material-icon-select.component.ts create mode 100644 ui-ngx/src/typings/rawloader.typings.d.ts diff --git a/ui-ngx/package-lock.json b/ui-ngx/package-lock.json index 4e56832b72..19e65a601c 100644 --- a/ui-ngx/package-lock.json +++ b/ui-ngx/package-lock.json @@ -5432,9 +5432,9 @@ "dev": true }, "https-proxy-agent": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.2.tgz", - "integrity": "sha512-c8Ndjc9Bkpfx/vCJueCPy0jlP4ccCCSNDp8xwCZzPjKJUm+B+u9WX2x98Qx4n1PiMNTWo3D7KK5ifNV/yJyRzg==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.3.tgz", + "integrity": "sha512-Ytgnz23gm2DVftnzqRRz2dOXZbGd2uiajSw/95bPp6v53zPRspQjLm/AfBgqbJ2qfeRXWIOMVLpp86+/5yX39Q==", "dev": true, "requires": { "agent-base": "^4.3.0", diff --git a/ui-ngx/src/app/core/api/widget-subscription.ts b/ui-ngx/src/app/core/api/widget-subscription.ts index 49b643886b..e66cb58c24 100644 --- a/ui-ngx/src/app/core/api/widget-subscription.ts +++ b/ui-ngx/src/app/core/api/widget-subscription.ts @@ -15,7 +15,8 @@ /// import { - IWidgetSubscription, SubscriptionEntityInfo, + IWidgetSubscription, + SubscriptionEntityInfo, WidgetSubscriptionCallbacks, WidgetSubscriptionContext, WidgetSubscriptionOptions @@ -48,6 +49,7 @@ import { deepClone, isDefined } from '@core/utils'; import { AlarmSourceListener } from '@core/http/alarm.service'; import { DatasourceListener } from '@core/api/datasource.service'; import * as deepEqual from 'deep-equal'; +import { EntityId } from '@app/shared/models/id/entity-id'; export class WidgetSubscription implements IWidgetSubscription { @@ -339,7 +341,44 @@ export class WidgetSubscription implements IWidgetSubscription { } getFirstEntityInfo(): SubscriptionEntityInfo { - return undefined; + let entityId: EntityId; + let entityName: string; + if (this.type === widgetType.rpc) { + if (this.targetDeviceId) { + entityId = { + entityType: EntityType.DEVICE, + id: this.targetDeviceId + }; + entityName = this.targetDeviceName; + } + } else if (this.type === widgetType.alarm) { + if (this.alarmSource && this.alarmSource.entityType && this.alarmSource.entityId) { + entityId = { + entityType: this.alarmSource.entityType, + id: this.alarmSource.entityId + }; + entityName = this.alarmSource.entityName; + } + } else { + for (const datasource of this.datasources) { + if (datasource && datasource.entityType && datasource.entityId) { + entityId = { + entityType: datasource.entityType, + id: datasource.entityId + }; + entityName = datasource.entityName; + break; + } + } + } + if (entityId) { + return { + entityId, + entityName + }; + } else { + return null; + } } onAliasesChanged(aliasIds: Array): boolean { diff --git a/ui-ngx/src/app/core/services/dialog.service.ts b/ui-ngx/src/app/core/services/dialog.service.ts index 99cdfe84d0..4f627e3a6e 100644 --- a/ui-ngx/src/app/core/services/dialog.service.ts +++ b/ui-ngx/src/app/core/services/dialog.service.ts @@ -26,6 +26,10 @@ import { ColorPickerDialogComponent, ColorPickerDialogData } from '@shared/components/dialog/color-picker-dialog.component'; +import { + MaterialIconsDialogComponent, + MaterialIconsDialogData +} from '@shared/components/dialog/material-icons-dialog.component'; @Injectable( { @@ -85,6 +89,17 @@ export class DialogService { }).afterClosed(); } + materialIconPicker(icon: string): Observable { + return this.dialog.open(MaterialIconsDialogComponent, + { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + icon + } + }).afterClosed(); + } + private permissionDenied() { this.alert( this.translate.instant('access.permission-denied'), diff --git a/ui-ngx/src/app/core/services/utils.service.ts b/ui-ngx/src/app/core/services/utils.service.ts index 69bf1328ec..26a9ade0ba 100644 --- a/ui-ngx/src/app/core/services/utils.service.ts +++ b/ui-ngx/src/app/core/services/utils.service.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Inject, Injectable } from '@angular/core'; +import { Inject, Injectable, NgZone } from '@angular/core'; import { WINDOW } from '@core/services/window.service'; import { ExceptionData } from '@app/shared/models/error.models'; import { deepClone, deleteNullProperties, isDefined, isUndefined } from '@core/utils'; @@ -28,6 +28,8 @@ import { alarmFields } 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'; +import * as materialIconsCodepoints from '!raw-loader!material-design-icons/iconfont/codepoints'; +import { Observable, of, ReplaySubject } from 'rxjs'; const varsRegex = /\$\{([^}]*)\}/g; @@ -58,6 +60,13 @@ const defaultAlarmFields: Array = [ alarmFields.status.keyName ]; +const commonMaterialIcons: Array = [ 'more_horiz', 'more_vert', 'open_in_new', + 'visibility', 'play_arrow', 'arrow_back', 'arrow_downward', + 'arrow_forward', 'arrow_upwards', 'close', 'refresh', 'menu', 'show_chart', 'multiline_chart', 'pie_chart', 'insert_chart', 'people', + 'person', 'domain', 'devices_other', 'now_widgets', 'dashboards', 'map', 'pin_drop', 'my_location', 'extension', 'search', + 'settings', 'notifications', 'notifications_active', 'info', 'info_outline', 'warning', 'list', 'file_download', 'import_export', + 'share', 'add', 'edit', 'done' ]; + @Injectable({ providedIn: 'root' }) @@ -85,7 +94,10 @@ export class UtilsService { defaultAlarmDataKeys: Array = []; + materialIcons: Array = []; + constructor(@Inject(WINDOW) private window: Window, + private zone: NgZone, private translate: TranslateService) { let frame: Element = null; try { @@ -282,6 +294,31 @@ export class UtilsService { return datasources; } + public getMaterialIcons(): Observable> { + if (this.materialIcons.length) { + return of(this.materialIcons); + } else { + const materialIconsSubject = new ReplaySubject>(); + this.zone.runOutsideAngular(() => { + const codepointsArray = materialIconsCodepoints + .split('\n') + .filter((codepoint) => codepoint && codepoint.length); + codepointsArray.forEach((codepoint) => { + const values = codepoint.split(' '); + if (values && values.length === 2) { + this.materialIcons.push(values[0]); + } + }); + materialIconsSubject.next(this.materialIcons); + }); + return materialIconsSubject.asObservable(); + } + } + + public getCommonMaterialIcons(): Array { + return commonMaterialIcons; + } + public getMaterialColor(index: number) { const colorIndex = index % materialColors.length; return materialColors[colorIndex].value; diff --git a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html index 135f397b20..6fe9e3f2bc 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html @@ -88,7 +88,7 @@
diff --git a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.scss b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.scss index b903b6fb2b..60c0f7c65e 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.scss +++ b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.scss @@ -39,6 +39,7 @@ gridster-item { transition: none; overflow: visible; + background: none; } } diff --git a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts index 605b2d43eb..b5e09abb49 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts @@ -15,12 +15,12 @@ /// import { - AfterViewInit, + AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, DoCheck, Input, IterableDiffers, - KeyValueDiffers, + KeyValueDiffers, NgZone, OnChanges, OnInit, SimpleChanges, @@ -162,7 +162,8 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo private dialogService: DialogService, private breakpointObserver: BreakpointObserver, private differs: IterableDiffers, - private kvDiffers: KeyValueDiffers) { + private kvDiffers: KeyValueDiffers, + private ngZone: NgZone) { super(store); this.authUser = getCurrentAuthUser(store); } @@ -259,20 +260,24 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo } onUpdateTimewindow(startTimeMs: number, endTimeMs: number, interval?: number): void { - if (!this.originalDashboardTimewindow) { - this.originalDashboardTimewindow = deepClone(this.dashboardTimewindow); - } - this.dashboardTimewindow = toHistoryTimewindow(this.dashboardTimewindow, - startTimeMs, endTimeMs, interval, this.timeService); - this.dashboardTimewindowChangedSubject.next(this.dashboardTimewindow); + this.ngZone.run(() => { + if (!this.originalDashboardTimewindow) { + this.originalDashboardTimewindow = deepClone(this.dashboardTimewindow); + } + this.dashboardTimewindow = toHistoryTimewindow(this.dashboardTimewindow, + startTimeMs, endTimeMs, interval, this.timeService); + this.dashboardTimewindowChangedSubject.next(this.dashboardTimewindow); + }); } onResetTimewindow(): void { - if (this.originalDashboardTimewindow) { - this.dashboardTimewindow = deepClone(this.originalDashboardTimewindow); - this.originalDashboardTimewindow = null; - this.dashboardTimewindowChangedSubject.next(this.dashboardTimewindow); - } + this.ngZone.run(() => { + if (this.originalDashboardTimewindow) { + this.dashboardTimewindow = deepClone(this.originalDashboardTimewindow); + this.originalDashboardTimewindow = null; + this.dashboardTimewindowChangedSubject.next(this.dashboardTimewindow); + } + }); } isAutofillHeight(): boolean { @@ -456,7 +461,7 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo this.gridsterOpts.draggable.enabled = this.isEdit; } - private notifyGridsterOptionsChanged() { + public notifyGridsterOptionsChanged() { if (this.gridster && this.gridster.options) { this.gridster.optionsChanged(); } 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 fa180657b5..a83616b654 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 @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index 64d59a15b1..6bb19470b7 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -50,6 +50,12 @@ import { EntityAliasSelectComponent } from './alias/entity-alias-select.componen import { DataKeysComponent } from '@home/components/widget/data-keys.component'; import { DataKeyConfigDialogComponent } from './widget/data-key-config-dialog.component'; import { DataKeyConfigComponent } from './widget/data-key-config.component'; +import { LegendConfigPanelComponent } from './widget/legend-config-panel.component'; +import { LegendConfigComponent } from './widget/legend-config.component'; +import { ManageWidgetActionsComponent } from './widget/action/manage-widget-actions.component'; +import { WidgetActionDialogComponent } from './widget/action/widget-action-dialog.component'; +import { CustomActionPrettyResourcesTabsComponent } from './widget/action/custom-action-pretty-resources-tabs.component'; +import { CustomActionPrettyEditorComponent } from './widget/action/custom-action-pretty-editor.component'; @NgModule({ entryComponents: [ @@ -64,7 +70,9 @@ import { DataKeyConfigComponent } from './widget/data-key-config.component'; AliasesEntitySelectPanelComponent, EntityAliasesDialogComponent, EntityAliasDialogComponent, - DataKeyConfigDialogComponent + DataKeyConfigDialogComponent, + LegendConfigPanelComponent, + WidgetActionDialogComponent ], declarations: [ @@ -99,7 +107,13 @@ import { DataKeyConfigComponent } from './widget/data-key-config.component'; EntityAliasSelectComponent, DataKeysComponent, DataKeyConfigComponent, - DataKeyConfigDialogComponent + DataKeyConfigDialogComponent, + LegendConfigPanelComponent, + LegendConfigComponent, + ManageWidgetActionsComponent, + WidgetActionDialogComponent, + CustomActionPrettyResourcesTabsComponent, + CustomActionPrettyEditorComponent ], imports: [ CommonModule, @@ -130,7 +144,12 @@ import { DataKeyConfigComponent } from './widget/data-key-config.component'; EntityAliasSelectComponent, DataKeysComponent, DataKeyConfigComponent, - DataKeyConfigDialogComponent + DataKeyConfigDialogComponent, + LegendConfigComponent, + ManageWidgetActionsComponent, + WidgetActionDialogComponent, + CustomActionPrettyResourcesTabsComponent, + CustomActionPrettyEditorComponent ], providers: [ WidgetComponentService diff --git a/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-editor.component.html b/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-editor.component.html new file mode 100644 index 0000000000..8b531c641f --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-editor.component.html @@ -0,0 +1,53 @@ + +
+
+ +
+
+
+ + +
+ +
+
+ + +
+
+ + +
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-editor.component.scss b/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-editor.component.scss new file mode 100644 index 0000000000..e4dc4f4a03 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-editor.component.scss @@ -0,0 +1,107 @@ +/** + * Copyright © 2016-2019 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-custom-action-pretty { + box-sizing: border-box; + position: relative; + padding: 8px; + background-color: #fff; + + .tb-fullscreen-panel { + .tb-custom-action-editor-container { + height: calc(100% - 40px); + } + + .right-panel { + padding-top: 8px; + padding-left: 3px; + } + + tb-js-func .tb-js-func-panel { + box-sizing: border-box; + } + + mat-tab-group { + .mat-tab-body-wrapper { + height: 100%; + mat-tab-body { + height: 100%; + & > div { + height: 100%; + } + } + } + } + } + + .tb-split { + box-sizing: border-box; + overflow-x: hidden; + overflow-y: auto; + } + + .tb-content { + border: 1px solid #c0c0c0; + } + + .gutter { + background-color: #eee; + background-repeat: no-repeat; + background-position: 50%; + } + + .gutter.gutter-horizontal { + cursor: col-resize; + background-image: url("../../../../../../assets/split.js/grips/vertical.png"); + } + + .tb-split.tb-split-horizontal, + .gutter.gutter-horizontal { + float: left; + height: 100%; + } + + .tb-action-expand-button { + position: absolute; + right: 14px; + z-index: 2; + + &.tb-fullscreen-editor { + position: relative; + right: 0; + .mat-button { + .mat-icon { + margin-right: 5px; + } + } + } + + .mat-button { + min-width: 36px; + padding: 0; + .mat-icon { + margin-right: 0; + } + } + } + + .tb-custom-action-editor { + &.tb-fullscreen-editor { + height: 100%; + } + } +} + + diff --git a/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-editor.component.ts b/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-editor.component.ts new file mode 100644 index 0000000000..c2570f2de3 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-editor.component.ts @@ -0,0 +1,138 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + AfterViewInit, + ChangeDetectionStrategy, + Component, + ElementRef, + forwardRef, + Input, + OnDestroy, + OnInit, + ViewChild, ViewEncapsulation, ViewChildren, QueryList, ComponentFactoryResolver +} from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { TranslateService } from '@ngx-translate/core'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { MatDialog } from '@angular/material/dialog'; +import { DialogService } from '@core/services/dialog.service'; +import { PageLink } from '@shared/models/page/page-link'; +import { Direction, SortOrder } from '@shared/models/page/sort-order'; +import { MatPaginator } from '@angular/material/paginator'; +import { MatSort } from '@angular/material/sort'; +import { combineLatest, fromEvent, merge } from 'rxjs'; +import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators'; +import { + WidgetActionDescriptorInfo, + WidgetActionsData, + WidgetActionsDatasource, + WidgetActionCallbacks, toWidgetActionDescriptor +} from '@home/components/widget/action/manage-widget-actions.component.models'; +import { UtilsService } from '@core/services/utils.service'; +import { EntityRelation, EntitySearchDirection, RelationTypeGroup } from '@shared/models/relation.models'; +import { RelationDialogComponent, RelationDialogData } from '@home/components/relation/relation-dialog.component'; +import { CustomActionDescriptor, WidgetActionDescriptor, WidgetActionSource } from '@shared/models/widget.models'; +import { + WidgetActionDialogComponent, + WidgetActionDialogData +} from '@home/components/widget/action/widget-action-dialog.component'; +import { deepClone } from '@core/utils'; +import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; +import { CustomActionPrettyResourcesTabsComponent } from '@home/components/widget/action/custom-action-pretty-resources-tabs.component'; +import { MatTab, MatTabGroup } from '@angular/material/tabs'; + +@Component({ + selector: 'tb-custom-action-pretty-editor', + templateUrl: './custom-action-pretty-editor.component.html', + styleUrls: ['./custom-action-pretty-editor.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => CustomActionPrettyEditorComponent), + multi: true + } + ], + encapsulation: ViewEncapsulation.None +}) +export class CustomActionPrettyEditorComponent extends PageComponent implements OnInit, AfterViewInit, OnDestroy, ControlValueAccessor { + + @Input() disabled: boolean; + + action: CustomActionDescriptor; + + fullscreen = false; + + @ViewChildren('leftPanel') + leftPanelElmRef: QueryList>; + + @ViewChildren('rightPanel') + rightPanelElmRef: QueryList>; + + private propagateChange = (_: any) => {}; + + constructor(protected store: Store) { + super(store); + } + + ngOnInit(): void { + } + + ngAfterViewInit(): void { + combineLatest(this.leftPanelElmRef.changes, this.rightPanelElmRef.changes).subscribe(() => { + if (this.leftPanelElmRef.length && this.rightPanelElmRef.length) { + this.initSplitLayout(this.leftPanelElmRef.first.nativeElement, + this.rightPanelElmRef.first.nativeElement); + } + }); + } + + private initSplitLayout(leftPanel: any, rightPanel: any) { + Split([leftPanel, rightPanel], { + sizes: [50, 50], + gutterSize: 8, + cursor: 'col-resize' + }); + } + + ngOnDestroy(): void { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + writeValue(obj: CustomActionDescriptor): void { + this.action = obj; + } + + public onActionUpdated(valid: boolean = true) { + if (!valid) { + this.propagateChange(null); + } else { + this.propagateChange(this.action); + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-resources-tabs.component.html b/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-resources-tabs.component.html new file mode 100644 index 0000000000..0ba01de5e6 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-resources-tabs.component.html @@ -0,0 +1,100 @@ + + + +
+
+
+ + + + +
+
+ +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ + + + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-resources-tabs.component.scss b/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-resources-tabs.component.scss new file mode 100644 index 0000000000..6176b1fb6a --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-resources-tabs.component.scss @@ -0,0 +1,79 @@ +/** + * Copyright © 2016-2019 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-custom-action-editor-container { + + mat-form-field.resource-field { + max-height: 40px; + margin: 10px 0px 0px 0px; + .mat-form-field-wrapper { + padding-bottom: 0; + .mat-form-field-flex { + max-height: 40px; + .mat-form-field-infix { + border: 0; + } + } + .mat-form-field-underline { + bottom: 0; + } + } + } + + .html-panel, + .css-panel { + width: 100%; + min-width: 200px; + height: 100%; + min-height: 200px; + } + + div.tb-editor-area-title-panel { + position: absolute; + top: 5px; + right: 20px; + z-index: 5; + font-size: .8rem; + font-weight: 500; + + label { + padding: 4px; + color: #00acc1; + text-transform: uppercase; + background: rgba(220, 220, 220, .35); + border-radius: 5px; + &:not(:last-child) { + margin-right: 4px; + } + } + + button.mat-button, button.mat-icon-button, button.mat-icon-button.tb-mat-32 { + align-items: center; + vertical-align: middle; + min-width: 32px; + min-height: 15px; + padding: 4px; + margin: 0; + font-size: .8rem; + line-height: 15px; + color: #7b7b7b; + background: rgba(220, 220, 220, .35); + &:not(:last-child) { + margin-right: 4px; + } + } + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-resources-tabs.component.ts b/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-resources-tabs.component.ts new file mode 100644 index 0000000000..3a7548843b --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/action/custom-action-pretty-resources-tabs.component.ts @@ -0,0 +1,211 @@ +/// +/// Copyright © 2016-2019 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, + OnChanges, + OnDestroy, + OnInit, + Output, QueryList, + SimpleChanges, + ViewChild, ViewChildren, ViewEncapsulation +} from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { CustomActionDescriptor } from '@shared/models/widget.models'; +import * as ace from 'ace-builds'; +import { CancelAnimationFrame, RafService } from '@core/services/raf.service'; +import { css_beautify, html_beautify } from 'js-beautify'; +import { MatTab } from '@angular/material/tabs'; +import { BehaviorSubject } from 'rxjs'; + +@Component({ + selector: 'tb-custom-action-pretty-resources-tabs', + templateUrl: './custom-action-pretty-resources-tabs.component.html', + styleUrls: ['./custom-action-pretty-resources-tabs.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class CustomActionPrettyResourcesTabsComponent extends PageComponent implements OnInit, OnChanges, OnDestroy { + + @Input() + action: CustomActionDescriptor; + + @Input() + hasCustomFunction: boolean; + + @Output() + actionUpdated: EventEmitter = new EventEmitter(); + + @ViewChild('htmlInput', {static: true}) + htmlInputElmRef: ElementRef; + + @ViewChild('cssInput', {static: true}) + cssInputElmRef: ElementRef; + + htmlFullscreen = false; + cssFullscreen = false; + + aceEditors: ace.Ace.Editor[] = []; + editorsResizeCafs: {[editorId: string]: CancelAnimationFrame} = {}; + aceResizeListeners: { element: any, resizeListener: any }[] = []; + htmlEditor: ace.Ace.Editor; + cssEditor: ace.Ace.Editor; + setValuesPending = false; + + constructor(protected store: Store, + private translate: TranslateService, + private raf: RafService) { + super(store); + } + + ngOnInit(): void { + this.initAceEditors(); + if (this.setValuesPending) { + this.setAceEditorValues(); + this.setValuesPending = false; + } + } + + ngOnDestroy(): void { + this.aceResizeListeners.forEach((resizeListener) => { + // @ts-ignore + removeResizeListener(resizeListener.element, resizeListener.resizeListener); + }); + } + + ngOnChanges(changes: SimpleChanges): void { + for (const propName of Object.keys(changes)) { + const change = changes[propName]; + if (propName === 'action') { + if (this.aceEditors.length) { + this.setAceEditorValues(); + } else { + this.setValuesPending = true; + } + } + } + } + + public notifyActionUpdated() { + this.actionUpdated.emit(this.validate() ? this.action : null); + } + + private validate(): boolean { + for (const resource of this.action.customResources) { + if (!resource.url) { + return false; + } + } + return true; + } + + public addResource() { + this.action.customResources.push({url: ''}); + this.notifyActionUpdated(); + } + + public removeResource(index: number) { + if (index > -1) { + if (this.action.customResources.splice(index, 1).length > 0) { + this.notifyActionUpdated(); + } + } + } + + public beautifyCss(): void { + const res = css_beautify(this.action.customCss, {indent_size: 4}); + if (this.action.customCss !== res) { + this.action.customCss = res; + this.cssEditor.setValue(this.action.customCss ? this.action.customCss : '', -1); + this.notifyActionUpdated(); + } + } + + public beautifyHtml(): void { + const res = html_beautify(this.action.customHtml, {indent_size: 4, wrap_line_length: 60}); + if (this.action.customHtml !== res) { + this.action.customHtml = res; + this.htmlEditor.setValue(this.action.customHtml ? this.action.customHtml : '', -1); + this.notifyActionUpdated(); + } + } + + private initAceEditors() { + this.htmlEditor = this.createAceEditor(this.htmlInputElmRef, 'html'); + this.htmlEditor.on('input', () => { + const editorValue = this.htmlEditor.getValue(); + if (this.action.customHtml !== editorValue) { + this.action.customHtml = editorValue; + this.notifyActionUpdated(); + } + }); + this.cssEditor = this.createAceEditor(this.cssInputElmRef, 'css'); + this.cssEditor.on('input', () => { + const editorValue = this.cssEditor.getValue(); + if (this.action.customCss !== editorValue) { + this.action.customCss = editorValue; + this.notifyActionUpdated(); + } + }); + } + + private createAceEditor(editorElementRef: ElementRef, mode: string): ace.Ace.Editor { + const editorElement = editorElementRef.nativeElement; + let editorOptions: Partial = { + mode: `ace/mode/${mode}`, + showGutter: true, + showPrintMargin: true + }; + const advancedOptions = { + enableSnippets: true, + enableBasicAutocompletion: true, + enableLiveAutocompletion: true + }; + editorOptions = {...editorOptions, ...advancedOptions}; + const aceEditor = ace.edit(editorElement, editorOptions); + aceEditor.session.setUseWrapMode(true); + this.aceEditors.push(aceEditor); + + const resizeListener = this.onAceEditorResize.bind(this, aceEditor); + + // @ts-ignore + addResizeListener(editorElement, resizeListener); + this.aceResizeListeners.push({element: editorElement, resizeListener}); + return aceEditor; + } + + private setAceEditorValues() { + this.htmlEditor.setValue(this.action.customHtml ? this.action.customHtml : '', -1); + this.cssEditor.setValue(this.action.customCss ? this.action.customCss : '', -1); + } + + private onAceEditorResize(aceEditor: ace.Ace.Editor) { + if (this.editorsResizeCafs[aceEditor.id]) { + this.editorsResizeCafs[aceEditor.id](); + delete this.editorsResizeCafs[aceEditor.id]; + } + this.editorsResizeCafs[aceEditor.id] = this.raf.raf(() => { + aceEditor.resize(); + aceEditor.renderer.updateFull(); + }); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/action/custom-sample-css.raw b/ui-ngx/src/app/modules/home/components/widget/action/custom-sample-css.raw new file mode 100644 index 0000000000..9167f2c10e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/action/custom-sample-css.raw @@ -0,0 +1,118 @@ +/*=======================================================================*/ +/*========== There are two examples: for edit and add entity ==========*/ +/*=======================================================================*/ +/*======================== Edit entity example ========================*/ +/*=======================================================================*/ +/* +.edit-entity-form md-input-container { + padding-right: 10px; +} + +.edit-entity-form .boolean-value-input { + padding-left: 5px; +} + +.edit-entity-form .boolean-value-input .checkbox-label { + margin-bottom: 8px; + color: rgba(0,0,0,0.54); + font-size: 12px; +} + +.relations-list .header { + padding-right: 5px; + padding-bottom: 5px; + padding-left: 5px; +} + +.relations-list .header .cell { + padding-right: 5px; + padding-left: 5px; + font-size: 12px; + font-weight: 700; + color: rgba(0, 0, 0, .54); + white-space: nowrap; +} + +.relations-list .body { + padding-right: 5px; + padding-bottom: 15px; + padding-left: 5px; +} + +.relations-list .body .row { + padding-top: 5px; +} + +.relations-list .body .cell { + padding-right: 5px; + padding-left: 5px; +} + +.relations-list .body md-autocomplete-wrap md-input-container { + height: 30px; +} + +.relations-list .body .md-button { + margin: 0; +} + +.relations-list.old-relations tb-entity-select tb-entity-autocomplete button { + display: none; +} +*/ +/*========================================================================*/ +/*========================= Add entity example =========================*/ +/*========================================================================*/ +/* +.add-entity-form md-input-container { + padding-right: 10px; +} + +.add-entity-form .boolean-value-input { + padding-left: 5px; +} + +.add-entity-form .boolean-value-input .checkbox-label { + margin-bottom: 8px; + color: rgba(0,0,0,0.54); + font-size: 12px; +} + +.relations-list .header { + padding-right: 5px; + padding-bottom: 5px; + padding-left: 5px; +} + +.relations-list .header .cell { + padding-right: 5px; + padding-left: 5px; + font-size: 12px; + font-weight: 700; + color: rgba(0, 0, 0, .54); + white-space: nowrap; +} + +.relations-list .body { + padding-right: 5px; + padding-bottom: 15px; + padding-left: 5px; +} + +.relations-list .body .row { + padding-top: 5px; +} + +.relations-list .body .cell { + padding-right: 5px; + padding-left: 5px; +} + +.relations-list .body md-autocomplete-wrap md-input-container { + height: 30px; +} + +.relations-list .body .md-button { + margin: 0; +} +*/ diff --git a/ui-ngx/src/app/modules/home/components/widget/action/custom-sample-html.raw b/ui-ngx/src/app/modules/home/components/widget/action/custom-sample-html.raw new file mode 100644 index 0000000000..5de572fd4d --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/action/custom-sample-html.raw @@ -0,0 +1,304 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/app/modules/home/components/widget/action/custom-sample-js.raw b/ui-ngx/src/app/modules/home/components/widget/action/custom-sample-js.raw new file mode 100644 index 0000000000..98ec6d044b --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/action/custom-sample-js.raw @@ -0,0 +1,397 @@ +/*=======================================================================*/ +/*===== There are three examples: for delete, edit and add entity =====*/ +/*=======================================================================*/ +/*======================= Delete entity example =======================*/ +/*=======================================================================*/ +// +//var $injector = widgetContext.$scope.$injector; +//var $mdDialog = $injector.get('$mdDialog'), +// $document = $injector.get('$document'), +// types = $injector.get('types'), +// assetService = $injector.get('assetService'), +// deviceService = $injector.get('deviceService') +// $rootScope = $injector.get('$rootScope'), +// $q = $injector.get('$q'); +// +//openDeleteEntityDialog(); +// +//function openDeleteEntityDialog() { +// var title = 'Delete ' + entityId.entityType +// .toLowerCase() + ' ' + +// entityName; +// var content = 'Are you sure you want to delete the ' + +// entityId.entityType.toLowerCase() + ' ' + +// entityName + '?'; +// var confirm = $mdDialog.confirm() +// .targetEvent($event) +// .title(title) +// .htmlContent(content) +// .ariaLabel(title) +// .cancel('Cancel') +// .ok('Delete'); +// $mdDialog.show(confirm).then(function() { +// deleteEntity(); +// }) +//} +// +//function deleteEntity() { +// deleteEntityPromise(entityId).then( +// function success() { +// updateAliasData(); +// }, +// function fail() { +// showErrorDialog(); +// } +// ); +//} +// +//function deleteEntityPromise(entityId) { +// if (entityId.entityType == types.entityType.asset) { +// return assetService.deleteAsset(entityId.id); +// } else if (entityId.entityType == types.entityType.device) { +// return deviceService.deleteDevice(entityId.id); +// } +//} +// +//function updateAliasData() { +// var aliasIds = []; +// for (var id in widgetContext.aliasController.resolvedAliases) { +// aliasIds.push(id); +// } +// var tasks = []; +// aliasIds.forEach(function(aliasId) { +// widgetContext.aliasController.setAliasUnresolved(aliasId); +// tasks.push(widgetContext.aliasController.getAliasInfo(aliasId)); +// }); +// $q.all(tasks).then(function() { +// $rootScope.$broadcast('entityAliasesChanged', aliasIds); +// }); +//} +// +//function showErrorDialog() { +// var title = 'Error'; +// var content = 'An error occurred while deleting the entity. Please try again.'; +// var alert = $mdDialog.alert() +// .title(title) +// .htmlContent(content) +// .ariaLabel(title) +// .parent(angular.element($document[0].body)) +// .targetEvent($event) +// .multiple(true) +// .clickOutsideToClose(true) +// .ok('CLOSE'); +// $mdDialog.show(alert); +//} +// +/*=======================================================================*/ +/*======================== Edit entity example ========================*/ +/*=======================================================================*/ +// +//var $injector = widgetContext.$scope.$injector; +//var $mdDialog = $injector.get('$mdDialog'), +// $document = $injector.get('$document'), +// $q = $injector.get('$q'), +// types = $injector.get('types'), +// $rootScope = $injector.get('$rootScope'), +// entityService = $injector.get('entityService'), +// attributeService = $injector.get('attributeService'), +// entityRelationService = $injector.get('entityRelationService'); +// +//openEditEntityDialog(); +// +//function openEditEntityDialog() { +// $mdDialog.show({ +// controller: ['$scope','$mdDialog', EditEntityDialogController], +// controllerAs: 'vm', +// template: htmlTemplate, +// locals: { +// entityId: entityId +// }, +// parent: angular.element($document[0].body), +// targetEvent: $event, +// multiple: true, +// clickOutsideToClose: false +// }); +//} +// +//function EditEntityDialogController($scope,$mdDialog) { +// var vm = this; +// vm.entityId = entityId; +// vm.entityName = entityName; +// vm.entityType = entityId.entityType; +// vm.allowedEntityTypes = [types.entityType.asset, types.entityType.device]; +// vm.allowedRelatedEntityTypes = []; +// vm.entitySearchDirection = types.entitySearchDirection; +// vm.attributes = {}; +// vm.serverAttributes = {}; +// vm.relations = []; +// vm.newRelations = []; +// vm.relationsToDelete = []; +// getEntityInfo(); +// +// vm.addRelation = function() { +// var relation = { +// direction: null, +// relationType: null, +// relatedEntity: null +// }; +// vm.newRelations.push(relation); +// $scope.editEntityForm.$setDirty(); +// }; +// vm.removeRelation = function(index) { +// if (index > -1) { +// vm.newRelations.splice(index, 1); +// $scope.editEntityForm.$setDirty(); +// } +// }; +// vm.removeOldRelation = function(index, relation) { +// if (index > -1) { +// vm.relations.splice(index, 1); +// vm.relationsToDelete.push(relation); +// $scope.editEntityForm.$setDirty(); +// } +// }; +// vm.save = function() { +// saveAttributes(); +// saveRelations(); +// $scope.editEntityForm.$setPristine(); +// }; +// vm.cancel = function() { +// $mdDialog.hide(); +// }; +// +// function getEntityAttributes(attributes) { +// for (var i = 0; i < attributes.length; i++) { +// vm.attributes[attributes[i].key] = attributes[i].value; +// } +// vm.serverAttributes = angular.copy(vm.attributes); +// } +// +// function getEntityRelations(relations) { +// var relationsFrom = relations[0]; +// var relationsTo = relations[1]; +// for (var i=0; i < relationsFrom.length; i++) { +// var relation = { +// direction: types.entitySearchDirection.from, +// relationType: relationsFrom[i].type, +// relatedEntity: relationsFrom[i].to +// }; +// vm.relations.push(relation); +// } +// for (var i=0; i < relationsTo.length; i++) { +// var relation = { +// direction: types.entitySearchDirection.to, +// relationType: relationsTo[i].type, +// relatedEntity: relationsTo[i].from +// }; +// vm.relations.push(relation); +// } +// } +// +// function getEntityInfo() { +// entityService.getEntity(entityId.entityType, entityId.id).then( +// function(entity) { +// vm.entity = entity; +// vm.type = vm.entity.type; +// }); +// attributeService.getEntityAttributesValues(entityId.entityType, entityId.id, 'SERVER_SCOPE').then( +// function(data){ +// if (data.length) { +// getEntityAttributes(data); +// } +// }); +// $q.all([entityRelationService.findInfoByFrom(entityId.id, entityId.entityType), entityRelationService.findInfoByTo(entityId.id, entityId.entityType)]).then( +// function(relations){ +// getEntityRelations(relations); +// }); +// } +// +// function saveAttributes() { +// var attributesArray = []; +// for (var key in vm.attributes) { +// if (vm.attributes[key] !== vm.serverAttributes[key]) { +// attributesArray.push({key: key, value: vm.attributes[key]}); +// } +// } +// if (attributesArray.length > 0) { +// attributeService.saveEntityAttributes(entityId.entityType, entityId.id, \"SERVER_SCOPE\", attributesArray); +// } +// } +// +// function saveRelations() { +// var tasks = []; +// for (var i=0; i < vm.newRelations.length; i++) { +// var relation = { +// type: vm.newRelations[i].relationType +// }; +// if (vm.newRelations[i].direction == types.entitySearchDirection.from) { +// relation.to = vm.newRelations[i].relatedEntity; +// relation.from = entityId; +// } else { +// relation.to = entityId; +// relation.from = vm.newRelations[i].relatedEntity; +// } +// tasks.push(entityRelationService.saveRelation(relation)); +// } +// for (var i=0; i < vm.relationsToDelete.length; i++) { +// var relation = { +// type: vm.relationsToDelete[i].relationType +// }; +// if (vm.relationsToDelete[i].direction == types.entitySearchDirection.from) { +// relation.to = vm.relationsToDelete[i].relatedEntity; +// relation.from = entityId; +// } else { +// relation.to = entityId; +// relation.from = vm.relationsToDelete[i].relatedEntity; +// } +// tasks.push(entityRelationService.deleteRelation(relation.from.id, relation.from.entityType, relation.type, relation.to.id, relation.to.entityType)); +// } +// $q.all(tasks).then(function(){ +// vm.relations = vm.relations.concat(vm.newRelations); +// vm.newRelations = []; +// vm.relationsToDelete = []; +// updateAliasData(); +// }); +// } +// +// function updateAliasData() { +// var aliasIds = []; +// for (var id in widgetContext.aliasController.resolvedAliases) { +// aliasIds.push(id); +// } +// var tasks = []; +// aliasIds.forEach(function(aliasId) { +// widgetContext.aliasController.setAliasUnresolved(aliasId); +// tasks.push(widgetContext.aliasController.getAliasInfo(aliasId)); +// }); +// $q.all(tasks).then(function() { +// $rootScope.$broadcast('entityAliasesChanged', aliasIds); +// }); +// } +//} +// +/*========================================================================*/ +/*========================= Add entity example =========================*/ +/*========================================================================*/ +// +//var $injector = widgetContext.$scope.$injector; +//var $mdDialog = $injector.get('$mdDialog'), +// $document = $injector.get('$document'), +// $q = $injector.get('$q'), +// $rootScope = $injector.get('$rootScope'), +// types = $injector.get('types'), +// assetService = $injector.get('assetService'), +// deviceService = $injector.get('deviceService'), +// attributeService = $injector.get('attributeService'), +// entityRelationService = $injector.get('entityRelationService'); +// +//openAddEntityDialog(); +// +//function openAddEntityDialog() { +// $mdDialog.show({ +// controller: ['$scope','$mdDialog', AddEntityDialogController], +// controllerAs: 'vm', +// template: htmlTemplate, +// locals: { +// entityId: entityId +// }, +// parent: angular.element($document[0].body), +// targetEvent: $event, +// multiple: true, +// clickOutsideToClose: false +// }); +//} +// +//function AddEntityDialogController($scope, $mdDialog) { +// var vm = this; +// vm.allowedEntityTypes = [types.entityType.asset, types.entityType.device]; +// vm.allowedRelatedEntityTypes = []; +// vm.entitySearchDirection = types.entitySearchDirection; +// vm.attributes = {}; +// vm.relations = []; +// +// vm.addRelation = function() { +// var relation = { +// direction: null, +// relationType: null, +// relatedEntity: null +// }; +// vm.relations.push(relation); +// }; +// vm.removeRelation = function(index) { +// if (index > -1) { +// vm.relations.splice(index, 1); +// } +// }; +// vm.save = function() { +// $scope.addEntityForm.$setPristine(); +// saveEntityPromise().then( +// function (entity) { +// saveAttributes(entity.id); +// saveRelations(entity.id); +// $mdDialog.hide(); +// } +// ); +// }; +// vm.cancel = function() { +// $mdDialog.hide(); +// }; +// +// +// function saveEntityPromise() { +// var entity = { +// name: vm.entityName, +// type: vm.type +// }; +// if (vm.entityType == types.entityType.asset) { +// return assetService.saveAsset(entity); +// } else if (vm.entityType == types.entityType.device) { +// return deviceService.saveDevice(entity); +// } +// } +// +// function saveAttributes(entityId) { +// var attributesArray = []; +// for (var key in vm.attributes) { +// attributesArray.push({key: key, value: vm.attributes[key]}); +// } +// if (attributesArray.length > 0) { +// attributeService.saveEntityAttributes(entityId.entityType, entityId.id, \"SERVER_SCOPE\", attributesArray); +// } +// } +// +// function saveRelations(entityId) { +// var tasks = []; +// for (var i=0; i < vm.relations.length; i++) { +// var relation = { +// type: vm.relations[i].relationType +// }; +// if (vm.relations[i].direction == types.entitySearchDirection.from) { +// relation.to = vm.relations[i].relatedEntity; +// relation.from = entityId; +// } else { +// relation.to = entityId; +// relation.from = vm.relations[i].relatedEntity; +// } +// tasks.push(entityRelationService.saveRelation(relation)); +// } +// $q.all(tasks).then(function(){ +// updateAliasData(); +// }); +// } +// +// function updateAliasData() { +// var aliasIds = []; +// for (var id in widgetContext.aliasController.resolvedAliases) { +// aliasIds.push(id); +// } +// var tasks = []; +// aliasIds.forEach(function(aliasId) { +// widgetContext.aliasController.setAliasUnresolved(aliasId); +// tasks.push(widgetContext.aliasController.getAliasInfo(aliasId)); +// }); +// $q.all(tasks).then(function() { +// $rootScope.$broadcast('entityAliasesChanged', aliasIds); +// }); +// } +//} diff --git a/ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.html b/ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.html new file mode 100644 index 0000000000..4c9cf6ead9 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.html @@ -0,0 +1,118 @@ + +
+
+ +
+ widget-config.actions + + + +
+
+ +
+ + +   + + + +
+
+
+ + + {{ 'widget-config.action-source' | translate }} + + {{ action.actionSourceName }} + + + + {{ 'widget-config.action-name' | translate }} + + {{ action.name }} + + + + {{ 'widget-config.action-icon' | translate }} + + {{ action.icon }} + + + + {{ 'widget-config.action-type' | translate }} + + {{ action.typeName }} + + + + + + +
+ + +
+
+
+ + +
+ {{ 'widget-config.no-actions-text' }} +
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.models.ts b/ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.models.ts new file mode 100644 index 0000000000..bd790c6d7f --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.models.ts @@ -0,0 +1,167 @@ +/// +/// Copyright © 2016-2019 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 { WidgetActionDescriptor, WidgetActionSource, + widgetActionTypeTranslationMap, CustomActionDescriptor } from '@app/shared/models/widget.models'; +import { CollectionViewer, DataSource } from '@angular/cdk/typings/collections'; +import { EntityRelationInfo, EntitySearchDirection } from '@shared/models/relation.models'; +import { BehaviorSubject, Observable, of, ReplaySubject } from 'rxjs'; +import { emptyPageData, PageData } from '@shared/models/page/page-data'; +import { SelectionModel } from '@angular/cdk/collections'; +import { EntityRelationService } from '@core/http/entity-relation.service'; +import { TranslateService } from '@ngx-translate/core'; +import { EntityId } from '@shared/models/id/entity-id'; +import { PageLink } from '@shared/models/page/page-link'; +import { catchError, map, publishReplay, refCount, share, take, tap } from 'rxjs/operators'; +import { entityTypeTranslations } from '@shared/models/entity-type.models'; +import { UtilsService } from '@core/services/utils.service'; +import { deepClone, isUndefined } from '@core/utils'; + +import * as customSampleJs from '!raw-loader!./custom-sample-js.raw'; +import * as customSampleCss from '!raw-loader!./custom-sample-css.raw'; +import * as customSampleHtml from '!raw-loader!./custom-sample-html.raw'; + +export interface WidgetActionCallbacks { + fetchDashboardStates: (query: string) => Array; +} + +export interface WidgetActionsData { + actionsMap: {[actionSourceId: string]: Array}; + actionSources: {[actionSourceId: string]: WidgetActionSource}; +} + +export interface WidgetActionDescriptorInfo extends WidgetActionDescriptor { + actionSourceId?: string; + actionSourceName?: string; + typeName?: string; +} + +export function toWidgetActionDescriptor(action: WidgetActionDescriptorInfo): WidgetActionDescriptor { + const copy = deepClone(action); + delete copy.actionSourceId; + delete copy.actionSourceName; + delete copy.typeName; + return copy; +} + +export function toCustomAction(action: WidgetActionDescriptorInfo): CustomActionDescriptor { + let result: CustomActionDescriptor; + if (!action || (isUndefined(action.customFunction) && isUndefined(action.customHtml) && isUndefined(action.customCss))) { + result = { + customHtml: customSampleHtml, + customCss: customSampleCss, + customFunction: customSampleJs + }; + } else { + result = { + customHtml: action.customHtml, + customCss: action.customCss, + customFunction: action.customFunction + }; + } + result.customResources = action ? deepClone(action.customResources) : []; + return result; +} + +export class WidgetActionsDatasource implements DataSource { + + private actionsSubject = new BehaviorSubject([]); + private pageDataSubject = new BehaviorSubject>(emptyPageData()); + + public pageData$ = this.pageDataSubject.asObservable(); + + private allActions: Observable>; + + private actionsMap: {[actionSourceId: string]: Array}; + private actionSources: {[actionSourceId: string]: WidgetActionSource}; + + constructor(private translate: TranslateService, + private utils: UtilsService) {} + + connect(collectionViewer: CollectionViewer): Observable> { + return this.actionsSubject.asObservable(); + } + + disconnect(collectionViewer: CollectionViewer): void { + this.actionsSubject.complete(); + this.pageDataSubject.complete(); + } + + setActions(actionsData: WidgetActionsData) { + this.actionsMap = actionsData.actionsMap; + this.actionSources = actionsData.actionSources; + } + + loadActions(pageLink: PageLink, reload: boolean = false): Observable> { + if (reload) { + this.allActions = null; + } + const result = new ReplaySubject>(); + this.fetchActions(pageLink).pipe( + catchError(() => of(emptyPageData())), + ).subscribe( + (pageData) => { + this.actionsSubject.next(pageData.data); + this.pageDataSubject.next(pageData); + result.next(pageData); + } + ); + return result; + } + + fetchActions(pageLink: PageLink): Observable> { + return this.getAllActions().pipe( + map((data) => pageLink.filterData(data)) + ); + } + + getAllActions(): Observable> { + if (!this.allActions) { + const actions: WidgetActionDescriptorInfo[] = []; + for (const actionSourceId of Object.keys(this.actionsMap)) { + const descriptors = this.actionsMap[actionSourceId]; + descriptors.forEach((descriptor) => { + actions.push(this.toWidgetActionDescriptorInfo(actionSourceId, descriptor)); + }); + } + this.allActions = of(actions).pipe( + publishReplay(1), + refCount() + ); + } + return this.allActions; + } + + private toWidgetActionDescriptorInfo(actionSourceId: string, action: WidgetActionDescriptor): WidgetActionDescriptorInfo { + const actionSource = this.actionSources[actionSourceId]; + const actionSourceName = actionSource ? this.utils.customTranslation(actionSource.name, actionSource.name) : actionSourceId; + const typeName = this.translate.instant(widgetActionTypeTranslationMap.get(action.type)); + return { actionSourceId, actionSourceName, typeName, ...action}; + } + + isEmpty(): Observable { + return this.actionsSubject.pipe( + map((actions) => !actions.length) + ); + } + + total(): Observable { + return this.pageDataSubject.pipe( + map((pageData) => pageData.totalElements) + ); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.scss b/ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.scss new file mode 100644 index 0000000000..0dd60c93b3 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.scss @@ -0,0 +1,43 @@ +/** + * Copyright © 2016-2019 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 { + width: 100%; + height: 100%; + .tb-entity-table { + .tb-entity-table-content { + width: 100%; + height: 100%; + background: #fff; + + .tb-entity-table-title { + padding-right: 20px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .table-container { + overflow: auto; + } + } + } +} + +:host ::ng-deep { + .mat-sort-header-sorted .mat-sort-header-arrow { + opacity: 1 !important; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.ts b/ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.ts new file mode 100644 index 0000000000..60bd8bb7eb --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.ts @@ -0,0 +1,316 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + AfterViewInit, + ChangeDetectionStrategy, + Component, + ElementRef, + forwardRef, + Input, + OnDestroy, + OnInit, + ViewChild +} from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { TranslateService } from '@ngx-translate/core'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { MatDialog } from '@angular/material/dialog'; +import { DialogService } from '@core/services/dialog.service'; +import { PageLink } from '@shared/models/page/page-link'; +import { Direction, SortOrder } from '@shared/models/page/sort-order'; +import { MatPaginator } from '@angular/material/paginator'; +import { MatSort } from '@angular/material/sort'; +import { fromEvent, merge } from 'rxjs'; +import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators'; +import { + WidgetActionDescriptorInfo, + WidgetActionsData, + WidgetActionsDatasource, + WidgetActionCallbacks, toWidgetActionDescriptor +} from '@home/components/widget/action/manage-widget-actions.component.models'; +import { UtilsService } from '@core/services/utils.service'; +import { EntityRelation, EntitySearchDirection, RelationTypeGroup } from '@shared/models/relation.models'; +import { RelationDialogComponent, RelationDialogData } from '@home/components/relation/relation-dialog.component'; +import { WidgetActionDescriptor, WidgetActionSource } from '@shared/models/widget.models'; +import { + WidgetActionDialogComponent, + WidgetActionDialogData +} from '@home/components/widget/action/widget-action-dialog.component'; +import { deepClone } from '@core/utils'; + +@Component({ + selector: 'tb-manage-widget-actions', + templateUrl: './manage-widget-actions.component.html', + styleUrls: ['./manage-widget-actions.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => ManageWidgetActionsComponent), + multi: true + } + ] +}) +export class ManageWidgetActionsComponent extends PageComponent implements OnInit, AfterViewInit, OnDestroy, ControlValueAccessor { + + @Input() disabled: boolean; + + @Input() callbacks: WidgetActionCallbacks; + + innerValue: WidgetActionsData; + + displayedColumns: string[]; + pageLink: PageLink; + textSearchMode = false; + dataSource: WidgetActionsDatasource; + + viewsInited = false; + dirtyValue = false; + + @ViewChild('searchInput', {static: false}) searchInputField: ElementRef; + + @ViewChild(MatPaginator, {static: false}) paginator: MatPaginator; + @ViewChild(MatSort, {static: false}) sort: MatSort; + + private propagateChange = (_: any) => {}; + + constructor(protected store: Store, + private translate: TranslateService, + private utils: UtilsService, + private dialog: MatDialog, + private dialogs: DialogService) { + super(store); + const sortOrder: SortOrder = { property: 'actionSourceName', direction: Direction.ASC }; + this.pageLink = new PageLink(10, 0, null, sortOrder); + this.dataSource = new WidgetActionsDatasource(this.translate, this.utils); + this.displayedColumns = ['actionSourceName', 'name', 'icon', 'typeName', 'actions']; + } + + ngOnInit(): void { + } + + ngOnDestroy(): void { + } + + ngAfterViewInit() { + + fromEvent(this.searchInputField.nativeElement, 'keyup') + .pipe( + debounceTime(150), + distinctUntilChanged(), + tap(() => { + this.paginator.pageIndex = 0; + this.updateData(); + }) + ) + .subscribe(); + + this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0); + + merge(this.sort.sortChange, this.paginator.page) + .pipe( + tap(() => this.updateData()) + ) + .subscribe(); + + this.viewsInited = true; + if (this.dirtyValue) { + this.dirtyValue = false; + this.updateData(true); + } + + } + + updateData(reload: boolean = false) { + this.pageLink.page = this.paginator.pageIndex; + this.pageLink.pageSize = this.paginator.pageSize; + this.pageLink.sortOrder.property = this.sort.active; + this.pageLink.sortOrder.direction = Direction[this.sort.direction.toUpperCase()]; + this.dataSource.loadActions(this.pageLink, reload); + } + + addAction($event: Event) { + this.openWidgetActionDialog($event); + } + + editAction($event: Event, action: WidgetActionDescriptorInfo) { + this.openWidgetActionDialog($event, action); + } + + openWidgetActionDialog($event: Event, action: WidgetActionDescriptorInfo = null) { + if ($event) { + $event.stopPropagation(); + } + const isAdd = action === null; + let prevActionSourceId = null; + if (!isAdd) { + prevActionSourceId = action.actionSourceId; + } + const availableActionSources: {[actionSourceId: string]: WidgetActionSource} = {}; + for (const id of Object.keys(this.innerValue.actionSources)) { + const actionSource = this.innerValue.actionSources[id]; + if (actionSource.multiple) { + availableActionSources[id] = actionSource; + } else { + if (!isAdd && action.actionSourceId === id) { + availableActionSources[id] = actionSource; + } else { + const existing = this.innerValue.actionsMap[id]; + if (!existing || !existing.length) { + availableActionSources[id] = actionSource; + } + } + } + } + + const actionsData: WidgetActionsData = { + actionsMap: this.innerValue.actionsMap, + actionSources: availableActionSources + }; + + this.dialog.open(WidgetActionDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + isAdd, + callbacks: this.callbacks, + actionsData, + action: deepClone(action) + } + }).afterClosed().subscribe( + (res) => { + if (res) { + this.saveAction(res, isAdd, prevActionSourceId); + } + } + ); + } + + private saveAction(actionInfo: WidgetActionDescriptorInfo, isAdd: boolean, prevActionSourceId: string) { + const actionSourceId = actionInfo.actionSourceId; + const action = toWidgetActionDescriptor(actionInfo); + if (isAdd) { + const targetActions = this.getOrCreateTargetActions(actionSourceId); + targetActions.push(action); + } else { + if (actionSourceId !== prevActionSourceId) { + let targetActions = this.getOrCreateTargetActions(prevActionSourceId); + const targetIndex = targetActions.findIndex((targetAction) => targetAction.id === action.id); + if (targetIndex > -1) { + targetActions.splice(targetIndex, 1); + } + targetActions = this.getOrCreateTargetActions(actionSourceId); + targetActions.push(action); + } else { + const targetActions = this.getOrCreateTargetActions(actionSourceId); + const targetIndex = targetActions.findIndex((targetAction) => targetAction.id === action.id); + if (targetIndex > -1) { + targetActions[targetIndex] = action; + } + } + } + this.onActionsUpdated(); + } + + private getOrCreateTargetActions(actionSourceId: string): Array { + const actionsMap = this.innerValue.actionsMap; + let targetActions = actionsMap[actionSourceId]; + if (!targetActions) { + targetActions = []; + actionsMap[actionSourceId] = targetActions; + } + return targetActions; + } + + deleteAction($event: Event, action: WidgetActionDescriptorInfo) { + if ($event) { + $event.stopPropagation(); + } + const title = this.translate.instant('widget-config.delete-action-title'); + const content = this.translate.instant('widget-config.delete-action-text', {actionName: action.name}); + this.dialogs.confirm(title, content, + this.translate.instant('action.no'), + this.translate.instant('action.yes'), true).subscribe( + (res) => { + if (res) { + const targetActions = this.getOrCreateTargetActions(action.actionSourceId); + const targetIndex = targetActions.findIndex((targetAction) => targetAction.id === action.id); + if (targetIndex > -1) { + targetActions.splice(targetIndex, 1); + this.onActionsUpdated(); + } + } + }); + } + + enterFilterMode() { + this.textSearchMode = true; + this.pageLink.textSearch = ''; + setTimeout(() => { + this.searchInputField.nativeElement.focus(); + this.searchInputField.nativeElement.setSelectionRange(0, 0); + }, 10); + } + + exitFilterMode() { + this.textSearchMode = false; + this.pageLink.textSearch = null; + this.paginator.pageIndex = 0; + this.updateData(); + } + + resetSortAndFilter(update: boolean = true) { + this.pageLink.textSearch = null; + this.paginator.pageIndex = 0; + const sortable = this.sort.sortables.get('actionSourceName'); + this.sort.active = sortable.id; + this.sort.direction = 'asc'; + if (update) { + this.updateData(true); + } + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + writeValue(obj: WidgetActionsData): void { + this.innerValue = obj; + setTimeout(() => { + this.dataSource.setActions(this.innerValue); + if (this.viewsInited) { + this.resetSortAndFilter(true); + } else { + this.dirtyValue = true; + } + }, 0); + } + + private onActionsUpdated() { + this.updateData(true); + this.propagateChange(this.innerValue); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html new file mode 100644 index 0000000000..06dcb65a68 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html @@ -0,0 +1,163 @@ + + + +

{{ (isAdd ? 'widget-config.add-action' : 'widget-config.edit-action' ) | translate }}

+ + +
+ + +
+
+
+ + widget-config.action-source + + + {{ actionSourceName(actionSourceItem.value) }} + + + + {{ 'widget-config.action-source-required' | translate }} + + + + widget-config.action-name + + + {{ 'widget-config.action-name-required' | translate }} + + + + + + + + widget-config.action-type + + + {{ widgetActionTypeTranslations.get(actionType) | translate }} + + + + {{ 'widget-config.action-type-required' | translate }} + + +
+ +
+
widget-action.target-dashboard
+ +
+
+ + + + + + + + + + + {{ 'widget-action.target-dashboard-state-required' | translate }} + + + + + + {{ 'widget-action.open-right-layout' | translate }} + + + +
+ + {{ 'widget-action.set-entity-from-widget' | translate }} + + + alias.state-entity-parameter-name + + +
+
+ + + + + + + +
+
+
+
+ + + +
+ diff --git a/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts b/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts new file mode 100644 index 0000000000..a3c85401be --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts @@ -0,0 +1,295 @@ +/// +/// Copyright © 2016-2019 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, Inject, OnInit, SkipSelf, ViewChild } from '@angular/core'; +import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { + FormBuilder, + FormControl, + FormGroup, + FormGroupDirective, + NgForm, + ValidatorFn, + Validators +} from '@angular/forms'; +import { Observable, of } from 'rxjs'; +import { Router } from '@angular/router'; +import { DialogComponent } from '@app/shared/components/dialog.component'; +import { + toCustomAction, + WidgetActionCallbacks, + WidgetActionDescriptorInfo, + WidgetActionsData +} from '@home/components/widget/action/manage-widget-actions.component.models'; +import { UtilsService } from '@core/services/utils.service'; +import { WidgetActionSource, WidgetActionType, widgetActionTypeTranslationMap } from '@shared/models/widget.models'; +import { map, mergeMap, startWith, tap } from 'rxjs/operators'; +import { DashboardService } from '@core/http/dashboard.service'; +import { Dashboard } from '@shared/models/dashboard.models'; +import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; + +export interface WidgetActionDialogData { + isAdd: boolean; + callbacks: WidgetActionCallbacks; + actionsData: WidgetActionsData; + action?: WidgetActionDescriptorInfo; +} + +@Component({ + selector: 'tb-widget-action-dialog', + templateUrl: './widget-action-dialog.component.html', + providers: [{provide: ErrorStateMatcher, useExisting: WidgetActionDialogComponent}], + styleUrls: [] +}) +export class WidgetActionDialogComponent extends DialogComponent implements OnInit, ErrorStateMatcher { + + @ViewChild('dashboardStateInput', {static: false}) dashboardStateInput: ElementRef; + + widgetActionFormGroup: FormGroup; + actionTypeFormGroup: FormGroup; + + isAdd: boolean; + action: WidgetActionDescriptorInfo; + + widgetActionTypes = Object.keys(WidgetActionType); + widgetActionTypeTranslations = widgetActionTypeTranslationMap; + widgetActionType = WidgetActionType; + + filteredDashboardStates: Observable>; + targetDashboardStateSearchText = ''; + selectedDashboardStateIds: Observable>; + + submitted = false; + + constructor(protected store: Store, + protected router: Router, + private utils: UtilsService, + private dashboardService: DashboardService, + private dashboardUtils: DashboardUtilsService, + @Inject(MAT_DIALOG_DATA) public data: WidgetActionDialogData, + @SkipSelf() private errorStateMatcher: ErrorStateMatcher, + public dialogRef: MatDialogRef, + public fb: FormBuilder) { + super(store, router, dialogRef); + this.isAdd = data.isAdd; + if (this.isAdd) { + this.action = { + id: this.utils.guid(), + name: '', + icon: 'more_horiz', + type: null + }; + } else { + this.action = this.data.action; + } + } + + ngOnInit(): void { + this.widgetActionFormGroup = this.fb.group({}); + this.widgetActionFormGroup.addControl('actionSourceId', + this.fb.control(this.action.actionSourceId, [Validators.required])); + this.widgetActionFormGroup.addControl('name', + this.fb.control(this.action.name, [this.validateActionName(), Validators.required])); + this.widgetActionFormGroup.addControl('icon', + this.fb.control(this.action.icon, [Validators.required])); + this.widgetActionFormGroup.addControl('type', + this.fb.control(this.action.type, [Validators.required])); + this.updateActionTypeFormGroup(this.action.type, this.action); + this.widgetActionFormGroup.get('type').valueChanges.subscribe((type: WidgetActionType) => { + this.updateActionTypeFormGroup(type); + }); + this.widgetActionFormGroup.get('actionSourceId').valueChanges.subscribe(() => { + this.widgetActionFormGroup.get('name').updateValueAndValidity(); + }); + } + + private updateActionTypeFormGroup(type?: WidgetActionType, action?: WidgetActionDescriptorInfo) { + this.actionTypeFormGroup = this.fb.group({}); + if (type) { + switch (type) { + case WidgetActionType.openDashboard: + case WidgetActionType.openDashboardState: + case WidgetActionType.updateDashboardState: + this.actionTypeFormGroup.addControl( + 'targetDashboardStateId', + this.fb.control(action ? action.targetDashboardStateId : null, + type === WidgetActionType.openDashboardState ? [Validators.required] : []) + ); + this.actionTypeFormGroup.addControl( + 'setEntityId', + this.fb.control(action ? action.setEntityId : true, []) + ); + this.actionTypeFormGroup.addControl( + 'stateEntityParamName', + this.fb.control(action ? action.stateEntityParamName : null, []) + ); + if (type === WidgetActionType.openDashboard) { + this.actionTypeFormGroup.addControl( + 'targetDashboardId', + this.fb.control(action ? action.targetDashboardId : null, + [Validators.required]) + ); + this.setupSelectedDashboardStateIds(action ? action.targetDashboardId : null); + } else { + this.actionTypeFormGroup.addControl( + 'openRightLayout', + this.fb.control(action ? action.openRightLayout : false, []) + ); + } + this.setupFilteredDashboardStates(); + break; + case WidgetActionType.custom: + this.actionTypeFormGroup.addControl( + 'customFunction', + this.fb.control(action ? action.customFunction : null, []) + ); + break; + case WidgetActionType.customPretty: + this.actionTypeFormGroup.addControl( + 'customAction', + this.fb.control(toCustomAction(action), [Validators.required]) + ); + break; + } + } + } + + private setupSelectedDashboardStateIds(targetDashboardId?: string) { + this.selectedDashboardStateIds = + this.actionTypeFormGroup.get('targetDashboardId').valueChanges.pipe( + // startWith(targetDashboardId), + tap(() => { + this.targetDashboardStateSearchText = ''; + }), + mergeMap((dashboardId) => { + if (dashboardId) { + return this.dashboardService.getDashboard(dashboardId); + } else { + return of(null); + } + }), + map((dashboard: Dashboard) => { + if (dashboard) { + dashboard = this.dashboardUtils.validateAndUpdateDashboard(dashboard); + const states = dashboard.configuration.states; + return Object.keys(states); + } else { + return []; + } + }) + ); + } + + private setupFilteredDashboardStates() { + this.targetDashboardStateSearchText = ''; + this.filteredDashboardStates = this.actionTypeFormGroup.get('targetDashboardStateId').valueChanges + .pipe( + startWith(''), + map(value => value ? value : ''), + mergeMap(name => this.fetchDashboardStates(name) ) + ); + } + + private fetchDashboardStates(searchText?: string): Observable> { + this.targetDashboardStateSearchText = searchText; + if (this.widgetActionFormGroup.get('type').value === WidgetActionType.openDashboard) { + return this.selectedDashboardStateIds.pipe( + map(stateIds => { + const result = searchText ? stateIds.filter(this.createFilterForDashboardState(searchText)) : stateIds; + if (result && result.length) { + return result; + } else { + return [searchText]; + } + }) + ); + } else { + return of(this.data.callbacks.fetchDashboardStates(searchText)); + } + } + + private createFilterForDashboardState(query: string): (stateId: string) => boolean { + const lowercaseQuery = query.toLowerCase(); + return stateId => stateId.toLowerCase().indexOf(lowercaseQuery) === 0; + } + + public clearTargetDashboardState(value: string = '') { + this.dashboardStateInput.nativeElement.value = value; + this.actionTypeFormGroup.get('targetDashboardStateId').patchValue(value, {emitEvent: true}); + setTimeout(() => { + this.dashboardStateInput.nativeElement.blur(); + this.dashboardStateInput.nativeElement.focus(); + }, 0); + } + + private validateActionName(): ValidatorFn { + return (c: FormControl) => { + const newName = c.value; + const valid = this.checkActionName(newName, this.widgetActionFormGroup.get('actionSourceId').value); + return !valid ? { + actionNameNotUnique: true + } : null; + }; + } + + private checkActionName(name: string, actionSourceId: string): boolean { + let actionNameIsUnique = true; + if (name && actionSourceId) { + const sourceActions = this.data.actionsData.actionsMap[actionSourceId]; + if (sourceActions) { + const result = sourceActions.filter((sourceAction) => sourceAction.name === name); + if (result && result.length && result[0].id !== this.action.id) { + actionNameIsUnique = false; + } + } + } + return actionNameIsUnique; + } + + isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { + const originalErrorState = this.errorStateMatcher.isErrorState(control, form); + const customErrorState = !!(control && control.invalid && this.submitted); + return originalErrorState || customErrorState; + } + + public actionSourceName(actionSource: WidgetActionSource): string { + if (actionSource) { + return this.utils.customTranslation(actionSource.name, actionSource.name); + } else { + return ''; + } + } + + cancel(): void { + this.dialogRef.close(null); + } + + save(): void { + this.submitted = true; + const type: WidgetActionType = this.widgetActionFormGroup.get('type').value; + let result: WidgetActionDescriptorInfo; + if (type === WidgetActionType.customPretty) { + result = {...this.widgetActionFormGroup.value, ...this.actionTypeFormGroup.get('customAction').value}; + } else { + result = {...this.widgetActionFormGroup.value, ...this.actionTypeFormGroup.value}; + } + result.id = this.action.id; + this.dialogRef.close(result); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/data-keys.component.ts b/ui-ngx/src/app/modules/home/components/widget/data-keys.component.ts index 77be49186c..b15c9d053e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/data-keys.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/data-keys.component.ts @@ -16,7 +16,7 @@ import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes'; import { - AfterViewInit, + AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, forwardRef, @@ -413,7 +413,7 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, AfterVie let fetchObservable: Observable> = null; if (this.datasourceType === DatasourceType.function || this.widgetType === widgetType.alarm) { const dataKeyFilter = this.createDataKeyFilter(this.searchText); - const targetKeysList = this.datasourceType === DatasourceType.function ? this.functionTypeKeys : this.alarmKeys; + const targetKeysList = this.widgetType === widgetType.alarm ? this.alarmKeys : this.functionTypeKeys; fetchObservable = of(targetKeysList.filter(dataKeyFilter)); } else { if (this.entityAliasId) { diff --git a/ui-ngx/src/app/modules/home/components/widget/legend-config-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/legend-config-panel.component.html new file mode 100644 index 0000000000..ac6722d78f --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/legend-config-panel.component.html @@ -0,0 +1,57 @@ + +
+
+
+
+
+ + legend.direction + + + {{ legendDirectionTranslations.get(direction) | translate }} + + + + + legend.position + + + {{ legendPositionTranslations.get(pos) | translate }} + + + + + {{ 'legend.show-min' | translate }} + + + {{ 'legend.show-max' | translate }} + + + {{ 'legend.show-avg' | translate }} + + + {{ 'legend.show-total' | translate }} + +
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/legend-config-panel.component.scss b/ui-ngx/src/app/modules/home/components/widget/legend-config-panel.component.scss new file mode 100644 index 0000000000..94ab8d5d45 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/legend-config-panel.component.scss @@ -0,0 +1,32 @@ +/** + * Copyright © 2016-2019 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 { + width: 100%; + height: 100%; + form, + fieldset { + height: 100%; + } + + .mat-content { + overflow: hidden; + background-color: #fff; + } + + .mat-padding { + padding: 16px; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/legend-config-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/legend-config-panel.component.ts new file mode 100644 index 0000000000..b0d31efe44 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/legend-config-panel.component.ts @@ -0,0 +1,118 @@ +/// +/// Copyright © 2016-2019 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, InjectionToken, OnInit, ViewContainerRef } from '@angular/core'; +import { Overlay, OverlayRef } from '@angular/cdk/overlay'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { + LegendConfig, + LegendDirection, + legendDirectionTranslationMap, + LegendPosition, + legendPositionTranslationMap +} from '@shared/models/widget.models'; + +export const LEGEND_CONFIG_PANEL_DATA = new InjectionToken('LegendConfigPanelData'); + +export interface LegendConfigPanelData { + legendConfig: LegendConfig; + legendConfigUpdated: (legendConfig: LegendConfig) => void; +} + +@Component({ + selector: 'tb-legend-config-panel', + templateUrl: './legend-config-panel.component.html', + styleUrls: ['./legend-config-panel.component.scss'] +}) +export class LegendConfigPanelComponent extends PageComponent implements OnInit { + + legendConfigForm: FormGroup; + + legendDirection = LegendDirection; + + legendDirections = Object.keys(LegendDirection); + + legendDirectionTranslations = legendDirectionTranslationMap; + + legendPosition = LegendPosition; + + legendPositions = Object.keys(LegendPosition); + + legendPositionTranslations = legendPositionTranslationMap; + + constructor(@Inject(LEGEND_CONFIG_PANEL_DATA) public data: LegendConfigPanelData, + public overlayRef: OverlayRef, + protected store: Store, + public fb: FormBuilder, + private overlay: Overlay, + public viewContainerRef: ViewContainerRef) { + super(store); + } + + ngOnInit(): void { + this.legendConfigForm = this.fb.group({ + direction: [this.data.legendConfig.direction, []], + position: [this.data.legendConfig.position, []], + showMin: [this.data.legendConfig.showMin, []], + showMax: [this.data.legendConfig.showMax, []], + showAvg: [this.data.legendConfig.showAvg, []], + showTotal: [this.data.legendConfig.showTotal, []] + }); + this.legendConfigForm.get('direction').valueChanges.subscribe((direction: LegendDirection) => { + this.onDirectionChanged(direction); + }); + this.onDirectionChanged(this.data.legendConfig.direction); + this.legendConfigForm.valueChanges.subscribe(() => { + this.update(); + }); + } + + private onDirectionChanged(direction: LegendDirection) { + if (direction === LegendDirection.row) { + let position: LegendPosition = this.legendConfigForm.get('position').value; + if (position !== LegendPosition.bottom && position !== LegendPosition.top) { + position = LegendPosition.bottom; + } + this.legendConfigForm.patchValue( + { + position, + showMin: false, + showMax: false, + showAvg: false, + showTotal: false + }, {emitEvent: false} + ); + this.legendConfigForm.get('showMin').disable({emitEvent: false}); + this.legendConfigForm.get('showMax').disable({emitEvent: false}); + this.legendConfigForm.get('showAvg').disable({emitEvent: false}); + this.legendConfigForm.get('showTotal').disable({emitEvent: false}); + } else { + this.legendConfigForm.get('showMin').enable({emitEvent: false}); + this.legendConfigForm.get('showMax').enable({emitEvent: false}); + this.legendConfigForm.get('showAvg').enable({emitEvent: false}); + this.legendConfigForm.get('showTotal').enable({emitEvent: false}); + } + } + + update() { + const newLegendConfig: LegendConfig = this.legendConfigForm.value; + this.data.legendConfigUpdated(newLegendConfig); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/legend-config.component.html b/ui-ngx/src/app/modules/home/components/widget/legend-config.component.html new file mode 100644 index 0000000000..f143856ec3 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/legend-config.component.html @@ -0,0 +1,22 @@ + + diff --git a/ui-ngx/src/app/modules/home/components/widget/legend-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/legend-config.component.ts new file mode 100644 index 0000000000..a61a1648f4 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/legend-config.component.ts @@ -0,0 +1,198 @@ +/// +/// Copyright © 2016-2019 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, Inject, + Input, + OnDestroy, + OnInit, + ViewChild, + ViewContainerRef +} from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { TranslateService } from '@ngx-translate/core'; +import { MillisecondsToTimeStringPipe } from '@shared/pipe/milliseconds-to-time-string.pipe'; +import { + HistoryWindowType, + Timewindow, + TimewindowType, + initModelFromDefaultTimewindow, cloneSelectedTimewindow +} from '@shared/models/time/time.models'; +import { DatePipe } from '@angular/common'; +import { + Overlay, + CdkOverlayOrigin, + OverlayConfig, + OverlayPositionBuilder, ConnectedPosition, PositionStrategy, OverlayRef +} from '@angular/cdk/overlay'; +import { + TIMEWINDOW_PANEL_DATA, + TimewindowPanelComponent, + TimewindowPanelData +} from '@shared/components/time/timewindow-panel.component'; +import { ComponentPortal, PortalInjector } from '@angular/cdk/portal'; +import { MediaBreakpoints } from '@shared/models/constants'; +import { BreakpointObserver } from '@angular/cdk/layout'; +import { DOCUMENT } from '@angular/common'; +import { WINDOW } from '@core/services/window.service'; +import { TimeService } from '@core/services/time.service'; +import { TooltipPosition } from '@angular/material/typings/tooltip'; +import { deepClone } from '@core/utils'; +import { LegendConfig } from '@shared/models/widget.models'; +import { + LEGEND_CONFIG_PANEL_DATA, + LegendConfigPanelComponent, + LegendConfigPanelData +} from '@home/components/widget/legend-config-panel.component'; + +@Component({ + selector: 'tb-legend-config', + templateUrl: './legend-config.component.html', + styleUrls: [], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => LegendConfigComponent), + multi: true + } + ] +}) +export class LegendConfigComponent implements OnInit, OnDestroy, ControlValueAccessor { + + @Input() disabled: boolean; + + @ViewChild('legendConfigPanelOrigin', {static: false}) legendConfigPanelOrigin: CdkOverlayOrigin; + + innerValue: LegendConfig; + + private propagateChange = (_: any) => {}; + + constructor(private overlay: Overlay, + public viewContainerRef: ViewContainerRef, + public breakpointObserver: BreakpointObserver, + @Inject(DOCUMENT) private document: Document, + @Inject(WINDOW) private window: Window) { + } + + ngOnInit(): void { + } + + ngOnDestroy(): void { + } + + openEditMode() { + if (this.disabled) { + return; + } + const isGtSm = this.breakpointObserver.isMatched(MediaBreakpoints['gt-sm']); + const position = this.overlay.position(); + const config = new OverlayConfig({ + panelClass: 'tb-legend-config-panel', + backdropClass: 'cdk-overlay-transparent-backdrop', + hasBackdrop: isGtSm, + }); + if (isGtSm) { + config.minWidth = '220px'; + config.maxHeight = '300px'; + const panelHeight = 220; + const panelWidth = 220; + const el = this.legendConfigPanelOrigin.elementRef.nativeElement; + const offset = el.getBoundingClientRect(); + const scrollTop = this.window.pageYOffset || this.document.documentElement.scrollTop || this.document.body.scrollTop || 0; + const scrollLeft = this.window.pageXOffset || this.document.documentElement.scrollLeft || this.document.body.scrollLeft || 0; + const bottomY = offset.bottom - scrollTop; + const leftX = offset.left - scrollLeft; + let originX; + let originY; + let overlayX; + let overlayY; + const wHeight = this.document.documentElement.clientHeight; + const wWidth = this.document.documentElement.clientWidth; + if (bottomY + panelHeight > wHeight) { + originY = 'top'; + overlayY = 'bottom'; + } else { + originY = 'bottom'; + overlayY = 'top'; + } + if (leftX + panelWidth > wWidth) { + originX = 'end'; + overlayX = 'end'; + } else { + originX = 'start'; + overlayX = 'start'; + } + const connectedPosition: ConnectedPosition = { + originX, + originY, + overlayX, + overlayY + }; + config.positionStrategy = position.flexibleConnectedTo(this.legendConfigPanelOrigin.elementRef) + .withPositions([connectedPosition]); + } else { + config.minWidth = '100%'; + config.minHeight = '100%'; + config.positionStrategy = position.global().top('0%').left('0%') + .right('0%').bottom('0%'); + } + + const overlayRef = this.overlay.create(config); + + overlayRef.backdropClick().subscribe(() => { + overlayRef.dispose(); + }); + + const injector = this._createLegendConfigPanelInjector( + overlayRef, + { + legendConfig: deepClone(this.innerValue), + legendConfigUpdated: this.legendConfigUpdated.bind(this) + } + ); + + overlayRef.attach(new ComponentPortal(LegendConfigPanelComponent, this.viewContainerRef, injector)); + } + + private _createLegendConfigPanelInjector(overlayRef: OverlayRef, data: LegendConfigPanelData): PortalInjector { + const injectionTokens = new WeakMap([ + [LEGEND_CONFIG_PANEL_DATA, data], + [OverlayRef, overlayRef] + ]); + return new PortalInjector(this.viewContainerRef.injector, injectionTokens); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + writeValue(obj: LegendConfig): void { + this.innerValue = obj; + } + + private legendConfigUpdated(legendConfig: LegendConfig) { + this.innerValue = legendConfig; + this.propagateChange(this.innerValue); + } +} 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 837d86f7fd..4f769b9051 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 @@ -181,6 +181,188 @@
+ + + + {{ 'widget-config.alarm-source' | translate }} + + +
+
+ + + + {{ datasourceTypesTranslations.get(datasourceType) | translate }} + + + +
+ + + + + + + + + + +
+ + +
+
+
+ + + +
+
+ widget-config.general-settings +
+ + widget-config.title + + +
+ +
+
+
+
+ + {{ 'widget-config.display-icon' | translate }} + +
+
+ + +
+ + + + widget-config.icon-size + + +
+
+
+ + {{ 'widget-config.display-title' | translate }} + +
+
+ + {{ 'widget-config.drop-shadow' | translate }} + +
+
+ + {{ 'widget-config.enable-fullscreen' | translate }} + +
+
+ +
+
+
+ + + + + + widget-config.padding + + + + widget-config.margin + + +
+
+ + widget-config.units + + + + widget-config.decimals + + +
+
+ + {{ 'widget-config.display-legend' | translate }} + +
+ + +
+
+
+
+ widget-config.mobile-mode-settings +
+ + widget-config.order + + + + widget-config.height + + +
+
@@ -191,6 +373,10 @@ - + + + diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.models.ts b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.models.ts index f5954f48ec..ce1ba0c4e3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.models.ts @@ -16,5 +16,6 @@ import { EntityAliasSelectCallbacks } from '../alias/entity-alias-select.component.models'; import { DataKeysCallbacks } from './data-keys.component.models'; +import { WidgetActionCallbacks } from './action/manage-widget-actions.component.models'; -export type WidgetConfigCallbacks = EntityAliasSelectCallbacks & DataKeysCallbacks; +export type WidgetConfigCallbacks = EntityAliasSelectCallbacks & DataKeysCallbacks & WidgetActionCallbacks; 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 85f82154f0..d3b160ef9e 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 @@ -18,6 +18,9 @@ :host { .tb-widget-config { + .tb-advanced-widget-config { + height: 100%; + } .tb-advanced-widget-config { height: 100%; } @@ -45,6 +48,13 @@ :host ::ng-deep { .tb-widget-config { + .mat-tab-body-wrapper { + position: absolute; + top: 49px; + left: 0; + right: 0; + bottom: 0; + } .mat-tab-body.mat-tab-body-active { .mat-tab-body-content > div { height: 100%; diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts index 9eb7cbb926..2b221be827 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts @@ -14,19 +14,17 @@ /// limitations under the License. /// -import { Component, forwardRef, Input, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, forwardRef, Input, OnInit } from '@angular/core'; import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { DataKey, Datasource, - DatasourceType, datasourceTypeTranslationMap, - LegendConfig, + DatasourceType, + datasourceTypeTranslationMap, defaultLegendConfig, WidgetActionDescriptor, - WidgetActionSource, - widgetType, - WidgetTypeParameters + widgetType } from '@shared/models/widget.models'; import { AbstractControl, @@ -37,7 +35,7 @@ import { FormGroup, NG_VALIDATORS, NG_VALUE_ACCESSOR, - Validator, ValidatorFn, + Validator, Validators } from '@angular/forms'; import { WidgetConfigComponentData } from '@home/models/widget-component.models'; @@ -55,10 +53,12 @@ import { EntityAliasDialogComponent, EntityAliasDialogData } from '@home/components/alias/entity-alias-dialog.component'; -import { tap, mergeMap, map, catchError } from 'rxjs/operators'; +import { catchError, map, mergeMap, tap } from 'rxjs/operators'; import { MatDialog } from '@angular/material/dialog'; import { EntityService } from '@core/http/entity.service'; import { JsonFormComponentData } from '@shared/components/json-form/json-form-component.models'; +import { WidgetActionsData } from './action/manage-widget-actions.component.models'; +import { Dashboard } from '@shared/models/dashboard.models'; const emptySettingsSchema = { type: 'object', @@ -106,6 +106,9 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont @Input() functionsOnly: boolean; + @Input() + dashboardStates: Array; + @Input() disabled: boolean; widgetType: widgetType; @@ -117,34 +120,13 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont widgetConfigCallbacks: WidgetConfigCallbacks = { createEntityAlias: this.createEntityAlias.bind(this), generateDataKey: this.generateDataKey.bind(this), - fetchEntityKeys: this.fetchEntityKeys.bind(this) + fetchEntityKeys: this.fetchEntityKeys.bind(this), + fetchDashboardStates: this.fetchDashboardStates.bind(this) }; widgetEditMode = this.utils.widgetEditMode; selectedTab: number; - title: string; - showTitleIcon: boolean; - titleIcon: string; - iconColor: string; - iconSize: string; - showTitle: boolean; - dropShadow: boolean; - enableFullscreen: boolean; - backgroundColor: string; - color: string; - padding: string; - margin: string; - widgetStyle: string; - titleStyle: string; - units: string; - decimals: number; - showLegend: boolean; - legendConfig: LegendConfig; - actions: {[actionSourceId: string]: Array}; - alarmSource: Datasource; - mobileOrder: number; - mobileHeight: number; private modelValue: WidgetConfigComponentData; @@ -152,11 +134,19 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont public dataSettings: FormGroup; public targetDeviceSettings: FormGroup; + public alarmSourceSettings: FormGroup; + public widgetSettings: FormGroup; + public layoutSettings: FormGroup; public advancedSettings: FormGroup; + public actionsSettings: FormGroup; private dataSettingsChangesSubscription: Subscription; private targetDeviceSettingsSubscription: Subscription; + private alarmSourceSettingsSubscription: Subscription; + private widgetSettingsSubscription: Subscription; + private layoutSettingsSubscription: Subscription; private advancedSettingsSubscription: Subscription; + private actionsSettingsSubscription: Subscription; constructor(protected store: Store, private utils: UtilsService, @@ -173,6 +163,47 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont } else { this.datasourceTypes = [DatasourceType.function, DatasourceType.entity]; } + this.widgetSettings = this.fb.group({ + title: [null, []], + showTitleIcon: [null, []], + titleIcon: [null, []], + iconColor: [null, []], + iconSize: [null, []], + showTitle: [null, []], + dropShadow: [null, []], + enableFullscreen: [null, []], + backgroundColor: [null, []], + color: [null, []], + padding: [null, []], + margin: [null, []], + widgetStyle: [null, []], + titleStyle: [null, []], + units: [null, []], + decimals: [null, [Validators.min(0), Validators.max(15), Validators.pattern(/^\d*$/)]], + showLegend: [null, []], + legendConfig: [null, []] + }); + this.widgetSettings.get('showTitleIcon').valueChanges.subscribe((value: boolean) => { + if (value) { + this.widgetSettings.get('titleIcon').enable({emitEvent: false}); + } else { + this.widgetSettings.get('titleIcon').disable({emitEvent: false}); + } + }); + this.widgetSettings.get('showLegend').valueChanges.subscribe((value: boolean) => { + if (value) { + this.widgetSettings.get('legendConfig').enable({emitEvent: false}); + } else { + this.widgetSettings.get('legendConfig').disable({emitEvent: false}); + } + }); + this.layoutSettings = this.fb.group({ + mobileOrder: [null, [Validators.pattern(/^-?[0-9]+$/)]], + mobileHeight: [null, [Validators.min(1), Validators.max(10), Validators.pattern(/^\d*$/)]] + }); + this.actionsSettings = this.fb.group({ + actionsData: [null, []] + }); } private removeChangeSubscriptions() { @@ -184,10 +215,26 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont this.targetDeviceSettingsSubscription.unsubscribe(); this.targetDeviceSettingsSubscription = null; } + if (this.alarmSourceSettingsSubscription) { + this.alarmSourceSettingsSubscription.unsubscribe(); + this.alarmSourceSettingsSubscription = null; + } + if (this.widgetSettingsSubscription) { + this.widgetSettingsSubscription.unsubscribe(); + this.widgetSettingsSubscription = null; + } + if (this.layoutSettingsSubscription) { + this.layoutSettingsSubscription.unsubscribe(); + this.layoutSettingsSubscription = null; + } if (this.advancedSettingsSubscription) { this.advancedSettingsSubscription.unsubscribe(); this.advancedSettingsSubscription = null; } + if (this.actionsSettingsSubscription) { + this.actionsSettingsSubscription.unsubscribe(); + this.actionsSettingsSubscription = null; + } } private createChangeSubscriptions() { @@ -197,14 +244,27 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont this.targetDeviceSettingsSubscription = this.targetDeviceSettings.valueChanges.subscribe( () => this.updateTargetDeviceSettings() ); + this.alarmSourceSettingsSubscription = this.alarmSourceSettings.valueChanges.subscribe( + () => this.updateAlarmSourceSettings() + ); + this.widgetSettingsSubscription = this.widgetSettings.valueChanges.subscribe( + () => this.updateWidgetSettings() + ); + this.layoutSettingsSubscription = this.layoutSettings.valueChanges.subscribe( + () => this.updateLayoutSettings() + ); this.advancedSettingsSubscription = this.advancedSettings.valueChanges.subscribe( () => this.updateAdvancedSettings() ); + this.actionsSettingsSubscription = this.actionsSettings.valueChanges.subscribe( + () => this.updateActionSettings() + ); } private buildForms() { this.dataSettings = this.fb.group({}); this.targetDeviceSettings = this.fb.group({}); + this.alarmSourceSettings = this.fb.group({}); this.advancedSettings = this.fb.group({}); if (this.widgetType === widgetType.timeseries || this.widgetType === widgetType.alarm) { this.dataSettings.addControl('useDashboardTimewindow', this.fb.control(null)); @@ -235,6 +295,8 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont this.targetDeviceSettings.addControl('targetDeviceAliasId', this.fb.control(null, this.widgetEditMode ? [] : [Validators.required])); + } else if (this.widgetType === widgetType.alarm) { + this.alarmSourceSettings = this.buildDatasourceForm(); } } this.advancedSettings.addControl('settings', @@ -264,31 +326,54 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont const layout = this.modelValue.layout; if (config) { this.selectedTab = 0; - this.title = config.title; - this.showTitleIcon = isDefined(config.showTitleIcon) ? config.showTitleIcon : false; - this.titleIcon = isDefined(config.titleIcon) ? config.titleIcon : ''; - this.iconColor = isDefined(config.iconColor) ? config.iconColor : 'rgba(0, 0, 0, 0.87)'; - this.iconSize = isDefined(config.iconSize) ? config.iconSize : '24px'; - this.showTitle = config.showTitle; - this.dropShadow = isDefined(config.dropShadow) ? config.dropShadow : true; - this.enableFullscreen = isDefined(config.enableFullscreen) ? config.enableFullscreen : true; - this.backgroundColor = config.backgroundColor; - this.color = config.color; - this.padding = config.padding; - this.margin = config.margin; - this.widgetStyle = - JSON.stringify(isDefined(config.widgetStyle) ? config.widgetStyle : {}, null, 2); - this.titleStyle = - JSON.stringify(isDefined(config.titleStyle) ? config.titleStyle : { - fontSize: '16px', - fontWeight: 400 - }, null, 2); - this.units = config.units; - this.decimals = config.decimals; - this.actions = config.actions; - if (!this.actions) { - this.actions = {}; + this.widgetSettings.patchValue({ + title: config.title, + showTitleIcon: isDefined(config.showTitleIcon) ? config.showTitleIcon : false, + titleIcon: isDefined(config.titleIcon) ? config.titleIcon : '', + iconColor: isDefined(config.iconColor) ? config.iconColor : 'rgba(0, 0, 0, 0.87)', + iconSize: isDefined(config.iconSize) ? config.iconSize : '24px', + showTitle: config.showTitle, + dropShadow: isDefined(config.dropShadow) ? config.dropShadow : true, + enableFullscreen: isDefined(config.enableFullscreen) ? config.enableFullscreen : true, + backgroundColor: config.backgroundColor, + color: config.color, + padding: config.padding, + margin: config.margin, + widgetStyle: isDefined(config.widgetStyle) ? config.widgetStyle : {}, + titleStyle: isDefined(config.titleStyle) ? config.titleStyle : { + fontSize: '16px', + fontWeight: 400 + }, + units: config.units, + decimals: config.decimals, + showLegend: isDefined(config.showLegend) ? config.showLegend : + this.widgetType === widgetType.timeseries, + legendConfig: config.legendConfig || defaultLegendConfig(this.widgetType) + }, + {emitEvent: false} + ); + const showTitleIcon: boolean = this.widgetSettings.get('showTitleIcon').value; + if (showTitleIcon) { + this.widgetSettings.get('titleIcon').enable({emitEvent: false}); + } else { + this.widgetSettings.get('titleIcon').disable({emitEvent: false}); + } + const showLegend: boolean = this.widgetSettings.get('showLegend').value; + if (showLegend) { + this.widgetSettings.get('legendConfig').enable({emitEvent: false}); + } else { + this.widgetSettings.get('legendConfig').disable({emitEvent: false}); } + const actionsData: WidgetActionsData = { + actionsMap: config.actions || {}, + actionSources: this.modelValue.actionSources || {} + }; + this.actionsSettings.patchValue( + { + actionsData + }, + {emitEvent: false} + ); if (this.widgetType === widgetType.timeseries || this.widgetType === widgetType.alarm) { const useDashboardTimewindow = isDefined(config.useDashboardTimewindow) ? config.useDashboardTimewindow : true; @@ -346,29 +431,42 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont { alarmsPollingInterval: isDefined(config.alarmsPollingInterval) ? config.alarmsPollingInterval : 5}, {emitEvent: false} ); - if (config.alarmSource) { - this.alarmSource = config.alarmSource; - } else { - this.alarmSource = null; - } + this.alarmSourceSettings.patchValue( + config.alarmSource, {emitEvent: false} + ); + const alarmSourceType: DatasourceType = this.alarmSourceSettings.get('type').value; + this.alarmSourceSettings.get('entityAliasId').setValidators( + alarmSourceType === DatasourceType.entity ? [Validators.required] : [] + ); + this.alarmSourceSettings.get('entityAliasId').updateValueAndValidity({emitEvent: false}); } } this.updateSchemaForm(config.settings); if (layout) { - this.mobileOrder = layout.mobileOrder; - this.mobileHeight = layout.mobileHeight; + this.layoutSettings.patchValue( + { + mobileOrder: layout.mobileOrder, + mobileHeight: layout.mobileHeight + }, + {emitEvent: false} + ); } else { - this.mobileOrder = undefined; - this.mobileHeight = undefined; + this.layoutSettings.patchValue( + { + mobileOrder: null, + mobileHeight: null + }, + {emitEvent: false} + ); } } this.createChangeSubscriptions(); } } - private buildDatasourceForm(datasource?: Datasource): AbstractControl { + private buildDatasourceForm(datasource?: Datasource): FormGroup { const dataKeysRequired = !this.modelValue.typeParameters || !this.modelValue.typeParameters.dataKeysOptional; const datasourceFormGroup = this.fb.group( { @@ -427,6 +525,34 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont } } + private updateAlarmSourceSettings() { + if (this.modelValue) { + if (this.modelValue.config) { + const alarmSource: Datasource = this.alarmSourceSettings.value; + this.modelValue.config.alarmSource = alarmSource; + } + this.propagateChange(this.modelValue); + } + } + + private updateWidgetSettings() { + if (this.modelValue) { + if (this.modelValue.config) { + Object.assign(this.modelValue.config, this.widgetSettings.value); + } + this.propagateChange(this.modelValue); + } + } + + private updateLayoutSettings() { + if (this.modelValue) { + if (this.modelValue.layout) { + Object.assign(this.modelValue.layout, this.layoutSettings.value); + } + this.propagateChange(this.modelValue); + } + } + private updateAdvancedSettings() { if (this.modelValue) { if (this.modelValue.config) { @@ -437,6 +563,16 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont } } + private updateActionSettings() { + if (this.modelValue) { + if (this.modelValue.config) { + const actions = (this.actionsSettings.get('actionsData').value as WidgetActionsData).actionsMap; + this.modelValue.config.actions = actions; + } + this.propagateChange(this.modelValue); + } + } + public displayAdvanced(): boolean { return this.modelValue.settingsSchema && this.modelValue.settingsSchema.schema; } @@ -596,6 +732,21 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont ); } + private fetchDashboardStates(query: string): Array { + const stateIds = Object.keys(this.dashboardStates); + const result = query ? stateIds.filter(this.createFilterForDashboardState(query)) : stateIds; + if (result && result.length) { + return result; + } else { + return [query]; + } + } + + private createFilterForDashboardState(query: string): (stateId: string) => boolean { + const lowercaseQuery = query.toLowerCase(); + return stateId => stateId.toLowerCase().indexOf(lowercaseQuery) === 0; + } + public validate(c: FormControl) { if (!this.dataSettings.valid) { return { @@ -603,6 +754,18 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont valid: false } }; + } else if (!this.widgetSettings.valid) { + return { + widgetSettings: { + valid: false + } + }; + } else if (!this.layoutSettings.valid) { + return { + widgetSettings: { + valid: false + } + }; } else if (!this.advancedSettings.valid) { return { advancedSettings: { @@ -636,24 +799,6 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont }; } } - try { - JSON.parse(this.widgetStyle); - } catch (e) { - return { - widgetStyle: { - valid: false - } - }; - } - try { - JSON.parse(this.titleStyle); - } catch (e) { - return { - titleStyle: { - valid: false - } - }; - } } return null; } 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 9f5d846a1e..90e7771bab 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 @@ -38,6 +38,7 @@ import { Datasource, LegendConfig, LegendData, + LegendDirection, LegendPosition, Widget, WidgetActionDescriptor, @@ -45,7 +46,8 @@ import { WidgetActionType, WidgetResource, widgetType, - WidgetTypeParameters + WidgetTypeParameters, + defaultLegendConfig } from '@shared/models/widget.models'; import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; @@ -165,6 +167,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI private ngZone: NgZone, private cd: ChangeDetectorRef) { super(store); + this.cssParser.testMode = false; } ngOnInit(): void { @@ -179,14 +182,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI this.legendContainerLayoutType = 'column'; if (this.displayLegend) { - this.legendConfig = this.widget.config.legendConfig || - { - position: LegendPosition.bottom, - showMin: false, - showMax: false, - showAvg: this.widget.type === widgetType.timeseries, - showTotal: false - }; + this.legendConfig = this.widget.config.legendConfig || defaultLegendConfig(this.widget.type); this.legendData = { keys: [], data: [] @@ -194,8 +190,10 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI if (this.legendConfig.position === LegendPosition.top || this.legendConfig.position === LegendPosition.bottom) { this.legendContainerLayoutType = 'column'; + this.isLegendFirst = this.legendConfig.position === LegendPosition.top; } else { this.legendContainerLayoutType = 'row'; + this.isLegendFirst = this.legendConfig.position === LegendPosition.left; } switch (this.legendConfig.position) { case LegendPosition.top: @@ -352,7 +350,9 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI this.loadFromWidgetInfo(); } ); - + setTimeout(() => { + this.dashboardWidget.updateWidgetParams(); + }, 0); } ngAfterViewInit(): void { @@ -764,6 +764,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI timeWindowUpdated: (subscription, timeWindowConfig) => { this.ngZone.run(() => { this.widget.config.timewindow = timeWindowConfig; + this.cd.detectChanges(); }); } }; @@ -924,24 +925,16 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI if (targetDashboardStateId) { stateObject.id = targetDashboardStateId; } - const stateParams = { - dashboardId: targetDashboardId, - state: objToBase64([ stateObject ]) - }; const state = objToBase64([ stateObject ]); - const currentUrl = this.route.snapshot.url; + const isSinglePage = this.route.snapshot.data.singlePageMode; let url; - if (currentUrl.length > 1) { - if (currentUrl[currentUrl.length - 2].path === 'dashboard') { - url = `/dashboard/${targetDashboardId}?state=${state}`; - } else { - url = `/dashboards/${targetDashboardId}?state=${state}`; - } - } - if (url) { - const urlTree = this.router.parseUrl(url); - this.router.navigateByUrl(url); + if (isSinglePage) { + url = `/dashboard/${targetDashboardId}?state=${state}`; + } else { + url = `/dashboards/${targetDashboardId}?state=${state}`; } + const urlTree = this.router.parseUrl(url); + this.router.navigateByUrl(url); break; case WidgetActionType.custom: const customFunction = descriptor.customFunction; 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 a0e6e2c24a..5b76b48eae 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 @@ -78,6 +78,7 @@ export interface IDashboardComponent { selectWidget(index: number, delay?: number); getSelectedWidget(): Widget; getEventGridPosition(event: Event): WidgetPosition; + notifyGridsterOptionsChanged(); } declare type DashboardWidgetUpdateOperation = 'add' | 'remove' | 'update'; @@ -185,7 +186,7 @@ export class DashboardWidgets implements Iterable { highlightWidget(index: number): DashboardWidget { const widget = this.findWidgetAtIndex(index); - if (widget && (!this.highlightedMode || !widget.highlighted)) { + if (widget && (!this.highlightedMode || !widget.highlighted || this.highlightedMode && widget.highlighted)) { this.highlightedMode = true; widget.highlighted = true; this.dashboardWidgets.forEach((dashboardWidget) => { @@ -248,6 +249,7 @@ export class DashboardWidgets implements Iterable { }); this.sortWidgets(); this.dashboard.gridsterOpts.maxRows = maxRows; + this.dashboard.notifyGridsterOptionsChanged(); } sortWidgets() { 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 34be6e1ee9..438b0110ae 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 @@ -68,7 +68,6 @@ export interface WidgetContext { width?: number; height?: number; $scope?: IDynamicWidgetComponent; - hideTitlePanel?: boolean; isEdit?: boolean; isMobile?: boolean; dashboard?: IDashboardComponent; @@ -87,15 +86,18 @@ export interface WidgetContext { stateController?: IStateController; aliasController?: IAliasController; activeEntityInfo?: SubscriptionEntityInfo; - widgetTitleTemplate?: string; - widgetTitle?: string; - customHeaderActions?: Array; - widgetActions?: Array; datasources?: Array; data?: Array; hiddenData?: Array<{data: DataSet}>; timeWindow?: WidgetTimewindow; + + hideTitlePanel?: boolean; + widgetTitleTemplate?: string; + widgetTitle?: string; + customHeaderActions?: Array; + widgetActions?: Array; + } export interface IDynamicWidgetComponent { @@ -122,7 +124,7 @@ export interface WidgetConfigComponentData { layout: WidgetLayout; widgetType: widgetType; typeParameters: WidgetTypeParameters; - actionSources: {[key: string]: WidgetActionSource}; + actionSources: {[actionSourceId: string]: WidgetActionSource}; isDataEnabled: boolean; settingsSchema: any; dataKeySettingsSchema: any; @@ -178,7 +180,7 @@ export interface WidgetTypeInstance { getDataKeySettingsSchema?: () => string; typeParameters?: () => WidgetTypeParameters; useCustomDatasources?: () => boolean; - actionSources?: () => {[key: string]: WidgetActionSource}; + actionSources?: () => {[actionSourceId: string]: WidgetActionSource}; onInit?: () => void; onDataUpdated?: () => void; diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.ts index 8ff27f9453..970e6548f6 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.ts @@ -14,7 +14,16 @@ /// limitations under the License. /// -import { Component, Inject, OnDestroy, OnInit, ViewEncapsulation, ViewChild, NgZone } from '@angular/core'; +import { + Component, + Inject, + OnDestroy, + OnInit, + ViewEncapsulation, + ViewChild, + NgZone, + ChangeDetectorRef, ChangeDetectionStrategy, ApplicationRef +} from '@angular/core'; import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; @@ -77,7 +86,8 @@ import { EditWidgetComponent } from '@home/pages/dashboard/edit-widget.component selector: 'tb-dashboard-page', templateUrl: './dashboard-page.component.html', styleUrls: ['./dashboard-page.component.scss'], - encapsulation: ViewEncapsulation.None + encapsulation: ViewEncapsulation.None, + // changeDetection: ChangeDetectionStrategy.OnPush }) export class DashboardPageComponent extends PageComponent implements IDashboardController, OnDestroy { @@ -149,7 +159,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC dashboardTimewindow: null, state: null, stateController: null, - aliasController: null + aliasController: null, + runChangeDetection: this.runChangeDetection.bind(this) }; addWidgetFabButtons: FooterFabButtons = { @@ -204,12 +215,15 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC private dashboardService: DashboardService, private itembuffer: ItemBufferService, private fb: FormBuilder, - private dialog: MatDialog) { + private dialog: MatDialog, + private ngZone: NgZone, + private cd: ChangeDetectorRef) { super(store); this.rxSubscriptions.push(this.route.data.subscribe( (data) => { this.init(data); + this.runChangeDetection(); } )); @@ -294,6 +308,12 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC this.rxSubscriptions.length = 0; } + public runChangeDetection() { + /*setTimeout(() => { + this.cd.detectChanges(); + });*/ + } + public openToolbar() { this.isToolbarOpenedAnimate = true; this.isToolbarOpened = true; @@ -646,7 +666,9 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC this.editingWidgetLayoutOriginal = widgetLayout; this.editingLayoutCtx.widgets[index] = widget; this.editingLayoutCtx.widgetLayouts[widget.id] = widgetLayout; - this.editingLayoutCtx.ctrl.highlightWidget(index, 0); + setTimeout(() => { + this.editingLayoutCtx.ctrl.highlightWidget(index, 0); + }, 0); } onEditWidgetClosed() { diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.models.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.models.ts index 60176173d8..3e829681ec 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.models.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.models.ts @@ -25,6 +25,7 @@ import { WidgetPosition } from '@home/models/dashboard-component.models'; import { Observable } from 'rxjs'; +import { ChangeDetectorRef } from '@angular/core'; export declare type DashboardPageScope = 'tenant' | 'customer'; @@ -34,6 +35,7 @@ export interface DashboardContext { dashboardTimewindow: Timewindow; aliasController: IAliasController; stateController: IStateController; + runChangeDetection: () => void; } export interface IDashboardController { diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/edit-widget.component.html b/ui-ngx/src/app/modules/home/pages/dashboard/edit-widget.component.html index 8ebde89527..db5b54e437 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/edit-widget.component.html +++ b/ui-ngx/src/app/modules/home/pages/dashboard/edit-widget.component.html @@ -21,6 +21,7 @@ [aliasController]="aliasController" [functionsOnly]="widgetEditMode" [entityAliases]="dashboard.configuration.entityAliases" + [dashboardStates]="dashboard.configuration.states" formControlName="widgetConfig"> diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/edit-widget.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/edit-widget.component.ts index 562a7339da..adb3fa2bfe 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/edit-widget.component.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/edit-widget.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, OnInit, Input, OnChanges, SimpleChanges, ViewChild } from '@angular/core'; +import { Component, OnInit, Input, OnChanges, SimpleChanges, ViewChild, ChangeDetectionStrategy } from '@angular/core'; import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/layout/dashboard-layout.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/layout/dashboard-layout.component.ts index f52e97ea7b..0421720692 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/layout/dashboard-layout.component.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/layout/dashboard-layout.component.ts @@ -85,8 +85,8 @@ export class DashboardLayoutComponent extends PageComponent implements ILayoutCo this.rxSubscriptions.push(this.dashboard.dashboardTimewindowChanged.subscribe( (dashboardTimewindow) => { this.dashboardCtx.dashboardTimewindow = dashboardTimewindow; - } - ) + this.dashboardCtx.runChangeDetection(); + }) ); this.initHotKeys(); } diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/default-state-controller.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/states/default-state-controller.component.ts index 4a6b2bcdf7..acf7f851f8 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/states/default-state-controller.component.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/default-state-controller.component.ts @@ -90,7 +90,7 @@ export class DefaultStateControllerComponent extends StateControllerComponent im protected onStateChanged() { this.stateObject = this.parseState(this.currentState); - this.gotoState(this.stateObject[0].id, true); + this.gotoState(this.stateObject[0].id, false); } protected stateControllerId(): string { diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/entity-state-controller.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/states/entity-state-controller.component.ts index 4c87aaf2df..12479b7c24 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/states/entity-state-controller.component.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/entity-state-controller.component.ts @@ -95,7 +95,7 @@ export class EntityStateControllerComponent extends StateControllerComponent imp protected onStateChanged() { this.stateObject = this.parseState(this.currentState); this.selectedStateIndex = this.stateObject.length - 1; - this.gotoState(this.stateObject[this.stateObject.length - 1].id, true); + this.gotoState(this.stateObject[this.stateObject.length - 1].id, false); } protected stateControllerId(): string { diff --git a/ui-ngx/src/app/shared/components/dashboard-autocomplete.component.ts b/ui-ngx/src/app/shared/components/dashboard-autocomplete.component.ts index 6b280bc40e..f4743ab4c5 100644 --- a/ui-ngx/src/app/shared/components/dashboard-autocomplete.component.ts +++ b/ui-ngx/src/app/shared/components/dashboard-autocomplete.component.ts @@ -120,7 +120,7 @@ export class DashboardAutocompleteComponent implements ControlValueAccessor, OnI } ngAfterViewInit(): void { - this.selectFirstDashboardIfNeeded(); + // this.selectFirstDashboardIfNeeded(); } selectFirstDashboardIfNeeded(): void { @@ -159,6 +159,7 @@ export class DashboardAutocompleteComponent implements ControlValueAccessor, OnI } else { this.modelValue = null; this.selectDashboardFormGroup.get('dashboard').patchValue(null, {emitEvent: true}); + this.selectFirstDashboardIfNeeded(); } } diff --git a/ui-ngx/src/app/shared/components/dialog/material-icons-dialog.component.html b/ui-ngx/src/app/shared/components/dialog/material-icons-dialog.component.html new file mode 100644 index 0000000000..dbbf9adc9d --- /dev/null +++ b/ui-ngx/src/app/shared/components/dialog/material-icons-dialog.component.html @@ -0,0 +1,78 @@ + +
+ +

{{ 'icon.select-icon' | translate }}

+ +
+ + + +
+ +
+ + +
+
+ +
+
+
+
+ + + + + + +
+
+
+
+ + +
+
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 new file mode 100644 index 0000000000..2a333c1a35 --- /dev/null +++ b/ui-ngx/src/app/shared/components/dialog/material-icons-dialog.component.scss @@ -0,0 +1,39 @@ +/** + * Copyright © 2016-2019 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-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-icon-button.tb-select-icon-button { + width: 56px; + height: 56px; + padding: 16px; + margin: 10px; + border: solid 1px #ffa500; + border-radius: 0%; + line-height: 0; + } + } +} 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 new file mode 100644 index 0000000000..643fc11d4b --- /dev/null +++ b/ui-ngx/src/app/shared/components/dialog/material-icons-dialog.component.ts @@ -0,0 +1,108 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Inject, OnInit, QueryList, ViewChildren, TemplateRef, AfterViewInit } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { Router } from '@angular/router'; +import { DialogComponent } from '@app/shared/components/dialog.component'; +import { UtilsService } from '@core/services/utils.service'; +import { FormControl } from '@angular/forms'; +import { Observable, of, merge, noop } from 'rxjs'; +import { delay, map, mergeMap, share, startWith, tap, mapTo } from 'rxjs/operators'; +import { DashboardInfo } from '@shared/models/dashboard.models'; +import { MatTab } from '@angular/material/tabs'; + +export interface MaterialIconsDialogData { + icon: string; +} + +@Component({ + selector: 'tb-material-icons-dialog', + templateUrl: './material-icons-dialog.component.html', + providers: [], + styleUrls: ['./material-icons-dialog.component.scss'] +}) +export class MaterialIconsDialogComponent extends DialogComponent + implements OnInit, AfterViewInit { + + @ViewChildren('iconButtons') iconButtons: QueryList; + + selectedIcon: string; + icons$: Observable>; + loadingIcons$: Observable; + + showAllControl: FormControl; + + constructor(protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) public data: MaterialIconsDialogData, + private utils: UtilsService, + public dialogRef: MatDialogRef) { + super(store, router, dialogRef); + this.selectedIcon = data.icon; + this.showAllControl = new FormControl(false); + } + + ngOnInit(): void { + this.icons$ = this.showAllControl.valueChanges.pipe( + map((showAll) => { + return {firstTime: false, showAll}; + }), + startWith<{firstTime: boolean, showAll: boolean}>({firstTime: true, showAll: false}), + mergeMap((data) => { + if (data.showAll) { + return this.utils.getMaterialIcons().pipe(delay(100)); + } else { + const res = of(this.utils.getCommonMaterialIcons()); + 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) { + this.dialogRef.close(icon); + } + + cancel(): void { + this.dialogRef.close(null); + } + +} diff --git a/ui-ngx/src/app/shared/components/footer-fab-buttons.component.ts b/ui-ngx/src/app/shared/components/footer-fab-buttons.component.ts index ba3b55d5bc..5d09ff806a 100644 --- a/ui-ngx/src/app/shared/components/footer-fab-buttons.component.ts +++ b/ui-ngx/src/app/shared/components/footer-fab-buttons.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, Input, HostListener } from '@angular/core'; +import { Component, HostListener, Input } from '@angular/core'; import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; diff --git a/ui-ngx/src/app/shared/components/fullscreen.directive.ts b/ui-ngx/src/app/shared/components/fullscreen.directive.ts index f220c6cc2e..b94b105d99 100644 --- a/ui-ngx/src/app/shared/components/fullscreen.directive.ts +++ b/ui-ngx/src/app/shared/components/fullscreen.directive.ts @@ -18,7 +18,7 @@ import { Directive, ElementRef, EventEmitter, - Input, OnChanges, + Input, OnChanges, OnDestroy, Output, SimpleChanges, ViewContainerRef } from '@angular/core'; @@ -29,7 +29,7 @@ import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; @Directive({ selector: '[tb-fullscreen]' }) -export class FullscreenDirective implements OnChanges { +export class FullscreenDirective implements OnChanges, OnDestroy { fullscreenValue = false; @@ -69,6 +69,12 @@ export class FullscreenDirective implements OnChanges { } } + ngOnDestroy(): void { + if (this.fullscreen) { + this.exitFullscreen(); + } + } + enterFullscreen() { const targetElement: HTMLElement = this.fullscreenElement || this.elementRef.nativeElement; this.parentElement = targetElement.parentElement; 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 f79b61f0d2..182d560bfc 100644 --- a/ui-ngx/src/app/shared/components/js-func.component.html +++ b/ui-ngx/src/app/shared/components/js-func.component.html @@ -17,7 +17,7 @@ -->
+ [fullscreen]="fullscreen" fxLayout="column">
@@ -25,6 +25,7 @@ {{'js-func.tidy' | translate }}
-
+
diff --git a/ui-ngx/src/app/shared/components/json-object-edit.component.ts b/ui-ngx/src/app/shared/components/json-object-edit.component.ts index f0789711ab..0ee763ecc0 100644 --- a/ui-ngx/src/app/shared/components/json-object-edit.component.ts +++ b/ui-ngx/src/app/shared/components/json-object-edit.component.ts @@ -15,7 +15,7 @@ /// import { - Attribute, + Attribute, ChangeDetectionStrategy, Component, ElementRef, forwardRef, @@ -60,6 +60,8 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va @Input() fillHeight: boolean; + @Input() editorStyle: {[klass: string]: any}; + private requiredValue: boolean; get required(): boolean { return this.requiredValue; 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 new file mode 100644 index 0000000000..b78b7711da --- /dev/null +++ b/ui-ngx/src/app/shared/components/material-icon-select.component.html @@ -0,0 +1,24 @@ + +
+ {{materialIconFormGroup.get('icon').value}} + + icon.icon + + +
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 new file mode 100644 index 0000000000..1d4532cd00 --- /dev/null +++ b/ui-ngx/src/app/shared/components/material-icon-select.component.scss @@ -0,0 +1,23 @@ +/** + * Copyright © 2016-2019 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 { + .mat-icon { + padding: 4px; + margin: 8px 4px 4px; + cursor: pointer; + border: solid 1px rgba(0, 0, 0, .27); + } +} 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 new file mode 100644 index 0000000000..ec994d3c04 --- /dev/null +++ b/ui-ngx/src/app/shared/components/material-icon-select.component.ts @@ -0,0 +1,125 @@ +/// +/// Copyright © 2016-2019 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, forwardRef, Input, OnInit, ViewChild } from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { DataKey, DatasourceType } from '@shared/models/widget.models'; +import { + ControlValueAccessor, + FormBuilder, + FormControl, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + Validator, + Validators +} from '@angular/forms'; +import { UtilsService } from '@core/services/utils.service'; +import { TranslateService } from '@ngx-translate/core'; +import { MatDialog } from '@angular/material/dialog'; +import { EntityService } from '@core/http/entity.service'; +import { DataKeysCallbacks } from '@home/components/widget/data-keys.component.models'; +import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; +import { Observable, of } from 'rxjs'; +import { map, mergeMap, tap } from 'rxjs/operators'; +import { alarmFields } from '@shared/models/alarm.models'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { DialogService } from '@core/services/dialog.service'; + +@Component({ + selector: 'tb-material-icon-select', + templateUrl: './material-icon-select.component.html', + styleUrls: ['./material-icon-select.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => MaterialIconSelectComponent), + multi: true + } + ] +}) +export class MaterialIconSelectComponent extends PageComponent implements OnInit, ControlValueAccessor { + + @Input() + disabled: boolean; + + private modelValue: string; + + private propagateChange = null; + + public materialIconFormGroup: FormGroup; + + constructor(protected store: Store, + private dialogs: DialogService, + private fb: FormBuilder) { + super(store); + } + + ngOnInit(): void { + this.materialIconFormGroup = this.fb.group({ + icon: [null, []] + }); + + this.materialIconFormGroup.valueChanges.subscribe(() => { + this.updateModel(); + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (isDisabled) { + this.materialIconFormGroup.disable({emitEvent: false}); + } else { + this.materialIconFormGroup.enable({emitEvent: false}); + } + } + + writeValue(value: string): void { + this.modelValue = value; + this.materialIconFormGroup.patchValue( + { icon: this.modelValue }, {emitEvent: false} + ); + } + + private updateModel() { + const icon: string = this.materialIconFormGroup.get('icon').value; + if (this.modelValue !== icon) { + this.modelValue = icon; + this.propagateChange(this.modelValue); + } + } + + openIconDialog() { + this.dialogs.materialIconPicker(this.materialIconFormGroup.get('icon').value).subscribe( + (icon) => { + if (icon) { + this.materialIconFormGroup.patchValue( + {icon}, {emitEvent: true} + ); + } + } + ); + } +} diff --git a/ui-ngx/src/app/shared/components/time/timewindow.component.html b/ui-ngx/src/app/shared/components/time/timewindow.component.html index 38b65e46a6..b7d093706e 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow.component.html @@ -18,7 +18,7 @@
@@ -32,7 +32,7 @@ (click)="openEditMode($event)" matTooltip="{{ 'timewindow.edit' | translate }}" [matTooltipPosition]="tooltipPosition"> - {{innerValue.displayValue}} + {{innerValue?.displayValue}} close diff --git a/ui-ngx/src/app/modules/home/components/widget/data-keys.component.scss b/ui-ngx/src/app/modules/home/components/widget/data-keys.component.scss index 409a4444d1..4a75664657 100644 --- a/ui-ngx/src/app/modules/home/components/widget/data-keys.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/data-keys.component.scss @@ -17,6 +17,7 @@ :host { .mat-chip.mat-standard-chip { .tb-attribute-chip { + max-width: 100%; color: rgb(66, 66, 66); font-weight: normal; font-size: 16px; diff --git a/ui-ngx/src/app/modules/home/components/widget/legend-config.component.html b/ui-ngx/src/app/modules/home/components/widget/legend-config.component.html index f143856ec3..b285e2d5ba 100644 --- a/ui-ngx/src/app/modules/home/components/widget/legend-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/legend-config.component.html @@ -16,6 +16,7 @@ -->
+ + + +
+
+ + +
+
+
+ + + +
+ diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/add-widget-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/add-widget-dialog.component.ts new file mode 100644 index 0000000000..7614f13a60 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/add-widget-dialog.component.ts @@ -0,0 +1,137 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Inject, OnInit, SkipSelf } from '@angular/core'; +import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm } from '@angular/forms'; +import { Router } from '@angular/router'; +import { DialogComponent } from '@app/shared/components/dialog.component'; +import { Widget, widgetTypesData } from '@shared/models/widget.models'; +import { UtilsService } from '@core/services/utils.service'; +import { TranslateService } from '@ngx-translate/core'; +import { EntityService } from '@core/http/entity.service'; +import { Dashboard } from '@app/shared/models/dashboard.models'; +import { IAliasController } from '@core/api/widget-api.models'; +import { WidgetConfigComponentData, WidgetInfo } from '@home/models/widget-component.models'; +import { isDefined, isString } from '@core/utils'; + +export interface AddWidgetDialogData { + dashboard: Dashboard; + aliasController: IAliasController; + widget: Widget; + widgetInfo: WidgetInfo; +} + +@Component({ + selector: 'tb-add-widget-dialog', + templateUrl: './add-widget-dialog.component.html', + providers: [{provide: ErrorStateMatcher, useExisting: AddWidgetDialogComponent}], + styleUrls: [] +}) +export class AddWidgetDialogComponent extends DialogComponent + implements OnInit, ErrorStateMatcher { + + widgetFormGroup: FormGroup; + + dashboard: Dashboard; + aliasController: IAliasController; + widget: Widget; + + submitted = false; + + constructor(protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) public data: AddWidgetDialogData, + @SkipSelf() private errorStateMatcher: ErrorStateMatcher, + public dialogRef: MatDialogRef, + private fb: FormBuilder, + private utils: UtilsService, + private translate: TranslateService, + private entityService: EntityService) { + super(store, router, dialogRef); + + this.dashboard = this.data.dashboard; + this.aliasController = this.data.aliasController; + this.widget = this.data.widget; + + const widgetInfo = this.data.widgetInfo; + + const rawSettingsSchema = widgetInfo.typeSettingsSchema || widgetInfo.settingsSchema; + const rawDataKeySettingsSchema = widgetInfo.typeDataKeySettingsSchema || widgetInfo.dataKeySettingsSchema; + const typeParameters = widgetInfo.typeParameters; + const actionSources = widgetInfo.actionSources; + const isDataEnabled = isDefined(widgetInfo.typeParameters) ? !widgetInfo.typeParameters.useCustomDatasources : true; + let settingsSchema; + if (!rawSettingsSchema || rawSettingsSchema === '') { + settingsSchema = {}; + } else { + settingsSchema = isString(rawSettingsSchema) ? JSON.parse(rawSettingsSchema) : rawSettingsSchema; + } + let dataKeySettingsSchema; + if (!rawDataKeySettingsSchema || rawDataKeySettingsSchema === '') { + dataKeySettingsSchema = {}; + } else { + dataKeySettingsSchema = isString(rawDataKeySettingsSchema) ? JSON.parse(rawDataKeySettingsSchema) : rawDataKeySettingsSchema; + } + const widgetConfig: WidgetConfigComponentData = { + config: this.widget.config, + layout: {}, + widgetType: this.widget.type, + typeParameters, + actionSources, + isDataEnabled, + settingsSchema, + dataKeySettingsSchema + }; + + this.widgetFormGroup = this.fb.group({ + widgetConfig: [widgetConfig, []] + } + ); + } + + ngOnInit(): void { + } + + isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { + const originalErrorState = this.errorStateMatcher.isErrorState(control, form); + const customErrorState = !!(control && control.invalid && this.submitted); + return originalErrorState || customErrorState; + } + + helpLinkIdForWidgetType(): string { + let link = 'widgetsConfig'; + if (this.widget && this.widget.type) { + link = widgetTypesData.get(this.widget.type).configHelpLinkId; + } + return link; + } + + cancel(): void { + this.dialogRef.close(null); + } + + add(): void { + this.submitted = true; + const widgetConfig: WidgetConfigComponentData = this.widgetFormGroup.get('widgetConfig').value; + this.widget.config = widgetConfig.config; + this.widget.config.mobileOrder = widgetConfig.layout.mobileOrder; + this.widget.config.mobileHeight = widgetConfig.layout.mobileHeight; + this.dialogRef.close(this.widget); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.html b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.html index 6556cb219e..7a13498ee7 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.html +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.html @@ -132,26 +132,13 @@
-
- - -
- - + + + + +
@@ -194,6 +194,37 @@ + + + +
+
+ {{ 'widgets-bundle.current' | translate }} + + +
+
+ + +
+
+
- diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.scss b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.scss index f85f59b5ac..322334c4a3 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.scss +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.scss @@ -32,9 +32,9 @@ div.tb-dashboard-page { background-color: #eee; } section.tb-dashboard-title { - position: absolute; - top: 0; - left: 20px; + position: relative; + padding-left: 20px; + max-height: 60px; mat-form-field { .mat-form-field-infix { width: 100%; @@ -47,17 +47,6 @@ div.tb-dashboard-page { letter-spacing: .005em; } } - div.tb-padded { - top: 60px; - } - - section.tb-padded { - top: 60px; - } - - div.tb-shrinked { - width: 40%; - } section.tb-dashboard-toolbar { position: absolute; @@ -106,21 +95,16 @@ div.tb-dashboard-page { transition: margin-top .3s cubic-bezier(.55, 0, .55, .2) .2s; } } + } - .tb-dashboard-layouts { - /*md-backdrop { - z-index: 1; - }*/ - #tb-right-layout { - mat-drawer { - z-index: 1; - } + mat-drawer-container.tb-dashboard-drawer-container { + mat-drawer-container.tb-dashboard-layouts { + width: 100%; + &.tb-shrinked { + width: 40%; } } - } - mat-drawer-container.tb-widget-details-sidenav { - position: initial; mat-drawer.tb-details-drawer { @media #{$mat-gt-sm} { width: 85% !important; @@ -136,10 +120,6 @@ div.tb-dashboard-page { } } - mat-drawer-container.tb-select-widget-sidenav { - position: initial; - } - section.tb-powered-by-footer { position: absolute; right: 25px; diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.ts index ae6f9fae38..b541f42ed3 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.ts @@ -104,9 +104,11 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC isFullscreen = false; isEdit = false; isEditingWidget = false; + isEditingWidgetClosed = true; isMobile = !this.breakpointObserver.isMatched(MediaBreakpoints['gt-sm']); forceDashboardMobileMode = false; isAddingWidget = false; + isAddingWidgetClosed = true; widgetsBundle: WidgetsBundle = null; isToolbarOpened = false; @@ -284,8 +286,10 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC this.isFullscreen = false; this.isEdit = false; this.isEditingWidget = false; + this.isEditingWidgetClosed = true; this.forceDashboardMobileMode = false; this.isAddingWidget = false; + this.isAddingWidgetClosed = true; this.widgetsBundle = null; this.isToolbarOpened = false; @@ -694,6 +698,31 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC this.isAddingWidget = false; } + detailsDrawerOpenedStart() { + if (this.isEditingWidget) { + this.isEditingWidgetClosed = false; + } else if (this.isAddingWidget) { + this.isAddingWidgetClosed = false; + } + setTimeout(() => { + this.cd.detach(); + }, 0); + } + + detailsDrawerOpened() { + this.cd.reattach(); + } + + detailsDrawerClosedStart() { + this.cd.detach(); + } + + detailsDrawerClosed() { + this.isEditingWidgetClosed = true; + this.isAddingWidgetClosed = true; + this.cd.reattach(); + } + private addWidgetToLayout(widget: Widget, layoutId: DashboardLayoutId) { this.dashboardUtils.addWidgetToLayout(this.dashboard, this.dashboardCtx.state, layoutId, widget); this.layouts[layoutId].layoutCtx.widgets.addWidgetId(widget.id); diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.models.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.models.ts index 490b8d2b89..278eb9241d 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.models.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.models.ts @@ -77,6 +77,8 @@ export class LayoutWidgetsArray implements Iterable { private widgetIds: string[] = []; private pointer = 0; + private loaded = false; + constructor(private dashboard: Dashboard) { } @@ -84,8 +86,17 @@ export class LayoutWidgetsArray implements Iterable { return this.widgetIds.length; } + isLoading() { + return !this.loaded; + } + + isEmpty() { + return this.loaded && this.widgetIds.length === 0; + } + setWidgetIds(widgetIds: string[]) { this.widgetIds = widgetIds; + this.loaded = true; } addWidgetId(widgetId: string) { diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/edit-widget.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/edit-widget.component.ts index 6d62a40b1c..9013913d4c 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/edit-widget.component.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/edit-widget.component.ts @@ -102,6 +102,9 @@ export class EditWidgetComponent extends PageComponent implements OnInit, OnChan } private loadWidgetConfig() { + if (!this.widget) { + return; + } const widgetInfo = this.widgetComponentService.getInstantWidgetInfo(this.widget); const rawSettingsSchema = widgetInfo.typeSettingsSchema || widgetInfo.settingsSchema; const rawDataKeySettingsSchema = widgetInfo.typeDataKeySettingsSchema || widgetInfo.dataKeySettingsSchema; diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/layout/dashboard-layout.component.html b/ui-ngx/src/app/modules/home/pages/dashboard/layout/dashboard-layout.component.html index 8fbaa37d54..61f122754a 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/layout/dashboard-layout.component.html +++ b/ui-ngx/src/app/modules/home/pages/dashboard/layout/dashboard-layout.component.html @@ -16,10 +16,15 @@ --> +
+ + +
-
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 182d560bfc..330fd269b6 100644 --- a/ui-ngx/src/app/shared/components/js-func.component.html +++ b/ui-ngx/src/app/shared/components/js-func.component.html @@ -31,8 +31,8 @@ {{ fullscreen ? 'fullscreen_exit' : 'fullscreen' }}
-
-
+
+
diff --git a/ui-ngx/src/app/shared/components/json-object-edit.component.html b/ui-ngx/src/app/shared/components/json-object-edit.component.html index 050214bad0..b0a966a31f 100644 --- a/ui-ngx/src/app/shared/components/json-object-edit.component.html +++ b/ui-ngx/src/app/shared/components/json-object-edit.component.html @@ -30,7 +30,7 @@ {{ fullscreen ? 'fullscreen_exit' : 'fullscreen' }}
-
diff --git a/ui-ngx/src/app/shared/components/mat-chip-draggable.directive.ts b/ui-ngx/src/app/shared/components/mat-chip-draggable.directive.ts index 2f624b8984..8fa1cd86d5 100644 --- a/ui-ngx/src/app/shared/components/mat-chip-draggable.directive.ts +++ b/ui-ngx/src/app/shared/components/mat-chip-draggable.directive.ts @@ -144,7 +144,7 @@ class DraggableChip { const dataTransfer = event.dataTransfer; dataTransfer.effectAllowed = 'copyMove'; dataTransfer.dropEffect = 'move'; - dataTransfer.setData('text/plain', this.index() + ''); + dataTransfer.setData('text', this.index() + ''); } } @@ -222,7 +222,7 @@ class DraggableChip { return; } event = (event as any).originalEvent || event; - const droppedItemIndex = parseInt(event.dataTransfer.getData('text/plain'), 10); + const droppedItemIndex = parseInt(event.dataTransfer.getData('text'), 10); const currentIndex = this.index(); let newIndex; if (this.dropPosition === 'before') { diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html index 5f85596833..a1c86ee82b 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html @@ -17,94 +17,92 @@ -->
-
-
- - -
- -
-
- -
- - -
- -
-
- -
- timewindow.time-period - -
-
-
-
-
-
-
- - aggregation.function - - - {{ aggregationTypesTranslations.get(aggregation) | translate }} - - - -
- aggregation.limit - - - - - +
+ + +
+
+
+ +
+ + +
+ +
+
+ +
+ timewindow.time-period + +
+
+
+
+
+
+
+ + aggregation.function + + + {{ aggregationTypesTranslations.get(aggregation) | translate }} + + + +
+ aggregation.limit + + + + +
-
- - -
-
- - -
-
+
+
+ + +
+
+ + +
From de60fedfa3fba437e7268d6060546ad97af2f002 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 4 Nov 2019 15:47:36 +0200 Subject: [PATCH 048/133] Manage dashboard states. --- ui-ngx/src/app/core/api/widget-api.models.ts | 19 +- .../src/app/core/api/widget-subscription.ts | 36 +++ ui-ngx/src/app/core/http/dashboard.service.ts | 37 ++- .../core/services/dashboard-utils.service.ts | 2 +- ui-ngx/src/app/core/utils.ts | 24 ++ .../aliases-entity-select-panel.component.ts | 2 +- .../dashboard/dashboard.component.html | 3 +- .../dashboard/dashboard.component.ts | 6 +- .../components/widget/widget.component.ts | 143 ++++------ .../home/models/dashboard-component.models.ts | 2 +- .../home/models/widget-component.models.ts | 116 +++++++-- .../dashboard/dashboard-page.component.html | 1 + .../dashboard/dashboard-page.component.ts | 72 +++--- .../pages/dashboard/dashboard-page.models.ts | 8 +- .../home/pages/dashboard/dashboard.module.ts | 10 +- .../layout/dashboard-layout.component.ts | 2 +- .../dashboard-state-dialog.component.html | 69 +++++ .../dashboard-state-dialog.component.ts | 145 +++++++++++ .../default-state-controller.component.scss | 8 + .../entity-state-controller.component.scss | 8 + ...age-dashboard-states-dialog.component.html | 153 +++++++++++ ...ashboard-states-dialog.component.models.ts | 100 +++++++ ...age-dashboard-states-dialog.component.scss | 45 ++++ ...anage-dashboard-states-dialog.component.ts | 244 ++++++++++++++++++ .../states/state-controller.component.ts | 16 +- .../assets/locale/locale.constant-en_US.json | 1 + 26 files changed, 1077 insertions(+), 195 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/pages/dashboard/states/dashboard-state-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/dashboard/states/dashboard-state-dialog.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.models.ts create mode 100644 ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.ts diff --git a/ui-ngx/src/app/core/api/widget-api.models.ts b/ui-ngx/src/app/core/api/widget-api.models.ts index c364479290..6d5a05e4cc 100644 --- a/ui-ngx/src/app/core/api/widget-api.models.ts +++ b/ui-ngx/src/app/core/api/widget-api.models.ts @@ -39,6 +39,7 @@ import { EntityInfo } from '@app/shared/models/entity.models'; import { Type } from '@angular/core'; import { AssetService } from '@core/http/asset.service'; import { DialogService } from '@core/services/dialog.service'; +import { IDashboardComponent } from '@home/models/dashboard-component.models'; export interface TimewindowFunctions { onUpdateTimewindow: (startTimeMs: number, endTimeMs: number, interval?: number) => void; @@ -148,7 +149,19 @@ export interface SubscriptionInfo { deviceIds?: Array; } -export interface WidgetSubscriptionContext { +export class WidgetSubscriptionContext { + + constructor(private dashboard: IDashboardComponent) {} + + get aliasController(): IAliasController { + return this.dashboard.aliasController; + } + + dashboardTimewindowApi: TimewindowFunctions = { + onResetTimewindow: this.dashboard.onResetTimewindow.bind(this.dashboard), + onUpdateTimewindow: this.dashboard.onUpdateTimewindow.bind(this.dashboard) + }; + timeService: TimeService; deviceService: DeviceService; alarmService: AlarmService; @@ -156,11 +169,7 @@ export interface WidgetSubscriptionContext { utils: UtilsService; raf: RafService; widgetUtils: IWidgetUtils; - dashboardTimewindowApi: TimewindowFunctions; getServerTimeDiff: () => Observable; - aliasController: IAliasController; - [key: string]: any; - // TODO: } export interface WidgetSubscriptionCallbacks { diff --git a/ui-ngx/src/app/core/api/widget-subscription.ts b/ui-ngx/src/app/core/api/widget-subscription.ts index e66cb58c24..bb9ecc560c 100644 --- a/ui-ngx/src/app/core/api/widget-subscription.ts +++ b/ui-ngx/src/app/core/api/widget-subscription.ts @@ -382,6 +382,13 @@ export class WidgetSubscription implements IWidgetSubscription { } onAliasesChanged(aliasIds: Array): boolean { + if (this.type === widgetType.rpc) { + return this.checkRpcTarget(aliasIds); + } else if (this.type === widgetType.alarm) { + return this.checkAlarmSource(aliasIds); + } else { + return this.checkSubscriptions(aliasIds); + } return false; } @@ -566,6 +573,35 @@ export class WidgetSubscription implements IWidgetSubscription { // TODO: } + private checkRpcTarget(aliasIds: Array): boolean { + if (aliasIds.indexOf(this.targetDeviceAliasId) > -1) { + return true; + } else { + return false; + } + } + + private checkAlarmSource(aliasIds: Array): boolean { + if (this.alarmSource && this.alarmSource.entityAliasId) { + return aliasIds.indexOf(this.alarmSource.entityAliasId) > -1; + } else { + return false; + } + } + + private checkSubscriptions(aliasIds: Array): boolean { + let subscriptionsChanged = false; + for (const listener of this.datasourceListeners) { + if (listener.datasource.entityAliasId) { + if (aliasIds.indexOf(listener.datasource.entityAliasId) > -1) { + subscriptionsChanged = true; + break; + } + } + } + return subscriptionsChanged; + } + destroy(): void { this.unsubscribe(); for (const cafId of Object.keys(this.cafs)) { diff --git a/ui-ngx/src/app/core/http/dashboard.service.ts b/ui-ngx/src/app/core/http/dashboard.service.ts index 9bd719663d..cff0385833 100644 --- a/ui-ngx/src/app/core/http/dashboard.service.ts +++ b/ui-ngx/src/app/core/http/dashboard.service.ts @@ -22,26 +22,29 @@ import {PageLink} from '@shared/models/page/page-link'; import {PageData} from '@shared/models/page/page-data'; import {Dashboard, DashboardInfo} from '@shared/models/dashboard.models'; import {WINDOW} from '@core/services/window.service'; -import { ActivationEnd, Router } from '@angular/router'; -import { filter } from 'rxjs/operators'; +import { ActivationEnd, NavigationEnd, Router } from '@angular/router'; +import { filter, map, publishReplay, refCount } from 'rxjs/operators'; @Injectable({ providedIn: 'root' }) export class DashboardService { - stDiffSubject: Subject; + stDiffObservable: Observable; + currentUrl: string; constructor( private http: HttpClient, private router: Router, @Inject(WINDOW) private window: Window ) { - this.router.events.pipe(filter(event => event instanceof ActivationEnd)).subscribe( + this.currentUrl = this.router.url.split('?')[0]; + this.router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe( () => { - if (this.stDiffSubject) { - this.stDiffSubject.complete(); - this.stDiffSubject = null; + const newUrl = this.router.url.split('?')[0]; + if (this.currentUrl !== newUrl) { + this.stDiffObservable = null; + this.currentUrl = newUrl; } } ); @@ -139,24 +142,20 @@ export class DashboardService { } public getServerTimeDiff(): Observable { - if (this.stDiffSubject) { - return this.stDiffSubject.asObservable(); - } else { - this.stDiffSubject = new ReplaySubject(1); + if (!this.stDiffObservable) { const url = '/api/dashboard/serverTime'; const ct1 = Date.now(); - this.http.get(url, defaultHttpOptions(true)).subscribe( - (st) => { + this.stDiffObservable = this.http.get(url, defaultHttpOptions(true)).pipe( + map((st) => { const ct2 = Date.now(); const stDiff = Math.ceil(st - (ct1 + ct2) / 2); - this.stDiffSubject.next(stDiff); - }, - () => { - this.stDiffSubject.error(null); - } + return stDiff; + }), + publishReplay(1), + refCount() ); - return this.stDiffSubject.asObservable(); } + return this.stDiffObservable; } } 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 d965912a12..976c36bf02 100644 --- a/ui-ngx/src/app/core/services/dashboard-utils.service.ts +++ b/ui-ngx/src/app/core/services/dashboard-utils.service.ts @@ -479,7 +479,7 @@ export class DashboardUtilsService { } } - private removeUnusedWidgets(dashboard: Dashboard) { + public removeUnusedWidgets(dashboard: Dashboard) { const dashboardConfiguration = dashboard.configuration; const states = dashboardConfiguration.states; const widgets = dashboardConfiguration.widgets; diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index 9960b5bbf4..a84ef56958 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -99,10 +99,34 @@ export function isNumber(value: any): boolean { return typeof value === 'number'; } +export function isNumeric(value: any): boolean { + return (value - parseFloat( value ) + 1) >= 0; +} + export function isString(value: any): boolean { return typeof value === 'string'; } +export function formatValue(value: any, dec?: number, units?: string, showZeroDecimals?: boolean): string | undefined { + if (isDefined(value) && + value != null && isNumeric(value)) { + let formatted: string | number = Number(value); + if (isDefined(dec)) { + formatted = formatted.toFixed(dec); + } + if (!showZeroDecimals) { + formatted = (Number(formatted) * 1); + } + formatted = formatted.toString(); + if (isDefined(units) && units.length > 0) { + formatted += ' ' + units; + } + return formatted; + } else { + return value; + } +} + export function deleteNullProperties(obj: any) { if (isUndefined(obj) || obj == null) { return; diff --git a/ui-ngx/src/app/modules/home/components/alias/aliases-entity-select-panel.component.ts b/ui-ngx/src/app/modules/home/components/alias/aliases-entity-select-panel.component.ts index f05f5bc69a..3494cfebea 100644 --- a/ui-ngx/src/app/modules/home/components/alias/aliases-entity-select-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/alias/aliases-entity-select-panel.component.ts @@ -52,7 +52,7 @@ export class AliasesEntitySelectPanelComponent { const resolvedEntities = this.entityAliasesInfo[aliasId].resolvedEntities; const selected = resolvedEntities.find((entity) => entity.id === selectedId); if (selected) { - this.data.aliasController.updateCurrentAliasEntity(aliasId, selected[0]); + this.data.aliasController.updateCurrentAliasEntity(aliasId, selected); } } diff --git a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html index 73945b8602..8bb638fad7 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html @@ -148,8 +148,7 @@ #widgetComponent [dashboardWidget]="widget" [isEdit]="isEdit" - [isMobile]="isMobileSize" - [dashboard]="this"> + [isMobile]="isMobileSize">
diff --git a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts index 47ef73c300..a69331e670 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts @@ -180,6 +180,8 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo this.gridsterOpts = { gridType: 'scrollVertical', keepFixedHeightInMobile: true, + disableWarnings: false, + disableAutoPositionOnConflict: false, pushItems: false, swap: false, maxRows: 100, @@ -228,7 +230,9 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo } ngDoCheck() { - this.dashboardWidgets.doCheck(); + if (!this.optionsChangeNotificationsPaused) { + this.dashboardWidgets.doCheck(); + } } ngOnChanges(changes: SimpleChanges): void { 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 faadceceef..fc9a7b5eb6 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 @@ -115,9 +115,6 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI @Input() isMobile: boolean; - @Input() - dashboard: IDashboardComponent; - @Input() dashboardWidget: DashboardWidget; @@ -146,11 +143,12 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI subscriptionContext: WidgetSubscriptionContext; subscriptionInited = false; + destroyed = false; widgetSizeDetected = false; cafs: {[cafId: string]: CancelAnimationFrame} = {}; - onResizeListener = this.onResize.bind(this); + onResizeListener = null; private cssParser = new cssjs(); @@ -252,30 +250,9 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI this.widgetContext = this.dashboardWidget.widgetContext; this.widgetContext.servicesMap = ServicesMap; - this.widgetContext.inited = false; - this.widgetContext.hideTitlePanel = false; this.widgetContext.isEdit = this.isEdit; this.widgetContext.isMobile = this.isMobile; - this.widgetContext.dashboard = this.dashboard; - this.widgetContext.widgetConfig = this.widget.config; - this.widgetContext.settings = this.widget.config.settings; - this.widgetContext.units = this.widget.config.units || ''; - this.widgetContext.decimals = isDefined(this.widget.config.decimals) ? this.widget.config.decimals : 2; - this.widgetContext.subscriptions = {}; - this.widgetContext.defaultSubscription = null; - this.widgetContext.dashboardTimewindow = this.dashboard.dashboardTimewindow; - this.widgetContext.timewindowFunctions = { - onUpdateTimewindow: (startTimeMs, endTimeMs, interval) => { - if (this.widgetContext.defaultSubscription) { - this.widgetContext.defaultSubscription.onUpdateTimewindow(startTimeMs, endTimeMs, interval); - } - }, - onResetTimewindow: () => { - if (this.widgetContext.defaultSubscription) { - this.widgetContext.defaultSubscription.onResetTimewindow(); - } - } - }; + this.widgetContext.subscriptionApi = { createSubscription: this.createSubscription.bind(this), createSubscriptionFromInfo: this.createSubscriptionFromInfo.bind(this), @@ -287,33 +264,13 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI } } }; - this.widgetContext.controlApi = { - sendOneWayCommand: (method, params, timeout) => { - if (this.widgetContext.defaultSubscription) { - return this.widgetContext.defaultSubscription.sendOneWayCommand(method, params, timeout); - } else { - return of(null); - } - }, - sendTwoWayCommand: (method, params, timeout) => { - if (this.widgetContext.defaultSubscription) { - return this.widgetContext.defaultSubscription.sendTwoWayCommand(method, params, timeout); - } else { - return of(null); - } - } - }; - this.widgetContext.utils = { - formatValue: this.formatValue.bind(this) - }; + this.widgetContext.actionsApi = { actionDescriptorsBySourceId, getActionDescriptors: this.getActionDescriptors.bind(this), handleWidgetAction: this.handleWidgetAction.bind(this), elementClick: this.elementClick.bind(this) }; - this.widgetContext.stateController = this.dashboard.stateController; - this.widgetContext.aliasController = this.dashboard.aliasController; this.widgetContext.customHeaderActions = []; const headerActionsDescriptors = this.getActionDescriptors(widgetActionSources.headerButton.value); @@ -333,21 +290,15 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI this.widgetContext.customHeaderActions.push(headerAction); }); - this.subscriptionContext = { - timeService: this.timeService, - deviceService: this.deviceService, - alarmService: this.alarmService, - datasourceService: this.datasourceService, - utils: this.utils, - raf: this.raf, - widgetUtils: this.widgetContext.utils, - dashboardTimewindowApi: { - onResetTimewindow: this.dashboard.onResetTimewindow.bind(this.dashboard), - onUpdateTimewindow: this.dashboard.onUpdateTimewindow.bind(this.dashboard) - }, - getServerTimeDiff: this.dashboardService.getServerTimeDiff.bind(this.dashboardService), - aliasController: this.dashboard.aliasController - }; + this.subscriptionContext = new WidgetSubscriptionContext(this.widgetContext.dashboard); + this.subscriptionContext.timeService = this.timeService; + this.subscriptionContext.deviceService = this.deviceService; + this.subscriptionContext.alarmService = this.alarmService; + this.subscriptionContext.datasourceService = this.datasourceService; + this.subscriptionContext.utils = this.utils; + this.subscriptionContext.raf = this.raf; + this.subscriptionContext.widgetUtils = this.widgetContext.utils; + this.subscriptionContext.getServerTimeDiff = this.dashboardService.getServerTimeDiff.bind(this.dashboardService); this.widgetComponentService.getWidgetInfo(this.widget.bundleAlias, this.widget.typeAlias, this.widget.isSystemType).subscribe( (widgetInfo) => { @@ -382,6 +333,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI } ngOnDestroy(): void { + this.destroyed = true; this.rxSubscriptions.forEach((subscription) => { subscription.unsubscribe(); }); @@ -481,7 +433,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI } private onInit(skipSizeCheck?: boolean) { - if (!this.widgetContext.$containerParent) { + if (!this.widgetContext.$containerParent || this.destroyed) { return; } if (!skipSizeCheck) { @@ -565,17 +517,35 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI } private reInit() { + if (this.cafs.reinit) { + this.cafs.reinit(); + this.cafs.reinit = null; + } + this.cafs.reinit = this.raf.raf(() => { + this.reInitImpl(); + }); + } + + private reInitImpl() { this.onDestroy(); this.configureDynamicWidgetComponent(); if (!this.typeParameters.useCustomDatasources) { this.createDefaultSubscription().subscribe( () => { - this.subscriptionInited = true; - this.onInit(); + if (this.destroyed) { + this.onDestroy(); + } else { + this.subscriptionInited = true; + this.onInit(); + } }, () => { - this.subscriptionInited = true; - this.onInit(); + if (this.destroyed) { + this.onDestroy(); + } else { + this.subscriptionInited = true; + this.onInit(); + } } ); } else { @@ -588,7 +558,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI const initSubject = new ReplaySubject(); - this.rxSubscriptions.push(this.dashboard.aliasController.entityAliasesChanged.subscribe( + this.rxSubscriptions.push(this.widgetContext.aliasController.entityAliasesChanged.subscribe( (aliasIds) => { let subscriptionChanged = false; for (const id of Object.keys(this.widgetContext.subscriptions)) { @@ -601,7 +571,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI } )); - this.rxSubscriptions.push(this.dashboard.dashboardTimewindowChanged.subscribe( + this.rxSubscriptions.push(this.widgetContext.dashboard.dashboardTimewindowChanged.subscribe( (dashboardTimewindow) => { for (const id of Object.keys(this.widgetContext.subscriptions)) { const subscription = this.widgetContext.subscriptions[id]; @@ -634,9 +604,10 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI } private destroyDynamicWidgetComponent() { - if (this.widgetContext.$containerParent) { + if (this.widgetContext.$containerParent && this.onResizeListener) { // @ts-ignore removeResizeListener(this.widgetContext.$containerParent[0], this.onResizeListener); + this.onResizeListener = null; } if (this.dynamicWidgetComponentRef) { this.dynamicWidgetComponentRef.destroy(); @@ -661,7 +632,8 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI const containerElement = $(this.elementRef.nativeElement.querySelector('#widget-container')); - this.widgetContext.$container = $('> ng-component', containerElement); + // this.widgetContext.$container = $('> ng-component:not([id="container"])', containerElement); + this.widgetContext.$container = $(this.dynamicWidgetComponentRef.location.nativeElement); this.widgetContext.$container.css('display', 'block'); this.widgetContext.$container.css('user-select', 'none'); this.widgetContext.$container.attr('id', 'container'); @@ -672,13 +644,14 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI this.widgetContext.$container.css('width', this.widgetContext.width + 'px'); } + this.onResizeListener = this.onResize.bind(this); // @ts-ignore addResizeListener(this.widgetContext.$containerParent[0], this.onResizeListener); } private createSubscription(options: WidgetSubscriptionOptions, subscribe?: boolean): Observable { const createSubscriptionSubject = new ReplaySubject(); - options.dashboardTimewindow = this.dashboard.dashboardTimewindow; + options.dashboardTimewindow = this.widgetContext.dashboardTimewindow; const subscription: IWidgetSubscription = new WidgetSubscription(this.subscriptionContext, options); subscription.init$.subscribe( () => { @@ -747,7 +720,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI ? this.widget.config.useDashboardTimewindow : true; options.displayTimewindow = isDefined(this.widget.config.displayTimewindow) ? this.widget.config.displayTimewindow : !options.useDashboardTimewindow; - options.timeWindowConfig = options.useDashboardTimewindow ? this.dashboard.dashboardTimewindow : this.widget.config.timewindow; + options.timeWindowConfig = options.useDashboardTimewindow ? this.widgetContext.dashboardTimewindow : this.widget.config.timewindow; options.legendConfig = null; if (this.displayLegend) { options.legendConfig = this.legendConfig; @@ -875,30 +848,6 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI return createSubscriptionSubject.asObservable(); } - private isNumeric(value: any): boolean { - return (value - parseFloat( value ) + 1) >= 0; - } - - private formatValue(value: any, dec?: number, units?: string, showZeroDecimals?: boolean): string | undefined { - if (isDefined(value) && - value != null && this.isNumeric(value)) { - let formatted: string | number = Number(value); - if (isDefined(dec)) { - formatted = formatted.toFixed(dec); - } - if (!showZeroDecimals) { - formatted = (Number(formatted) * 1); - } - formatted = formatted.toString(); - if (isDefined(units) && units.length > 0) { - formatted += ' ' + units; - } - return formatted; - } else { - return value; - } - } - private getActionDescriptors(actionSourceId: string): Array { let result = this.widgetContext.actionsApi.actionDescriptorsBySourceId[actionSourceId]; if (!result) { 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 267278a8a7..a03cbd4ea3 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 @@ -302,7 +302,7 @@ export class DashboardWidget implements GridsterItem { customHeaderActions: Array; widgetActions: Array; - widgetContext: WidgetContext = {}; + widgetContext = new WidgetContext(this.dashboard, this.widget); widgetId: string; 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 764d26d4a0..4a9701f839 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 @@ -26,7 +26,8 @@ import { WidgetType, widgetType, WidgetTypeDescriptor, - WidgetTypeParameters + WidgetTypeParameters, + Widget } from '@shared/models/widget.models'; import { Timewindow, WidgetTimewindow } from '@shared/models/time/time.models'; import { @@ -34,10 +35,10 @@ import { IStateController, IWidgetSubscription, IWidgetUtils, - RpcApi, SubscriptionEntityInfo, + RpcApi, SubscriptionEntityInfo, SubscriptionInfo, TimewindowFunctions, WidgetActionsApi, - WidgetSubscriptionApi + WidgetSubscriptionApi, WidgetSubscriptionContext, WidgetSubscriptionOptions } from '@core/api/widget-api.models'; import { ComponentFactory, Type } from '@angular/core'; import { HttpErrorResponse } from '@angular/common/http'; @@ -49,6 +50,9 @@ import { DeviceService } from '@core/http/device.service'; import { AssetService } from '@app/core/http/asset.service'; import { DialogService } from '@core/services/dialog.service'; import { CustomDialogService } from '@home/components/widget/dialog/custom-dialog.service'; +import { isDefined, formatValue } from '@core/utils'; +import { Observable, of, ReplaySubject } from 'rxjs'; +import { WidgetSubscription } from '@core/api/widget-subscription'; export interface IWidgetAction { name: string; @@ -65,30 +69,89 @@ export interface WidgetAction extends IWidgetAction { show: boolean; } -export interface WidgetContext { - inited?: boolean; - $container?: JQuery; - $containerParent?: JQuery; - width?: number; - height?: number; - $scope?: IDynamicWidgetComponent; - isEdit?: boolean; - isMobile?: boolean; - dashboard?: IDashboardComponent; - widgetConfig?: WidgetConfig; - settings?: any; - units?: string; - decimals?: number; - subscriptions?: {[id: string]: IWidgetSubscription}; - defaultSubscription?: IWidgetSubscription; - dashboardTimewindow?: Timewindow; - timewindowFunctions?: TimewindowFunctions; +export class WidgetContext { + + constructor(public dashboard: IDashboardComponent, + private widget: Widget) {} + + get stateController(): IStateController { + return this.dashboard.stateController; + } + + get aliasController(): IAliasController { + return this.dashboard.aliasController; + } + + get dashboardTimewindow(): Timewindow { + return this.dashboard.dashboardTimewindow; + } + + get widgetConfig(): WidgetConfig { + return this.widget.config; + } + + get settings(): any { + return this.widget.config.settings; + } + + get units(): string { + return this.widget.config.units || ''; + } + + get decimals(): number { + return isDefined(this.widget.config.decimals) ? this.widget.config.decimals : 2; + } + + inited = false; + + subscriptions: {[id: string]: IWidgetSubscription} = {}; + defaultSubscription: IWidgetSubscription = null; + + timewindowFunctions: TimewindowFunctions = { + onUpdateTimewindow: (startTimeMs, endTimeMs, interval) => { + if (this.defaultSubscription) { + this.defaultSubscription.onUpdateTimewindow(startTimeMs, endTimeMs, interval); + } + }, + onResetTimewindow: () => { + if (this.defaultSubscription) { + this.defaultSubscription.onResetTimewindow(); + } + } + }; + + controlApi: RpcApi = { + sendOneWayCommand: (method, params, timeout) => { + if (this.defaultSubscription) { + return this.defaultSubscription.sendOneWayCommand(method, params, timeout); + } else { + return of(null); + } + }, + sendTwoWayCommand: (method, params, timeout) => { + if (this.defaultSubscription) { + return this.defaultSubscription.sendTwoWayCommand(method, params, timeout); + } else { + return of(null); + } + } + }; + + utils: IWidgetUtils = { + formatValue + }; + + $container: JQuery; + $containerParent: JQuery; + width: number; + height: number; + $scope: IDynamicWidgetComponent; + isEdit: boolean; + isMobile: boolean; + subscriptionApi?: WidgetSubscriptionApi; - controlApi?: RpcApi; - utils?: IWidgetUtils; + actionsApi?: WidgetActionsApi; - stateController?: IStateController; - aliasController?: IAliasController; activeEntityInfo?: SubscriptionEntityInfo; datasources?: Array; @@ -96,7 +159,8 @@ export interface WidgetContext { hiddenData?: Array<{data: DataSet}>; timeWindow?: WidgetTimewindow; - hideTitlePanel?: boolean; + hideTitlePanel = false; + widgetTitleTemplate?: string; widgetTitle?: string; customHeaderActions?: Array; diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.html b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.html index 0db11b7fdc..cf2f4e1c0f 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.html +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.html @@ -141,6 +141,7 @@ height: rightLayoutHeight(), borderLeft: 'none'}" disableClose="true" + [@.disabled]="!isMobile" position="end" [mode]="isMobile ? 'over' : 'side'" [(opened)]="rightLayoutOpened"> diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.ts index b541f42ed3..29901bd857 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.ts @@ -26,7 +26,7 @@ import { DashboardConfiguration, DashboardLayoutId, DashboardLayoutInfo, - DashboardLayoutsInfo, + DashboardLayoutsInfo, DashboardState, DashboardStateLayouts, GridSettings, WidgetLayout } from '@app/shared/models/dashboard.models'; @@ -79,6 +79,10 @@ import { DashboardSettingsDialogComponent, DashboardSettingsDialogData } from '@home/pages/dashboard/dashboard-settings-dialog.component'; +import { + ManageDashboardStatesDialogComponent, + ManageDashboardStatesDialogData +} from '@home/pages/dashboard/states/manage-dashboard-states-dialog.component'; @Component({ selector: 'tb-dashboard-page', @@ -130,6 +134,16 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC addingLayoutCtx: DashboardPageLayoutContext; + + dashboardCtx: DashboardContext = { + getDashboard: () => this.dashboard, + dashboardTimewindow: null, + state: null, + stateController: null, + aliasController: null, + runChangeDetection: this.runChangeDetection.bind(this) + }; + layouts: DashboardPageLayouts = { main: { show: false, @@ -157,15 +171,6 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC } }; - dashboardCtx: DashboardContext = { - dashboard: null, - dashboardTimewindow: null, - state: null, - stateController: null, - aliasController: null, - runChangeDetection: this.runChangeDetection.bind(this) - }; - addWidgetFabButtons: FooterFabButtons = { fabTogglerName: 'dashboard.add-widget', fabTogglerIcon: 'add', @@ -255,13 +260,12 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC this.dashboard = data.dashboard; this.dashboardConfiguration = this.dashboard.configuration; - this.layouts.main.layoutCtx.widgets = new LayoutWidgetsArray(this.dashboard); - this.layouts.right.layoutCtx.widgets = new LayoutWidgetsArray(this.dashboard); + this.dashboardCtx.dashboardTimewindow = this.dashboardConfiguration.timewindow; + this.layouts.main.layoutCtx.widgets = new LayoutWidgetsArray(this.dashboardCtx); + this.layouts.right.layoutCtx.widgets = new LayoutWidgetsArray(this.dashboardCtx); this.widgetEditMode = data.widgetEditMode; this.singlePageMode = data.singlePageMode; - this.dashboardCtx.dashboard = this.dashboard; - this.dashboardCtx.dashboardTimewindow = this.dashboardConfiguration.timewindow; this.dashboardCtx.aliasController = new AliasController(this.utils, this.entityService, () => this.dashboardCtx.stateController, @@ -514,8 +518,18 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC if ($event) { $event.stopPropagation(); } - // TODO: - this.dialogService.todo(); + this.dialog.open(ManageDashboardStatesDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + states: deepClone(this.dashboard.configuration.states) + } + }).afterClosed().subscribe((states) => { + if (states) { + this.updateStates(states); + } + }); } public manageDashboardLayouts($event: Event) { @@ -541,6 +555,16 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC this.updateLayouts(); } + private updateStates(states: {[id: string]: DashboardState }) { + this.dashboard.configuration.states = states; + this.dashboardUtils.removeUnusedWidgets(this.dashboard); + let targetState = this.dashboardCtx.state; + if (!this.dashboard.configuration.states[targetState]) { + targetState = this.dashboardUtils.getRootStateId(this.dashboardConfiguration.states); + } + this.openDashboardState(targetState); + } + private importWidget($event: Event) { if ($event) { $event.stopPropagation(); @@ -577,20 +601,6 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC if (layoutsData) { this.dashboardCtx.state = state; this.dashboardCtx.aliasController.dashboardStateChanged(); - let layoutVisibilityChanged = false; - for (const l of Object.keys(this.layouts)) { - const layout: DashboardPageLayout = this.layouts[l]; - let showLayout; - if (layoutsData[l]) { - showLayout = true; - } else { - showLayout = false; - } - if (layout.show !== showLayout) { - layout.show = showLayout; - layoutVisibilityChanged = !this.isMobile; - } - } this.isRightLayoutOpened = openRightLayout ? true : false; this.updateLayouts(layoutsData); } @@ -603,9 +613,11 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC for (const l of Object.keys(this.layouts)) { const layout: DashboardPageLayout = this.layouts[l]; if (layoutsData[l]) { + layout.show = true; const layoutInfo: DashboardLayoutInfo = layoutsData[l]; this.updateLayout(layout, layoutInfo); } else { + layout.show = false; this.updateLayout(layout, {widgetIds: [], widgetLayouts: {}, gridSettings: null}); } } diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.models.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.models.ts index 278eb9241d..41f56ec592 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.models.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.models.ts @@ -30,7 +30,7 @@ export declare type DashboardPageScope = 'tenant' | 'customer'; export interface DashboardContext { state: string; - dashboard: Dashboard; + getDashboard: () => Dashboard; dashboardTimewindow: Timewindow; aliasController: IAliasController; stateController: IStateController; @@ -79,7 +79,7 @@ export class LayoutWidgetsArray implements Iterable { private loaded = false; - constructor(private dashboard: Dashboard) { + constructor(private dashboardCtx: DashboardContext) { } size() { @@ -115,7 +115,7 @@ export class LayoutWidgetsArray implements Iterable { [Symbol.iterator](): Iterator { let pointer = 0; const widgetIds = this.widgetIds; - const dashboard = this.dashboard; + const dashboard = this.dashboardCtx.getDashboard(); return { next(value?: any): IteratorResult { if (pointer < widgetIds.length) { @@ -145,7 +145,7 @@ export class LayoutWidgetsArray implements Iterable { } private widgetById(widgetId: string): Widget { - return this.dashboard.configuration.widgets[widgetId]; + return this.dashboardCtx.getDashboard().configuration.widgets[widgetId]; } } diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard.module.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard.module.ts index c2df50b36a..406a107434 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard.module.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard.module.ts @@ -34,6 +34,8 @@ import { AddWidgetDialogComponent } from './add-widget-dialog.component'; import { ManageDashboardLayoutsDialogComponent } from './layout/manage-dashboard-layouts-dialog.component'; import { SelectTargetLayoutDialogComponent } from './layout/select-target-layout-dialog.component'; import { DashboardSettingsDialogComponent } from './dashboard-settings-dialog.component'; +import { ManageDashboardStatesDialogComponent } from './states/manage-dashboard-states-dialog.component'; +import { DashboardStateDialogComponent } from './states/dashboard-state-dialog.component'; @NgModule({ entryComponents: [ @@ -44,7 +46,9 @@ import { DashboardSettingsDialogComponent } from './dashboard-settings-dialog.co AddWidgetDialogComponent, ManageDashboardLayoutsDialogComponent, SelectTargetLayoutDialogComponent, - DashboardSettingsDialogComponent + DashboardSettingsDialogComponent, + ManageDashboardStatesDialogComponent, + DashboardStateDialogComponent ], declarations: [ DashboardFormComponent, @@ -59,7 +63,9 @@ import { DashboardSettingsDialogComponent } from './dashboard-settings-dialog.co AddWidgetDialogComponent, ManageDashboardLayoutsDialogComponent, SelectTargetLayoutDialogComponent, - DashboardSettingsDialogComponent + DashboardSettingsDialogComponent, + ManageDashboardStatesDialogComponent, + DashboardStateDialogComponent ], imports: [ CommonModule, diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/layout/dashboard-layout.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/layout/dashboard-layout.component.ts index 3c074f9f83..8e695060c1 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/layout/dashboard-layout.component.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/layout/dashboard-layout.component.ts @@ -147,7 +147,7 @@ export class DashboardLayoutComponent extends PageComponent implements ILayoutCo this.hotkeysService.add( new Hotkey('ctrl+i', (event: KeyboardEvent) => { if (this.isEdit && !this.isEditingWidget && !this.widgetEditMode) { - if (this.itembuffer.canPasteWidgetReference(this.dashboardCtx.dashboard, + if (this.itembuffer.canPasteWidgetReference(this.dashboardCtx.getDashboard(), this.dashboardCtx.state, this.layoutCtx.id)) { event.preventDefault(); this.pasteWidgetReference(event); diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/dashboard-state-dialog.component.html b/ui-ngx/src/app/modules/home/pages/dashboard/states/dashboard-state-dialog.component.html new file mode 100644 index 0000000000..c28a2890b8 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/dashboard-state-dialog.component.html @@ -0,0 +1,69 @@ + + + +

{{ isAdd ? 'dashboard.add-state' : 'dashboard.edit-state' }}

+ + +
+ + +
+
+ + dashboard.state-name + + + {{ 'dashboard.state-name-required' | translate }} + + + + dashboard.state-id + + + {{ 'dashboard.state-id-required' | translate }} + + + {{ 'dashboard.state-id-exists' | translate }} + + + + {{ 'dashboard.is-root-state' | translate }} + +
+
+
+ + + +
+ diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/dashboard-state-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/states/dashboard-state-dialog.component.ts new file mode 100644 index 0000000000..973b3e40e3 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/dashboard-state-dialog.component.ts @@ -0,0 +1,145 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Inject, OnInit, SkipSelf } from '@angular/core'; +import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { + FormBuilder, + FormControl, + FormGroup, + FormGroupDirective, + NgForm, + ValidatorFn, + Validators +} from '@angular/forms'; +import { Router } from '@angular/router'; +import { DialogComponent } from '@app/shared/components/dialog.component'; +import { DashboardState } from '@app/shared/models/dashboard.models'; +import { MatDialog } from '@angular/material/dialog'; +import { DashboardStateInfo } from '@home/pages/dashboard/states/manage-dashboard-states-dialog.component.models'; +import { TranslateService } from '@ngx-translate/core'; +import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; + +export interface DashboardStateDialogData { + states: {[id: string]: DashboardState }; + state: DashboardStateInfo; + isAdd: boolean; +} + +@Component({ + selector: 'tb-dashboard-state-dialog', + templateUrl: './dashboard-state-dialog.component.html', + providers: [{provide: ErrorStateMatcher, useExisting: DashboardStateDialogComponent}], + styleUrls: [] +}) +export class DashboardStateDialogComponent extends + DialogComponent + implements OnInit, ErrorStateMatcher { + + stateFormGroup: FormGroup; + + states: {[id: string]: DashboardState }; + state: DashboardStateInfo; + prevStateId: string; + + stateIdTouched: boolean; + + isAdd: boolean; + + submitted = false; + + constructor(protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) public data: DashboardStateDialogData, + @SkipSelf() private errorStateMatcher: ErrorStateMatcher, + public dialogRef: MatDialogRef, + private fb: FormBuilder, + private translate: TranslateService, + private dashboardUtils: DashboardUtilsService, + private dialog: MatDialog) { + super(store, router, dialogRef); + + this.states = this.data.states; + this.isAdd = this.data.isAdd; + if (this.isAdd) { + this.state = {id: '', ...this.dashboardUtils.createDefaultState('', false)}; + this.prevStateId = ''; + } else { + this.state = this.data.state; + this.prevStateId = this.state.id; + } + + this.stateFormGroup = this.fb.group({ + name: [this.state.name, [Validators.required]], + id: [this.state.id, [Validators.required, this.validateDuplicateStateId()]], + root: [this.state.root, []], + }); + + this.stateFormGroup.get('name').valueChanges.subscribe((name: string) => { + this.checkStateName(name); + }); + + this.stateFormGroup.get('id').valueChanges.subscribe((id: string) => { + this.stateIdTouched = true; + }); + } + + private checkStateName(name: string) { + if (name && !this.stateIdTouched && this.isAdd) { + this.stateFormGroup.get('id').setValue( + name.toLowerCase().replace(/\W/g, '_'), + { emitEvent: false } + ); + } + } + + private validateDuplicateStateId(): ValidatorFn { + return (c: FormControl) => { + const newStateId: string = c.value; + if (newStateId) { + const existing = this.states[newStateId]; + if (existing && newStateId !== this.prevStateId) { + return { + stateExists: true + }; + } + } + return null; + }; + } + + ngOnInit(): void { + } + + isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { + const originalErrorState = this.errorStateMatcher.isErrorState(control, form); + const customErrorState = !!(control && control.invalid && this.submitted); + return originalErrorState || customErrorState; + } + + cancel(): void { + this.dialogRef.close(null); + } + + save(): void { + this.submitted = true; + this.state = {...this.state, ...this.stateFormGroup.value}; + this.state.id = this.state.id.trim(); + this.dialogRef.close(this.state); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/default-state-controller.component.scss b/ui-ngx/src/app/modules/home/pages/dashboard/states/default-state-controller.component.scss index 691099ef0b..275788b793 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/states/default-state-controller.component.scss +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/default-state-controller.component.scss @@ -18,3 +18,11 @@ margin: 0; } } + +:host ::ng-deep { + mat-select.default-state-controller { + .mat-select-value { + max-width: 200px; + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/entity-state-controller.component.scss b/ui-ngx/src/app/modules/home/pages/dashboard/states/entity-state-controller.component.scss index 4cbec02a67..855edfe80c 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/states/entity-state-controller.component.scss +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/entity-state-controller.component.scss @@ -26,6 +26,7 @@ } .state-entry { + pointer-events: all; overflow: hidden; font-size: 18px; text-overflow: ellipsis; @@ -35,7 +36,14 @@ mat-select { margin: 0; + } + } +} +:host ::ng-deep { + mat-select { + .mat-select-value { + max-width: 200px; .mat-select-value-text { font-size: 18px; font-weight: 700; diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.html b/ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.html new file mode 100644 index 0000000000..681ff8cd59 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.html @@ -0,0 +1,153 @@ + +
+ +

dashboard.manage-states

+ + +
+ + +
+
+
+
+
+ +
+ dashboard.states + + + +
+
+ +
+ + +   + + + +
+
+
+ + + {{ 'dashboard.state-name' | translate }} + + {{ state.name }} + + + + {{ 'dashboard.state-id' | translate }} + + {{ state.id }} + + + + {{ 'dashboard.is-root-state' | translate }} + + {{state.root ? 'check_box' : 'check_box_outline_blank'}} + + + + + + +
+ + +
+
+
+ + +
+ {{ 'dashboard.no-states-text' }} +
+ + +
+
+
+
+
+
+ + + +
+
diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.models.ts b/ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.models.ts new file mode 100644 index 0000000000..c33b85103a --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.models.ts @@ -0,0 +1,100 @@ +/// +/// Copyright © 2016-2019 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 { DashboardState } from '@shared/models/dashboard.models'; +import { CollectionViewer, DataSource } from '@angular/cdk/typings/collections'; +import { WidgetActionDescriptorInfo } from '@home/components/widget/action/manage-widget-actions.component.models'; +import { BehaviorSubject, Observable, of, ReplaySubject } from 'rxjs'; +import { emptyPageData, PageData } from '@shared/models/page/page-data'; +import { PageLink } from '@shared/models/page/page-link'; +import { catchError, map, publishReplay, refCount } from 'rxjs/operators'; + +export interface DashboardStateInfo extends DashboardState { + id: string; +} + +export class DashboardStatesDatasource implements DataSource { + + private statesSubject = new BehaviorSubject([]); + private pageDataSubject = new BehaviorSubject>(emptyPageData()); + + public pageData$ = this.pageDataSubject.asObservable(); + + private allStates: Observable>; + + constructor(private states: {[id: string]: DashboardState }) { + } + + connect(collectionViewer: CollectionViewer): Observable> { + return this.statesSubject.asObservable(); + } + + disconnect(collectionViewer: CollectionViewer): void { + this.statesSubject.complete(); + this.pageDataSubject.complete(); + } + + loadStates(pageLink: PageLink, reload: boolean = false): Observable> { + if (reload) { + this.allStates = null; + } + const result = new ReplaySubject>(); + this.fetchStates(pageLink).pipe( + catchError(() => of(emptyPageData())), + ).subscribe( + (pageData) => { + this.statesSubject.next(pageData.data); + this.pageDataSubject.next(pageData); + result.next(pageData); + } + ); + return result; + } + + fetchStates(pageLink: PageLink): Observable> { + return this.getAllStates().pipe( + map((data) => pageLink.filterData(data)) + ); + } + + getAllStates(): Observable> { + if (!this.allStates) { + const states: DashboardStateInfo[] = []; + for (const id of Object.keys(this.states)) { + const state = this.states[id]; + states.push({id, ...state}); + } + this.allStates = of(states).pipe( + publishReplay(1), + refCount() + ); + } + return this.allStates; + } + + isEmpty(): Observable { + return this.statesSubject.pipe( + map((states) => !states.length) + ); + } + + total(): Observable { + return this.pageDataSubject.pipe( + map((pageData) => pageData.totalElements) + ); + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.scss b/ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.scss new file mode 100644 index 0000000000..68cb50ccc0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.scss @@ -0,0 +1,45 @@ +/** + * Copyright © 2016-2019 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 { + .manage-dashboard-states { + .tb-entity-table { + .tb-entity-table-content { + width: 100%; + height: 100%; + background: #fff; + + .tb-entity-table-title { + padding-right: 20px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .table-container { + overflow: auto; + } + } + } + } +} + +:host ::ng-deep { + .manage-dashboard-states { + .mat-sort-header-sorted .mat-sort-header-arrow { + opacity: 1 !important; + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.ts new file mode 100644 index 0000000000..b20b47c662 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.ts @@ -0,0 +1,244 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { AfterViewInit, Component, ElementRef, Inject, OnInit, SkipSelf, ViewChild } from '@angular/core'; +import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm } from '@angular/forms'; +import { Router } from '@angular/router'; +import { DialogComponent } from '@app/shared/components/dialog.component'; +import { DashboardState } from '@app/shared/models/dashboard.models'; +import { MatDialog } from '@angular/material/dialog'; +import { PageLink } from '@shared/models/page/page-link'; +import { + WidgetActionDescriptorInfo, + WidgetActionsDatasource +} from '@home/components/widget/action/manage-widget-actions.component.models'; +import { + DashboardStateInfo, + DashboardStatesDatasource +} from '@home/pages/dashboard/states/manage-dashboard-states-dialog.component.models'; +import { Direction, SortOrder } from '@shared/models/page/sort-order'; +import { MatPaginator } from '@angular/material/paginator'; +import { MatSort } from '@angular/material/sort'; +import { fromEvent, merge } from 'rxjs'; +import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators'; +import { TranslateService } from '@ngx-translate/core'; +import { DialogService } from '@core/services/dialog.service'; +import { + WidgetActionDialogComponent, + WidgetActionDialogData +} from '@home/components/widget/action/widget-action-dialog.component'; +import { deepClone } from '@core/utils'; +import { + DashboardStateDialogComponent, + DashboardStateDialogData +} from '@home/pages/dashboard/states/dashboard-state-dialog.component'; + +export interface ManageDashboardStatesDialogData { + states: {[id: string]: DashboardState }; +} + +@Component({ + selector: 'tb-manage-dashboard-states-dialog', + templateUrl: './manage-dashboard-states-dialog.component.html', + providers: [{provide: ErrorStateMatcher, useExisting: ManageDashboardStatesDialogComponent}], + styleUrls: ['./manage-dashboard-states-dialog.component.scss'] +}) +export class ManageDashboardStatesDialogComponent extends + DialogComponent + implements OnInit, ErrorStateMatcher, AfterViewInit { + + statesFormGroup: FormGroup; + + states: {[id: string]: DashboardState }; + + displayedColumns: string[]; + pageLink: PageLink; + textSearchMode = false; + dataSource: DashboardStatesDatasource; + + submitted = false; + + @ViewChild('searchInput', {static: false}) searchInputField: ElementRef; + + @ViewChild(MatPaginator, {static: false}) paginator: MatPaginator; + @ViewChild(MatSort, {static: false}) sort: MatSort; + + constructor(protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) public data: ManageDashboardStatesDialogData, + @SkipSelf() private errorStateMatcher: ErrorStateMatcher, + public dialogRef: MatDialogRef, + private fb: FormBuilder, + private translate: TranslateService, + private dialogs: DialogService, + private dialog: MatDialog) { + super(store, router, dialogRef); + + this.states = this.data.states; + this.statesFormGroup = this.fb.group({}); + + const sortOrder: SortOrder = { property: 'name', direction: Direction.ASC }; + this.pageLink = new PageLink(5, 0, null, sortOrder); + this.displayedColumns = ['name', 'id', 'root', 'actions']; + this.dataSource = new DashboardStatesDatasource(this.states); + } + + ngOnInit(): void { + this.dataSource.loadStates(this.pageLink); + } + + ngAfterViewInit() { + fromEvent(this.searchInputField.nativeElement, 'keyup') + .pipe( + debounceTime(150), + distinctUntilChanged(), + tap(() => { + this.paginator.pageIndex = 0; + this.updateData(); + }) + ) + .subscribe(); + + this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0); + + merge(this.sort.sortChange, this.paginator.page) + .pipe( + tap(() => this.updateData()) + ) + .subscribe(); + } + + updateData(reload: boolean = false) { + this.pageLink.page = this.paginator.pageIndex; + this.pageLink.pageSize = this.paginator.pageSize; + this.pageLink.sortOrder.property = this.sort.active; + this.pageLink.sortOrder.direction = Direction[this.sort.direction.toUpperCase()]; + this.dataSource.loadStates(this.pageLink, reload); + } + + addState($event: Event) { + this.openStateDialog($event); + } + + editState($event: Event, state: DashboardStateInfo) { + this.openStateDialog($event, state); + } + + deleteState($event: Event, state: DashboardStateInfo) { + if ($event) { + $event.stopPropagation(); + } + const title = this.translate.instant('dashboard.delete-state-title'); + const content = this.translate.instant('dashboard.delete-state-text', {stateName: state.name}); + this.dialogs.confirm(title, content, this.translate.instant('action.no'), + this.translate.instant('action.yes')).subscribe( + (res) => { + if (res) { + delete this.states[state.id]; + this.onStatesUpdated(); + } + } + ); + } + + enterFilterMode() { + this.textSearchMode = true; + this.pageLink.textSearch = ''; + setTimeout(() => { + this.searchInputField.nativeElement.focus(); + this.searchInputField.nativeElement.setSelectionRange(0, 0); + }, 10); + } + + exitFilterMode() { + this.textSearchMode = false; + this.pageLink.textSearch = null; + this.paginator.pageIndex = 0; + this.updateData(); + } + + openStateDialog($event: Event, state: DashboardStateInfo = null) { + if ($event) { + $event.stopPropagation(); + } + const isAdd = state === null; + let prevStateId = null; + if (!isAdd) { + prevStateId = state.id; + } + this.dialog.open(DashboardStateDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + isAdd, + states: this.states, + state: deepClone(state) + } + }).afterClosed().subscribe( + (res) => { + if (res) { + this.saveState(res, prevStateId); + } + } + ); + } + + saveState(state: DashboardStateInfo, prevStateId: string) { + const newState: DashboardState = { + name: state.name, + root: state.root, + layouts: state.layouts + }; + if (prevStateId) { + this.states[prevStateId] = newState; + } else { + this.states[state.id] = newState; + } + if (state.root) { + for (const id of Object.keys(this.states)) { + const otherState = this.states[id]; + if (id !== state.id) { + otherState.root = false; + } + } + } + this.onStatesUpdated(); + } + + private onStatesUpdated() { + this.statesFormGroup.markAsDirty(); + this.updateData(true); + } + + isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { + const originalErrorState = this.errorStateMatcher.isErrorState(control, form); + const customErrorState = !!(control && control.invalid && this.submitted); + return originalErrorState || customErrorState; + } + + cancel(): void { + this.dialogRef.close(null); + } + + save(): void { + this.submitted = true; + this.dialogRef.close(this.states); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/state-controller.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/states/state-controller.component.ts index 4ef1e83d83..cfe3e9bf8f 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/states/state-controller.component.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/state-controller.component.ts @@ -84,6 +84,8 @@ export abstract class StateControllerComponent implements IStateControllerCompon currentState: string; + currentUrl: string; + private rxSubscriptions = new Array(); private inited = false; @@ -94,12 +96,16 @@ export abstract class StateControllerComponent implements IStateControllerCompon } ngOnInit(): void { + this.currentUrl = this.router.url.split('?')[0]; this.rxSubscriptions.push(this.route.queryParamMap.subscribe((paramMap) => { - const newState = paramMap.get('state'); - if (this.currentState !== newState) { - this.currentState = newState; - if (this.inited) { - this.onStateChanged(); + const newUrl = this.router.url.split('?')[0]; + if (this.currentUrl === newUrl) { + const newState = paramMap.get('state'); + if (this.currentState !== newState) { + this.currentState = newState; + if (this.inited) { + this.onStateChanged(); + } } } })); 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 320a55def2..9f85270dfd 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -557,6 +557,7 @@ "edit-state": "Edit dashboard state", "delete-state": "Delete dashboard state", "add-state": "Add dashboard state", + "no-states-text": "No states found", "state": "Dashboard state", "state-name": "Name", "state-name-required": "Dashboard state name is required.", From 8ee3f0bf7e01c8269389b331a54ae25992079aa8 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 8 Nov 2019 14:17:15 +0200 Subject: [PATCH 049/133] Add Import/Export service. --- ui-ngx/src/app/core/http/entity.service.ts | 18 + .../app/core/services/item-buffer.service.ts | 7 +- .../alias/entity-aliases-dialog.component.ts | 13 +- .../home/components/home-components.module.ts | 14 +- .../import-dialog.component.html | 60 +++ .../import-export/import-dialog.component.ts | 107 ++++ .../import-export/import-export.models.ts | 23 + .../import-export/import-export.service.ts | 457 ++++++++++++++++++ .../dashboard/dashboard-page.component.ts | 20 +- .../dashboards-table-config.resolver.ts | 17 +- .../components/file-input.component.html | 45 ++ .../components/file-input.component.scss | 80 +++ .../shared/components/file-input.component.ts | 150 ++++++ .../components/image-input.component.html | 2 +- ui-ngx/src/app/shared/models/alias.models.ts | 7 +- ui-ngx/src/app/shared/shared.module.ts | 3 + 16 files changed, 991 insertions(+), 32 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/import-export/import-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/components/import-export/import-dialog.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/import-export/import-export.models.ts create mode 100644 ui-ngx/src/app/modules/home/components/import-export/import-export.service.ts create mode 100644 ui-ngx/src/app/shared/components/file-input.component.html create mode 100644 ui-ngx/src/app/shared/components/file-input.component.scss create mode 100644 ui-ngx/src/app/shared/components/file-input.component.ts diff --git a/ui-ngx/src/app/core/http/entity.service.ts b/ui-ngx/src/app/core/http/entity.service.ts index b8a0e200dc..644bede83c 100644 --- a/ui-ngx/src/app/core/http/entity.service.ts +++ b/ui-ngx/src/app/core/http/entity.service.ts @@ -782,6 +782,24 @@ export class EntityService { } } + public checkEntityAlias(entityAlias: EntityAlias): Observable { + return this.resolveAliasFilter(entityAlias.filter, null, 1, true).pipe( + map((result) => { + if (result.stateEntity) { + return true; + } else { + const entities = result.entities; + if (entities && entities.length) { + return true; + } else { + return false; + } + } + }), + catchError(err => of(false)) + ); + } + private entitiesToEntitiesInfo(entities: Array>): Array { const entitiesInfo = []; if (entities) { diff --git a/ui-ngx/src/app/core/services/item-buffer.service.ts b/ui-ngx/src/app/core/services/item-buffer.service.ts index a7dd4db778..2c1e041176 100644 --- a/ui-ngx/src/app/core/services/item-buffer.service.ts +++ b/ui-ngx/src/app/core/services/item-buffer.service.ts @@ -16,7 +16,7 @@ import { Injectable } from '@angular/core'; import { Dashboard, DashboardLayoutId } from '@app/shared/models/dashboard.models'; -import { EntityAlias, EntityAliasFilter, EntityAliases, EntityAliasInfo } from '@shared/models/alias.models'; +import { EntityAlias, EntityAliasFilter, EntityAliases, EntityAliasInfo, AliasesInfo } from '@shared/models/alias.models'; import { DatasourceType, Widget, WidgetPosition, WidgetSize } from '@shared/models/widget.models'; import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; import { deepClone } from '@core/utils'; @@ -29,11 +29,6 @@ const WIDGET_ITEM = 'widget_item'; const WIDGET_REFERENCE = 'widget_reference'; const RULE_NODES = 'rule_nodes'; -export interface AliasesInfo { - datasourceAliases: {[datasourceIndex: number]: EntityAliasInfo}; - targetDeviceAliases: {[targetDeviceAliasIndex: number]: EntityAliasInfo}; -} - export interface WidgetItem { widget: Widget; aliasesInfo: AliasesInfo; diff --git a/ui-ngx/src/app/modules/home/components/alias/entity-aliases-dialog.component.ts b/ui-ngx/src/app/modules/home/components/alias/entity-aliases-dialog.component.ts index 305b35e1f4..095435bfa3 100644 --- a/ui-ngx/src/app/modules/home/components/alias/entity-aliases-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/alias/entity-aliases-dialog.component.ts @@ -37,14 +37,14 @@ import { UtilsService } from '@core/services/utils.service'; import { TranslateService } from '@ngx-translate/core'; import { ActionNotificationShow } from '@core/notification/notification.actions'; import { DialogService } from '@core/services/dialog.service'; -import { deepClone } from '@core/utils'; +import { deepClone, isUndefined } from '@core/utils'; import { MatDialog } from '@angular/material/dialog'; import { EntityAliasDialogComponent, EntityAliasDialogData } from './entity-alias-dialog.component'; export interface EntityAliasesDialogData { entityAliases: EntityAliases; widgets: Array; - isSingleEntityAlias: boolean; + isSingleEntityAlias?: boolean; isSingleWidget?: boolean; allowedEntityTypes?: Array; disableAdd?: boolean; @@ -125,14 +125,13 @@ export class EntityAliasesDialogComponent extends DialogComponent = []; for (const aliasId of Object.keys(this.data.entityAliases)) { const entityAlias = this.data.entityAliases[aliasId]; - let filter = entityAlias.filter; - if (!filter) { - filter = { + if (!entityAlias.filter) { + entityAlias.filter = { resolveMultiple: false }; } - if (!filter.resolveMultiple) { - filter.resolveMultiple = false; + if (isUndefined(entityAlias.filter.resolveMultiple)) { + entityAlias.filter.resolveMultiple = false; } entityAliasControls.push(this.createEntityAliasFormControl(aliasId, entityAlias)); } diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index 3409a20a3c..85e708b6c0 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -58,6 +58,8 @@ import { CustomActionPrettyResourcesTabsComponent } from './widget/action/custom import { CustomActionPrettyEditorComponent } from './widget/action/custom-action-pretty-editor.component'; import { CustomDialogService } from './widget/dialog/custom-dialog.service'; import { CustomDialogContainerComponent } from './widget/dialog/custom-dialog-container.component'; +import { ImportExportService } from './import-export/import-export.service'; +import { ImportDialogComponent } from './import-export/import-dialog.component'; @NgModule({ entryComponents: [ @@ -75,7 +77,8 @@ import { CustomDialogContainerComponent } from './widget/dialog/custom-dialog-co DataKeyConfigDialogComponent, LegendConfigPanelComponent, WidgetActionDialogComponent, - CustomDialogContainerComponent + CustomDialogContainerComponent, + ImportDialogComponent ], declarations: [ @@ -117,7 +120,8 @@ import { CustomDialogContainerComponent } from './widget/dialog/custom-dialog-co WidgetActionDialogComponent, CustomActionPrettyResourcesTabsComponent, CustomActionPrettyEditorComponent, - CustomDialogContainerComponent + CustomDialogContainerComponent, + ImportDialogComponent ], imports: [ CommonModule, @@ -154,11 +158,13 @@ import { CustomDialogContainerComponent } from './widget/dialog/custom-dialog-co WidgetActionDialogComponent, CustomActionPrettyResourcesTabsComponent, CustomActionPrettyEditorComponent, - CustomDialogContainerComponent + CustomDialogContainerComponent, + ImportDialogComponent ], providers: [ WidgetComponentService, - CustomDialogService + CustomDialogService, + ImportExportService ] }) export class HomeComponentsModule { } diff --git a/ui-ngx/src/app/modules/home/components/import-export/import-dialog.component.html b/ui-ngx/src/app/modules/home/components/import-export/import-dialog.component.html new file mode 100644 index 0000000000..404ca24792 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/import-export/import-dialog.component.html @@ -0,0 +1,60 @@ + +
+ +

{{ importTitle }}

+ + +
+ + +
+
+
+ + +
+
+
+
+ + + +
+
diff --git a/ui-ngx/src/app/modules/home/components/import-export/import-dialog.component.ts b/ui-ngx/src/app/modules/home/components/import-export/import-dialog.component.ts new file mode 100644 index 0000000000..1b15984776 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/import-export/import-dialog.component.ts @@ -0,0 +1,107 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Inject, OnInit, SkipSelf } from '@angular/core'; +import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { + AbstractControl, + FormArray, + FormBuilder, + FormControl, + FormGroup, + FormGroupDirective, + NgForm, Validators, ValidatorFn +} from '@angular/forms'; +import { Router } from '@angular/router'; +import { DialogComponent } from '@app/shared/components/dialog.component'; +import { AttributeData } from '@shared/models/telemetry/telemetry.models'; +import { EntityAlias, EntityAliases, EntityAliasFilter } from '@shared/models/alias.models'; +import { DatasourceType, Widget, widgetType } from '@shared/models/widget.models'; +import { AliasEntityType, EntityType } from '@shared/models/entity-type.models'; +import { UtilsService } from '@core/services/utils.service'; +import { TranslateService } from '@ngx-translate/core'; +import { ActionNotificationShow } from '@core/notification/notification.actions'; +import { DialogService } from '@core/services/dialog.service'; +import { EntityService } from '@core/http/entity.service'; +import { Observable, of } from 'rxjs'; + +export interface ImportDialogData { + importTitle: string; + importFileLabel: string; +} + +@Component({ + selector: 'tb-import-dialog', + templateUrl: './import-dialog.component.html', + providers: [{provide: ErrorStateMatcher, useExisting: ImportDialogComponent}], + styleUrls: [] +}) +export class ImportDialogComponent extends DialogComponent + implements OnInit, ErrorStateMatcher { + + importTitle: string; + importFileLabel: string; + + importFormGroup: FormGroup; + + submitted = false; + + constructor(protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) public data: ImportDialogData, + @SkipSelf() private errorStateMatcher: ErrorStateMatcher, + public dialogRef: MatDialogRef, + private fb: FormBuilder) { + super(store, router, dialogRef); + this.importTitle = data.importTitle; + this.importFileLabel = data.importFileLabel; + + this.importFormGroup = this.fb.group({ + jsonContent: [null, [Validators.required]] + }); + } + + ngOnInit(): void { + } + + isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { + const originalErrorState = this.errorStateMatcher.isErrorState(control, form); + const customErrorState = !!(control && control.invalid && this.submitted); + return originalErrorState || customErrorState; + } + + loadDataFromJsonContent(content: string): any { + try { + const importData = JSON.parse(content); + return importData; + } catch (err) { + this.store.dispatch(new ActionNotificationShow({message: err.message, type: 'error'})); + return null; + } + } + + cancel(): void { + this.dialogRef.close(null); + } + + importFromJson(): void { + this.submitted = true; + const importData = this.importFormGroup.get('jsonContent').value; + this.dialogRef.close(importData); + } +} diff --git a/ui-ngx/src/app/modules/home/components/import-export/import-export.models.ts b/ui-ngx/src/app/modules/home/components/import-export/import-export.models.ts new file mode 100644 index 0000000000..24e152cb29 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/import-export/import-export.models.ts @@ -0,0 +1,23 @@ +/// +/// Copyright © 2016-2019 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 { Widget } from '@app/shared/models/widget.models'; +import { DashboardLayoutId } from '@shared/models/dashboard.models'; + +export interface ImportWidgetResult { + widget: Widget; + layoutId: DashboardLayoutId; +} diff --git a/ui-ngx/src/app/modules/home/components/import-export/import-export.service.ts b/ui-ngx/src/app/modules/home/components/import-export/import-export.service.ts new file mode 100644 index 0000000000..59905423f2 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/import-export/import-export.service.ts @@ -0,0 +1,457 @@ +/// +/// Copyright © 2016-2019 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 { Inject, Injectable } from '@angular/core'; +import { DashboardService } from '@core/http/dashboard.service'; +import { TranslateService } from '@ngx-translate/core'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { ActionNotificationShow } from '@core/notification/notification.actions'; +import { Dashboard, DashboardLayoutId } from '@shared/models/dashboard.models'; +import { deepClone, isDefined, isObject, isUndefined } from '@core/utils'; +import { WINDOW } from '@core/services/window.service'; +import { DOCUMENT } from '@angular/common'; +import { + AliasesInfo, + AliasFilterType, + EntityAlias, + EntityAliases, + EntityAliasFilter, + EntityAliasInfo +} from '@shared/models/alias.models'; +import { MatDialog } from '@angular/material/dialog'; +import { ImportDialogComponent, ImportDialogData } from '@home/components/import-export/import-dialog.component'; +import { forkJoin, Observable, of } from 'rxjs'; +import { catchError, map, mergeMap } from 'rxjs/operators'; +import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; +import { EntityService } from '@core/http/entity.service'; +import { Widget, WidgetSize } from '@shared/models/widget.models'; +import { + EntityAliasesDialogComponent, + EntityAliasesDialogData +} from '@home/components/alias/entity-aliases-dialog.component'; +import { ItemBufferService, WidgetItem } from '@core/services/item-buffer.service'; +import { ImportWidgetResult } from './import-export.models'; +import { EntityType } from '@shared/models/entity-type.models'; +import { UtilsService } from '@core/services/utils.service'; + +@Injectable() +export class ImportExportService { + + constructor(@Inject(WINDOW) private window: Window, + @Inject(DOCUMENT) private document: Document, + private store: Store, + private translate: TranslateService, + private dashboardService: DashboardService, + private dashboardUtils: DashboardUtilsService, + private entityService: EntityService, + private utils: UtilsService, + private itembuffer: ItemBufferService, + private dialog: MatDialog) { + + } + + public exportDashboard(dashboardId: string) { + this.dashboardService.getDashboard(dashboardId).subscribe( + (dashboard) => { + let name = dashboard.title; + name = name.toLowerCase().replace(/\W/g, '_'); + this.exportToPc(this.prepareDashboardExport(dashboard), name + '.json'); + }, + (e) => { + let message = e; + if (!message) { + message = this.translate.instant('error.unknown-error'); + } + this.store.dispatch(new ActionNotificationShow( + {message: this.translate.instant('dashboard.export-failed-error', {error: message}), + type: 'error'})); + } + ); + } + + public importDashboard(): Observable { + return this.openImportDialog('dashboard.import', 'dashboard.dashboard-file').pipe( + mergeMap((dashboard: Dashboard) => { + if (!this.validateImportedDashboard(dashboard)) { + this.store.dispatch(new ActionNotificationShow( + {message: this.translate.instant('dashboard.invalid-dashboard-file-error'), + type: 'error'})); + throw new Error('Invalid dashboard file'); + } else { + dashboard = this.dashboardUtils.validateAndUpdateDashboard(dashboard); + let aliasIds = null; + const entityAliases = dashboard.configuration.entityAliases; + if (entityAliases) { + aliasIds = Object.keys(entityAliases); + } + if (aliasIds && aliasIds.length > 0) { + return this.processEntityAliases(entityAliases, aliasIds).pipe( + mergeMap((missingEntityAliases) => { + if (Object.keys(missingEntityAliases).length > 0) { + return this.editMissingAliases(this.dashboardUtils.getWidgetsArray(dashboard), + false, 'dashboard.dashboard-import-missing-aliases-title', + missingEntityAliases).pipe( + mergeMap((updatedEntityAliases) => { + for (const aliasId of Object.keys(updatedEntityAliases)) { + entityAliases[aliasId] = updatedEntityAliases[aliasId]; + } + return this.saveImportedDashboard(dashboard); + }) + ); + } else { + return this.saveImportedDashboard(dashboard); + } + } + )); + } else { + return this.saveImportedDashboard(dashboard); + } + } + }), + catchError((err) => { + return of(null); + }) + ); + } + + public exportWidget(dashboard: Dashboard, sourceState: string, sourceLayout: DashboardLayoutId, widget: Widget) { + const widgetItem = this.itembuffer.prepareWidgetItem(dashboard, sourceState, sourceLayout, widget); + let name = widgetItem.widget.config.title; + name = name.toLowerCase().replace(/\W/g, '_'); + this.exportToPc(this.prepareExport(widgetItem), name + '.json'); + } + + public importWidget(dashboard: Dashboard, targetState: string, + targetLayoutFunction: () => Observable, + onAliasesUpdateFunction: () => void): Observable { + return this.openImportDialog('dashboard.import-widget', 'dashboard.widget-file').pipe( + mergeMap((widgetItem: WidgetItem) => { + if (!this.validateImportedWidget(widgetItem)) { + this.store.dispatch(new ActionNotificationShow( + {message: this.translate.instant('dashboard.invalid-widget-file-error'), + type: 'error'})); + throw new Error('Invalid widget file'); + } else { + let widget = widgetItem.widget; + widget = this.dashboardUtils.validateAndUpdateWidget(widget); + const aliasesInfo = this.prepareAliasesInfo(widgetItem.aliasesInfo); + const originalColumns = widgetItem.originalColumns; + const originalSize = widgetItem.originalSize; + + const datasourceAliases = aliasesInfo.datasourceAliases; + const targetDeviceAliases = aliasesInfo.targetDeviceAliases; + if (datasourceAliases || targetDeviceAliases) { + const entityAliases: EntityAliases = {}; + const datasourceAliasesMap: {[aliasId: string]: number} = {}; + const targetDeviceAliasesMap: {[aliasId: string]: number} = {}; + let aliasId: string; + let datasourceIndex: number; + if (datasourceAliases) { + for (const strIndex of Object.keys(datasourceAliases)) { + datasourceIndex = Number(strIndex); + aliasId = this.utils.guid(); + datasourceAliasesMap[aliasId] = datasourceIndex; + entityAliases[aliasId] = {id: aliasId, ...datasourceAliases[datasourceIndex]}; + } + } + if (targetDeviceAliases) { + for (const strIndex of Object.keys(targetDeviceAliases)) { + datasourceIndex = Number(strIndex); + aliasId = this.utils.guid(); + targetDeviceAliasesMap[aliasId] = datasourceIndex; + entityAliases[aliasId] = {id: aliasId, ...targetDeviceAliases[datasourceIndex]}; + } + } + const aliasIds = Object.keys(entityAliases); + if (aliasIds.length > 0) { + return this.processEntityAliases(entityAliases, aliasIds).pipe( + mergeMap((missingEntityAliases) => { + if (Object.keys(missingEntityAliases).length > 0) { + return this.editMissingAliases([widget], + false, 'dashboard.widget-import-missing-aliases-title', + missingEntityAliases).pipe( + mergeMap((updatedEntityAliases) => { + for (const id of Object.keys(updatedEntityAliases)) { + const entityAlias = updatedEntityAliases[id]; + let index; + if (isDefined(datasourceAliasesMap[id])) { + index = datasourceAliasesMap[id]; + datasourceAliases[index] = entityAlias; + } else if (isDefined(targetDeviceAliasesMap[id])) { + index = targetDeviceAliasesMap[id]; + targetDeviceAliases[index] = entityAlias; + } + } + return this.addImportedWidget(dashboard, targetState, targetLayoutFunction, widget, + aliasesInfo, onAliasesUpdateFunction, originalColumns, originalSize); + } + )); + } else { + return this.addImportedWidget(dashboard, targetState, targetLayoutFunction, widget, + aliasesInfo, onAliasesUpdateFunction, originalColumns, originalSize); + } + } + ) + ); + } else { + return this.addImportedWidget(dashboard, targetState, targetLayoutFunction, widget, + aliasesInfo, onAliasesUpdateFunction, originalColumns, originalSize); + } + } else { + return this.addImportedWidget(dashboard, targetState, targetLayoutFunction, widget, + aliasesInfo, onAliasesUpdateFunction, originalColumns, originalSize); + } + } + }), + catchError((err) => { + return of(null); + }) + ); + } + + private validateImportedDashboard(dashboard: Dashboard): boolean { + if (isUndefined(dashboard.title) || isUndefined(dashboard.configuration)) { + return false; + } + return true; + } + + private validateImportedWidget(widgetItem: WidgetItem): boolean { + if (isUndefined(widgetItem.widget) + || isUndefined(widgetItem.aliasesInfo) + || isUndefined(widgetItem.originalColumns)) { + return false; + } + const widget = widgetItem.widget; + if (isUndefined(widget.isSystemType) || + isUndefined(widget.bundleAlias) || + isUndefined(widget.typeAlias) || + isUndefined(widget.type)) { + return false; + } + return true; + } + + private saveImportedDashboard(dashboard: Dashboard): Observable { + return this.dashboardService.saveDashboard(dashboard); + } + + private addImportedWidget(dashboard: Dashboard, targetState: string, + targetLayoutFunction: () => Observable, + widget: Widget, aliasesInfo: AliasesInfo, onAliasesUpdateFunction: () => void, + originalColumns: number, originalSize: WidgetSize): Observable { + return targetLayoutFunction().pipe( + mergeMap((targetLayout) => { + return this.itembuffer.addWidgetToDashboard(dashboard, targetState, targetLayout, + widget, aliasesInfo, onAliasesUpdateFunction, originalColumns, originalSize, -1, -1).pipe( + map(() => ({widget, layoutId: targetLayout} as ImportWidgetResult)) + ); + } + )); + } + + private processEntityAliases(entityAliases: EntityAliases, aliasIds: string[]): Observable { + const tasks: Observable[] = []; + for (const aliasId of aliasIds) { + const entityAlias = entityAliases[aliasId]; + tasks.push( + this.entityService.checkEntityAlias(entityAlias).pipe( + map((result) => { + if (!result) { + const missingEntityAlias = deepClone(entityAlias); + missingEntityAlias.filter = null; + return missingEntityAlias; + } + return null; + } + ) + ) + ); + } + return forkJoin(tasks).pipe( + map((missingAliasesArray) => { + missingAliasesArray = missingAliasesArray.filter(alias => alias !== null); + const missingEntityAliases: EntityAliases = {}; + for (const missingAlias of missingAliasesArray) { + missingEntityAliases[missingAlias.id] = missingAlias; + } + return missingEntityAliases; + } + ) + ); + } + + private editMissingAliases(widgets: Array, isSingleWidget: boolean, + customTitle: string, missingEntityAliases: EntityAliases): Observable { + return this.dialog.open(EntityAliasesDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + entityAliases: missingEntityAliases, + widgets, + customTitle, + isSingleWidget, + disableAdd: true + } + }).afterClosed().pipe( + map((updatedEntityAliases) => { + if (updatedEntityAliases) { + return updatedEntityAliases; + } else { + throw new Error('Unable to resolve missing entity aliases!'); + } + } + )); + } + + private prepareAliasesInfo(aliasesInfo: AliasesInfo): AliasesInfo { + const datasourceAliases = aliasesInfo.datasourceAliases; + const targetDeviceAliases = aliasesInfo.targetDeviceAliases; + if (datasourceAliases || targetDeviceAliases) { + if (datasourceAliases) { + for (const strIndex of Object.keys(datasourceAliases)) { + const datasourceIndex = Number(strIndex); + datasourceAliases[datasourceIndex] = this.prepareEntityAlias(datasourceAliases[datasourceIndex]); + } + } + if (targetDeviceAliases) { + for (const strIndex of Object.keys(targetDeviceAliases)) { + const datasourceIndex = Number(strIndex); + targetDeviceAliases[datasourceIndex] = this.prepareEntityAlias(targetDeviceAliases[datasourceIndex]); + } + } + } + return aliasesInfo; + } + + private prepareEntityAlias(aliasInfo: EntityAliasInfo): EntityAliasInfo { + let alias: string; + let filter: EntityAliasFilter; + if (aliasInfo.deviceId) { + alias = aliasInfo.aliasName; + filter = { + type: AliasFilterType.entityList, + entityType: EntityType.DEVICE, + entityList: [aliasInfo.deviceId], + resolveMultiple: false + }; + } else if (aliasInfo.deviceFilter) { + alias = aliasInfo.aliasName; + filter = { + type: aliasInfo.deviceFilter.useFilter ? AliasFilterType.entityName : AliasFilterType.entityList, + entityType: EntityType.DEVICE, + resolveMultiple: false + }; + if (filter.type === AliasFilterType.entityList) { + filter.entityList = aliasInfo.deviceFilter.deviceList; + } else { + filter.entityNameFilter = aliasInfo.deviceFilter.deviceNameFilter; + } + } else if (aliasInfo.entityFilter) { + alias = aliasInfo.aliasName; + filter = { + type: aliasInfo.entityFilter.useFilter ? AliasFilterType.entityName : AliasFilterType.entityList, + entityType: aliasInfo.entityType, + resolveMultiple: false + }; + if (filter.type === AliasFilterType.entityList) { + filter.entityList = aliasInfo.entityFilter.entityList; + } else { + filter.entityNameFilter = aliasInfo.entityFilter.entityNameFilter; + } + } else { + alias = aliasInfo.alias; + filter = aliasInfo.filter; + } + return { + alias, + filter + }; + } + + private openImportDialog(importTitle: string, importFileLabel: string): Observable { + return this.dialog.open(ImportDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + importTitle, + importFileLabel + } + }).afterClosed().pipe( + map((importedData) => { + if (importedData) { + return importedData; + } else { + throw new Error('No file selected!'); + } + } + )); + } + + private exportToPc(data: any, filename: string) { + if (!data) { + console.error('No data'); + return; + } + if (!filename) { + filename = 'download.json'; + } + if (isObject(data)) { + data = JSON.stringify(data, null, 2); + } + const blob = new Blob([data], {type: 'text/json'}); + if (this.window.navigator && this.window.navigator.msSaveOrOpenBlob) { + this.window.navigator.msSaveOrOpenBlob(blob, filename); + } else { + const e = this.document.createEvent('MouseEvents'); + const a = this.document.createElement('a'); + a.download = filename; + a.href = this.window.URL.createObjectURL(blob); + a.dataset.downloadurl = ['text/json', a.download, a.href].join(':'); + // @ts-ignore + e.initEvent('click', true, false, this.window, + 0, 0, 0, 0, 0, false, false, false, false, 0, null); + a.dispatchEvent(e); + } + } + + private prepareDashboardExport(dashboard: Dashboard): Dashboard { + dashboard = this.prepareExport(dashboard); + delete dashboard.assignedCustomers; + return dashboard; + } + + private prepareExport(data: any): any { + const exportedData = deepClone(data); + if (isDefined(exportedData.id)) { + delete exportedData.id; + } + if (isDefined(exportedData.createdTime)) { + delete exportedData.createdTime; + } + if (isDefined(exportedData.tenantId)) { + delete exportedData.tenantId; + } + if (isDefined(exportedData.customerId)) { + delete exportedData.customerId; + } + return exportedData; + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.ts index 29901bd857..bda1f6fa30 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.ts @@ -83,6 +83,7 @@ import { ManageDashboardStatesDialogComponent, ManageDashboardStatesDialogData } from '@home/pages/dashboard/states/manage-dashboard-states-dialog.component'; +import { ImportExportService } from '@home/components/import-export/import-export.service'; @Component({ selector: 'tb-dashboard-page', @@ -222,6 +223,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC private widgetComponentService: WidgetComponentService, private dashboardService: DashboardService, private itembuffer: ItemBufferService, + private importExport: ImportExportService, private fb: FormBuilder, private dialog: MatDialog, private translate: TranslateService, @@ -459,8 +461,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC if ($event) { $event.stopPropagation(); } - // TODO: - this.dialogService.todo(); + this.importExport.exportDashboard(this.currentDashboardId); } public openEntityAliases($event: Event) { @@ -569,8 +570,16 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC if ($event) { $event.stopPropagation(); } - // TODO: - this.dialogService.todo(); + this.importExport.importWidget(this.dashboard, this.dashboardCtx.state, + this.selectTargetLayout.bind(this), this.entityAliasesUpdated.bind(this)).subscribe( + (importData) => { + if (importData) { + const widget = importData.widget; + const layoutId = importData.layoutId; + this.layouts[layoutId].layoutCtx.widgets.addWidgetId(widget.id); + } + } + ); } public currentDashboardIdChanged(dashboardId: string) { @@ -915,8 +924,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC exportWidget($event: Event, layoutCtx: DashboardPageLayoutContext, widget: Widget) { $event.stopPropagation(); - // TODO: - this.dialogService.todo(); + this.importExport.exportWidget(this.dashboard, this.dashboardCtx.state, layoutCtx.id, widget); } widgetClicked($event: Event, layoutCtx: DashboardPageLayoutContext, widget: Widget) { diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts index 93335e7aa2..e7d2070d91 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts @@ -63,6 +63,7 @@ import { MakeDashboardPublicDialogData } from '@modules/home/pages/dashboard/make-dashboard-public-dialog.component'; import { DashboardTabsComponent } from '@home/pages/dashboard/dashboard-tabs.component'; +import { ImportExportService } from '@home/components/import-export/import-export.service'; @Injectable() export class DashboardsTableConfigResolver implements Resolve> { @@ -73,6 +74,7 @@ export class DashboardsTableConfigResolver implements Resolve { + if (dashboard) { + this.config.table.updateData(); + } + } + ); } exportDashboard($event: Event, dashboard: DashboardInfo) { if ($event) { $event.stopPropagation(); } - // TODO: - this.dialogService.todo(); + this.importExport.exportDashboard(dashboard.id.id); } addDashboardsToCustomer($event: Event) { diff --git a/ui-ngx/src/app/shared/components/file-input.component.html b/ui-ngx/src/app/shared/components/file-input.component.html new file mode 100644 index 0000000000..0446834e92 --- /dev/null +++ b/ui-ngx/src/app/shared/components/file-input.component.html @@ -0,0 +1,45 @@ + +
+ + +
+
+ +
+
+ + +
+
+
+
+
+
import.no-file
+
{{ fileName }}
+
diff --git a/ui-ngx/src/app/shared/components/file-input.component.scss b/ui-ngx/src/app/shared/components/file-input.component.scss new file mode 100644 index 0000000000..d2e2d4f3c1 --- /dev/null +++ b/ui-ngx/src/app/shared/components/file-input.component.scss @@ -0,0 +1,80 @@ +/** + * Copyright © 2016-2019 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"; + +$previewSize: 100px !default; + +:host { + + .tb-container { + margin-top: 0px; + label.tb-title { + display: block; + padding-bottom: 8px; + } + } + + .tb-file-select-container { + position: relative; + width: 100%; + height: $previewSize; + } + + .tb-file-preview { + width: auto; + max-width: $previewSize; + height: auto; + max-height: $previewSize; + } + + .tb-file-clear-container { + position: relative; + float: right; + width: 48px; + height: $previewSize; + } + + .tb-file-clear-btn { + position: absolute !important; + top: 50%; + transform: translate(0%, -50%) !important; + } + + .file-input { + display: none; + } + + .tb-flow-drop { + position: relative; + height: $previewSize; + overflow: hidden; + border: dashed 2px; + + label { + display: flex; + flex-direction: column; + justify-content: center; + width: 100%; + height: 100%; + font-size: 16px; + text-align: center; + + @media #{$mat-gt-sm} { + font-size: 24px; + } + } + } +} diff --git a/ui-ngx/src/app/shared/components/file-input.component.ts b/ui-ngx/src/app/shared/components/file-input.component.ts new file mode 100644 index 0000000000..c7275d6e46 --- /dev/null +++ b/ui-ngx/src/app/shared/components/file-input.component.ts @@ -0,0 +1,150 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { AfterViewInit, Component, forwardRef, Input, OnDestroy, ViewChild } from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { Subscription } from 'rxjs'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { FlowDirective } from '@flowjs/ngx-flow'; + +@Component({ + selector: 'tb-file-input', + templateUrl: './file-input.component.html', + styleUrls: ['./file-input.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => FileInputComponent), + multi: true + } + ] +}) +export class FileInputComponent extends PageComponent implements AfterViewInit, OnDestroy, ControlValueAccessor { + + @Input() + label: string; + + @Input() + accept = '*/*'; + + @Input() + allowedExtensions: string; + + @Input() + dropLabel: string; + + @Input() + contentConvertFunction: (content: string) => any; + + private requiredValue: boolean; + get required(): boolean { + return this.requiredValue; + } + @Input() + set required(value: boolean) { + const newVal = coerceBooleanProperty(value); + if (this.requiredValue !== newVal) { + this.requiredValue = newVal; + } + } + + @Input() + disabled: boolean; + + fileName: string; + fileContent: any; + + @ViewChild('flow', {static: true}) + flow: FlowDirective; + + autoUploadSubscription: Subscription; + + private propagateChange = null; + + constructor(protected store: Store) { + super(store); + } + + ngAfterViewInit() { + this.autoUploadSubscription = this.flow.events$.subscribe(event => { + if (event.type === 'fileAdded') { + const file = event.event[0] as flowjs.FlowFile; + if (this.filterFile(file)) { + const reader = new FileReader(); + reader.onload = (loadEvent) => { + if (typeof reader.result === 'string') { + const fileContent = reader.result; + if (fileContent && fileContent.length > 0) { + if (this.contentConvertFunction) { + this.fileContent = this.contentConvertFunction(fileContent); + } else { + this.fileContent = fileContent; + } + if (this.fileContent) { + this.fileName = file.name; + } else { + this.fileName = null; + } + this.updateModel(); + } + } + }; + reader.readAsText(file.file); + } + } + }); + } + + private filterFile(file: flowjs.FlowFile): boolean { + if (this.allowedExtensions) { + return this.allowedExtensions.split(',').indexOf(file.getExtension()) > -1; + } else { + return true; + } + } + + ngOnDestroy() { + this.autoUploadSubscription.unsubscribe(); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + writeValue(value: any): void { + this.fileName = null; + } + + private updateModel() { + this.propagateChange(this.fileContent); + } + + clearFile() { + this.fileName = null; + this.fileContent = null; + this.updateModel(); + } +} diff --git a/ui-ngx/src/app/shared/components/image-input.component.html b/ui-ngx/src/app/shared/components/image-input.component.html index 6b306552ba..ab49bf213c 100644 --- a/ui-ngx/src/app/shared/components/image-input.component.html +++ b/ui-ngx/src/app/shared/components/image-input.component.html @@ -18,7 +18,7 @@
+ [flowConfig]="{singleFile: true, allowDuplicateUploads: true}">
dashboard.no-image
diff --git a/ui-ngx/src/app/shared/models/alias.models.ts b/ui-ngx/src/app/shared/models/alias.models.ts index d6e356580a..419e01b9c0 100644 --- a/ui-ngx/src/app/shared/models/alias.models.ts +++ b/ui-ngx/src/app/shared/models/alias.models.ts @@ -135,11 +135,16 @@ export interface EntityAliasFilter extends EntityFilters { export interface EntityAliasInfo { alias: string; filter: EntityAliasFilter; + [key: string]: any; +} + +export interface AliasesInfo { + datasourceAliases: {[datasourceIndex: number]: EntityAliasInfo}; + targetDeviceAliases: {[targetDeviceAliasIndex: number]: EntityAliasInfo}; } export interface EntityAlias extends EntityAliasInfo { id: string; - [key: string]: any; } export interface EntityAliases { diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index 9495b3b339..d5d4bd73e4 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -111,6 +111,7 @@ import { JsonFormComponent } from './components/json-form/json-form.component'; import { MaterialIconsDialogComponent } from '@shared/components/dialog/material-icons-dialog.component'; import { MaterialIconSelectComponent } from '@shared/components/material-icon-select.component'; import { ImageInputComponent } from './components/image-input.component'; +import { FileInputComponent } from './components/file-input.component'; @NgModule({ providers: [ @@ -181,6 +182,7 @@ import { ImageInputComponent } from './components/image-input.component'; MaterialIconSelectComponent, JsonFormComponent, ImageInputComponent, + FileInputComponent, NospacePipe, MillisecondsToTimeStringPipe, EnumToArrayPipe, @@ -318,6 +320,7 @@ import { ImageInputComponent } from './components/image-input.component'; MaterialIconSelectComponent, JsonFormComponent, ImageInputComponent, + FileInputComponent, NospacePipe, MillisecondsToTimeStringPipe, EnumToArrayPipe, From 65b7c139cf31cfc6dd78af08ca509924d00d5579 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 8 Nov 2019 17:42:31 +0200 Subject: [PATCH 050/133] Widgets library import export support. --- .../json/system/widget_bundles/charts.json | 2 +- ui-ngx/src/app/core/http/widget.service.ts | 55 +++++- .../import-export/import-export.models.ts | 8 +- .../import-export/import-export.service.ts | 157 +++++++++++++++++- .../home/models/dashboard-component.models.ts | 2 +- .../widget/widget-library-routing.module.ts | 50 +----- .../pages/widget/widget-library.component.ts | 36 +++- .../widgets-bundles-table-config.resolver.ts | 17 +- ui-ngx/src/scss/mixins.scss | 4 +- 9 files changed, 253 insertions(+), 78 deletions(-) diff --git a/application/src/main/data/json/system/widget_bundles/charts.json b/application/src/main/data/json/system/widget_bundles/charts.json index 71e9fa7ace..9dcf90d45b 100644 --- a/application/src/main/data/json/system/widget_bundles/charts.json +++ b/application/src/main/data/json/system/widget_bundles/charts.json @@ -55,7 +55,7 @@ ], "templateHtml": "\n", "templateCss": "", - "controllerScript": "self.onInit = function() {\n var pieData = {\n labels: [],\n datasets: []\n };\n\n var dataset = {\n data: [],\n backgroundColor: [],\n borderColor: [],\n borderWidth: [],\n hoverBackgroundColor: []\n }\n \n var borderColor = self.ctx.settings.borderColor || '#fff';\n var borderWidth = angular.isDefined(self.ctx.settings.borderWidth) ? self.ctx.settings.borderWidth : 5;\n \n pieData.datasets.push(dataset);\n \n for (var i=0; i < self.ctx.data.length; i++) {\n var dataKey = self.ctx.data[i].dataKey;\n pieData.labels.push(dataKey.label);\n dataset.data.push(0);\n var hoverBackgroundColor = tinycolor(dataKey.color).lighten(15);\n dataset.backgroundColor.push(dataKey.color);\n dataset.borderColor.push(borderColor);\n dataset.borderWidth.push(borderWidth);\n dataset.hoverBackgroundColor.push(hoverBackgroundColor.toRgbString());\n }\n\n var options = {\n responsive: false,\n maintainAspectRatio: false,\n legend: {\n display: true,\n labels: {\n fontColor: '#666'\n }\n },\n tooltips: {\n callbacks: {\n label: function(tooltipItem, data) {\n var label = data.labels[tooltipItem.index];\n var value = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];\n var content = label + ': ' + value;\n var units = self.ctx.settings.units ? self.ctx.settings.units : self.ctx.units;\n if (units) {\n content += ' ' + units;\n } \n return content;\n }\n }\n }\n };\n\n if (self.ctx.settings.legend) {\n options.legend.display = self.ctx.settings.legend.display !== false;\n options.legend.labels.fontColor = self.ctx.settings.legend.labelsFontColor || '#666';\n }\n\n var ctx = $('#pieChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'doughnut',\n data: pieData,\n options: options\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.data.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData.data.length > 0) {\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = tvPair[1];\n self.ctx.chart.data.datasets[0].data[i] = parseFloat(value);\n }\n }\n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n self.ctx.chart.resize();\n}\n\n", + "controllerScript": "self.onInit = function() {\n var pieData = {\n labels: [],\n datasets: []\n };\n\n var dataset = {\n data: [],\n backgroundColor: [],\n borderColor: [],\n borderWidth: [],\n hoverBackgroundColor: []\n }\n \n var borderColor = self.ctx.settings.borderColor || '#fff';\n var borderWidth = typeof self.ctx.settings.borderWidth !== 'undefined' ? self.ctx.settings.borderWidth : 5;\n \n pieData.datasets.push(dataset);\n \n for (var i=0; i < self.ctx.data.length; i++) {\n var dataKey = self.ctx.data[i].dataKey;\n pieData.labels.push(dataKey.label);\n dataset.data.push(0);\n var hoverBackgroundColor = tinycolor(dataKey.color).lighten(15);\n dataset.backgroundColor.push(dataKey.color);\n dataset.borderColor.push(borderColor);\n dataset.borderWidth.push(borderWidth);\n dataset.hoverBackgroundColor.push(hoverBackgroundColor.toRgbString());\n }\n\n var options = {\n responsive: false,\n maintainAspectRatio: false,\n legend: {\n display: true,\n labels: {\n fontColor: '#666'\n }\n },\n tooltips: {\n callbacks: {\n label: function(tooltipItem, data) {\n var label = data.labels[tooltipItem.index];\n var value = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];\n var content = label + ': ' + value;\n var units = self.ctx.settings.units ? self.ctx.settings.units : self.ctx.units;\n if (units) {\n content += ' ' + units;\n } \n return content;\n }\n }\n }\n };\n\n if (self.ctx.settings.legend) {\n options.legend.display = self.ctx.settings.legend.display !== false;\n options.legend.labels.fontColor = self.ctx.settings.legend.labelsFontColor || '#666';\n }\n\n var ctx = $('#pieChart', self.ctx.$container);\n self.ctx.chart = new Chart(ctx, {\n type: 'doughnut',\n data: pieData,\n options: options\n });\n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.data.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData.data.length > 0) {\n var tvPair = cellData.data[cellData.data.length - 1];\n var value = tvPair[1];\n self.ctx.chart.data.datasets[0].data[i] = parseFloat(value);\n }\n }\n self.ctx.chart.update();\n}\n\nself.onResize = function() {\n self.ctx.chart.resize();\n}\n\n", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"borderWidth\": {\n \"title\": \"Border width\",\n \"type\": \"number\",\n \"default\": 5\n },\n \"borderColor\": {\n \"title\": \"Border color\",\n \"type\": \"string\",\n \"default\": \"#fff\"\n },\n \"legend\": {\n \"title\": \"Legend settings\",\n \"type\": \"object\",\n \"properties\": {\n \"display\": {\n \"title\": \"Display legend\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"labelsFontColor\": {\n \"title\": \"Labels font color\",\n \"type\": \"string\",\n \"default\": \"#666\"\n }\n }\n }\n },\n \"required\": []\n },\n \"form\": [\n \"borderWidth\", \n {\n \"key\": \"borderColor\",\n \"type\": \"color\"\n }, \n {\n \"key\": \"legend\",\n \"items\": [\n \"legend.display\",\n {\n \"key\": \"legend.labelsFontColor\",\n \"type\": \"color\"\n }\n ]\n }\n ]\n}", "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#26a69a\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#f57c00\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#afb42b\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#673ab7\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"borderWidth\":5,\"borderColor\":\"#fff\",\"legend\":{\"display\":true,\"labelsFontColor\":\"#666666\"}},\"title\":\"Doughnut - Chart.js\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}" diff --git a/ui-ngx/src/app/core/http/widget.service.ts b/ui-ngx/src/app/core/http/widget.service.ts index 306f3e6daa..a036511883 100644 --- a/ui-ngx/src/app/core/http/widget.service.ts +++ b/ui-ngx/src/app/core/http/widget.service.ts @@ -21,7 +21,7 @@ import { HttpClient } from '@angular/common/http'; import { PageLink } from '@shared/models/page/page-link'; import { PageData } from '@shared/models/page/page-data'; import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; -import { WidgetType, widgetType, WidgetTypeData, widgetTypesData } from '@shared/models/widget.models'; +import { Widget, WidgetType, widgetType, WidgetTypeData, widgetTypesData } from '@shared/models/widget.models'; import { UtilsService } from '@core/services/utils.service'; import { TranslateService } from '@ngx-translate/core'; import { ResourcesService } from '../services/resources.service'; @@ -119,6 +119,59 @@ export class WidgetService { defaultHttpOptions(ignoreLoading, ignoreErrors)); } + public loadBundleLibraryWidgets(bundleAlias: string, isSystem: boolean, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + return this.getBundleWidgetTypes(bundleAlias, isSystem, ignoreErrors, ignoreLoading).pipe( + map((types) => { + types = types.sort((a, b) => { + let result = widgetType[b.descriptor.type].localeCompare(widgetType[a.descriptor.type]); + if (result === 0) { + result = b.createdTime - a.createdTime; + } + return result; + }); + const widgetTypes = new Array(); + let top = 0; + const lastTop = [0, 0, 0]; + let col = 0; + let column = 0; + types.forEach((type) => { + const widgetTypeInfo = toWidgetInfo(type); + const sizeX = 8; + const sizeY = Math.floor(widgetTypeInfo.sizeY); + const widget: Widget = { + typeId: type.id, + isSystemType: isSystem, + bundleAlias, + typeAlias: widgetTypeInfo.alias, + type: widgetTypeInfo.type, + title: widgetTypeInfo.widgetName, + sizeX, + sizeY, + row: top, + col, + config: JSON.parse(widgetTypeInfo.defaultConfig) + }; + + widget.config.title = widgetTypeInfo.widgetName; + + widgetTypes.push(widget); + top += sizeY; + if (top > lastTop[column] + 10) { + lastTop[column] = top; + column++; + if (column > 2) { + column = 0; + } + top = lastTop[column]; + col = column * 8; + } + }); + return widgetTypes; + }) + ); + } + public getWidgetType(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { return this.http.get(`/api/widgetType?isSystem=${isSystem}&bundleAlias=${bundleAlias}&alias=${widgetTypeAlias}`, diff --git a/ui-ngx/src/app/modules/home/components/import-export/import-export.models.ts b/ui-ngx/src/app/modules/home/components/import-export/import-export.models.ts index 24e152cb29..c9502eb380 100644 --- a/ui-ngx/src/app/modules/home/components/import-export/import-export.models.ts +++ b/ui-ngx/src/app/modules/home/components/import-export/import-export.models.ts @@ -14,10 +14,16 @@ /// limitations under the License. /// -import { Widget } from '@app/shared/models/widget.models'; +import { Widget, WidgetType } from '@app/shared/models/widget.models'; import { DashboardLayoutId } from '@shared/models/dashboard.models'; +import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; export interface ImportWidgetResult { widget: Widget; layoutId: DashboardLayoutId; } + +export interface WidgetsBundleItem { + widgetsBundle: WidgetsBundle; + widgetTypes: WidgetType[]; +} diff --git a/ui-ngx/src/app/modules/home/components/import-export/import-export.service.ts b/ui-ngx/src/app/modules/home/components/import-export/import-export.service.ts index 59905423f2..b8001cbc34 100644 --- a/ui-ngx/src/app/modules/home/components/import-export/import-export.service.ts +++ b/ui-ngx/src/app/modules/home/components/import-export/import-export.service.ts @@ -38,15 +38,18 @@ import { forkJoin, Observable, of } from 'rxjs'; import { catchError, map, mergeMap } from 'rxjs/operators'; import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; import { EntityService } from '@core/http/entity.service'; -import { Widget, WidgetSize } from '@shared/models/widget.models'; +import { Widget, WidgetSize, WidgetType } from '@shared/models/widget.models'; import { EntityAliasesDialogComponent, EntityAliasesDialogData } from '@home/components/alias/entity-aliases-dialog.component'; import { ItemBufferService, WidgetItem } from '@core/services/item-buffer.service'; -import { ImportWidgetResult } from './import-export.models'; +import { ImportWidgetResult, WidgetsBundleItem } from './import-export.models'; import { EntityType } from '@shared/models/entity-type.models'; import { UtilsService } from '@core/services/utils.service'; +import { WidgetService } from '@core/http/widget.service'; +import { NULL_UUID } from '@shared/models/id/has-uuid'; +import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; @Injectable() export class ImportExportService { @@ -57,6 +60,7 @@ export class ImportExportService { private translate: TranslateService, private dashboardService: DashboardService, private dashboardUtils: DashboardUtilsService, + private widgetService: WidgetService, private entityService: EntityService, private utils: UtilsService, private itembuffer: ItemBufferService, @@ -72,13 +76,7 @@ export class ImportExportService { this.exportToPc(this.prepareDashboardExport(dashboard), name + '.json'); }, (e) => { - let message = e; - if (!message) { - message = this.translate.instant('error.unknown-error'); - } - this.store.dispatch(new ActionNotificationShow( - {message: this.translate.instant('dashboard.export-failed-error', {error: message}), - type: 'error'})); + this.handleExportError(e, 'dashboard.export-failed-error'); } ); } @@ -223,6 +221,119 @@ export class ImportExportService { ); } + public exportWidgetType(widgetTypeId: string) { + this.widgetService.getWidgetTypeById(widgetTypeId).subscribe( + (widgetType) => { + if (isDefined(widgetType.bundleAlias)) { + delete widgetType.bundleAlias; + } + let name = widgetType.name; + name = name.toLowerCase().replace(/\W/g, '_'); + this.exportToPc(this.prepareExport(widgetType), name + '.json'); + }, + (e) => { + this.handleExportError(e, 'widget-type.export-failed-error'); + } + ); + } + + public importWidgetType(bundleAlias: string): Observable { + return this.openImportDialog('widget-type.import', 'widget-type.widget-type-file').pipe( + mergeMap((widgetType: WidgetType) => { + if (!this.validateImportedWidgetType(widgetType)) { + this.store.dispatch(new ActionNotificationShow( + {message: this.translate.instant('widget-type.invalid-widget-type-file-error'), + type: 'error'})); + throw new Error('Invalid widget type file'); + } else { + widgetType.bundleAlias = bundleAlias; + return this.widgetService.saveImportedWidgetType(widgetType); + } + }), + catchError((err) => { + return of(null); + }) + ); + } + + public exportWidgetsBundle(widgetsBundleId: string) { + this.widgetService.getWidgetsBundle(widgetsBundleId).subscribe( + (widgetsBundle) => { + const bundleAlias = widgetsBundle.alias; + const isSystem = widgetsBundle.tenantId.id === NULL_UUID; + this.widgetService.getBundleWidgetTypes(bundleAlias, isSystem).subscribe( + (widgetTypes) => { + const widgetsBundleItem: WidgetsBundleItem = { + widgetsBundle: this.prepareExport(widgetsBundle), + widgetTypes: [] + }; + for (const widgetType of widgetTypes) { + if (isDefined(widgetType.bundleAlias)) { + delete widgetType.bundleAlias; + } + widgetsBundleItem.widgetTypes.push(this.prepareExport(widgetType)); + } + let name = widgetsBundle.title; + name = name.toLowerCase().replace(/\W/g, '_'); + this.exportToPc(widgetsBundleItem, name + '.json'); + }, + (e) => { + this.handleExportError(e, 'widgets-bundle.export-failed-error'); + } + ); + }, + (e) => { + this.handleExportError(e, 'widgets-bundle.export-failed-error'); + } + ); + } + + public importWidgetsBundle(): Observable { + return this.openImportDialog('widgets-bundle.import', 'widgets-bundle.widgets-bundle-file').pipe( + mergeMap((widgetsBundleItem: WidgetsBundleItem) => { + if (!this.validateImportedWidgetsBundle(widgetsBundleItem)) { + this.store.dispatch(new ActionNotificationShow( + {message: this.translate.instant('widgets-bundle.invalid-widgets-bundle-file-error'), + type: 'error'})); + throw new Error('Invalid widgets bundle file'); + } else { + const widgetsBundle = widgetsBundleItem.widgetsBundle; + return this.widgetService.saveWidgetsBundle(widgetsBundle).pipe( + mergeMap((savedWidgetsBundle) => { + const bundleAlias = savedWidgetsBundle.alias; + const widgetTypes = widgetsBundleItem.widgetTypes; + if (widgetTypes.length) { + const saveWidgetTypesObservables: Array> = []; + for (const widgetType of widgetTypes) { + widgetType.bundleAlias = bundleAlias; + saveWidgetTypesObservables.push(this.widgetService.saveImportedWidgetType(widgetType)); + } + return forkJoin(saveWidgetTypesObservables).pipe( + map(() => savedWidgetsBundle) + ); + } else { + return of(savedWidgetsBundle); + } + } + )); + } + }), + catchError((err) => { + return of(null); + }) + ); + } + + private handleExportError(e: any, errorDetailsMessageId: string) { + let message = e; + if (!message) { + message = this.translate.instant('error.unknown-error'); + } + this.store.dispatch(new ActionNotificationShow( + {message: this.translate.instant(errorDetailsMessageId, {error: message}), + type: 'error'})); + } + private validateImportedDashboard(dashboard: Dashboard): boolean { if (isUndefined(dashboard.title) || isUndefined(dashboard.configuration)) { return false; @@ -246,6 +357,34 @@ export class ImportExportService { return true; } + private validateImportedWidgetType(widgetType: WidgetType): boolean { + if (isUndefined(widgetType.name) + || isUndefined(widgetType.descriptor)) { + return false; + } + return true; + } + + private validateImportedWidgetsBundle(widgetsBundleItem: WidgetsBundleItem): boolean { + if (isUndefined(widgetsBundleItem.widgetsBundle)) { + return false; + } + if (isUndefined(widgetsBundleItem.widgetTypes)) { + return false; + } + const widgetsBundle = widgetsBundleItem.widgetsBundle; + if (isUndefined(widgetsBundle.title)) { + return false; + } + const widgetTypes = widgetsBundleItem.widgetTypes; + for (const widgetType of widgetTypes) { + if (!this.validateImportedWidgetType(widgetType)) { + return false; + } + } + return true; + } + private saveImportedDashboard(dashboard: Dashboard): Observable { return this.dashboardService.saveDashboard(dashboard); } 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 a03cbd4ea3..a3c237b115 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 @@ -114,7 +114,7 @@ export class DashboardWidgets implements Iterable { updateRecords.push({ widget: added.item, widgetId: added.item.id, - widgetLayout: this.widgetLayouts[added.item.id], + widgetLayout: this.widgetLayouts ? this.widgetLayouts[added.item.id] : null, operation: 'add' }); }); diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts b/ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts index 96c9de20a4..6e0de4ae21 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts @@ -63,54 +63,10 @@ export class WidgetsTypesDataResolver implements Resolve { const widgetsBundle: WidgetsBundle = route.parent.data.widgetsBundle; const bundleAlias = widgetsBundle.alias; const isSystem = widgetsBundle.tenantId.id === NULL_UUID; - return this.widgetsService.getBundleWidgetTypes(bundleAlias, + return this.widgetsService.loadBundleLibraryWidgets(bundleAlias, isSystem).pipe( - map((types) => { - types = types.sort((a, b) => { - let result = widgetType[b.descriptor.type].localeCompare(widgetType[a.descriptor.type]); - if (result === 0) { - result = b.createdTime - a.createdTime; - } - return result; - }); - const widgetTypes = new Array(); - let top = 0; - const lastTop = [0, 0, 0]; - let col = 0; - let column = 0; - types.forEach((type) => { - const widgetTypeInfo = toWidgetInfo(type); - const sizeX = 8; - const sizeY = Math.floor(widgetTypeInfo.sizeY); - const widget: Widget = { - typeId: type.id, - isSystemType: isSystem, - bundleAlias, - typeAlias: widgetTypeInfo.alias, - type: widgetTypeInfo.type, - title: widgetTypeInfo.widgetName, - sizeX, - sizeY, - row: top, - col, - config: JSON.parse(widgetTypeInfo.defaultConfig) - }; - - widget.config.title = widgetTypeInfo.widgetName; - - widgetTypes.push(widget); - top += sizeY; - if (top > lastTop[column] + 10) { - lastTop[column] = top; - column++; - if (column > 2) { - column = 0; - } - top = lastTop[column]; - col = column * 8; - } - }); - return { widgets: widgetTypes }; + map((widgets) => { + return { widgets }; } )); } diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.ts b/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.ts index 81ac1fb653..2d50ee8684 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.ts +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, OnInit, ViewChild } from '@angular/core'; +import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { PageComponent } from '@shared/components/page.component'; @@ -24,7 +24,7 @@ import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; import { ActivatedRoute, Router } from '@angular/router'; import { Authority } from '@shared/models/authority.enum'; import { NULL_UUID } from '@shared/models/id/has-uuid'; -import { Observable, of } from 'rxjs'; +import { Observable, of, Subscription } from 'rxjs'; import { Widget, widgetType } from '@app/shared/models/widget.models'; import { WidgetService } from '@core/http/widget.service'; import { map, mergeMap, share } from 'rxjs/operators'; @@ -42,6 +42,7 @@ import { DeviceCredentials } from '@shared/models/device.models'; import { MatDialog } from '@angular/material/dialog'; import { SelectWidgetTypeDialogComponent } from '@home/pages/widget/select-widget-type-dialog.component'; import { TranslateService } from '@ngx-translate/core'; +import { ImportExportService } from '@home/components/import-export/import-export.service'; @Component({ selector: 'tb-widget-library', @@ -94,11 +95,12 @@ export class WidgetLibraryComponent extends PageComponent implements OnInit { private router: Router, private widgetService: WidgetService, private dialogService: DialogService, + private importExport: ImportExportService, private dialog: MatDialog, private translate: TranslateService) { super(store); - this.authUser = getCurrentAuthUser(store); + this.authUser = getCurrentAuthUser(this.store); this.widgetsBundle = this.route.snapshot.data.widgetsBundle; this.widgetsData = this.route.snapshot.data.widgetsData; if (this.authUser.authority === Authority.TENANT_ADMIN) { @@ -119,7 +121,23 @@ export class WidgetLibraryComponent extends PageComponent implements OnInit { if ($event) { $event.stopPropagation(); } - this.dialogService.todo(); + this.importExport.importWidgetType(this.widgetsBundle.alias).subscribe( + (widgetTypeInstance) => { + if (widgetTypeInstance) { + this.reload(); + } + } + ); + } + + private reload() { + const bundleAlias = this.widgetsBundle.alias; + const isSystem = this.widgetsBundle.tenantId.id === NULL_UUID; + this.widgetService.loadBundleLibraryWidgets(bundleAlias, isSystem).subscribe( + (widgets) => { + this.widgetsData = {widgets}; + } + ); } openWidgetType($event: Event, widget?: Widget): void { @@ -147,14 +165,14 @@ export class WidgetLibraryComponent extends PageComponent implements OnInit { if ($event) { $event.stopPropagation(); } - this.dialogService.todo(); + this.importExport.exportWidgetType(widget.typeId.id); } - removeWidgetType($event: Event, widget: Widget): Observable { + removeWidgetType($event: Event, widget: Widget): void { if ($event) { $event.stopPropagation(); } - return this.dialogService.confirm( + this.dialogService.confirm( this.translate.instant('widget.remove-widget-type-title', {widgetName: widget.config.title}), this.translate.instant('widget.remove-widget-type-text'), this.translate.instant('action.no'), @@ -169,13 +187,13 @@ export class WidgetLibraryComponent extends PageComponent implements OnInit { }), map((result) => { if (result !== false) { - this.widgetsData.widgets.splice(this.widgetsData.widgets.indexOf(widget), 1); + this.reload(); return true; } else { return false; } } - )); + )).subscribe(); } } diff --git a/ui-ngx/src/app/modules/home/pages/widget/widgets-bundles-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/widget/widgets-bundles-table-config.resolver.ts index 47f7128af9..12c9b044fe 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widgets-bundles-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/widget/widgets-bundles-table-config.resolver.ts @@ -36,6 +36,7 @@ import {AppState} from '@core/core.state'; import {getCurrentAuthUser} from '@app/core/auth/auth.selectors'; import {Authority} from '@shared/models/authority.enum'; import {DialogService} from '@core/services/dialog.service'; +import { ImportExportService } from '@home/components/import-export/import-export.service'; @Injectable() export class WidgetsBundlesTableConfigResolver implements Resolve> { @@ -46,6 +47,7 @@ export class WidgetsBundlesTableConfigResolver implements Resolve { + if (widgetsBundle) { + this.config.table.updateData(); + } + } + ); } openWidgetsBundle($event: Event, widgetsBundle: WidgetsBundle) { @@ -142,8 +146,7 @@ export class WidgetsBundlesTableConfigResolver implements Resolve): boolean { diff --git a/ui-ngx/src/scss/mixins.scss b/ui-ngx/src/scss/mixins.scss index 1cde9ee21a..3b31b0e10c 100644 --- a/ui-ngx/src/scss/mixins.scss +++ b/ui-ngx/src/scss/mixins.scss @@ -21,12 +21,12 @@ min-height: #{$size}px; font-size: #{$size}px; line-height: #{$size}px; - svg { +/* svg { width: 24px; height: 24px; transform: scale($size/24); transform-origin: top; - } + }*/ } @mixin tb-mat-icon-button-size($size) { From 3d6b058b9dbaf485994bc68a98d6b9c71b9d0530 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 15 Nov 2019 12:22:14 +0200 Subject: [PATCH 051/133] Add widget to dashboard. CSV bulk import. User default place management. --- ui-ngx/package-lock.json | 31 ++- ui-ngx/package.json | 1 + ui-ngx/src/app/core/auth/auth.actions.ts | 12 +- ui-ngx/src/app/core/auth/auth.models.ts | 5 + ui-ngx/src/app/core/auth/auth.reducer.ts | 8 +- ui-ngx/src/app/core/auth/auth.service.ts | 254 +++++++++++++---- ui-ngx/src/app/core/guards/auth.guard.ts | 34 ++- ui-ngx/src/app/core/http/admin.service.ts | 28 +- ui-ngx/src/app/core/http/alarm.service.ts | 30 +- ui-ngx/src/app/core/http/asset.service.ts | 72 ++--- ui-ngx/src/app/core/http/attribute.service.ts | 52 +++- ui-ngx/src/app/core/http/audit-log.service.ts | 18 +- ui-ngx/src/app/core/http/customer.service.ts | 19 +- ui-ngx/src/app/core/http/dashboard.service.ts | 62 ++--- ui-ngx/src/app/core/http/device.service.ts | 68 ++--- .../app/core/http/entity-relation.service.ts | 50 ++-- .../src/app/core/http/entity-view.service.ts | 50 ++-- ui-ngx/src/app/core/http/entity.service.ts | 254 ++++++++++++----- ui-ngx/src/app/core/http/event.service.ts | 6 +- ui-ngx/src/app/core/http/http-utils.ts | 13 + .../src/app/core/http/rule-chain.service.ts | 23 +- ui-ngx/src/app/core/http/tenant.service.ts | 18 +- ui-ngx/src/app/core/http/user.service.ts | 30 +- ui-ngx/src/app/core/http/widget.service.ts | 74 +++-- .../core/services/dashboard-utils.service.ts | 28 +- .../src/app/core/services/dialog.service.ts | 2 - ui-ngx/src/app/core/services/utils.service.ts | 38 +++ .../src/app/core/settings/settings.effects.ts | 24 +- ...-widget-to-dashboard-dialog.component.html | 77 ++++++ ...-widget-to-dashboard-dialog.component.scss | 29 ++ ...dd-widget-to-dashboard-dialog.component.ts | 222 +++++++++++++++ .../attribute/attribute-table.component.html | 81 +++++- .../attribute/attribute-table.component.scss | 32 +++ .../attribute/attribute-table.component.ts | 177 +++++++++++- .../home/components/home-components.module.ts | 16 +- .../import-dialog-csv.component.html | 144 ++++++++++ .../import-dialog-csv.component.scss | 31 +++ .../import-dialog-csv.component.ts | 256 ++++++++++++++++++ .../import-export/import-export.models.ts | 115 ++++++++ .../import-export/import-export.service.ts | 56 +++- .../table-columns-assignment.component.html | 58 ++++ .../table-columns-assignment.component.scss | 26 ++ .../table-columns-assignment.component.ts | 175 ++++++++++++ .../widget/widget-component.service.ts | 2 +- .../widget/widget-config.component.ts | 3 +- .../home/dialogs/home-dialogs.module.ts | 4 + .../home/dialogs/home-dialogs.service.ts | 57 ++++ .../src/app/modules/home/home.component.html | 7 +- ui-ngx/src/app/modules/home/home.component.ts | 14 +- .../models/datasource/attribute-datasource.ts | 5 +- .../pages/asset/asset-tabs.component.html | 2 + .../asset/assets-table-config.resolver.ts | 59 ++-- .../customer/customer-tabs.component.html | 2 + .../dashboard/dashboard-page.component.html | 2 +- .../dashboard/dashboard-page.component.ts | 9 +- .../home/pages/dashboard/dashboard.module.ts | 7 +- .../entity-state-controller.component.ts | 2 +- .../select-target-state-dialog.component.html | 57 ++++ .../select-target-state-dialog.component.ts | 85 ++++++ .../states/state-controller.component.ts | 17 +- .../states/state-controller.models.ts | 1 + .../states/states-component.directive.ts | 5 + .../pages/device/device-tabs.component.html | 2 + .../device/devices-table-config.resolver.ts | 62 +++-- .../entity-view-tabs.component.html | 2 + .../rulechain/rulechain-tabs.component.html | 2 + .../pages/tenant/tenant-tabs.component.html | 2 + .../login/pages/login/login.component.ts | 5 +- .../dashboard-autocomplete.component.ts | 9 +- .../components/dashboard-select.component.ts | 5 +- .../entity/entity-autocomplete.component.ts | 6 +- .../entity/entity-keys-list.component.ts | 2 +- .../entity/entity-list.component.ts | 2 +- .../entity-subtype-autocomplete.component.ts | 6 +- .../entity/entity-subtype-list.component.ts | 6 +- .../entity-subtype-select.component.html | 3 +- .../entity/entity-subtype-select.component.ts | 20 +- .../shared/components/fullscreen.directive.ts | 4 +- ui-ngx/src/app/shared/models/asset.models.ts | 4 +- ui-ngx/src/app/shared/models/constants.ts | 1 + .../src/app/shared/models/dashboard.models.ts | 1 - ui-ngx/src/app/shared/models/device.models.ts | 6 +- ui-ngx/src/app/shared/models/entity.models.ts | 24 ++ ui-ngx/src/app/shared/models/login.models.ts | 11 +- .../src/app/shared/models/settings.models.ts | 5 + .../models/telemetry/telemetry.models.ts | 18 +- ui-ngx/src/app/shared/models/widget.models.ts | 1 - ui-ngx/src/app/shared/shared.module.ts | 3 + .../assets/locale/locale.constant-en_US.json | 3 +- 89 files changed, 2775 insertions(+), 579 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/attribute/add-widget-to-dashboard-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/components/attribute/add-widget-to-dashboard-dialog.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/attribute/add-widget-to-dashboard-dialog.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/import-export/import-dialog-csv.component.html create mode 100644 ui-ngx/src/app/modules/home/components/import-export/import-dialog-csv.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/import-export/import-dialog-csv.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/import-export/table-columns-assignment.component.html create mode 100644 ui-ngx/src/app/modules/home/components/import-export/table-columns-assignment.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/import-export/table-columns-assignment.component.ts create mode 100644 ui-ngx/src/app/modules/home/dialogs/home-dialogs.service.ts create mode 100644 ui-ngx/src/app/modules/home/pages/dashboard/states/select-target-state-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/dashboard/states/select-target-state-dialog.component.ts diff --git a/ui-ngx/package-lock.json b/ui-ngx/package-lock.json index 7a5069a590..6a21376c02 100644 --- a/ui-ngx/package-lock.json +++ b/ui-ngx/package-lock.json @@ -5140,9 +5140,9 @@ "dev": true }, "handlebars": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.4.3.tgz", - "integrity": "sha512-B0W4A2U1ww3q7VVthTKfh+epHx+q4mCt6iK+zEAzbMBpWQAwxCeKxEGpj/1oQTpzPXDNSOG7hmG14TsISH50yw==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.1.tgz", + "integrity": "sha512-C29UoFzHe9yM61lOsIlCE5/mQVGrnIOrOq7maQl76L7tYPCgC1og0Ajt6uWnX4ZTxBPnjw+CUvawphwCfJgUnA==", "dev": true, "requires": { "neo-async": "^2.6.0", @@ -7411,6 +7411,16 @@ "resolved": "https://registry.npmjs.org/ngx-color-picker/-/ngx-color-picker-8.2.0.tgz", "integrity": "sha512-rzR+cByjNG9M/UskU5vNoH7cUc6oM8STTDFKOZmnlX4ALOuM1+61CBjsNTGETWfo9a/h5mbGX02oh5/iNAa7vA==" }, + "ngx-hm-carousel": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/ngx-hm-carousel/-/ngx-hm-carousel-1.7.2.tgz", + "integrity": "sha512-l53iWKO+brHPCgveqz9eBYvs0/YUp3EAIaxY30c361heMfegNI9yX/xDNXcwCZiKY80KKeT04Qqxos+EyZl/VQ==", + "requires": { + "hammerjs": "^2.0.8", + "resize-observer-polyfill": "^1.5.1", + "tslib": "^1.9.0" + } + }, "ngx-translate-messageformat-compiler": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/ngx-translate-messageformat-compiler/-/ngx-translate-messageformat-compiler-4.5.0.tgz", @@ -10786,23 +10796,16 @@ "dev": true }, "uglify-js": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.2.tgz", - "integrity": "sha512-+gh/xFte41GPrgSMJ/oJVq15zYmqr74pY9VoM69UzMzq9NFk4YDylclb1/bhEzZSaUQjbW5RvniHeq1cdtRYjw==", + "version": "3.6.8", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.8.tgz", + "integrity": "sha512-XhHJ3S3ZyMwP8kY1Gkugqx3CJh2C3O0y8NPiSxtm1tyD/pktLAkFZsFGpuNfTZddKDQ/bbDBLAd2YyA1pbi8HQ==", "dev": true, "optional": true, "requires": { - "commander": "2.20.0", + "commander": "~2.20.3", "source-map": "~0.6.1" }, "dependencies": { - "commander": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", - "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", - "dev": true, - "optional": true - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", diff --git a/ui-ngx/package.json b/ui-ngx/package.json index 57760a98e3..0dab581312 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -59,6 +59,7 @@ "moment": "^2.24.0", "ngx-clipboard": "^12.2.0", "ngx-color-picker": "^8.2.0", + "ngx-hm-carousel": "^1.7.2", "ngx-translate-messageformat-compiler": "^4.5.0", "objectpath": "^1.2.2", "prop-types": "^15.7.2", diff --git a/ui-ngx/src/app/core/auth/auth.actions.ts b/ui-ngx/src/app/core/auth/auth.actions.ts index d5a30c0856..8cf40da702 100644 --- a/ui-ngx/src/app/core/auth/auth.actions.ts +++ b/ui-ngx/src/app/core/auth/auth.actions.ts @@ -22,7 +22,8 @@ export enum AuthActionTypes { AUTHENTICATED = '[Auth] Authenticated', UNAUTHENTICATED = '[Auth] Unauthenticated', LOAD_USER = '[Auth] Load User', - UPDATE_USER_DETAILS = '[Auth] Update User Details' + UPDATE_USER_DETAILS = '[Auth] Update User Details', + UPDATE_LAST_PUBLIC_DASHBOARD_ID = '[Auth] Update Last Public Dashboard Id' } export class ActionAuthAuthenticated implements Action { @@ -47,4 +48,11 @@ export class ActionAuthUpdateUserDetails implements Action { constructor(readonly payload: { userDetails: User }) {} } -export type AuthActions = ActionAuthAuthenticated | ActionAuthUnauthenticated | ActionAuthLoadUser | ActionAuthUpdateUserDetails; +export class ActionAuthUpdateLastPublicDashboardId implements Action { + readonly type = AuthActionTypes.UPDATE_LAST_PUBLIC_DASHBOARD_ID; + + constructor(readonly payload: { lastPublicDashboardId: string }) {} +} + +export type AuthActions = ActionAuthAuthenticated | ActionAuthUnauthenticated | + ActionAuthLoadUser | ActionAuthUpdateUserDetails | ActionAuthUpdateLastPublicDashboardId; diff --git a/ui-ngx/src/app/core/auth/auth.models.ts b/ui-ngx/src/app/core/auth/auth.models.ts index 21daba10ea..f861609514 100644 --- a/ui-ngx/src/app/core/auth/auth.models.ts +++ b/ui-ngx/src/app/core/auth/auth.models.ts @@ -20,6 +20,8 @@ export interface AuthPayload { authUser: AuthUser; userDetails: User; userTokenAccessEnabled: boolean; + allowedDashboardIds: string[]; + forceFullscreen: boolean; } export interface AuthState { @@ -28,4 +30,7 @@ export interface AuthState { authUser: AuthUser; userDetails: User; userTokenAccessEnabled: boolean; + allowedDashboardIds: string[]; + forceFullscreen: boolean; + lastPublicDashboardId: string; } diff --git a/ui-ngx/src/app/core/auth/auth.reducer.ts b/ui-ngx/src/app/core/auth/auth.reducer.ts index b82f76f9c4..7fb20d8921 100644 --- a/ui-ngx/src/app/core/auth/auth.reducer.ts +++ b/ui-ngx/src/app/core/auth/auth.reducer.ts @@ -20,12 +20,15 @@ import { AuthActions, AuthActionTypes } from './auth.actions'; const emptyUserAuthState: AuthPayload = { authUser: null, userDetails: null, - userTokenAccessEnabled: false + userTokenAccessEnabled: false, + forceFullscreen: false, + allowedDashboardIds: [] }; export const initialState: AuthState = { isAuthenticated: false, isUserLoaded: false, + lastPublicDashboardId: null, ...emptyUserAuthState }; @@ -47,6 +50,9 @@ export function authReducer( case AuthActionTypes.UPDATE_USER_DETAILS: return { ...state, ...action.payload}; + case AuthActionTypes.UPDATE_LAST_PUBLIC_DASHBOARD_ID: + return { ...state, ...action.payload}; + default: return state; } diff --git a/ui-ngx/src/app/core/auth/auth.service.ts b/ui-ngx/src/app/core/auth/auth.service.ts index c6ede83445..03253918fa 100644 --- a/ui-ngx/src/app/core/auth/auth.service.ts +++ b/ui-ngx/src/app/core/auth/auth.service.ts @@ -14,36 +14,41 @@ /// limitations under the License. /// -import {Injectable, NgZone} from '@angular/core'; -import {JwtHelperService} from '@auth0/angular-jwt'; -import {HttpClient} from '@angular/common/http'; - -import {combineLatest, forkJoin, Observable, of} from 'rxjs'; -import {distinctUntilChanged, filter, map, skip, tap} from 'rxjs/operators'; - -import {LoginRequest, LoginResponse} from '../../shared/models/login.models'; -import {ActivatedRoute, Router, UrlTree} from '@angular/router'; -import {defaultHttpOptions} from '../http/http-utils'; -import {ReplaySubject} from 'rxjs/internal/ReplaySubject'; -import {UserService} from '../http/user.service'; -import {select, Store} from '@ngrx/store'; -import {AppState} from '../core.state'; -import {ActionAuthAuthenticated, ActionAuthLoadUser, ActionAuthUnauthenticated} from './auth.actions'; -import {getCurrentAuthUser, selectIsAuthenticated, selectIsUserLoaded} from './auth.selectors'; -import {Authority} from '../../shared/models/authority.enum'; -import {ActionSettingsChangeLanguage} from '@app/core/settings/settings.actions'; -import {AuthPayload} from '@core/auth/auth.models'; -import {TranslateService} from '@ngx-translate/core'; -import {AuthUser} from '@shared/models/user.model'; -import {TimeService} from '@core/services/time.service'; +import { Injectable, NgZone } from '@angular/core'; +import { JwtHelperService } from '@auth0/angular-jwt'; +import { HttpClient } from '@angular/common/http'; + +import { combineLatest, forkJoin, Observable, of, throwError } from 'rxjs'; +import { catchError, distinctUntilChanged, filter, map, mergeMap, skip, tap } from 'rxjs/operators'; + +import { LoginRequest, LoginResponse, PublicLoginRequest } from '../../shared/models/login.models'; +import { ActivatedRoute, Router, UrlTree } from '@angular/router'; +import { defaultHttpOptions } from '../http/http-utils'; +import { ReplaySubject } from 'rxjs/internal/ReplaySubject'; +import { UserService } from '../http/user.service'; +import { select, Store } from '@ngrx/store'; +import { AppState } from '../core.state'; +import { ActionAuthAuthenticated, ActionAuthLoadUser, ActionAuthUnauthenticated } from './auth.actions'; +import { getCurrentAuthState, getCurrentAuthUser, selectIsAuthenticated, selectIsUserLoaded } from './auth.selectors'; +import { Authority } from '../../shared/models/authority.enum'; +import { ActionSettingsChangeLanguage } from '@app/core/settings/settings.actions'; +import { AuthPayload, AuthState } from '@core/auth/auth.models'; +import { TranslateService } from '@ngx-translate/core'; +import { AuthUser } from '@shared/models/user.model'; +import { TimeService } from '@core/services/time.service'; +import { UtilsService } from '@core/services/utils.service'; +import { DashboardService } from '@core/http/dashboard.service'; +import { PageLink } from '@shared/models/page/page-link'; +import { DashboardInfo } from '@shared/models/dashboard.models'; +import { PageData } from '@app/shared/models/page/page-data'; +import { AdminService } from '@core/http/admin.service'; +import { ActionNotificationShow } from '@core/notification/notification.actions'; @Injectable({ providedIn: 'root' }) export class AuthService { - forceFullscreen = false; // TODO: - constructor( private store: Store, private http: HttpClient, @@ -52,6 +57,9 @@ export class AuthService { private router: Router, private route: ActivatedRoute, private zone: NgZone, + private utils: UtilsService, + private dashboardService: DashboardService, + private adminService: AdminService, private translate: TranslateService ) { combineLatest( @@ -119,6 +127,13 @@ export class AuthService { )); } + public publicLogin(publicId: string): Observable { + const publicLoginRequest: PublicLoginRequest = { + publicId + }; + return this.http.post('/api/auth/login/public', publicLoginRequest, defaultHttpOptions()); + } + public sendResetPasswordLink(email: string) { return this.http.post('/api/noauth/resetPasswordByEmail', {email}, defaultHttpOptions()); @@ -182,39 +197,118 @@ export class AuthService { } public gotoDefaultPlace(isAuthenticated: boolean) { - const url = this.defaultUrl(isAuthenticated); + const authState = getCurrentAuthState(this.store); + const url = this.defaultUrl(isAuthenticated, authState); this.zone.run(() => { this.router.navigateByUrl(url); }); } - public defaultUrl(isAuthenticated: boolean): UrlTree { - if (isAuthenticated) { - if (this.redirectUrl) { - const redirectUrl = this.redirectUrl; - this.redirectUrl = null; - return this.router.parseUrl(redirectUrl); - } else { - - // TODO: + private forceDefaultPlace(authState?: AuthState, path?: string, params?: any): boolean { + 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') { + if (this.userHasProfile(authState.authUser)) { + return false; + } else { + return true; + } + } else if (path.startsWith('dashboard.') || path.startsWith('dashboards.') && + authState.allowedDashboardIds.indexOf(params.dashboardId) > -1) { + return false; + } else { + return true; + } + } + } + } + return false; + } - return this.router.parseUrl('home'); + public defaultUrl(isAuthenticated: boolean, authState?: AuthState, path?: string, params?: any): UrlTree { + let result: UrlTree = null; + if (isAuthenticated) { + if (!path || path === 'login' || this.forceDefaultPlace(authState, path, params)) { + if (this.redirectUrl) { + const redirectUrl = this.redirectUrl; + this.redirectUrl = null; + result = this.router.parseUrl(redirectUrl); + } else { + result = this.router.parseUrl('home'); + } + if (authState.authUser.authority === Authority.TENANT_ADMIN || authState.authUser.authority === Authority.CUSTOMER_USER) { + if (this.userHasDefaultDashboard(authState)) { + const dashboardId = authState.userDetails.additionalInfo.defaultDashboardId; + if (authState.forceFullscreen) { + result = this.router.parseUrl(`dashboard/${dashboardId}`); + } else { + result = this.router.parseUrl(`dashboards/${dashboardId}`); + } + } else if (authState.authUser.isPublic) { + result = this.router.parseUrl(`dashboard/${authState.lastPublicDashboardId}`); + } + } else if (authState.authUser.authority === Authority.SYS_ADMIN) { + this.adminService.checkUpdates().subscribe((updateMessage) => { + if (updateMessage && updateMessage.updateAvailable) { + this.store.dispatch(new ActionNotificationShow( + {message: updateMessage.message, + type: 'info', + verticalPosition: 'bottom', + horizontalPosition: 'right'})); + } + }); + } } } else { - return this.router.parseUrl('login'); + result = this.router.parseUrl('login'); } + return result; } private loadUser(doTokenRefresh): Observable { const authUser = getCurrentAuthUser(this.store); if (!authUser) { + const publicId = this.utils.getQueryParam('publicId'); + const accessToken = this.utils.getQueryParam('accessToken'); + const refreshToken = this.utils.getQueryParam('refreshToken'); + if (publicId) { + return this.publicLogin(publicId).pipe( + mergeMap((response) => { + this.updateAndValidateToken(response.token, 'jwt_token', false); + this.updateAndValidateToken(response.refreshToken, 'refresh_token', false); + return this.procceedJwtTokenValidate(); + }), + catchError((err) => { + this.utils.updateQueryParam('publicId', null); + throw Error(); + }) + ); + } else if (accessToken) { + this.utils.updateQueryParam('accessToken', null); + if (refreshToken) { + this.utils.updateQueryParam('refreshToken', null); + } + try { + this.updateAndValidateToken(accessToken, 'jwt_token', false); + if (refreshToken) { + this.updateAndValidateToken(refreshToken, 'refresh_token', false); + } else { + localStorage.removeItem('refresh_token'); + localStorage.removeItem('refresh_token_expiration'); + } + } catch (e) { + return throwError(e); + } + return this.procceedJwtTokenValidate(); + } return this.procceedJwtTokenValidate(doTokenRefresh); } else { return of({} as AuthPayload); } } - private procceedJwtTokenValidate(doTokenRefresh: boolean): Observable { + private procceedJwtTokenValidate(doTokenRefresh?: boolean): Observable { const loadUserSubject = new ReplaySubject(); this.validateJwtToken(doTokenRefresh).subscribe( () => { @@ -226,18 +320,31 @@ export class AuthService { } else if (authPayload.authUser) { authPayload.authUser.authority = Authority.ANONYMOUS; } - const sysParamsObservable = this.loadSystemParams(authPayload.authUser); if (authPayload.authUser.isPublic) { - - // TODO: - + authPayload.forceFullscreen = true; + } + if (authPayload.authUser.isPublic) { + this.loadSystemParams(authPayload).subscribe( + (sysParams) => { + authPayload = {...authPayload, ...sysParams}; + loadUserSubject.next(authPayload); + loadUserSubject.complete(); + }, + (err) => { + loadUserSubject.error(err); + } + ); } else if (authPayload.authUser.userId) { this.userService.getUser(authPayload.authUser.userId).subscribe( (user) => { - sysParamsObservable.subscribe( + authPayload.userDetails = user; + authPayload.forceFullscreen = false; + if (this.userForceFullscreen(authPayload)) { + authPayload.forceFullscreen = true; + } + this.loadSystemParams(authPayload).subscribe( (sysParams) => { authPayload = {...authPayload, ...sysParams}; - authPayload.userDetails = user; let userLang; if (authPayload.userDetails.additionalInfo && authPayload.userDetails.additionalInfo.lang) { userLang = authPayload.userDetails.additionalInfo.lang; @@ -278,13 +385,15 @@ export class AuthService { } } - private loadSystemParams(authUser: AuthUser): Observable { - const sources: Array> = [this.loadIsUserTokenAccessEnabled(authUser), + private loadSystemParams(authPayload: AuthPayload): Observable { + const sources: Array> = [this.loadIsUserTokenAccessEnabled(authPayload.authUser), + this.fetchAllowedDashboardIds(authPayload), this.timeService.loadMaxDatapointsLimit()]; return forkJoin(sources) .pipe(map((data) => { const userTokenAccessEnabled: boolean = data[0]; - return {userTokenAccessEnabled}; + const allowedDashboardIds: string[] = data[1]; + return {userTokenAccessEnabled, allowedDashboardIds}; })); } @@ -369,9 +478,20 @@ export class AuthService { } ); } else { - this.loadUser(false); + this.loadUser(false).subscribe(); + } + } + } + + public parsePublicId(): string { + const token = AuthService.getJwtToken(); + if (token) { + const tokenData = this.jwtHelper.decodeToken(token); + if (tokenData && tokenData.isPublic) { + return tokenData.sub; } } + return null; } private notifyUnauthenticated() { @@ -409,4 +529,44 @@ export class AuthService { this.setUserFromJwtToken(null, null, true); } + private userForceFullscreen(authPayload: AuthPayload): boolean { + return (authPayload.authUser && authPayload.authUser.isPublic) || + (authPayload.userDetails && authPayload.userDetails.additionalInfo && + authPayload.userDetails.additionalInfo.defaultDashboardFullscreen && + authPayload.userDetails.additionalInfo.defaultDashboardFullscreen === true); + } + + private userHasProfile(authUser: AuthUser): boolean { + return authUser && !authUser.isPublic; + } + + private userHasDefaultDashboard(authState: AuthState): boolean { + if (authState && authState.userDetails && authState.userDetails.additionalInfo + && authState.userDetails.additionalInfo.defaultDashboardId) { + return true; + } else { + return false; + } + } + + private fetchAllowedDashboardIds(authPayload: AuthPayload): Observable { + if (authPayload.forceFullscreen && (authPayload.authUser.authority === Authority.TENANT_ADMIN || + authPayload.authUser.authority === Authority.CUSTOMER_USER)) { + const pageLink = new PageLink(100); + let fetchDashboardsObservable: Observable>; + if (authPayload.authUser.authority === Authority.TENANT_ADMIN) { + fetchDashboardsObservable = this.dashboardService.getTenantDashboards(pageLink); + } else { + fetchDashboardsObservable = this.dashboardService.getCustomerDashboards(authPayload.authUser.customerId, pageLink); + } + return fetchDashboardsObservable.pipe( + map((result) => { + const dashboards = result.data; + return dashboards.map(dashboard => dashboard.id.id); + }) + ); + } else { + return of([]); + } + } } diff --git a/ui-ngx/src/app/core/guards/auth.guard.ts b/ui-ngx/src/app/core/guards/auth.guard.ts index f022d6d44b..b6a3a1ae72 100644 --- a/ui-ngx/src/app/core/guards/auth.guard.ts +++ b/ui-ngx/src/app/core/guards/auth.guard.ts @@ -32,6 +32,7 @@ import { enterZone } from '@core/operator/enterZone'; import { Authority } from '@shared/models/authority.enum'; import { DialogService } from '@core/services/dialog.service'; import { TranslateService } from '@ngx-translate/core'; +import { UtilsService } from '@core/services/utils.service'; @Injectable({ providedIn: 'root' @@ -41,6 +42,7 @@ export class AuthGuard implements CanActivate, CanActivateChild { constructor(private store: Store, private authService: AuthService, private dialogService: DialogService, + private utils: UtilsService, private translate: TranslateService, private zone: NgZone) {} @@ -61,14 +63,28 @@ export class AuthGuard implements CanActivate, CanActivateChild { const url: string = state.url; let lastChild = state.root; + const urlSegments: string[] = []; + if (lastChild.url) { + urlSegments.push(...lastChild.url.map(segment => segment.path)); + } while (lastChild.children.length) { lastChild = lastChild.children[0]; + if (lastChild.url) { + urlSegments.push(...lastChild.url.map(segment => segment.path)); + } } + const path = urlSegments.join('.'); + const publicId = this.utils.getQueryParam('publicId'); const data = lastChild.data || {}; + const params = lastChild.params || {}; const isPublic = data.module === 'public'; if (!authState.isAuthenticated) { - if (!isPublic) { + if (publicId && publicId.length > 0) { + this.authService.setUserFromJwtToken(null, null, false); + this.authService.reloadUser(); + return false; + } else if (!isPublic) { this.authService.redirectUrl = url; // this.authService.gotoDefaultPlace(false); return this.authService.defaultUrl(false); @@ -76,9 +92,21 @@ export class AuthGuard implements CanActivate, CanActivateChild { return true; } } else { - if (url === '/login') { + if (authState.authUser.isPublic) { + if (this.authService.parsePublicId() !== publicId) { + if (publicId && publicId.length > 0) { + this.authService.setUserFromJwtToken(null, null, false); + this.authService.reloadUser(); + } else { + this.authService.logout(); + } + return false; + } + } + const defaultUrl = this.authService.defaultUrl(true, authState, path, params); + if (defaultUrl) { // this.authService.gotoDefaultPlace(true); - return this.authService.defaultUrl(true); + return defaultUrl; } else { const authority = Authority[authState.authUser.authority]; if (data.auth && data.auth.indexOf(authority) === -1) { diff --git a/ui-ngx/src/app/core/http/admin.service.ts b/ui-ngx/src/app/core/http/admin.service.ts index 589fc4b45f..0d7fef6477 100644 --- a/ui-ngx/src/app/core/http/admin.service.ts +++ b/ui-ngx/src/app/core/http/admin.service.ts @@ -15,10 +15,10 @@ /// import { Injectable } from '@angular/core'; -import { defaultHttpOptions } from './http-utils'; +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; import { Observable } from 'rxjs/index'; import { HttpClient } from '@angular/common/http'; -import {AdminSettings, MailServerSettings, SecuritySettings} from '@shared/models/settings.models'; +import { AdminSettings, MailServerSettings, SecuritySettings, UpdateMessage } from '@shared/models/settings.models'; @Injectable({ providedIn: 'root' @@ -29,27 +29,31 @@ export class AdminService { private http: HttpClient ) { } - public getAdminSettings(key: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { - return this.http.get>(`/api/admin/settings/${key}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public getAdminSettings(key: string, config?: RequestConfig): Observable> { + return this.http.get>(`/api/admin/settings/${key}`, defaultHttpOptionsFromConfig(config)); } public saveAdminSettings(adminSettings: AdminSettings, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { - return this.http.post>('/api/admin/settings', adminSettings, defaultHttpOptions(ignoreLoading, ignoreErrors)); + config?: RequestConfig): Observable> { + return this.http.post>('/api/admin/settings', adminSettings, defaultHttpOptionsFromConfig(config)); } public sendTestMail(adminSettings: AdminSettings, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { - return this.http.post('/api/admin/settings/testMail', adminSettings, defaultHttpOptions(ignoreLoading, ignoreErrors)); + config?: RequestConfig): Observable { + return this.http.post('/api/admin/settings/testMail', adminSettings, defaultHttpOptionsFromConfig(config)); } - public getSecuritySettings(ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { - return this.http.get(`/api/admin/securitySettings`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public getSecuritySettings(config?: RequestConfig): Observable { + return this.http.get(`/api/admin/securitySettings`, defaultHttpOptionsFromConfig(config)); } public saveSecuritySettings(securitySettings: SecuritySettings, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + config?: RequestConfig): Observable { return this.http.post('/api/admin/securitySettings', securitySettings, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); + } + + public checkUpdates(config?: RequestConfig): Observable { + return this.http.get(`/api/admin/updates`, defaultHttpOptionsFromConfig(config)); } } diff --git a/ui-ngx/src/app/core/http/alarm.service.ts b/ui-ngx/src/app/core/http/alarm.service.ts index ed6d783118..a1fcdb9810 100644 --- a/ui-ngx/src/app/core/http/alarm.service.ts +++ b/ui-ngx/src/app/core/http/alarm.service.ts @@ -15,7 +15,7 @@ /// import { Injectable } from '@angular/core'; -import { defaultHttpOptions } from './http-utils'; +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; import { Observable } from 'rxjs/index'; import { HttpClient } from '@angular/common/http'; import { PageData } from '@shared/models/page/page-data'; @@ -55,34 +55,34 @@ export class AlarmService { private http: HttpClient ) { } - public getAlarm(alarmId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { - return this.http.get(`/api/alarm/${alarmId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public getAlarm(alarmId: string, config?: RequestConfig): Observable { + return this.http.get(`/api/alarm/${alarmId}`, defaultHttpOptionsFromConfig(config)); } - public getAlarmInfo(alarmId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { - return this.http.get(`/api/alarm/info/${alarmId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public getAlarmInfo(alarmId: string, config?: RequestConfig): Observable { + return this.http.get(`/api/alarm/info/${alarmId}`, defaultHttpOptionsFromConfig(config)); } - public saveAlarm(alarm: Alarm, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { - return this.http.post('/api/alarm', alarm, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public saveAlarm(alarm: Alarm, config?: RequestConfig): Observable { + return this.http.post('/api/alarm', alarm, defaultHttpOptionsFromConfig(config)); } - public ackAlarm(alarmId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { - return this.http.post(`/api/alarm/${alarmId}/ack`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public ackAlarm(alarmId: string, config?: RequestConfig): Observable { + return this.http.post(`/api/alarm/${alarmId}/ack`, null, defaultHttpOptionsFromConfig(config)); } - public clearAlarm(alarmId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { - return this.http.post(`/api/alarm/${alarmId}/clear`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public clearAlarm(alarmId: string, config?: RequestConfig): Observable { + return this.http.post(`/api/alarm/${alarmId}/clear`, null, defaultHttpOptionsFromConfig(config)); } public getAlarms(query: AlarmQuery, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + config?: RequestConfig): Observable> { return this.http.get>(`/api/alarm${query.toQuery()}`, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } public getHighestAlarmSeverity(entityId: EntityId, alarmSearchStatus: AlarmSearchStatus, alarmStatus: AlarmStatus, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + config?: RequestConfig): Observable { let url = `/api/alarm/highestSeverity/${entityId.entityType}/${entityId.entityType}`; if (alarmSearchStatus) { url += `?searchStatus=${alarmSearchStatus}`; @@ -90,6 +90,6 @@ export class AlarmService { url += `?status=${alarmStatus}`; } return this.http.get(url, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } } diff --git a/ui-ngx/src/app/core/http/asset.service.ts b/ui-ngx/src/app/core/http/asset.service.ts index 079a4b500d..e92d307ef0 100644 --- a/ui-ngx/src/app/core/http/asset.service.ts +++ b/ui-ngx/src/app/core/http/asset.service.ts @@ -14,15 +14,14 @@ /// limitations under the License. /// -import {Injectable} from '@angular/core'; -import {defaultHttpOptions} from './http-utils'; -import {Observable} from 'rxjs/index'; -import {HttpClient} from '@angular/common/http'; -import {PageLink} from '@shared/models/page/page-link'; -import {PageData} from '@shared/models/page/page-data'; -import {EntitySubtype} from '@app/shared/models/entity-type.models'; -import {Asset, AssetInfo, AssetSearchQuery} from '@app/shared/models/asset.models'; -import { Device, DeviceSearchQuery } from '@shared/models/device.models'; +import { Injectable } from '@angular/core'; +import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; +import { Observable } from 'rxjs/index'; +import { HttpClient } from '@angular/common/http'; +import { PageLink } from '@shared/models/page/page-link'; +import { PageData } from '@shared/models/page/page-data'; +import { EntitySubtype } from '@app/shared/models/entity-type.models'; +import { Asset, AssetInfo, AssetSearchQuery } from '@app/shared/models/asset.models'; @Injectable({ providedIn: 'root' @@ -33,58 +32,61 @@ export class AssetService { private http: HttpClient ) { } - public getTenantAssetInfos(pageLink: PageLink, type: string = '', ignoreErrors: boolean = false, - ignoreLoading: boolean = false): Observable> { + public getTenantAssetInfos(pageLink: PageLink, type: string = '', config?: RequestConfig): Observable> { return this.http.get>(`/api/tenant/assetInfos${pageLink.toQuery()}&type=${type}`, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } - public getCustomerAssetInfos(customerId: string, pageLink: PageLink, type: string = '', ignoreErrors: boolean = false, - ignoreLoading: boolean = false): Observable> { + public getCustomerAssetInfos(customerId: string, pageLink: PageLink, type: string = '', + config?: RequestConfig): Observable> { return this.http.get>(`/api/customer/${customerId}/assetInfos${pageLink.toQuery()}&type=${type}`, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } - public getAsset(assetId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { - return this.http.get(`/api/asset/${assetId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public getAsset(assetId: string, config?: RequestConfig): Observable { + return this.http.get(`/api/asset/${assetId}`, defaultHttpOptionsFromConfig(config)); } - public getAssets(assetIds: Array, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { - return this.http.get>(`/api/assets?assetIds=${assetIds.join(',')}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public getAssets(assetIds: Array, config?: RequestConfig): Observable> { + return this.http.get>(`/api/assets?assetIds=${assetIds.join(',')}`, defaultHttpOptionsFromConfig(config)); } - public getAssetInfo(assetId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { - return this.http.get(`/api/asset/info/${assetId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public getAssetInfo(assetId: string, config?: RequestConfig): Observable { + return this.http.get(`/api/asset/info/${assetId}`, defaultHttpOptionsFromConfig(config)); } - public saveAsset(asset: Asset, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { - return this.http.post('/api/asset', asset, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public saveAsset(asset: Asset, config?: RequestConfig): Observable { + return this.http.post('/api/asset', asset, defaultHttpOptionsFromConfig(config)); } - public deleteAsset(assetId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { - return this.http.delete(`/api/asset/${assetId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public deleteAsset(assetId: string, config?: RequestConfig) { + return this.http.delete(`/api/asset/${assetId}`, defaultHttpOptionsFromConfig(config)); } - public getAssetTypes(ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { - return this.http.get>('/api/asset/types', defaultHttpOptions(ignoreLoading, ignoreErrors)); + public getAssetTypes(config?: RequestConfig): Observable> { + return this.http.get>('/api/asset/types', defaultHttpOptionsFromConfig(config)); } - public makeAssetPublic(assetId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { - return this.http.post(`/api/customer/public/asset/${assetId}`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public makeAssetPublic(assetId: string, config?: RequestConfig): Observable { + return this.http.post(`/api/customer/public/asset/${assetId}`, null, defaultHttpOptionsFromConfig(config)); } public assignAssetToCustomer(customerId: string, assetId: string, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { - return this.http.post(`/api/customer/${customerId}/asset/${assetId}`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); + config?: RequestConfig): Observable { + return this.http.post(`/api/customer/${customerId}/asset/${assetId}`, null, defaultHttpOptionsFromConfig(config)); } - public unassignAssetFromCustomer(assetId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { - return this.http.delete(`/api/customer/asset/${assetId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public unassignAssetFromCustomer(assetId: string, config?: RequestConfig) { + return this.http.delete(`/api/customer/asset/${assetId}`, defaultHttpOptionsFromConfig(config)); } public findByQuery(query: AssetSearchQuery, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { - return this.http.post>('/api/assets', query, defaultHttpOptions(ignoreLoading, ignoreErrors)); + config?: RequestConfig): Observable> { + return this.http.post>('/api/assets', query, defaultHttpOptionsFromConfig(config)); + } + + public findByName(assetName: string, config?: RequestConfig): Observable { + return this.http.get(`/api/tenant/assets?assetName=${assetName}`, defaultHttpOptionsFromConfig(config)); } } diff --git a/ui-ngx/src/app/core/http/attribute.service.ts b/ui-ngx/src/app/core/http/attribute.service.ts index c05d10de45..852d15cfb8 100644 --- a/ui-ngx/src/app/core/http/attribute.service.ts +++ b/ui-ngx/src/app/core/http/attribute.service.ts @@ -15,7 +15,7 @@ /// import { Injectable } from '@angular/core'; -import { defaultHttpOptions } from './http-utils'; +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; import { forkJoin, Observable, of } from 'rxjs/index'; import { HttpClient } from '@angular/common/http'; import { EntityId } from '@shared/models/id/entity-id'; @@ -31,22 +31,30 @@ export class AttributeService { ) { } public getEntityAttributes(entityId: EntityId, attributeScope: AttributeScope, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + config?: RequestConfig): Observable> { return this.http.get>(`/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/values/attributes/` + `${attributeScope}`, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } public deleteEntityAttributes(entityId: EntityId, attributeScope: AttributeScope, attributes: Array, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + config?: RequestConfig): Observable { const keys = attributes.map(attribute => attribute.key).join(','); return this.http.delete(`/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/${attributeScope}` + `?keys=${keys}`, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); + } + + public deleteEntityTimeseries(entityId: EntityId, timeseries: Array, + config?: RequestConfig): Observable { + const keys = timeseries.map(attribute => attribute.key).join(','); + return this.http.delete(`/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/timeseries/delete` + + `?keys=${keys}`, + defaultHttpOptionsFromConfig(config)); } public saveEntityAttributes(entityId: EntityId, attributeScope: AttributeScope, attributes: Array, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + config?: RequestConfig): Observable { const attributesData: {[key: string]: any} = {}; const deleteAttributes: AttributeData[] = []; attributes.forEach((attribute) => { @@ -58,17 +66,45 @@ export class AttributeService { }); let deleteEntityAttributesObservable: Observable; if (deleteAttributes.length) { - deleteEntityAttributesObservable = this.deleteEntityAttributes(entityId, attributeScope, deleteAttributes); + deleteEntityAttributesObservable = this.deleteEntityAttributes(entityId, attributeScope, deleteAttributes, config); } else { deleteEntityAttributesObservable = of(null); } let saveEntityAttributesObservable: Observable; if (Object.keys(attributesData).length) { saveEntityAttributesObservable = this.http.post(`/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/${attributeScope}`, - attributesData, defaultHttpOptions(ignoreLoading, ignoreErrors)); + attributesData, defaultHttpOptionsFromConfig(config)); } else { saveEntityAttributesObservable = of(null); } return forkJoin(saveEntityAttributesObservable, deleteEntityAttributesObservable); } + + public saveEntityTimeseries(entityId: EntityId, timeseriesScope: string, timeseries: Array, + config?: RequestConfig): Observable { + const timeseriesData: {[key: string]: any} = {}; + const deleteTimeseries: AttributeData[] = []; + timeseries.forEach((attribute) => { + if (attribute.value !== null) { + timeseriesData[attribute.key] = attribute.value; + } else { + deleteTimeseries.push(attribute); + } + }); + let deleteEntityTimeseriesObservable: Observable; + if (deleteTimeseries.length) { + deleteEntityTimeseriesObservable = this.deleteEntityTimeseries(entityId, deleteTimeseries, config); + } else { + deleteEntityTimeseriesObservable = of(null); + } + let saveEntityTimeseriesObservable: Observable; + if (Object.keys(timeseriesData).length) { + saveEntityTimeseriesObservable = + this.http.post(`/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/timeseries/${timeseriesScope}`, + timeseriesData, defaultHttpOptionsFromConfig(config)); + } else { + saveEntityTimeseriesObservable = of(null); + } + return forkJoin(saveEntityTimeseriesObservable, deleteEntityTimeseriesObservable); + } } diff --git a/ui-ngx/src/app/core/http/audit-log.service.ts b/ui-ngx/src/app/core/http/audit-log.service.ts index 5c98d849e8..3621ac6b04 100644 --- a/ui-ngx/src/app/core/http/audit-log.service.ts +++ b/ui-ngx/src/app/core/http/audit-log.service.ts @@ -15,7 +15,7 @@ /// import { Injectable } from '@angular/core'; -import { defaultHttpOptions } from './http-utils'; +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; import { Observable } from 'rxjs/index'; import { HttpClient } from '@angular/common/http'; import { PageLink, TimePageLink } from '@shared/models/page/page-link'; @@ -33,27 +33,27 @@ export class AuditLogService { ) { } public getAuditLogs(pageLink: TimePageLink, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + config?: RequestConfig): Observable> { return this.http.get>(`/api/audit/logs${pageLink.toQuery()}`, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } public getAuditLogsByCustomerId(customerId: string, pageLink: TimePageLink, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + config?: RequestConfig): Observable> { return this.http.get>(`/api/audit/logs/customer/${customerId}${pageLink.toQuery()}`, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } public getAuditLogsByUserId(userId: string, pageLink: TimePageLink, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + config?: RequestConfig): Observable> { return this.http.get>(`/api/audit/logs/user/${userId}${pageLink.toQuery()}`, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } public getAuditLogsByEntityId(entityId: EntityId, pageLink: TimePageLink, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + config?: RequestConfig): Observable> { return this.http.get>(`/api/audit/logs/entity/${entityId.entityType}/${entityId.id}${pageLink.toQuery()}`, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } } diff --git a/ui-ngx/src/app/core/http/customer.service.ts b/ui-ngx/src/app/core/http/customer.service.ts index f36d2dcf41..037ce99e0b 100644 --- a/ui-ngx/src/app/core/http/customer.service.ts +++ b/ui-ngx/src/app/core/http/customer.service.ts @@ -15,7 +15,7 @@ /// import { Injectable } from '@angular/core'; -import { defaultHttpOptions } from './http-utils'; +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; import { Observable } from 'rxjs/index'; import { HttpClient } from '@angular/common/http'; import { PageLink } from '@shared/models/page/page-link'; @@ -31,22 +31,21 @@ export class CustomerService { private http: HttpClient ) { } - public getCustomers(pageLink: PageLink, ignoreErrors: boolean = false, - ignoreLoading: boolean = false): Observable> { + public getCustomers(pageLink: PageLink, config?: RequestConfig): Observable> { return this.http.get>(`/api/customers${pageLink.toQuery()}`, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } - public getCustomer(customerId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { - return this.http.get(`/api/customer/${customerId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public getCustomer(customerId: string, config?: RequestConfig): Observable { + return this.http.get(`/api/customer/${customerId}`, defaultHttpOptionsFromConfig(config)); } - public saveCustomer(customer: Customer, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { - return this.http.post('/api/customer', customer, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public saveCustomer(customer: Customer, config?: RequestConfig): Observable { + return this.http.post('/api/customer', customer, defaultHttpOptionsFromConfig(config)); } - public deleteCustomer(customerId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { - return this.http.delete(`/api/customer/${customerId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public deleteCustomer(customerId: string, config?: RequestConfig) { + return this.http.delete(`/api/customer/${customerId}`, defaultHttpOptionsFromConfig(config)); } } diff --git a/ui-ngx/src/app/core/http/dashboard.service.ts b/ui-ngx/src/app/core/http/dashboard.service.ts index cff0385833..96b714a1c1 100644 --- a/ui-ngx/src/app/core/http/dashboard.service.ts +++ b/ui-ngx/src/app/core/http/dashboard.service.ts @@ -15,7 +15,7 @@ /// import {Inject, Injectable} from '@angular/core'; -import {defaultHttpOptions} from './http-utils'; +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; import { Observable, ReplaySubject, Subject } from 'rxjs/index'; import {HttpClient} from '@angular/common/http'; import {PageLink} from '@shared/models/page/page-link'; @@ -50,77 +50,75 @@ export class DashboardService { ); } - public getTenantDashboards(pageLink: PageLink, ignoreErrors: boolean = false, - ignoreLoading: boolean = false): Observable> { + public getTenantDashboards(pageLink: PageLink, config?: RequestConfig): Observable> { return this.http.get>(`/api/tenant/dashboards${pageLink.toQuery()}`, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } - public getTenantDashboardsByTenantId(tenantId: string, pageLink: PageLink, ignoreErrors: boolean = false, - ignoreLoading: boolean = false): Observable> { + public getTenantDashboardsByTenantId(tenantId: string, pageLink: PageLink, + config?: RequestConfig): Observable> { return this.http.get>(`/api/tenant/${tenantId}/dashboards${pageLink.toQuery()}`, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } - public getCustomerDashboards(customerId: string, pageLink: PageLink, ignoreErrors: boolean = false, - ignoreLoading: boolean = false): Observable> { + public getCustomerDashboards(customerId: string, pageLink: PageLink, config?: RequestConfig): Observable> { return this.http.get>(`/api/customer/${customerId}/dashboards${pageLink.toQuery()}`, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } - public getDashboard(dashboardId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { - return this.http.get(`/api/dashboard/${dashboardId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public getDashboard(dashboardId: string, config?: RequestConfig): Observable { + return this.http.get(`/api/dashboard/${dashboardId}`, defaultHttpOptionsFromConfig(config)); } - public getDashboardInfo(dashboardId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { - return this.http.get(`/api/dashboard/info/${dashboardId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public getDashboardInfo(dashboardId: string, config?: RequestConfig): Observable { + return this.http.get(`/api/dashboard/info/${dashboardId}`, defaultHttpOptionsFromConfig(config)); } - public saveDashboard(dashboard: Dashboard, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { - return this.http.post('/api/dashboard', dashboard, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public saveDashboard(dashboard: Dashboard, config?: RequestConfig): Observable { + return this.http.post('/api/dashboard', dashboard, defaultHttpOptionsFromConfig(config)); } - public deleteDashboard(dashboardId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { - return this.http.delete(`/api/dashboard/${dashboardId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public deleteDashboard(dashboardId: string, config?: RequestConfig) { + return this.http.delete(`/api/dashboard/${dashboardId}`, defaultHttpOptionsFromConfig(config)); } public assignDashboardToCustomer(customerId: string, dashboardId: string, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + config?: RequestConfig): Observable { return this.http.post(`/api/customer/${customerId}/dashboard/${dashboardId}`, - null, defaultHttpOptions(ignoreLoading, ignoreErrors)); + null, defaultHttpOptionsFromConfig(config)); } public unassignDashboardFromCustomer(customerId: string, dashboardId: string, - ignoreErrors: boolean = false, ignoreLoading: boolean = false) { - return this.http.delete(`/api/customer/${customerId}/dashboard/${dashboardId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + config?: RequestConfig) { + return this.http.delete(`/api/customer/${customerId}/dashboard/${dashboardId}`, defaultHttpOptionsFromConfig(config)); } - public makeDashboardPublic(dashboardId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + public makeDashboardPublic(dashboardId: string, config?: RequestConfig): Observable { return this.http.post(`/api/customer/public/dashboard/${dashboardId}`, null, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } - public makeDashboardPrivate(dashboardId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + public makeDashboardPrivate(dashboardId: string, config?: RequestConfig): Observable { return this.http.delete(`/api/customer/public/dashboard/${dashboardId}`, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } public updateDashboardCustomers(dashboardId: string, customerIds: Array, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + config?: RequestConfig): Observable { return this.http.post(`/api/dashboard/${dashboardId}/customers`, customerIds, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } public addDashboardCustomers(dashboardId: string, customerIds: Array, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + config?: RequestConfig): Observable { return this.http.post(`/api/dashboard/${dashboardId}/customers/add`, customerIds, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } public removeDashboardCustomers(dashboardId: string, customerIds: Array, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + config?: RequestConfig): Observable { return this.http.post(`/api/dashboard/${dashboardId}/customers/remove`, customerIds, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } public getPublicDashboardLink(dashboard: DashboardInfo): string | null { diff --git a/ui-ngx/src/app/core/http/device.service.ts b/ui-ngx/src/app/core/http/device.service.ts index d1b0a48464..d09fcd72c1 100644 --- a/ui-ngx/src/app/core/http/device.service.ts +++ b/ui-ngx/src/app/core/http/device.service.ts @@ -15,7 +15,7 @@ /// import { Injectable } from '@angular/core'; -import { defaultHttpOptions } from './http-utils'; +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; import { Observable, Subject, ReplaySubject } from 'rxjs/index'; import { HttpClient } from '@angular/common/http'; import { PageLink } from '@shared/models/page/page-link'; @@ -36,44 +36,43 @@ export class DeviceService { private http: HttpClient ) { } - public getTenantDeviceInfos(pageLink: PageLink, type: string = '', ignoreErrors: boolean = false, - ignoreLoading: boolean = false): Observable> { + public getTenantDeviceInfos(pageLink: PageLink, type: string = '', + config?: RequestConfig): Observable> { return this.http.get>(`/api/tenant/deviceInfos${pageLink.toQuery()}&type=${type}`, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } - public getCustomerDeviceInfos(customerId: string, pageLink: PageLink, type: string = '', ignoreErrors: boolean = false, - ignoreLoading: boolean = false): Observable> { + public getCustomerDeviceInfos(customerId: string, pageLink: PageLink, type: string = '', + config?: RequestConfig): Observable> { return this.http.get>(`/api/customer/${customerId}/deviceInfos${pageLink.toQuery()}&type=${type}`, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } - public getDevice(deviceId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { - return this.http.get(`/api/device/${deviceId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public getDevice(deviceId: string, config?: RequestConfig): Observable { + return this.http.get(`/api/device/${deviceId}`, defaultHttpOptionsFromConfig(config)); } - public getDevices(deviceIds: Array, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { - return this.http.get>(`/api/devices?deviceIds=${deviceIds.join(',')}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public getDevices(deviceIds: Array, config?: RequestConfig): Observable> { + return this.http.get>(`/api/devices?deviceIds=${deviceIds.join(',')}`, defaultHttpOptionsFromConfig(config)); } - public getDeviceInfo(deviceId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { - return this.http.get(`/api/device/info/${deviceId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public getDeviceInfo(deviceId: string, config?: RequestConfig): Observable { + return this.http.get(`/api/device/info/${deviceId}`, defaultHttpOptionsFromConfig(config)); } - public saveDevice(device: Device, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { - return this.http.post('/api/device', device, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public saveDevice(device: Device, config?: RequestConfig): Observable { + return this.http.post('/api/device', device, defaultHttpOptionsFromConfig(config)); } - public deleteDevice(deviceId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { - return this.http.delete(`/api/device/${deviceId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public deleteDevice(deviceId: string, config?: RequestConfig) { + return this.http.delete(`/api/device/${deviceId}`, defaultHttpOptionsFromConfig(config)); } - public getDeviceTypes(ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { - return this.http.get>('/api/device/types', defaultHttpOptions(ignoreLoading, ignoreErrors)); + public getDeviceTypes(config?: RequestConfig): Observable> { + return this.http.get>('/api/device/types', defaultHttpOptionsFromConfig(config)); } - public getDeviceCredentials(deviceId: string, sync: boolean = false, ignoreErrors: boolean = false, - ignoreLoading: boolean = false): Observable { + public getDeviceCredentials(deviceId: string, sync: boolean = false, config?: RequestConfig): Observable { const url = `/api/device/${deviceId}/credentials`; if (sync) { const responseSubject = new ReplaySubject(); @@ -93,31 +92,34 @@ export class DeviceService { } return responseSubject.asObservable(); } else { - return this.http.get(url, defaultHttpOptions(ignoreLoading, ignoreErrors)); + return this.http.get(url, defaultHttpOptionsFromConfig(config)); } } - public saveDeviceCredentials(deviceCredentials: DeviceCredentials, ignoreErrors: boolean = false, - ignoreLoading: boolean = false): Observable { - return this.http.post('/api/device/credentials', deviceCredentials, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public saveDeviceCredentials(deviceCredentials: DeviceCredentials, config?: RequestConfig): Observable { + return this.http.post('/api/device/credentials', deviceCredentials, defaultHttpOptionsFromConfig(config)); } - public makeDevicePublic(deviceId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { - return this.http.post(`/api/customer/public/device/${deviceId}`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public makeDevicePublic(deviceId: string, config?: RequestConfig): Observable { + return this.http.post(`/api/customer/public/device/${deviceId}`, null, defaultHttpOptionsFromConfig(config)); } public assignDeviceToCustomer(customerId: string, deviceId: string, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { - return this.http.post(`/api/customer/${customerId}/device/${deviceId}`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); + config?: RequestConfig): Observable { + return this.http.post(`/api/customer/${customerId}/device/${deviceId}`, null, defaultHttpOptionsFromConfig(config)); } - public unassignDeviceFromCustomer(deviceId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { - return this.http.delete(`/api/customer/device/${deviceId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public unassignDeviceFromCustomer(deviceId: string, config?: RequestConfig) { + return this.http.delete(`/api/customer/device/${deviceId}`, defaultHttpOptionsFromConfig(config)); } public findByQuery(query: DeviceSearchQuery, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { - return this.http.post>('/api/devices', query, defaultHttpOptions(ignoreLoading, ignoreErrors)); + config?: RequestConfig): Observable> { + return this.http.post>('/api/devices', query, defaultHttpOptionsFromConfig(config)); + } + + public findByName(deviceName: string, config?: RequestConfig): Observable { + return this.http.get(`/api/tenant/devices?deviceName=${deviceName}`, defaultHttpOptionsFromConfig(config)); } } diff --git a/ui-ngx/src/app/core/http/entity-relation.service.ts b/ui-ngx/src/app/core/http/entity-relation.service.ts index b11b67ec4a..091ce6a980 100644 --- a/ui-ngx/src/app/core/http/entity-relation.service.ts +++ b/ui-ngx/src/app/core/http/entity-relation.service.ts @@ -15,7 +15,7 @@ /// import { Injectable } from '@angular/core'; -import { defaultHttpOptions } from './http-utils'; +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; import { Observable } from 'rxjs/index'; import { HttpClient } from '@angular/common/http'; import { EntityRelation, EntityRelationInfo, EntityRelationsQuery } from '@shared/models/relation.models'; @@ -30,84 +30,84 @@ export class EntityRelationService { private http: HttpClient ) { } - public saveRelation(relation: EntityRelation, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { - return this.http.post('/api/relation', relation, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public saveRelation(relation: EntityRelation, config?: RequestConfig): Observable { + return this.http.post('/api/relation', relation, defaultHttpOptionsFromConfig(config)); } public deleteRelation(fromId: EntityId, relationType: string, toId: EntityId, - ignoreErrors: boolean = false, ignoreLoading: boolean = false) { + config?: RequestConfig) { return this.http.delete(`/api/relation?fromId=${fromId.id}&fromType=${fromId.entityType}` + `&relationType=${relationType}&toId=${toId.id}&toType=${toId.entityType}`, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } public deleteRelations(entityId: EntityId, - ignoreErrors: boolean = false, ignoreLoading: boolean = false) { + config?: RequestConfig) { return this.http.delete(`/api/relations?entityId=${entityId.id}&entityType=${entityId.entityType}`, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } public getRelation(fromId: EntityId, relationType: string, toId: EntityId, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + config?: RequestConfig): Observable { return this.http.get(`/api/relation?fromId=${fromId.id}&fromType=${fromId.entityType}` + `&relationType=${relationType}&toId=${toId.id}&toType=${toId.entityType}`, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } public findByFrom(fromId: EntityId, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + config?: RequestConfig): Observable> { return this.http.get>( `/api/relations?fromId=${fromId.id}&fromType=${fromId.entityType}`, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } public findInfoByFrom(fromId: EntityId, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + config?: RequestConfig): Observable> { return this.http.get>( `/api/relations/info?fromId=${fromId.id}&fromType=${fromId.entityType}`, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } public findByFromAndType(fromId: EntityId, relationType: string, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + config?: RequestConfig): Observable> { return this.http.get>( `/api/relations?fromId=${fromId.id}&fromType=${fromId.entityType}&relationType=${relationType}`, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } public findByTo(toId: EntityId, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + config?: RequestConfig): Observable> { return this.http.get>( `/api/relations?toId=${toId.id}&toType=${toId.entityType}`, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } public findInfoByTo(toId: EntityId, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + config?: RequestConfig): Observable> { return this.http.get>( `/api/relations/info?toId=${toId.id}&toType=${toId.entityType}`, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } public findByToAndType(toId: EntityId, relationType: string, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + config?: RequestConfig): Observable> { return this.http.get>( `/api/relations?toId=${toId.id}&toType=${toId.entityType}&relationType=${relationType}`, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } public findByQuery(query: EntityRelationsQuery, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + config?: RequestConfig): Observable> { return this.http.post>( '/api/relations', query, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } public findInfoByQuery(query: EntityRelationsQuery, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + config?: RequestConfig): Observable> { return this.http.post>( '/api/relations/info', query, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } } diff --git a/ui-ngx/src/app/core/http/entity-view.service.ts b/ui-ngx/src/app/core/http/entity-view.service.ts index 353eae5ed0..4735ffd102 100644 --- a/ui-ngx/src/app/core/http/entity-view.service.ts +++ b/ui-ngx/src/app/core/http/entity-view.service.ts @@ -15,7 +15,7 @@ /// import {Injectable} from '@angular/core'; -import {defaultHttpOptions} from './http-utils'; +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; import {Observable} from 'rxjs/index'; import {HttpClient} from '@angular/common/http'; import {PageLink} from '@shared/models/page/page-link'; @@ -33,57 +33,55 @@ export class EntityViewService { private http: HttpClient ) { } - public getTenantEntityViewInfos(pageLink: PageLink, type: string = '', ignoreErrors: boolean = false, - ignoreLoading: boolean = false): Observable> { + public getTenantEntityViewInfos(pageLink: PageLink, type: string = '', config?: RequestConfig): Observable> { return this.http.get>(`/api/tenant/entityViewInfos${pageLink.toQuery()}&type=${type}`, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } - public getCustomerEntityViewInfos(customerId: string, pageLink: PageLink, type: string = '', ignoreErrors: boolean = false, - ignoreLoading: boolean = false): Observable> { + public getCustomerEntityViewInfos(customerId: string, pageLink: PageLink, type: string = '', + config?: RequestConfig): Observable> { return this.http.get>(`/api/customer/${customerId}/entityViewInfos${pageLink.toQuery()}&type=${type}`, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } - public getEntityView(entityViewId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { - return this.http.get(`/api/entityView/${entityViewId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public getEntityView(entityViewId: string, config?: RequestConfig): Observable { + return this.http.get(`/api/entityView/${entityViewId}`, defaultHttpOptionsFromConfig(config)); } - public getEntityViewInfo(entityViewId: string, ignoreErrors: boolean = false, - ignoreLoading: boolean = false): Observable { - return this.http.get(`/api/entityView/info/${entityViewId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public getEntityViewInfo(entityViewId: string, config?: RequestConfig): Observable { + return this.http.get(`/api/entityView/info/${entityViewId}`, defaultHttpOptionsFromConfig(config)); } - public saveEntityView(entityView: EntityView, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { - return this.http.post('/api/entityView', entityView, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public saveEntityView(entityView: EntityView, config?: RequestConfig): Observable { + return this.http.post('/api/entityView', entityView, defaultHttpOptionsFromConfig(config)); } - public deleteEntityView(entityViewId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { - return this.http.delete(`/api/entityView/${entityViewId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public deleteEntityView(entityViewId: string, config?: RequestConfig) { + return this.http.delete(`/api/entityView/${entityViewId}`, defaultHttpOptionsFromConfig(config)); } - public getEntityViewTypes(ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { - return this.http.get>('/api/entityView/types', defaultHttpOptions(ignoreLoading, ignoreErrors)); + public getEntityViewTypes(config?: RequestConfig): Observable> { + return this.http.get>('/api/entityView/types', defaultHttpOptionsFromConfig(config)); } - public makeEntityViewPublic(entityViewId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + public makeEntityViewPublic(entityViewId: string, config?: RequestConfig): Observable { return this.http.post(`/api/customer/public/entityView/${entityViewId}`, null, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } public assignEntityViewToCustomer(customerId: string, entityViewId: string, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + config?: RequestConfig): Observable { return this.http.post(`/api/customer/${customerId}/entityView/${entityViewId}`, null, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } - public unassignEntityViewFromCustomer(entityViewId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { - return this.http.delete(`/api/customer/entityView/${entityViewId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public unassignEntityViewFromCustomer(entityViewId: string, config?: RequestConfig) { + return this.http.delete(`/api/customer/entityView/${entityViewId}`, defaultHttpOptionsFromConfig(config)); } public findByQuery(query: EntityViewSearchQuery, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { - return this.http.post>('/api/entityViews', query, defaultHttpOptions(ignoreLoading, ignoreErrors)); + config?: RequestConfig): Observable> { + return this.http.post>('/api/entityViews', query, defaultHttpOptionsFromConfig(config)); } } diff --git a/ui-ngx/src/app/core/http/entity.service.ts b/ui-ngx/src/app/core/http/entity.service.ts index 644bede83c..98deee4337 100644 --- a/ui-ngx/src/app/core/http/entity.service.ts +++ b/ui-ngx/src/app/core/http/entity.service.ts @@ -37,14 +37,14 @@ import { catchError, concatMap, expand, map, mergeMap, toArray } from 'rxjs/oper import { Customer } from '@app/shared/models/customer.model'; import { AssetService } from '@core/http/asset.service'; import { EntityViewService } from '@core/http/entity-view.service'; -import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; -import { defaultHttpOptions } from '@core/http/http-utils'; +import { AttributeScope, DataKeyType } from '@shared/models/telemetry/telemetry.models'; +import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils'; import { RuleChainService } from '@core/http/rule-chain.service'; import { AliasInfo, StateParams, SubscriptionInfo } from '@core/api/widget-api.models'; import { Datasource, DatasourceType, KeyInfo } from '@app/shared/models/widget.models'; import { UtilsService } from '@core/services/utils.service'; import { AliasFilterType, EntityAlias, EntityAliasFilter, EntityAliasFilterResult } from '@shared/models/alias.models'; -import { EntityInfo } from '@shared/models/entity.models'; +import { EntityInfo, ImportEntityData, ImportEntitiesResultInfo } from '@shared/models/entity.models'; import { EntityRelationInfo, EntityRelationsQuery, @@ -53,9 +53,10 @@ import { } from '@shared/models/relation.models'; import { EntityRelationService } from '@core/http/entity-relation.service'; import { isDefined } from '../utils'; -import { AssetSearchQuery } from '@shared/models/asset.models'; -import { DeviceSearchQuery } from '@shared/models/device.models'; +import { Asset, AssetSearchQuery } from '@shared/models/asset.models'; +import { Device, DeviceCredentialsType, DeviceSearchQuery } from '@shared/models/device.models'; import { EntityViewSearchQuery } from '@shared/models/entity-view.models'; +import { AttributeService } from '@core/http/attribute.service'; @Injectable({ providedIn: 'root' @@ -74,37 +75,38 @@ export class EntityService { private ruleChainService: RuleChainService, private dashboardService: DashboardService, private entityRelationService: EntityRelationService, + private attributeService: AttributeService, private utils: UtilsService ) { } private getEntityObservable(entityType: EntityType, entityId: string, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + config?: RequestConfig): Observable> { let observable: Observable>; switch (entityType) { case EntityType.DEVICE: - observable = this.deviceService.getDevice(entityId, ignoreErrors, ignoreLoading); + observable = this.deviceService.getDevice(entityId, config); break; case EntityType.ASSET: - observable = this.assetService.getAsset(entityId, ignoreErrors, ignoreLoading); + observable = this.assetService.getAsset(entityId, config); break; case EntityType.ENTITY_VIEW: - observable = this.entityViewService.getEntityView(entityId, ignoreErrors, ignoreLoading); + observable = this.entityViewService.getEntityView(entityId, config); break; case EntityType.TENANT: - observable = this.tenantService.getTenant(entityId, ignoreErrors, ignoreLoading); + observable = this.tenantService.getTenant(entityId, config); break; case EntityType.CUSTOMER: - observable = this.customerService.getCustomer(entityId, ignoreErrors, ignoreLoading); + observable = this.customerService.getCustomer(entityId, config); break; case EntityType.DASHBOARD: - observable = this.dashboardService.getDashboardInfo(entityId, ignoreErrors, ignoreLoading); + observable = this.dashboardService.getDashboardInfo(entityId, config); break; case EntityType.USER: - observable = this.userService.getUser(entityId, ignoreErrors, ignoreLoading); + observable = this.userService.getUser(entityId, config); break; case EntityType.RULE_CHAIN: - observable = this.ruleChainService.getRuleChain(entityId, ignoreErrors, ignoreLoading); + observable = this.ruleChainService.getRuleChain(entityId, config); break; case EntityType.ALARM: console.error('Get Alarm Entity is not implemented!'); @@ -113,8 +115,8 @@ export class EntityService { return observable; } public getEntity(entityType: EntityType, entityId: string, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { - const entityObservable = this.getEntityObservable(entityType, entityId, ignoreErrors, ignoreLoading); + config?: RequestConfig): Observable> { + const entityObservable = this.getEntityObservable(entityType, entityId, config); if (entityObservable) { return entityObservable; } else { @@ -146,38 +148,38 @@ export class EntityService { private getEntitiesObservable(entityType: EntityType, entityIds: Array, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable>> { + config?: RequestConfig): Observable>> { let observable: Observable>>; switch (entityType) { case EntityType.DEVICE: - observable = this.deviceService.getDevices(entityIds, ignoreErrors, ignoreLoading); + observable = this.deviceService.getDevices(entityIds, config); break; case EntityType.ASSET: - observable = this.assetService.getAssets(entityIds, ignoreErrors, ignoreLoading); + observable = this.assetService.getAssets(entityIds, config); break; case EntityType.ENTITY_VIEW: observable = this.getEntitiesByIdsObservable( - (id) => this.entityViewService.getEntityView(id, ignoreErrors, ignoreLoading), + (id) => this.entityViewService.getEntityView(id, config), entityIds); break; case EntityType.TENANT: observable = this.getEntitiesByIdsObservable( - (id) => this.tenantService.getTenant(id, ignoreErrors, ignoreLoading), + (id) => this.tenantService.getTenant(id, config), entityIds); break; case EntityType.CUSTOMER: observable = this.getEntitiesByIdsObservable( - (id) => this.customerService.getCustomer(id, ignoreErrors, ignoreLoading), + (id) => this.customerService.getCustomer(id, config), entityIds); break; case EntityType.DASHBOARD: observable = this.getEntitiesByIdsObservable( - (id) => this.dashboardService.getDashboardInfo(id, ignoreErrors, ignoreLoading), + (id) => this.dashboardService.getDashboardInfo(id, config), entityIds); break; case EntityType.USER: observable = this.getEntitiesByIdsObservable( - (id) => this.userService.getUser(id, ignoreErrors, ignoreLoading), + (id) => this.userService.getUser(id, config), entityIds); break; case EntityType.ALARM: @@ -188,8 +190,8 @@ export class EntityService { } public getEntities(entityType: EntityType, entityIds: Array, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable>> { - const entitiesObservable = this.getEntitiesObservable(entityType, entityIds, ignoreErrors, ignoreLoading); + config?: RequestConfig): Observable>> { + const entitiesObservable = this.getEntitiesObservable(entityType, entityIds, config); if (entitiesObservable) { return entitiesObservable; } else { @@ -198,11 +200,10 @@ export class EntityService { } private getSingleTenantByPageLinkObservable(pageLink: PageLink, - ignoreErrors: boolean = false, - ignoreLoading: boolean = false): Observable> { + config?: RequestConfig): Observable> { const authUser = getCurrentAuthUser(this.store); const tenantId = authUser.tenantId; - return this.tenantService.getTenant(tenantId, ignoreErrors, ignoreLoading).pipe( + return this.tenantService.getTenant(tenantId, config).pipe( map((tenant) => { const result = { data: [], @@ -221,11 +222,10 @@ export class EntityService { } private getSingleCustomerByPageLinkObservable(pageLink: PageLink, - ignoreErrors: boolean = false, - ignoreLoading: boolean = false): Observable> { + config?: RequestConfig): Observable> { const authUser = getCurrentAuthUser(this.store); const customerId = authUser.customerId; - return this.customerService.getCustomer(customerId, ignoreErrors, ignoreLoading).pipe( + return this.customerService.getCustomer(customerId, config).pipe( map((customer) => { const result = { data: [], @@ -244,8 +244,7 @@ export class EntityService { } private getEntitiesByPageLinkObservable(entityType: EntityType, pageLink: PageLink, subType: string = '', - ignoreErrors: boolean = false, - ignoreLoading: boolean = false): Observable>> { + config?: RequestConfig): Observable>> { let entitiesObservable: Observable>>; const authUser = getCurrentAuthUser(this.store); const customerId = authUser.customerId; @@ -253,54 +252,54 @@ export class EntityService { case EntityType.DEVICE: pageLink.sortOrder.property = 'name'; if (authUser.authority === Authority.CUSTOMER_USER) { - entitiesObservable = this.deviceService.getCustomerDeviceInfos(customerId, pageLink, subType, ignoreErrors, ignoreLoading); + entitiesObservable = this.deviceService.getCustomerDeviceInfos(customerId, pageLink, subType, config); } else { - entitiesObservable = this.deviceService.getTenantDeviceInfos(pageLink, subType, ignoreErrors, ignoreLoading); + entitiesObservable = this.deviceService.getTenantDeviceInfos(pageLink, subType, config); } break; case EntityType.ASSET: pageLink.sortOrder.property = 'name'; if (authUser.authority === Authority.CUSTOMER_USER) { - entitiesObservable = this.assetService.getCustomerAssetInfos(customerId, pageLink, subType, ignoreErrors, ignoreLoading); + entitiesObservable = this.assetService.getCustomerAssetInfos(customerId, pageLink, subType, config); } else { - entitiesObservable = this.assetService.getTenantAssetInfos(pageLink, subType, ignoreErrors, ignoreLoading); + entitiesObservable = this.assetService.getTenantAssetInfos(pageLink, subType, config); } break; case EntityType.ENTITY_VIEW: pageLink.sortOrder.property = 'name'; if (authUser.authority === Authority.CUSTOMER_USER) { entitiesObservable = this.entityViewService.getCustomerEntityViewInfos(customerId, pageLink, - subType, ignoreErrors, ignoreLoading); + subType, config); } else { - entitiesObservable = this.entityViewService.getTenantEntityViewInfos(pageLink, subType, ignoreErrors, ignoreLoading); + entitiesObservable = this.entityViewService.getTenantEntityViewInfos(pageLink, subType, config); } break; case EntityType.TENANT: pageLink.sortOrder.property = 'title'; if (authUser.authority === Authority.TENANT_ADMIN) { - entitiesObservable = this.getSingleTenantByPageLinkObservable(pageLink, ignoreErrors, ignoreLoading); + entitiesObservable = this.getSingleTenantByPageLinkObservable(pageLink, config); } else { - entitiesObservable = this.tenantService.getTenants(pageLink, ignoreErrors, ignoreLoading); + entitiesObservable = this.tenantService.getTenants(pageLink, config); } break; case EntityType.CUSTOMER: pageLink.sortOrder.property = 'title'; if (authUser.authority === Authority.CUSTOMER_USER) { - entitiesObservable = this.getSingleCustomerByPageLinkObservable(pageLink, ignoreErrors, ignoreLoading); + entitiesObservable = this.getSingleCustomerByPageLinkObservable(pageLink, config); } else { - entitiesObservable = this.customerService.getCustomers(pageLink, ignoreErrors, ignoreLoading); + entitiesObservable = this.customerService.getCustomers(pageLink, config); } break; case EntityType.RULE_CHAIN: pageLink.sortOrder.property = 'name'; - entitiesObservable = this.ruleChainService.getRuleChains(pageLink, ignoreErrors, ignoreLoading); + entitiesObservable = this.ruleChainService.getRuleChains(pageLink, config); break; case EntityType.DASHBOARD: pageLink.sortOrder.property = 'title'; if (authUser.authority === Authority.CUSTOMER_USER) { - entitiesObservable = this.dashboardService.getCustomerDashboards(customerId, pageLink, ignoreErrors, ignoreLoading); + entitiesObservable = this.dashboardService.getCustomerDashboards(customerId, pageLink, config); } else { - entitiesObservable = this.dashboardService.getTenantDashboards(pageLink, ignoreErrors, ignoreLoading); + entitiesObservable = this.dashboardService.getTenantDashboards(pageLink, config); } break; case EntityType.USER: @@ -314,16 +313,15 @@ export class EntityService { } private getEntitiesByPageLink(entityType: EntityType, pageLink: PageLink, subType: string = '', - ignoreErrors: boolean = false, - ignoreLoading: boolean = false): Observable>> { + config?: RequestConfig): Observable>> { const entitiesObservable: Observable>> = - this.getEntitiesByPageLinkObservable(entityType, pageLink, subType, ignoreErrors, ignoreLoading); + this.getEntitiesByPageLinkObservable(entityType, pageLink, subType, config); if (entitiesObservable) { return entitiesObservable.pipe( expand((data) => { if (data.hasNext) { pageLink.page += 1; - return this.getEntitiesByPageLinkObservable(entityType, pageLink, subType, ignoreErrors, ignoreLoading); + return this.getEntitiesByPageLinkObservable(entityType, pageLink, subType, config); } else { return EMPTY; } @@ -339,19 +337,19 @@ export class EntityService { public getEntitiesByNameFilter(entityType: EntityType, entityNameFilter: string, pageSize: number, subType: string = '', - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable>> { + config?: RequestConfig): Observable>> { const pageLink = new PageLink(pageSize, 0, entityNameFilter, { property: 'name', direction: Direction.ASC }); if (pageSize === -1) { // all pageLink.pageSize = 100; - return this.getEntitiesByPageLink(entityType, pageLink, subType, ignoreErrors, ignoreLoading).pipe( + return this.getEntitiesByPageLink(entityType, pageLink, subType, config).pipe( map((data) => data && data.length ? data : null) ); } else { const entitiesObservable: Observable>> = - this.getEntitiesByPageLinkObservable(entityType, pageLink, subType, ignoreErrors, ignoreLoading); + this.getEntitiesByPageLinkObservable(entityType, pageLink, subType, config); if (entitiesObservable) { return entitiesObservable.pipe( map((data) => data && data.data.length ? data.data : null) @@ -506,7 +504,7 @@ export class EntityService { } public getEntityKeys(entityId: EntityId, query: string, type: DataKeyType, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + config?: RequestConfig): Observable> { let url = `/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/keys/`; if (type === DataKeyType.timeseries) { url += 'timeseries'; @@ -514,7 +512,7 @@ export class EntityService { url += 'attributes'; } return this.http.get>(url, - defaultHttpOptions(ignoreLoading, ignoreErrors)) + defaultHttpOptionsFromConfig(config)) .pipe( map( (dataKeys) => { @@ -549,7 +547,7 @@ export class EntityService { public createAlarmSourceFromSubscriptionInfo(subscriptionInfo: SubscriptionInfo): Observable { if (subscriptionInfo.entityId && subscriptionInfo.entityType) { return this.getEntity(subscriptionInfo.entityType, subscriptionInfo.entityId, - true, true).pipe( + {ignoreLoading: true, ignoreErrors: true}).pipe( map((entity) => { const alarmSource = this.createDatasourceFromSubscription(subscriptionInfo, entity); this.utils.generateColors([alarmSource]); @@ -594,7 +592,7 @@ export class EntityService { switch (filter.type) { case AliasFilterType.singleEntity: const aliasEntityId = this.resolveAliasEntityId(filter.singleEntity.entityType, filter.singleEntity.id); - return this.getEntity(aliasEntityId.entityType as EntityType, aliasEntityId.id, true, true).pipe( + return this.getEntity(aliasEntityId.entityType as EntityType, aliasEntityId.id, {ignoreLoading: true, ignoreErrors: true}).pipe( map((entity) => { result.entities = this.entitiesToEntitiesInfo([entity]); return result; @@ -602,7 +600,7 @@ export class EntityService { )); break; case AliasFilterType.entityList: - return this.getEntities(filter.entityType, filter.entityList, true, true).pipe( + return this.getEntities(filter.entityType, filter.entityList, {ignoreLoading: true, ignoreErrors: true}).pipe( map((entities) => { if (entities && entities.length || !failOnEmpty) { result.entities = this.entitiesToEntitiesInfo(entities); @@ -615,7 +613,7 @@ export class EntityService { break; case AliasFilterType.entityName: return this.getEntitiesByNameFilter(filter.entityType, filter.entityNameFilter, maxItems, - '', true, true).pipe( + '', {ignoreLoading: true, ignoreErrors: true}).pipe( map((entities) => { if (entities && entities.length || !failOnEmpty) { result.entities = this.entitiesToEntitiesInfo(entities); @@ -630,7 +628,7 @@ export class EntityService { case AliasFilterType.stateEntity: result.stateEntity = true; if (stateEntityId) { - return this.getEntity(stateEntityId.entityType as EntityType, stateEntityId.id, true, true).pipe( + return this.getEntity(stateEntityId.entityType as EntityType, stateEntityId.id, {ignoreLoading: true, ignoreErrors: true}).pipe( map((entity) => { result.entities = this.entitiesToEntitiesInfo([entity]); return result; @@ -642,7 +640,7 @@ export class EntityService { break; case AliasFilterType.assetType: return this.getEntitiesByNameFilter(EntityType.ASSET, filter.assetNameFilter, maxItems, - filter.assetType, true, true).pipe( + filter.assetType, {ignoreLoading: true, ignoreErrors: true}).pipe( map((entities) => { if (entities && entities.length || !failOnEmpty) { result.entities = this.entitiesToEntitiesInfo(entities); @@ -656,7 +654,7 @@ export class EntityService { break; case AliasFilterType.deviceType: return this.getEntitiesByNameFilter(EntityType.DEVICE, filter.deviceNameFilter, maxItems, - filter.deviceType, true, true).pipe( + filter.deviceType, {ignoreLoading: true, ignoreErrors: true}).pipe( map((entities) => { if (entities && entities.length || !failOnEmpty) { result.entities = this.entitiesToEntitiesInfo(entities); @@ -670,7 +668,7 @@ export class EntityService { break; case AliasFilterType.entityViewType: return this.getEntitiesByNameFilter(EntityType.ENTITY_VIEW, filter.entityViewNameFilter, maxItems, - filter.entityViewType, true, true).pipe( + filter.entityViewType, {ignoreLoading: true, ignoreErrors: true}).pipe( map((entities) => { if (entities && entities.length || !failOnEmpty) { result.entities = this.entitiesToEntitiesInfo(entities); @@ -704,7 +702,7 @@ export class EntityService { filters: filter.filters }; searchQuery.parameters.maxLevel = filter.maxLevel && filter.maxLevel > 0 ? filter.maxLevel : -1; - return this.entityRelationService.findInfoByQuery(searchQuery, true, true).pipe( + return this.entityRelationService.findInfoByQuery(searchQuery, {ignoreLoading: true, ignoreErrors: true}).pipe( mergeMap((allRelations) => { if (allRelations && allRelations.length || !failOnEmpty) { if (isDefined(maxItems) && maxItems > 0 && allRelations) { @@ -751,15 +749,15 @@ export class EntityService { if (filter.type === AliasFilterType.assetSearchQuery) { const assetSearchQuery = searchQuery as AssetSearchQuery; assetSearchQuery.assetTypes = filter.assetTypes; - findByQueryObservable = this.assetService.findByQuery(assetSearchQuery, true, true); + findByQueryObservable = this.assetService.findByQuery(assetSearchQuery, {ignoreLoading: true, ignoreErrors: true}); } else if (filter.type === AliasFilterType.deviceSearchQuery) { const deviceSearchQuery = searchQuery as DeviceSearchQuery; deviceSearchQuery.deviceTypes = filter.deviceTypes; - findByQueryObservable = this.deviceService.findByQuery(deviceSearchQuery, true, true); + findByQueryObservable = this.deviceService.findByQuery(deviceSearchQuery, {ignoreLoading: true, ignoreErrors: true}); } else if (filter.type === AliasFilterType.entityViewSearchQuery) { const entityViewSearchQuery = searchQuery as EntityViewSearchQuery; entityViewSearchQuery.entityViewTypes = filter.entityViewTypes; - findByQueryObservable = this.entityViewService.findByQuery(entityViewSearchQuery, true, true); + findByQueryObservable = this.entityViewService.findByQuery(entityViewSearchQuery, {ignoreLoading: true, ignoreErrors: true}); } return findByQueryObservable.pipe( map((entities) => { @@ -800,6 +798,115 @@ export class EntityService { ); } + public saveEntityParameters(entityType: EntityType, entityData: ImportEntityData, update: boolean, + config?: RequestConfig): Observable { + let saveEntityObservable: Observable>; + switch (entityType) { + case EntityType.DEVICE: + const device: Device = { + name: entityData.name, + type: entityData.type + }; + saveEntityObservable = this.deviceService.saveDevice(device, config); + break; + case EntityType.ASSET: + const asset: Asset = { + name: entityData.name, + type: entityData.type + }; + saveEntityObservable = this.assetService.saveAsset(asset, config); + break; + } + return saveEntityObservable.pipe( + mergeMap((entity) => { + return this.saveEntityData(entity.id, entityData, config).pipe( + map(() => { + return { create: { entity: 1 } } as ImportEntitiesResultInfo; + }), + catchError(err => of({ error: { entity: 1 } } as ImportEntitiesResultInfo)) + ); + }), + catchError(err => { + if (update) { + let findEntityObservable: Observable>; + switch (entityType) { + case EntityType.DEVICE: + findEntityObservable = this.deviceService.findByName(entityData.name, config); + break; + case EntityType.ASSET: + findEntityObservable = this.assetService.findByName(entityData.name, config); + break; + } + return findEntityObservable.pipe( + mergeMap((entity) => { + return this.saveEntityData(entity.id, entityData, config).pipe( + map(() => { + return { update: { entity: 1 } } as ImportEntitiesResultInfo; + }), + catchError(updateError => of({ error: { entity: 1 } } as ImportEntitiesResultInfo)) + ); + }), + catchError(findErr => of({ error: { entity: 1 } } as ImportEntitiesResultInfo)) + ); + } else { + return of({ error: { entity: 1 } } as ImportEntitiesResultInfo); + } + }) + ); + } + + public saveEntityData(entityId: EntityId, entityData: ImportEntityData, config?: RequestConfig): Observable { + const observables: Observable[] = []; + let observable: Observable; + if (entityData.accessToken && entityData.accessToken !== '') { + observable = this.deviceService.getDeviceCredentials(entityId.id, false, config).pipe( + mergeMap((credentials) => { + credentials.credentialsId = entityData.accessToken; + credentials.credentialsType = DeviceCredentialsType.ACCESS_TOKEN; + credentials.credentialsValue = null; + return this.deviceService.saveDeviceCredentials(credentials, config).pipe( + map(() => 'ok'), + catchError(err => of('error')) + ); + }) + ); + observables.push(observable); + } + if (entityData.attributes.shared && entityData.attributes.shared.length) { + observable = this.attributeService.saveEntityAttributes(entityId, AttributeScope.SHARED_SCOPE, + entityData.attributes.shared, config).pipe( + map(() => 'ok'), + catchError(err => of('error')) + ); + observables.push(observable); + } + if (entityData.attributes.server && entityData.attributes.server.length) { + observable = this.attributeService.saveEntityAttributes(entityId, AttributeScope.SERVER_SCOPE, + entityData.attributes.server, config).pipe( + map(() => 'ok'), + catchError(err => of('error')) + ); + observables.push(observable); + } + if (entityData.timeseries && entityData.timeseries.length) { + observable = this.attributeService.saveEntityTimeseries(entityId, 'time', entityData.timeseries, config).pipe( + map(() => 'ok'), + catchError(err => of('error')) + ); + observables.push(observable); + } + return forkJoin(observables).pipe( + map((response) => { + const hasError = response.filter((status) => status === 'error').length > 0; + if (hasError) { + throw Error(); + } else { + return response; + } + }) + ); + } + private entitiesToEntitiesInfo(entities: Array>): Array { const entitiesInfo = []; if (entities) { @@ -836,7 +943,7 @@ export class EntityService { private entityRelationInfoToEntityInfo(entityRelationInfo: EntityRelationInfo, direction: EntitySearchDirection): Observable { const entityId = direction === EntitySearchDirection.FROM ? entityRelationInfo.to : entityRelationInfo.from; - return this.getEntity(entityId.entityType as EntityType, entityId.id, true, true).pipe( + return this.getEntity(entityId.entityType as EntityType, entityId.id, {ignoreLoading: true, ignoreErrors: true}).pipe( map((entity) => { return this.entityToEntityInfo(entity); }) @@ -907,7 +1014,7 @@ export class EntityService { return of([entity]); } else { return this.getEntity(subscriptionInfo.entityType, subscriptionInfo.entityId, - true, true).pipe( + {ignoreLoading: true, ignoreErrors: true}).pipe( map((entity) => [entity]), catchError(e => of([])) ); @@ -916,12 +1023,13 @@ export class EntityService { let entitiesObservable: Observable>>; if (subscriptionInfo.entityName) { entitiesObservable = this.getEntitiesByNameFilter(subscriptionInfo.entityType, subscriptionInfo.entityName, - 1, null, true, true); + 1, null, {ignoreLoading: true, ignoreErrors: true}); } else if (subscriptionInfo.entityNamePrefix) { entitiesObservable = this.getEntitiesByNameFilter(subscriptionInfo.entityType, subscriptionInfo.entityNamePrefix, - 100, null, true, true); + 100, null, {ignoreLoading: true, ignoreErrors: true}); } else if (subscriptionInfo.entityIds) { - entitiesObservable = this.getEntities(subscriptionInfo.entityType, subscriptionInfo.entityIds, true, true); + entitiesObservable = this.getEntities(subscriptionInfo.entityType, subscriptionInfo.entityIds, + {ignoreLoading: true, ignoreErrors: true}); } return entitiesObservable.pipe( catchError(e => of([])) diff --git a/ui-ngx/src/app/core/http/event.service.ts b/ui-ngx/src/app/core/http/event.service.ts index fa1a519546..8224f8564a 100644 --- a/ui-ngx/src/app/core/http/event.service.ts +++ b/ui-ngx/src/app/core/http/event.service.ts @@ -15,7 +15,7 @@ /// import { Injectable } from '@angular/core'; -import { defaultHttpOptions } from './http-utils'; +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; import { Observable } from 'rxjs/index'; import { HttpClient } from '@angular/common/http'; import { TimePageLink } from '@shared/models/page/page-link'; @@ -33,9 +33,9 @@ export class EventService { ) { } public getEvents(entityId: EntityId, eventType: EventType | DebugEventType, tenantId: string, pageLink: TimePageLink, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + config?: RequestConfig): Observable> { return this.http.get>(`/api/events/${entityId.entityType}/${entityId.id}/${eventType}` + `${pageLink.toQuery()}&tenantId=${tenantId}`, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } } diff --git a/ui-ngx/src/app/core/http/http-utils.ts b/ui-ngx/src/app/core/http/http-utils.ts index 7a5b38cf7c..c288e6b48d 100644 --- a/ui-ngx/src/app/core/http/http-utils.ts +++ b/ui-ngx/src/app/core/http/http-utils.ts @@ -18,6 +18,19 @@ import { InterceptorHttpParams } from '../interceptors/interceptor-http-params'; import { HttpHeaders } from '@angular/common/http'; import { InterceptorConfig } from '../interceptors/interceptor-config'; +export interface RequestConfig { + ignoreLoading?: boolean; + ignoreErrors?: boolean; + resendRequest?: boolean; +} + +export function defaultHttpOptionsFromConfig(config?: RequestConfig) { + if (!config) { + config = {}; + } + return defaultHttpOptions(config.ignoreLoading, config.ignoreErrors, config.resendRequest); +} + export function defaultHttpOptions(ignoreLoading: boolean = false, ignoreErrors: boolean = false, resendRequest: boolean = false) { diff --git a/ui-ngx/src/app/core/http/rule-chain.service.ts b/ui-ngx/src/app/core/http/rule-chain.service.ts index 95cb1e9900..f1379c8e6a 100644 --- a/ui-ngx/src/app/core/http/rule-chain.service.ts +++ b/ui-ngx/src/app/core/http/rule-chain.service.ts @@ -15,7 +15,7 @@ /// import {Injectable} from '@angular/core'; -import {defaultHttpOptions} from './http-utils'; +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; import {Observable} from 'rxjs/index'; import {HttpClient} from '@angular/common/http'; import {PageLink} from '@shared/models/page/page-link'; @@ -31,26 +31,25 @@ export class RuleChainService { private http: HttpClient ) { } - public getRuleChains(pageLink: PageLink, ignoreErrors: boolean = false, - ignoreLoading: boolean = false): Observable> { + public getRuleChains(pageLink: PageLink, config?: RequestConfig): Observable> { return this.http.get>(`/api/ruleChains${pageLink.toQuery()}`, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } - public getRuleChain(ruleChainId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { - return this.http.get(`/api/ruleChain/${ruleChainId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public getRuleChain(ruleChainId: string, config?: RequestConfig): Observable { + return this.http.get(`/api/ruleChain/${ruleChainId}`, defaultHttpOptionsFromConfig(config)); } - public saveRuleChain(ruleChain: RuleChain, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { - return this.http.post('/api/ruleChain', ruleChain, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public saveRuleChain(ruleChain: RuleChain, config?: RequestConfig): Observable { + return this.http.post('/api/ruleChain', ruleChain, defaultHttpOptionsFromConfig(config)); } - public deleteRuleChain(ruleChainId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { - return this.http.delete(`/api/ruleChain/${ruleChainId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public deleteRuleChain(ruleChainId: string, config?: RequestConfig) { + return this.http.delete(`/api/ruleChain/${ruleChainId}`, defaultHttpOptionsFromConfig(config)); } - public setRootRuleChain(ruleChainId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { - return this.http.post(`/api/ruleChain/${ruleChainId}/root`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public setRootRuleChain(ruleChainId: string, config?: RequestConfig): Observable { + return this.http.post(`/api/ruleChain/${ruleChainId}/root`, null, defaultHttpOptionsFromConfig(config)); } } diff --git a/ui-ngx/src/app/core/http/tenant.service.ts b/ui-ngx/src/app/core/http/tenant.service.ts index 7574720ca0..4b144cef98 100644 --- a/ui-ngx/src/app/core/http/tenant.service.ts +++ b/ui-ngx/src/app/core/http/tenant.service.ts @@ -15,7 +15,7 @@ /// import { Injectable } from '@angular/core'; -import { defaultHttpOptions } from './http-utils'; +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; import { Observable } from 'rxjs/index'; import { HttpClient } from '@angular/common/http'; import { PageLink } from '@shared/models/page/page-link'; @@ -31,20 +31,20 @@ export class TenantService { private http: HttpClient ) { } - public getTenants(pageLink: PageLink, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { - return this.http.get>(`/api/tenants${pageLink.toQuery()}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public getTenants(pageLink: PageLink, config?: RequestConfig): Observable> { + return this.http.get>(`/api/tenants${pageLink.toQuery()}`, defaultHttpOptionsFromConfig(config)); } - public getTenant(tenantId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { - return this.http.get(`/api/tenant/${tenantId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public getTenant(tenantId: string, config?: RequestConfig): Observable { + return this.http.get(`/api/tenant/${tenantId}`, defaultHttpOptionsFromConfig(config)); } - public saveTenant(tenant: Tenant, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { - return this.http.post('/api/tenant', tenant, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public saveTenant(tenant: Tenant, config?: RequestConfig): Observable { + return this.http.post('/api/tenant', tenant, defaultHttpOptionsFromConfig(config)); } - public deleteTenant(tenantId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { - return this.http.delete(`/api/tenant/${tenantId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public deleteTenant(tenantId: string, config?: RequestConfig) { + return this.http.delete(`/api/tenant/${tenantId}`, defaultHttpOptionsFromConfig(config)); } } diff --git a/ui-ngx/src/app/core/http/user.service.ts b/ui-ngx/src/app/core/http/user.service.ts index 1e031289fd..85986b3da1 100644 --- a/ui-ngx/src/app/core/http/user.service.ts +++ b/ui-ngx/src/app/core/http/user.service.ts @@ -15,7 +15,7 @@ /// import { Injectable } from '@angular/core'; -import { defaultHttpOptions } from './http-utils'; +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; import { User } from '../../shared/models/user.model'; import { Observable } from 'rxjs/index'; import { HttpClient, HttpResponse } from '@angular/common/http'; @@ -33,39 +33,39 @@ export class UserService { ) { } public getTenantAdmins(tenantId: string, pageLink: PageLink, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + config?: RequestConfig): Observable> { return this.http.get>(`/api/tenant/${tenantId}/users${pageLink.toQuery()}`, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } public getCustomerUsers(customerId: string, pageLink: PageLink, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + config?: RequestConfig): Observable> { return this.http.get>(`/api/customer/${customerId}/users${pageLink.toQuery()}`, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } - public getUser(userId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { - return this.http.get(`/api/user/${userId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public getUser(userId: string, config?: RequestConfig): Observable { + return this.http.get(`/api/user/${userId}`, defaultHttpOptionsFromConfig(config)); } public saveUser(user: User, sendActivationMail: boolean = false, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + config?: RequestConfig): Observable { let url = '/api/user'; url += '?sendActivationMail=' + sendActivationMail; - return this.http.post(url, user, defaultHttpOptions(ignoreLoading, ignoreErrors)); + return this.http.post(url, user, defaultHttpOptionsFromConfig(config)); } - public deleteUser(userId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { - return this.http.delete(`/api/user/${userId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public deleteUser(userId: string, config?: RequestConfig) { + return this.http.delete(`/api/user/${userId}`, defaultHttpOptionsFromConfig(config)); } - public getActivationLink(userId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + public getActivationLink(userId: string, config?: RequestConfig): Observable { return this.http.get(`/api/user/${userId}/activationLink`, - {...{responseType: 'text'}, ...defaultHttpOptions(ignoreLoading, ignoreErrors)}); + {...{responseType: 'text'}, ...defaultHttpOptionsFromConfig(config)}); } - public sendActivationEmail(email: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { - return this.http.post(`/api/user/sendActivationMail?email=${email}`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); + public sendActivationEmail(email: string, config?: RequestConfig) { + return this.http.post(`/api/user/sendActivationMail?email=${email}`, null, defaultHttpOptionsFromConfig(config)); } } diff --git a/ui-ngx/src/app/core/http/widget.service.ts b/ui-ngx/src/app/core/http/widget.service.ts index a036511883..177c1eb3ba 100644 --- a/ui-ngx/src/app/core/http/widget.service.ts +++ b/ui-ngx/src/app/core/http/widget.service.ts @@ -15,7 +15,7 @@ /// import { Injectable } from '@angular/core'; -import { defaultHttpOptions } from './http-utils'; +import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; import { Observable, Subject, of, ReplaySubject } from 'rxjs/index'; import { HttpClient } from '@angular/common/http'; import { PageLink } from '@shared/models/page/page-link'; @@ -57,53 +57,49 @@ export class WidgetService { ); } - public getAllWidgetsBundles(ignoreErrors: boolean = false, - ignoreLoading: boolean = false): Observable> { - return this.loadWidgetsBundleCache(ignoreErrors, ignoreLoading).pipe( + public getAllWidgetsBundles(config?: RequestConfig): Observable> { + return this.loadWidgetsBundleCache(config).pipe( map(() => this.allWidgetsBundles) ); } - public getSystemWidgetsBundles(ignoreErrors: boolean = false, - ignoreLoading: boolean = false): Observable> { - return this.loadWidgetsBundleCache(ignoreErrors, ignoreLoading).pipe( + public getSystemWidgetsBundles(config?: RequestConfig): Observable> { + return this.loadWidgetsBundleCache(config).pipe( map(() => this.systemWidgetsBundles) ); } - public getTenantWidgetsBundles(ignoreErrors: boolean = false, - ignoreLoading: boolean = false): Observable> { - return this.loadWidgetsBundleCache(ignoreErrors, ignoreLoading).pipe( + public getTenantWidgetsBundles(config?: RequestConfig): Observable> { + return this.loadWidgetsBundleCache(config).pipe( map(() => this.tenantWidgetsBundles) ); } - public getWidgetBundles(pageLink: PageLink, ignoreErrors: boolean = false, - ignoreLoading: boolean = false): Observable> { + public getWidgetBundles(pageLink: PageLink, config?: RequestConfig): Observable> { return this.http.get>(`/api/widgetsBundles${pageLink.toQuery()}`, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } public getWidgetsBundle(widgetsBundleId: string, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { - return this.http.get(`/api/widgetsBundle/${widgetsBundleId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + config?: RequestConfig): Observable { + return this.http.get(`/api/widgetsBundle/${widgetsBundleId}`, defaultHttpOptionsFromConfig(config)); } public saveWidgetsBundle(widgetsBundle: WidgetsBundle, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + config?: RequestConfig): Observable { return this.http.post('/api/widgetsBundle', widgetsBundle, - defaultHttpOptions(ignoreLoading, ignoreErrors)).pipe( + defaultHttpOptionsFromConfig(config)).pipe( tap(() => { this.invalidateWidgetsBundleCache(); }) ); } - public deleteWidgetsBundle(widgetsBundleId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { - return this.getWidgetsBundle(widgetsBundleId, ignoreErrors, ignoreLoading).pipe( + public deleteWidgetsBundle(widgetsBundleId: string, config?: RequestConfig) { + return this.getWidgetsBundle(widgetsBundleId, config).pipe( mergeMap((widgetsBundle) => { return this.http.delete(`/api/widgetsBundle/${widgetsBundleId}`, - defaultHttpOptions(ignoreLoading, ignoreErrors)).pipe( + defaultHttpOptionsFromConfig(config)).pipe( tap(() => { this.invalidateWidgetsBundleCache(); this.widgetsBundleDeletedSubject.next(widgetsBundle); @@ -114,14 +110,14 @@ export class WidgetService { } public getBundleWidgetTypes(bundleAlias: string, isSystem: boolean, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + config?: RequestConfig): Observable> { return this.http.get>(`/api/widgetTypes?isSystem=${isSystem}&bundleAlias=${bundleAlias}`, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } public loadBundleLibraryWidgets(bundleAlias: string, isSystem: boolean, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { - return this.getBundleWidgetTypes(bundleAlias, isSystem, ignoreErrors, ignoreLoading).pipe( + config?: RequestConfig): Observable> { + return this.getBundleWidgetTypes(bundleAlias, isSystem, config).pipe( map((types) => { types = types.sort((a, b) => { let result = widgetType[b.descriptor.type].localeCompare(widgetType[a.descriptor.type]); @@ -173,38 +169,38 @@ export class WidgetService { } public getWidgetType(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + config?: RequestConfig): Observable { return this.http.get(`/api/widgetType?isSystem=${isSystem}&bundleAlias=${bundleAlias}&alias=${widgetTypeAlias}`, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } public saveWidgetType(widgetInfo: WidgetInfo, id: WidgetTypeId, bundleAlias: string, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + config?: RequestConfig): Observable { const widgetTypeInstance = toWidgetType(widgetInfo, id, undefined, bundleAlias); return this.http.post('/api/widgetType', widgetTypeInstance, - defaultHttpOptions(ignoreLoading, ignoreErrors)).pipe( + defaultHttpOptionsFromConfig(config)).pipe( tap((savedWidgetType) => { this.widgetTypeUpdatedSubject.next(savedWidgetType); })); } public saveImportedWidgetType(widgetTypeInstance: WidgetType, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + config?: RequestConfig): Observable { return this.http.post('/api/widgetType', widgetTypeInstance, - defaultHttpOptions(ignoreLoading, ignoreErrors)).pipe( + defaultHttpOptionsFromConfig(config)).pipe( tap((savedWidgetType) => { this.widgetTypeUpdatedSubject.next(savedWidgetType); })); } public deleteWidgetType(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean, - ignoreErrors: boolean = false, ignoreLoading: boolean = false) { - return this.getWidgetType(bundleAlias, widgetTypeAlias, isSystem, ignoreErrors, ignoreLoading).pipe( + config?: RequestConfig) { + return this.getWidgetType(bundleAlias, widgetTypeAlias, isSystem, config).pipe( mergeMap((widgetTypeInstance) => { return this.http.delete(`/api/widgetType/${widgetTypeInstance.id.id}`, - defaultHttpOptions(ignoreLoading, ignoreErrors)).pipe( + defaultHttpOptionsFromConfig(config)).pipe( tap(() => { this.widgetTypeUpdatedSubject.next(widgetTypeInstance); }) @@ -214,16 +210,16 @@ export class WidgetService { } public getWidgetTypeById(widgetTypeId: string, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + config?: RequestConfig): Observable { return this.http.get(`/api/widgetType/${widgetTypeId}`, - defaultHttpOptions(ignoreLoading, ignoreErrors)); + defaultHttpOptionsFromConfig(config)); } public getWidgetTemplate(widgetTypeParam: widgetType, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + config?: RequestConfig): Observable { const templateWidgetType = widgetTypesData.get(widgetTypeParam); return this.getWidgetType(templateWidgetType.template.bundleAlias, templateWidgetType.template.alias, true, - ignoreErrors, ignoreLoading).pipe( + config).pipe( map((result) => { const widgetInfo = toWidgetInfo(result); widgetInfo.alias = undefined; @@ -240,11 +236,11 @@ export class WidgetService { return this.widgetsBundleDeletedSubject.asObservable(); } - private loadWidgetsBundleCache(ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + private loadWidgetsBundleCache(config?: RequestConfig): Observable { if (!this.allWidgetsBundles) { const loadWidgetsBundleCacheSubject = new ReplaySubject(); this.http.get>('/api/widgetsBundles', - defaultHttpOptions(ignoreLoading, ignoreErrors)).subscribe( + defaultHttpOptionsFromConfig(config)).subscribe( (allWidgetsBundles) => { this.allWidgetsBundles = allWidgetsBundles; this.systemWidgetsBundles = new Array(); 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 976c36bf02..da66a0331c 100644 --- a/ui-ngx/src/app/core/services/dashboard-utils.service.ts +++ b/ui-ngx/src/app/core/services/dashboard-utils.service.ts @@ -19,17 +19,21 @@ import { UtilsService } from '@core/services/utils.service'; import { TimeService } from '@core/services/time.service'; import { Dashboard, - DashboardLayout, - DashboardStateLayouts, - DashboardState, DashboardConfiguration, + DashboardLayout, + DashboardLayoutId, DashboardLayoutInfo, - DashboardLayoutsInfo, DashboardLayoutId, WidgetLayout, GridSettings + DashboardLayoutsInfo, + DashboardState, + DashboardStateLayouts, + GridSettings, + WidgetLayout } from '@shared/models/dashboard.models'; -import { isUndefined, isDefined, isString } from '@core/utils'; -import { DatasourceType, Widget, Datasource } from '@app/shared/models/widget.models'; +import { isDefined, isString, isUndefined } from '@core/utils'; +import { Datasource, DatasourceType, Widget } from '@app/shared/models/widget.models'; import { EntityType } from '@shared/models/entity-type.models'; -import { EntityAlias, AliasFilterType } from '@app/shared/models/alias.models'; +import { AliasFilterType, EntityAlias, EntityAliasFilter } from '@app/shared/models/alias.models'; +import { EntityId } from '@app/shared/models/id/entity-id'; @Injectable({ providedIn: 'root' @@ -207,7 +211,7 @@ export class DashboardUtilsService { delete datasource.deviceAliasId; } }); - // TODO: Temp workaround + // Temp workaround if (widget.isSystemType && widget.bundleAlias === 'charts' && widget.typeAlias === 'timeseries') { widget.typeAlias = 'basic_timeseries'; } @@ -245,6 +249,14 @@ export class DashboardUtilsService { }; } + public createSingleEntityFilter(entityId: EntityId): EntityAliasFilter { + return { + type: AliasFilterType.singleEntity, + singleEntity: entityId, + resolveMultiple: false + }; + } + private validateAndUpdateState(state: DashboardState) { if (!state.layouts) { state.layouts = this.createDefaultLayouts(); diff --git a/ui-ngx/src/app/core/services/dialog.service.ts b/ui-ngx/src/app/core/services/dialog.service.ts index 0e0c87d689..4f627e3a6e 100644 --- a/ui-ngx/src/app/core/services/dialog.service.ts +++ b/ui-ngx/src/app/core/services/dialog.service.ts @@ -30,7 +30,6 @@ import { MaterialIconsDialogComponent, MaterialIconsDialogData } from '@shared/components/dialog/material-icons-dialog.component'; -import { DynamicComponentFactoryService } from '@core/services/dynamic-component-factory.service'; @Injectable( { @@ -42,7 +41,6 @@ export class DialogService { constructor( private translate: TranslateService, private authService: AuthService, - private dynamicComponentFactoryService: DynamicComponentFactoryService, public dialog: MatDialog ) { } diff --git a/ui-ngx/src/app/core/services/utils.service.ts b/ui-ngx/src/app/core/services/utils.service.ts index 3bbd97e30e..fd9e64c2d9 100644 --- a/ui-ngx/src/app/core/services/utils.service.ts +++ b/ui-ngx/src/app/core/services/utils.service.ts @@ -396,4 +396,42 @@ export class UtilsService { this.window.performance.now() : Date.now(); } + public getQueryParam(name: string): string { + const url = this.window.location.href; + name = name.replace(/[\[\]]/g, '\\$&'); + const regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'); + const results = regex.exec(url); + if (!results) { + return null; + } + if (!results[2]) { + return ''; + } + return decodeURIComponent(results[2].replace(/\+/g, ' ')); + } + + public updateQueryParam(name: string, value: string | null) { + const baseUrl = [this.window.location.protocol, '//', this.window.location.host, this.window.location.pathname].join(''); + const urlQueryString = this.window.location.search; + let newParam = ''; + let params = ''; + if (value !== null) { + newParam = name + '=' + value; + } + if (urlQueryString) { + const keyRegex = new RegExp('([\?&])' + name + '[^&]*'); + if (urlQueryString.match(keyRegex) !== null) { + if (newParam) { + newParam = '$1' + newParam; + } + params = urlQueryString.replace(keyRegex, newParam); + } else if (newParam) { + params = urlQueryString + '&' + newParam; + } + } else if (newParam) { + params = '?' + newParam; + } + this.window.history.replaceState({}, '', baseUrl + params); + } + } diff --git a/ui-ngx/src/app/core/settings/settings.effects.ts b/ui-ngx/src/app/core/settings/settings.effects.ts index e0b4993719..8416c45a24 100644 --- a/ui-ngx/src/app/core/settings/settings.effects.ts +++ b/ui-ngx/src/app/core/settings/settings.effects.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { ActivationEnd, Router } from '@angular/router'; +import { ActivationEnd, ActivationStart, Router } from '@angular/router'; import { Injectable } from '@angular/core'; import { select, Store } from '@ngrx/store'; import { Actions, Effect, ofType } from '@ngrx/effects'; @@ -39,6 +39,10 @@ import { AppState } from '@app/core/core.state'; import { LocalStorageService } from '@app/core/local-storage/local-storage.service'; import { TitleService } from '@app/core/services/title.service'; import { updateUserLang } from '@app/core/settings/settings.utils'; +import { AuthService } from '@core/auth/auth.service'; +import { UtilsService } from '@core/services/utils.service'; +import { getCurrentAuthUser } from '@core/auth/auth.selectors'; +import { ActionAuthUpdateLastPublicDashboardId } from '../auth/auth.actions'; export const SETTINGS_KEY = 'SETTINGS'; @@ -47,6 +51,8 @@ export class SettingsEffects { constructor( private actions$: Actions, private store: Store, + private authService: AuthService, + private utils: UtilsService, private router: Router, private localStorageService: LocalStorageService, private titleService: TitleService, @@ -85,4 +91,20 @@ export class SettingsEffects { ); }) ); + + @Effect({dispatch: false}) + setPublicId = merge( + this.router.events.pipe(filter(event => event instanceof ActivationEnd)) + ).pipe( + tap((event) => { + const authUser = getCurrentAuthUser(this.store); + const snapshot = (event as ActivationEnd).snapshot; + if (authUser && authUser.isPublic && snapshot.url && snapshot.url.length + && snapshot.url[0].path === 'dashboard') { + this.utils.updateQueryParam('publicId', authUser.sub); + this.store.dispatch(new ActionAuthUpdateLastPublicDashboardId( + { lastPublicDashboardId: snapshot.params.dashboardId})); + } + }) + ); } diff --git a/ui-ngx/src/app/modules/home/components/attribute/add-widget-to-dashboard-dialog.component.html b/ui-ngx/src/app/modules/home/components/attribute/add-widget-to-dashboard-dialog.component.html new file mode 100644 index 0000000000..bf38dee2d5 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/attribute/add-widget-to-dashboard-dialog.component.html @@ -0,0 +1,77 @@ + +
+ +

attribute.add-widget-to-dashboard

+ + +
+ + +
+
+ + +
+ dashboard.select-existing + + +
+
+ +
+ dashboard.create-new + + dashboard.new-dashboard-title + + + {{ 'dashboard.title-required' | translate }} + + +
+
+
+
+
+
+ + + {{ 'dashboard.open-dashboard' | translate }} + + + +
+
diff --git a/ui-ngx/src/app/modules/home/components/attribute/add-widget-to-dashboard-dialog.component.scss b/ui-ngx/src/app/modules/home/components/attribute/add-widget-to-dashboard-dialog.component.scss new file mode 100644 index 0000000000..091e6e1174 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/attribute/add-widget-to-dashboard-dialog.component.scss @@ -0,0 +1,29 @@ +/** + * Copyright © 2016-2019 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 ::ng-deep { + mat-radio-button { + display: block; + margin-bottom: 16px; + .mat-radio-label { + width: 100%; + align-items: start; + .mat-radio-label-content { + width: 100%; + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/attribute/add-widget-to-dashboard-dialog.component.ts b/ui-ngx/src/app/modules/home/components/attribute/add-widget-to-dashboard-dialog.component.ts new file mode 100644 index 0000000000..519bb48a58 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/attribute/add-widget-to-dashboard-dialog.component.ts @@ -0,0 +1,222 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Inject, OnInit, SkipSelf } from '@angular/core'; +import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; +import { Router } from '@angular/router'; +import { DialogComponent } from '@app/shared/components/dialog.component'; +import { UtilsService } from '@core/services/utils.service'; +import { TranslateService } from '@ngx-translate/core'; +import { + DashboardLayoutId, + DashboardSettings, + GridSettings, + StateControllerId, + Dashboard +} from '@app/shared/models/dashboard.models'; +import { isUndefined, objToBase64 } from '@core/utils'; +import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; +import { EntityId } from '@app/shared/models/id/entity-id'; +import { Widget } from '@app/shared/models/widget.models'; +import { DashboardService } from '@core/http/dashboard.service'; +import { forkJoin, Observable, of } from 'rxjs'; +import { SelectTargetLayoutDialogComponent } from '@home/pages/dashboard/layout/select-target-layout-dialog.component'; +import { MatDialog } from '@angular/material/dialog'; +import { + SelectTargetStateDialogComponent, + SelectTargetStateDialogData +} from '@home/pages/dashboard/states/select-target-state-dialog.component'; +import { mergeMap, map } from 'rxjs/operators'; +import { AliasesInfo } from '@shared/models/alias.models'; +import { ItemBufferService } from '@core/services/item-buffer.service'; +import { StateObject } from '@core/api/widget-api.models'; + +export interface AddWidgetToDashboardDialogData { + entityId: EntityId; + entityName: string; + widget: Widget; +} + +@Component({ + selector: 'tb-add-widget-to-dashboard-dialog', + templateUrl: './add-widget-to-dashboard-dialog.component.html', + providers: [{provide: ErrorStateMatcher, useExisting: AddWidgetToDashboardDialogComponent}], + styleUrls: ['./add-widget-to-dashboard-dialog.component.scss'] +}) +export class AddWidgetToDashboardDialogComponent extends + DialogComponent + implements OnInit, ErrorStateMatcher { + + addWidgetFormGroup: FormGroup; + + submitted = false; + + constructor(protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) public data: AddWidgetToDashboardDialogData, + @SkipSelf() private errorStateMatcher: ErrorStateMatcher, + public dialogRef: MatDialogRef, + private fb: FormBuilder, + private utils: UtilsService, + private dashboardUtils: DashboardUtilsService, + private dashboardService: DashboardService, + private itembuffer: ItemBufferService, + private dialog: MatDialog) { + super(store, router, dialogRef); + + this.addWidgetFormGroup = this.fb.group( + { + addToDashboardType: [0, []], + dashboardId: [null, [Validators.required]], + newDashboardTitle: [{value: null, disabled: true}, []], + openDashboard: [false, []] + } + ); + + this.addWidgetFormGroup.get('addToDashboardType').valueChanges.subscribe( + (addToDashboardType: number) => { + if (addToDashboardType === 0) { + this.addWidgetFormGroup.get('dashboardId').setValidators([Validators.required]); + this.addWidgetFormGroup.get('dashboardId').enable(); + this.addWidgetFormGroup.get('newDashboardTitle').setValidators([]); + this.addWidgetFormGroup.get('newDashboardTitle').disable(); + this.addWidgetFormGroup.get('dashboardId').updateValueAndValidity(); + this.addWidgetFormGroup.get('newDashboardTitle').updateValueAndValidity(); + } else { + this.addWidgetFormGroup.get('dashboardId').setValidators([]); + this.addWidgetFormGroup.get('dashboardId').disable(); + this.addWidgetFormGroup.get('newDashboardTitle').setValidators([Validators.required]); + this.addWidgetFormGroup.get('newDashboardTitle').enable(); + this.addWidgetFormGroup.get('dashboardId').updateValueAndValidity(); + this.addWidgetFormGroup.get('newDashboardTitle').updateValueAndValidity(); + } + } + ); + } + + ngOnInit(): void { + } + + isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { + const originalErrorState = this.errorStateMatcher.isErrorState(control, form); + const customErrorState = !!(control && control.invalid && this.submitted); + return originalErrorState || customErrorState; + } + + cancel(): void { + this.dialogRef.close(null); + } + + add(): void { + this.submitted = true; + const addToDashboardType: number = this.addWidgetFormGroup.get('addToDashboardType').value; + if (addToDashboardType === 0) { + const dashboardId: string = this.addWidgetFormGroup.get('dashboardId').value; + this.dashboardService.getDashboard(dashboardId).pipe( + mergeMap((dashboard) => { + dashboard = this.dashboardUtils.validateAndUpdateDashboard(dashboard); + return this.selectTargetState(dashboard).pipe( + mergeMap((targetState) => { + return forkJoin([of(dashboard), of(targetState), this.selectTargetLayout(dashboard, targetState)]); + }) + ); + }) + ).subscribe((res) => { + this.addWidgetToDashboard(res[0], res[1], res[2]); + }); + } else { + const dashboardTitle: string = this.addWidgetFormGroup.get('newDashboardTitle').value; + const newDashboard: Dashboard = { + title: dashboardTitle + }; + this.addWidgetToDashboard(newDashboard, 'default', 'main'); + } + } + + private selectTargetState(dashboard: Dashboard): Observable { + const states = dashboard.configuration.states; + const stateIds = Object.keys(states); + if (stateIds.length > 1) { + return this.dialog.open(SelectTargetStateDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + states + } + }).afterClosed(); + } else { + return of(stateIds[0]); + } + } + + private selectTargetLayout(dashboard: Dashboard, targetState: string): Observable { + const layouts = dashboard.configuration.states[targetState].layouts; + const layoutIds = Object.keys(layouts); + if (layoutIds.length > 1) { + return this.dialog.open(SelectTargetLayoutDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'] + }).afterClosed(); + } else { + return of(layoutIds[0] as DashboardLayoutId); + } + } + + private addWidgetToDashboard(dashboard: Dashboard, targetState: string, targetLayout: DashboardLayoutId) { + const aliasesInfo: AliasesInfo = { + datasourceAliases: {}, + targetDeviceAliases: {} + }; + aliasesInfo.datasourceAliases[0] = { + alias: this.data.entityName, + filter: this.dashboardUtils.createSingleEntityFilter(this.data.entityId) + }; + this.itembuffer.addWidgetToDashboard(dashboard, targetState, + targetLayout, this.data.widget, aliasesInfo, null, + 48, null, -1, -1).pipe( + mergeMap((theDashboard) => { + return this.dashboardService.saveDashboard(theDashboard); + }) + ).subscribe( + (theDashboard) => { + const openDashboard: boolean = this.addWidgetFormGroup.get('openDashboard').value; + this.dialogRef.close(); + if (openDashboard) { + let url; + const stateIds = Object.keys(dashboard.configuration.states); + const stateIndex = stateIds.indexOf(targetState); + if (stateIndex > 0) { + const stateObject: StateObject = { + id: targetState, + params: {} + }; + const state = objToBase64([ stateObject ]); + url = `/dashboards/${theDashboard.id.id}?state=${state}`; + } else { + url = `/dashboards/${theDashboard.id.id}`; + } + const urlTree = this.router.parseUrl(url); + this.router.navigateByUrl(url); + } + } + ); + } +} diff --git a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html index 4a6f72b54f..4c30c33dcd 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html +++ b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html @@ -104,20 +104,26 @@
-
+
{{ 'widgets-bundle.current' | translate }} - TODO: + +
- + (click)="addWidgetToDashboard()"> + dashboard + attribute.add-to-dashboard + + + + + + + +
+
+ + + widgets-bundle.empty + widget.select-widgets-bundle
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 96a70f6e8c..3b525bae39 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 @@ -60,4 +60,36 @@ color: #757575 } } + + .carousel { + .aniT { + transition: all 1s linear; + } + + .transition { + transition: all 0.3s ease-in-out !important; + } + .content { + display: flex; + height: 100%; + .item { + position: relative; + } + } + .direction { + width: 60px; + } + .ball { + width: 10px; + height: 10px; + border-radius: 50%; + background: black; + border: 2px solid; + opacity: 0.5; + + &.visible { + opacity: 1; + } + } + } } diff --git a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts index 202d7a5b3e..d2fcbe6297 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts @@ -19,7 +19,7 @@ import { ChangeDetectionStrategy, Component, ElementRef, - Input, + Input, NgZone, OnInit, ViewChild, ViewContainerRef @@ -40,7 +40,9 @@ import { EntityId } from '@shared/models/id/entity-id'; import { AttributeData, AttributeScope, - isClientSideTelemetryType, LatestTelemetry, + DataKeyType, + isClientSideTelemetryType, + LatestTelemetry, TelemetryType, telemetryTypeTranslations } from '@shared/models/telemetry/telemetry.models'; @@ -48,13 +50,11 @@ import { AttributeDatasource } from '@home/models/datasource/attribute-datasourc import { AttributeService } from '@app/core/http/attribute.service'; import { EntityType } from '@shared/models/entity-type.models'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; -import { RelationDialogComponent, RelationDialogData } from '@home/components/relation/relation-dialog.component'; import { AddAttributeDialogComponent, AddAttributeDialogData } from '@home/components/attribute/add-attribute-dialog.component'; import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; -import { TIMEWINDOW_PANEL_DATA, TimewindowPanelComponent } from '@shared/components/time/timewindow-panel.component'; import { EDIT_ATTRIBUTE_VALUE_PANEL_DATA, EditAttributeValuePanelComponent, @@ -62,6 +62,24 @@ import { } from './edit-attribute-value-panel.component'; import { ComponentPortal, PortalInjector } from '@angular/cdk/portal'; import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service'; +import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; +import { DataKey, Datasource, DatasourceType, Widget, widgetType } from '@shared/models/widget.models'; +import { IAliasController, IStateController, StateParams } from '@core/api/widget-api.models'; +import { AliasController, DummyAliasController } from '@core/api/alias-controller'; +import { EntityAlias, EntityAliases } from '@shared/models/alias.models'; +import { UtilsService } from '@core/services/utils.service'; +import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; +import { NULL_UUID } from '@shared/models/id/has-uuid'; +import { WidgetService } from '@core/http/widget.service'; +import { toWidgetInfo } from '../../models/widget-component.models'; +import { EntityService } from '@core/http/entity.service'; +import { SelectTargetLayoutDialogComponent } from '@home/pages/dashboard/layout/select-target-layout-dialog.component'; +import { DashboardLayoutId } from '@shared/models/dashboard.models'; +import { + AddWidgetToDashboardDialogComponent, + AddWidgetToDashboardDialogData +} from '@home/components/attribute/add-widget-to-dashboard-dialog.component'; +import { deepClone } from '@core/utils'; @Component({ @@ -95,6 +113,15 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI viewsInited = false; + selectedWidgetsBundleAlias: string = null; + widgetsBundle: WidgetsBundle = null; + widgetsLoaded = false; + widgetsCarouselIndex = 0; + widgetsList: Array> = []; + widgetsListCache: Array> = []; + aliasController: IAliasController; + private widgetDatasource: Datasource; + private disableAttributeScopeSelectionValue: boolean; get disableAttributeScopeSelection(): boolean { return this.disableAttributeScopeSelectionValue; @@ -131,6 +158,9 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI } } + @Input() + entityName: string; + @ViewChild('searchInput', {static: false}) searchInputField: ElementRef; @ViewChild(MatPaginator, {static: false}) paginator: MatPaginator; @@ -143,12 +173,17 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI public dialog: MatDialog, private overlay: Overlay, private viewContainerRef: ViewContainerRef, - private dialogService: DialogService) { + private dialogService: DialogService, + private entityService: EntityService, + private utils: UtilsService, + private dashboardUtils: DashboardUtilsService, + private widgetService: WidgetService, + private zone: NgZone) { super(store); this.dirtyValue = !this.activeValue; const sortOrder: SortOrder = { property: 'key', direction: Direction.ASC }; this.pageLink = new PageLink(10, 0, null, sortOrder); - this.dataSource = new AttributeDatasource(this.attributeService, this.telemetryWsService, this.translate); + this.dataSource = new AttributeDatasource(this.attributeService, this.telemetryWsService, this.zone, this.translate); } ngOnInit() { @@ -329,15 +364,137 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI enterWidgetMode() { this.mode = 'widget'; + this.widgetsList = []; + this.widgetsListCache = []; + this.widgetsLoaded = false; + this.widgetsCarouselIndex = 0; + this.widgetsBundle = null; + this.selectedWidgetsBundleAlias = 'cards'; + + const entityAlias: EntityAlias = { + id: this.utils.guid(), + alias: this.entityName, + filter: this.dashboardUtils.createSingleEntityFilter(this.entityIdValue) + }; + const entitiAliases: EntityAliases = {}; + entitiAliases[entityAlias.id] = entityAlias; + + // @ts-ignore + const stateController: IStateController = { + getStateParams(): StateParams { + return {}; + } + }; + + this.aliasController = new AliasController(this.utils, + this.entityService, + () => stateController, entitiAliases); + + const dataKeyType: DataKeyType = this.attributeScope === LatestTelemetry.LATEST_TELEMETRY ? + DataKeyType.timeseries : DataKeyType.attribute; + + this.widgetDatasource = { + type: DatasourceType.entity, + entityAliasId: entityAlias.id, + dataKeys: [] + }; + + for (let i = 0; i < this.dataSource.selection.selected.length; i++) { + const attribute = this.dataSource.selection.selected[i]; + const dataKey: DataKey = { + name: attribute.key, + label: attribute.key, + type: dataKeyType, + color: this.utils.getMaterialColor(i), + settings: {}, + _hash: Math.random() + }; + this.widgetDatasource.dataKeys.push(dataKey); + } + } - // TODO: + onWidgetsCarouselIndexChanged() { + if (this.mode === 'widget') { + for (let i = 0; i < this.widgetsList.length; i++) { + this.widgetsList[i].splice(0, this.widgetsList[i].length); + if (i === this.widgetsCarouselIndex) { + this.widgetsList[i].push(this.widgetsListCache[i][0]); + } + } + } + } + + onWidgetsBundleChanged() { + if (this.mode === 'widget') { + this.widgetsList = []; + this.widgetsListCache = []; + this.widgetsCarouselIndex = 0; + if (this.widgetsBundle) { + this.widgetsLoaded = false; + const bundleAlias = this.widgetsBundle.alias; + const isSystem = this.widgetsBundle.tenantId.id === NULL_UUID; + this.widgetService.getBundleWidgetTypes(bundleAlias, isSystem).subscribe( + (widgetTypes) => { + widgetTypes = widgetTypes.sort((a, b) => { + let result = widgetType[b.descriptor.type].localeCompare(widgetType[a.descriptor.type]); + if (result === 0) { + result = b.createdTime - a.createdTime; + } + return result; + }); + for (const type of widgetTypes) { + const widgetInfo = toWidgetInfo(type); + if (widgetInfo.type !== widgetType.static) { + const sizeX = widgetInfo.sizeX * 2; + const sizeY = widgetInfo.sizeY * 2; + const col = Math.floor(Math.max(0, (20 - sizeX) / 2)); + const widget: Widget = { + isSystemType: isSystem, + bundleAlias, + typeAlias: widgetInfo.alias, + type: widgetInfo.type, + title: widgetInfo.widgetName, + sizeX, + sizeY, + row: 0, + col, + config: JSON.parse(widgetInfo.defaultConfig) + }; + widget.config.title = widgetInfo.widgetName; + widget.config.datasources = [this.widgetDatasource]; + if ((this.attributeScope === LatestTelemetry.LATEST_TELEMETRY && widgetInfo.type !== widgetType.rpc) || + widgetInfo.type === widgetType.latest) { + const length = this.widgetsListCache.push([widget]); + this.widgetsList.push(length === 1 ? [widget] : []); + } + } + } + this.widgetsLoaded = true; + } + ); + } + } + } + + addWidgetToDashboard() { + if (this.mode === 'widget' && this.widgetsListCache.length > 0) { + const widget = this.widgetsListCache[this.widgetsCarouselIndex][0]; + this.dialog.open + (AddWidgetToDashboardDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + entityId: this.entityIdValue, + entityName: this.entityName, + widget: deepClone(widget) + } + }).afterClosed(); + } } exitWidgetMode() { + this.selectedWidgetsBundleAlias = null; this.mode = 'default'; - // this.reloadAttributes(); - - // TODO: } } diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index 85e708b6c0..65c32d68bf 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -60,6 +60,9 @@ import { CustomDialogService } from './widget/dialog/custom-dialog.service'; import { CustomDialogContainerComponent } from './widget/dialog/custom-dialog-container.component'; import { ImportExportService } from './import-export/import-export.service'; import { ImportDialogComponent } from './import-export/import-dialog.component'; +import { AddWidgetToDashboardDialogComponent } from './attribute/add-widget-to-dashboard-dialog.component'; +import { ImportDialogCsvComponent } from './import-export/import-dialog-csv.component'; +import { TableColumnsAssignmentComponent } from './import-export/table-columns-assignment.component'; @NgModule({ entryComponents: [ @@ -78,7 +81,9 @@ import { ImportDialogComponent } from './import-export/import-dialog.component'; LegendConfigPanelComponent, WidgetActionDialogComponent, CustomDialogContainerComponent, - ImportDialogComponent + ImportDialogComponent, + ImportDialogCsvComponent, + AddWidgetToDashboardDialogComponent ], declarations: [ @@ -121,7 +126,10 @@ import { ImportDialogComponent } from './import-export/import-dialog.component'; CustomActionPrettyResourcesTabsComponent, CustomActionPrettyEditorComponent, CustomDialogContainerComponent, - ImportDialogComponent + ImportDialogComponent, + ImportDialogCsvComponent, + AddWidgetToDashboardDialogComponent, + TableColumnsAssignmentComponent ], imports: [ CommonModule, @@ -159,7 +167,9 @@ import { ImportDialogComponent } from './import-export/import-dialog.component'; CustomActionPrettyResourcesTabsComponent, CustomActionPrettyEditorComponent, CustomDialogContainerComponent, - ImportDialogComponent + ImportDialogComponent, + ImportDialogCsvComponent, + TableColumnsAssignmentComponent ], providers: [ WidgetComponentService, diff --git a/ui-ngx/src/app/modules/home/components/import-export/import-dialog-csv.component.html b/ui-ngx/src/app/modules/home/components/import-export/import-dialog-csv.component.html new file mode 100644 index 0000000000..ad50a8646c --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/import-export/import-dialog-csv.component.html @@ -0,0 +1,144 @@ + +
+ +

{{ importTitle }}

+ +
+ +
+ + +
+ + + + {{ 'import.stepper-text.select-file' | translate }} +
+ + +
+ +
+ + + +
+
+ +
+ {{ 'import.stepper-text.configuration' | translate }} +
+ + import.csv-delimiter + + + {{ delimiter.value }} + + + +
+ + {{ 'import.csv-first-line-header' | translate }} + + + {{ 'import.csv-update-data' | translate }} + +
+
+
+
+ + + + +
+
+ +
+ {{ 'import.stepper-text.column-type' | translate }} + +
+
+ + + + +
+
+ + {{ 'import.stepper-text.creat-entities' | translate }} + + + + + {{ 'import.stepper-text.done' | translate }} +
+

+ {{ translate.instant('import.message.create-entities', {count: this.statistical.create.entity}) }} +

+

+ {{ translate.instant('import.message.update-entities', {count: this.statistical.update.entity}) }} +

+

+ {{ translate.instant('import.message.error-entities', {count: this.statistical.error.entity}) }} +

+
+
+ + +
+
+
+
+ diff --git a/ui-ngx/src/app/modules/home/components/import-export/import-dialog-csv.component.scss b/ui-ngx/src/app/modules/home/components/import-export/import-dialog-csv.component.scss new file mode 100644 index 0000000000..114392938e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/import-export/import-dialog-csv.component.scss @@ -0,0 +1,31 @@ +/** + * Copyright © 2016-2019 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 ::ng-deep { + .mat-dialog-content { + .mat-vertical-content { + form { + overflow: hidden !important; + padding-bottom: 12px; + } + } + .mat-vertical-stepper-header { + pointer-events: none !important; + } + .tb-import-progress{ + margin: 7px 0; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/import-export/import-dialog-csv.component.ts b/ui-ngx/src/app/modules/home/components/import-export/import-dialog-csv.component.ts new file mode 100644 index 0000000000..b140733e83 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/import-export/import-dialog-csv.component.ts @@ -0,0 +1,256 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Inject, OnInit, ViewChild } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +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 '@app/shared/components/dialog.component'; +import { EntityType } from '@shared/models/entity-type.models'; +import { TranslateService } from '@ngx-translate/core'; +import { ActionNotificationShow } from '@core/notification/notification.actions'; +import { MatVerticalStepper } from '@angular/material/stepper'; +import { + convertCSVToJson, + CsvColumnParam, + CsvToJsonConfig, + CsvToJsonResult, + ImportEntityColumnType +} from '@home/components/import-export/import-export.models'; +import { ImportEntitiesResultInfo, ImportEntityData } from '@app/shared/models/entity.models'; +import { ImportExportService } from '@home/components/import-export/import-export.service'; + +export interface ImportDialogCsvData { + entityType: EntityType; + importTitle: string; + importFileLabel: string; +} + +@Component({ + selector: 'tb-import-csv-dialog', + templateUrl: './import-dialog-csv.component.html', + providers: [], + styleUrls: ['./import-dialog-csv.component.scss'] +}) +export class ImportDialogCsvComponent extends DialogComponent + implements OnInit { + + @ViewChild('importStepper', {static: true}) importStepper: MatVerticalStepper; + + entityType: EntityType; + importTitle: string; + importFileLabel: string; + + delimiters: {key: string, value: string}[] = [{ + key: ',', + value: ',' + }, { + key: ';', + value: ';' + }, { + key: '|', + value: '|' + }, { + key: '\t', + value: 'Tab' + }]; + + selectedIndex = 0; + + selectFileFormGroup: FormGroup; + importParametersFormGroup: FormGroup; + columnTypesFormGroup: FormGroup; + + isImportData = false; + progressCreate = 0; + statistical: ImportEntitiesResultInfo; + + private parseData: CsvToJsonResult; + + constructor(protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) public data: ImportDialogCsvData, + public dialogRef: MatDialogRef, + public translate: TranslateService, + private importExport: ImportExportService, + private fb: FormBuilder) { + super(store, router, dialogRef); + this.entityType = data.entityType; + this.importTitle = data.importTitle; + this.importFileLabel = data.importFileLabel; + + this.selectFileFormGroup = this.fb.group( + { + importData: [null, [Validators.required]] + } + ); + this.importParametersFormGroup = this.fb.group({ + delim: [',', [Validators.required]], + isHeader: [true, []], + isUpdate: [true, []], + }); + this.columnTypesFormGroup = this.fb.group({ + columnsParam: [[], []] + }); + } + + ngOnInit(): void { + } + + cancel(): void { + this.dialogRef.close(false); + } + + previousStep() { + this.importStepper.previous(); + } + + nextStep(step: number) { + switch (step) { + case 2: + this.importStepper.next(); + break; + case 3: + const importData: string = this.selectFileFormGroup.get('importData').value; + const parseData = this.parseCSV(importData); + if (parseData === -1) { + this.importStepper.previous(); + this.importStepper.selected.reset(); + } else { + this.parseData = parseData as CsvToJsonResult; + const columnsParam = this.createColumnsData(); + this.columnTypesFormGroup.patchValue({columnsParam}, {emitEvent: true}); + this.importStepper.next(); + } + break; + case 4: + this.importStepper.next(); + this.isImportData = true; + this.addEntities(); + break; + case 6: + this.dialogRef.close(true); + break; + } + } + + private parseCSV(importData: string): CsvToJsonResult | number { + const config: CsvToJsonConfig = { + delim: this.importParametersFormGroup.get('delim').value, + header: this.importParametersFormGroup.get('isHeader').value + }; + return convertCSVToJson(importData, config, + (messageId, params) => { + this.store.dispatch(new ActionNotificationShow( + {message: this.translate.instant(messageId, params), + type: 'error'})); + } + ); + } + + private createColumnsData(): CsvColumnParam[] { + const columnsParam: CsvColumnParam[] = []; + const isHeader: boolean = this.importParametersFormGroup.get('isHeader').value; + for (let i = 0; i < this.parseData.headers.length; i++) { + let columnParam: CsvColumnParam; + if (isHeader && this.parseData.headers[i].search(/^(name|type)$/im) === 0) { + columnParam = { + type: ImportEntityColumnType[this.parseData.headers[i].toLowerCase()], + key: this.parseData.headers[i].toLowerCase(), + sampleData: this.parseData.rows[0][i] + }; + } else { + columnParam = { + type: ImportEntityColumnType.serverAttribute, + key: isHeader ? this.parseData.headers[i] : '', + sampleData: this.parseData.rows[0][i] + }; + } + columnsParam.push(columnParam); + } + return columnsParam; + } + + private addEntities() { + const importData = this.parseData; + const parameterColumns: CsvColumnParam[] = this.columnTypesFormGroup.get('columnsParam').value; + const entitiesData: ImportEntityData[] = []; + let sentDataLength = 0; + for (let row = 0; row < importData.rows.length; row++) { + const entityData: ImportEntityData = { + name: '', + type: '', + accessToken: '', + attributes: { + server: [], + shared: [] + }, + timeseries: [] + }; + const i = row; + for (let j = 0; j < parameterColumns.length; j++) { + switch (parameterColumns[j].type) { + case ImportEntityColumnType.serverAttribute: + entityData.attributes.server.push({ + key: parameterColumns[j].key, + value: importData.rows[i][j] + }); + break; + case ImportEntityColumnType.timeseries: + entityData.timeseries.push({ + key: parameterColumns[j].key, + value: importData.rows[i][j] + }); + break; + case ImportEntityColumnType.sharedAttribute: + entityData.attributes.shared.push({ + key: parameterColumns[j].key, + value: importData.rows[i][j] + }); + break; + case ImportEntityColumnType.accessToken: + entityData.accessToken = importData.rows[i][j]; + break; + case ImportEntityColumnType.name: + entityData.name = importData.rows[i][j]; + break; + case ImportEntityColumnType.type: + entityData.type = importData.rows[i][j]; + break; + } + } + entitiesData.push(entityData); + } + const createImportEntityCompleted = () => { + sentDataLength++; + this.progressCreate = Math.round((sentDataLength / importData.rows.length) * 100); + }; + + const isUpdate: boolean = this.importParametersFormGroup.get('isUpdate').value; + + this.importExport.importEntities(entitiesData, this.entityType, isUpdate, + createImportEntityCompleted, {ignoreErrors: true, resendRequest: true}).subscribe( + (result) => { + this.statistical = result; + this.isImportData = false; + this.importStepper.next(); + } + ); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/import-export/import-export.models.ts b/ui-ngx/src/app/modules/home/components/import-export/import-export.models.ts index c9502eb380..1d41c4da02 100644 --- a/ui-ngx/src/app/modules/home/components/import-export/import-export.models.ts +++ b/ui-ngx/src/app/modules/home/components/import-export/import-export.models.ts @@ -17,6 +17,8 @@ import { Widget, WidgetType } from '@app/shared/models/widget.models'; import { DashboardLayoutId } from '@shared/models/dashboard.models'; import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; +import { ActionNotificationShow } from '@core/notification/notification.actions'; +import { ActionType } from '@shared/models/audit-log.models'; export interface ImportWidgetResult { widget: Widget; @@ -27,3 +29,116 @@ export interface WidgetsBundleItem { widgetsBundle: WidgetsBundle; widgetTypes: WidgetType[]; } + +export interface CsvToJsonConfig { + delim?: string; + header?: boolean; +} + +export interface CsvToJsonResult { + headers?: string[]; + rows?: any[][]; +} + +export enum ImportEntityColumnType { + name = 'NAME', + type = 'TYPE', + clientAttribute = 'CLIENT_ATTRIBUTE', + sharedAttribute = 'SHARED_ATTRIBUTE', + serverAttribute = 'SERVER_ATTRIBUTE', + timeseries = 'TIMESERIES', + entityField = 'ENTITY_FIELD', + accessToken = 'ACCESS_TOKEN' +} + +export const importEntityObjectColumns = + [ImportEntityColumnType.name, ImportEntityColumnType.type, ImportEntityColumnType.accessToken]; + +export const importEntityColumnTypeTranslations = new Map( + [ + [ImportEntityColumnType.name, 'import.column-type.name'], + [ImportEntityColumnType.type, 'import.column-type.type'], + [ImportEntityColumnType.clientAttribute, 'import.column-type.client-attribute'], + [ImportEntityColumnType.sharedAttribute, 'import.column-type.shared-attribute'], + [ImportEntityColumnType.serverAttribute, 'import.column-type.server-attribute'], + [ImportEntityColumnType.timeseries, 'import.column-type.timeseries'], + [ImportEntityColumnType.entityField, 'import.column-type.entity-field'], + [ImportEntityColumnType.accessToken, 'import.column-type.access-token'], + ] +); + +export interface CsvColumnParam { + type: ImportEntityColumnType; + key: string; + sampleData: any; +} + +export function convertCSVToJson(csvdata: string, config: CsvToJsonConfig, + onError: (messageId: string, params?: any) => void): CsvToJsonResult | number { + config = config || {}; + const delim = config.delim || ','; + const header = config.header || false; + const result: CsvToJsonResult = {}; + const csvlines = csvdata.split(/[\r\n]+/); + const csvheaders = splitCSV(csvlines[0], delim); + if (csvheaders.length < 2) { + onError('import.import-csv-number-columns-error'); + return -1; + } + const csvrows = header ? csvlines.slice(1, csvlines.length) : csvlines; + result.headers = csvheaders; + result.rows = []; + for (const row of csvrows) { + if (row.length === 0) { + break; + } + const rowitems: any[] = splitCSV(row, delim); + if (rowitems.length !== result.headers.length) { + onError('import.import-csv-invalid-format-error', {line: (header ? result.rows.length + 2 : result.rows.length + 1)}); + return -1; + } + for (let i = 0; i < rowitems.length; i++) { + rowitems[i] = convertStringToJSType(rowitems[i]); + } + result.rows.push(rowitems); + } + return result; +} + +function splitCSV(str: string, sep: string): string[] { + let foo: string[]; + let x: number; + let tl: string; + for (foo = str.split(sep = sep || ','), x = foo.length - 1, tl; x >= 0; x--) { + if (foo[x].replace(/"\s+$/, '"').charAt(foo[x].length - 1) === '"') { + if ((tl = foo[x].replace(/^\s+"/, '"')).length > 1 && tl.charAt(0) === '"') { + foo[x] = foo[x].replace(/^\s*"|"\s*$/g, '').replace(/""/g, '"'); + } else if (x) { + foo.splice(x - 1, 2, [foo[x - 1], foo[x]].join(sep)); + } else { + foo = foo.shift().split(sep).concat(foo); + } + } else { + foo[x].replace(/""/g, '"'); + } + } + return foo; +} + +function isNumeric(str: any): boolean { + str = str.replace(',', '.'); + return !isNaN(parseFloat(str)) && isFinite(str); +} + +function convertStringToJSType(str: string): any { + if (isNumeric(str.replace(',', '.'))) { + return parseFloat(str.replace(',', '.')); + } + if (str.search(/^(true|false)$/im) === 0) { + return str === 'true'; + } + if (str === '') { + return null; + } + return str; +} diff --git a/ui-ngx/src/app/modules/home/components/import-export/import-export.service.ts b/ui-ngx/src/app/modules/home/components/import-export/import-export.service.ts index b8001cbc34..e7945fa401 100644 --- a/ui-ngx/src/app/modules/home/components/import-export/import-export.service.ts +++ b/ui-ngx/src/app/modules/home/components/import-export/import-export.service.ts @@ -35,7 +35,7 @@ import { import { MatDialog } from '@angular/material/dialog'; import { ImportDialogComponent, ImportDialogData } from '@home/components/import-export/import-dialog.component'; import { forkJoin, Observable, of } from 'rxjs'; -import { catchError, map, mergeMap } from 'rxjs/operators'; +import { catchError, map, mergeMap, tap } from 'rxjs/operators'; import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; import { EntityService } from '@core/http/entity.service'; import { Widget, WidgetSize, WidgetType } from '@shared/models/widget.models'; @@ -44,12 +44,15 @@ import { EntityAliasesDialogData } from '@home/components/alias/entity-aliases-dialog.component'; import { ItemBufferService, WidgetItem } from '@core/services/item-buffer.service'; -import { ImportWidgetResult, WidgetsBundleItem } from './import-export.models'; +import { CsvToJsonConfig, ImportWidgetResult, WidgetsBundleItem, CsvToJsonResult } from './import-export.models'; import { EntityType } from '@shared/models/entity-type.models'; import { UtilsService } from '@core/services/utils.service'; import { WidgetService } from '@core/http/widget.service'; import { NULL_UUID } from '@shared/models/id/has-uuid'; import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; +import { ImportDialogCsvComponent, ImportDialogCsvData } from './import-dialog-csv.component'; +import { ImportEntityData, ImportEntitiesResultInfo } from '@shared/models/entity.models'; +import { RequestConfig } from '@core/http/http-utils'; @Injectable() export class ImportExportService { @@ -324,6 +327,55 @@ export class ImportExportService { ); } + public importEntities(entitiesData: ImportEntityData[], entityType: EntityType, updateData: boolean, + importEntityCompleted?: () => void, config?: RequestConfig): Observable { + let partSize = 100; + partSize = entitiesData.length > partSize ? partSize : entitiesData.length; + + let statisticalInfo: ImportEntitiesResultInfo = {}; + const importEntitiesObservables: Observable[] = []; + for (let i = 0; i < partSize; i++) { + const importEntityPromise = + this.entityService.saveEntityParameters(entityType, entitiesData[i], updateData, config).pipe( + tap((res) => { + if (importEntityCompleted) { + importEntityCompleted(); + } + }) + ); + importEntitiesObservables.push(importEntityPromise); + } + return forkJoin(importEntitiesObservables).pipe( + mergeMap((responses) => { + for (const response of responses) { + statisticalInfo = this.sumObject(statisticalInfo, response); + } + entitiesData.splice(0, partSize); + if (entitiesData.length) { + return this.importEntities(entitiesData, entityType, updateData, importEntityCompleted, config).pipe( + map((response) => { + return this.sumObject(statisticalInfo, response) as ImportEntitiesResultInfo; + }) + ); + } else { + return of(statisticalInfo); + } + }) + ); + } + + private sumObject(obj1: any, obj2: any): any { + Object.keys(obj2).map((key) => { + if (isObject(obj2[key])) { + obj1[key] = obj1[key] || {}; + obj1[key] = {...obj1[key], ...this.sumObject(obj1[key], obj2[key])}; + } else { + obj1[key] = (obj1[key] || 0) + obj2[key]; + } + }); + return obj1; + } + private handleExportError(e: any, errorDetailsMessageId: string) { let message = e; if (!message) { diff --git a/ui-ngx/src/app/modules/home/components/import-export/table-columns-assignment.component.html b/ui-ngx/src/app/modules/home/components/import-export/table-columns-assignment.component.html new file mode 100644 index 0000000000..639603842c --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/import-export/table-columns-assignment.component.html @@ -0,0 +1,58 @@ + + + + + + {{ (i+1) }} + + + + {{ 'import.column-example' | translate }} + + {{column.sampleData}} + + + + {{ 'import.column-type.column-type' | translate }} + + + + {{ columnTypesTranslations.get(type.value) | translate }} + + + + + + {{ 'import.column-key' | translate }} + + + + + + + + + + + diff --git a/ui-ngx/src/app/modules/home/components/import-export/table-columns-assignment.component.scss b/ui-ngx/src/app/modules/home/components/import-export/table-columns-assignment.component.scss new file mode 100644 index 0000000000..e4bf86dfc9 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/import-export/table-columns-assignment.component.scss @@ -0,0 +1,26 @@ +/** + * Copyright © 2016-2019 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 { + .mat-column-order { + flex: 0 0 40px; + } + .mat-column-sampleData { + flex: 0 0 120px; + } + .mat-column-type { + flex: 0 0 120px; + } +} diff --git a/ui-ngx/src/app/modules/home/components/import-export/table-columns-assignment.component.ts b/ui-ngx/src/app/modules/home/components/import-export/table-columns-assignment.component.ts new file mode 100644 index 0000000000..7bf8f2410a --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/import-export/table-columns-assignment.component.ts @@ -0,0 +1,175 @@ +/// +/// Copyright © 2016-2019 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, forwardRef, Input, OnInit } from '@angular/core'; +import { ControlValueAccessor, FormControl, NG_VALIDATORS, NG_VALUE_ACCESSOR, Validator } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { EntityType } from '@shared/models/entity-type.models'; +import { CsvColumnParam, ImportEntityColumnType, importEntityColumnTypeTranslations, + importEntityObjectColumns } from '@home/components/import-export/import-export.models'; +import { CollectionViewer, DataSource } from '@angular/cdk/typings/collections'; +import { BehaviorSubject, Observable } from 'rxjs'; + +@Component({ + selector: 'tb-table-columns-assignment', + templateUrl: './table-columns-assignment.component.html', + styleUrls: ['./table-columns-assignment.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => TableColumnsAssignmentComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => TableColumnsAssignmentComponent), + multi: true, + } + ] +}) +export class TableColumnsAssignmentComponent implements OnInit, ControlValueAccessor, Validator { + + @Input() entityType: EntityType; + + @Input() disabled: boolean; + + dataSource = new CsvColumnsDatasource(); + + displayedColumns = ['order', 'sampleData', 'type', 'key']; + + importEntityColumnType = ImportEntityColumnType; + + columnTypes: AssignmentColumnType[] = []; + + columnTypesTranslations = importEntityColumnTypeTranslations; + + private columns: CsvColumnParam[]; + + private valid = true; + + private propagateChangePending = false; + private propagateChange = null; + + constructor(public elementRef: ElementRef, + protected store: Store) { + } + + ngOnInit(): void { + this.columnTypes.push( + { value: ImportEntityColumnType.name }, + { value: ImportEntityColumnType.type }, + ); + switch (this.entityType) { + case EntityType.DEVICE: + this.columnTypes.push( + { value: ImportEntityColumnType.sharedAttribute }, + { value: ImportEntityColumnType.serverAttribute }, + { value: ImportEntityColumnType.timeseries }, + { value: ImportEntityColumnType.accessToken } + ); + break; + case EntityType.ASSET: + this.columnTypes.push( + { value: ImportEntityColumnType.serverAttribute }, + { value: ImportEntityColumnType.timeseries } + ); + break; + } + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + if (this.propagateChangePending) { + this.propagateChange(this.columns); + this.propagateChangePending = false; + } + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + columnsUpdated() { + const isSelectName = this.columns.findIndex((column) => column.type === ImportEntityColumnType.name) > -1; + const isSelectType = this.columns.findIndex((column) => column.type === ImportEntityColumnType.type) > -1; + const isSelectCredentials = this.columns.findIndex((column) => column.type === ImportEntityColumnType.accessToken) > -1; + const hasInvalidColumn = this.columns.findIndex((column) => !this.columnValid(column)) > -1; + + this.valid = isSelectName && isSelectType && !hasInvalidColumn; + + this.columnTypes.find((columnType) => columnType.value === ImportEntityColumnType.name).disabled = isSelectName; + this.columnTypes.find((columnType) => columnType.value === ImportEntityColumnType.type).disabled = isSelectType; + const accessTokenColumnType = this.columnTypes.find((columnType) => columnType.value === ImportEntityColumnType.accessToken); + if (accessTokenColumnType) { + accessTokenColumnType.disabled = isSelectCredentials; + } + if (this.propagateChange) { + this.propagateChange(this.columns); + } else { + this.propagateChangePending = true; + } + } + + private columnValid(column: CsvColumnParam): boolean { + if (!importEntityObjectColumns.includes(column.type)) { + return column.key && column.key.trim().length > 0; + } else { + return true; + } + } + + public validate(c: FormControl) { + return (this.valid) ? null : { + columnsInvalid: true + }; + return null; + } + + writeValue(value: CsvColumnParam[]): void { + this.columns = value; + this.dataSource.setColumns(this.columns); + this.columnsUpdated(); + } +} + +interface AssignmentColumnType { + value: ImportEntityColumnType; + disabled?: boolean; +} + +class CsvColumnsDatasource implements DataSource { + + private columnsSubject = new BehaviorSubject([]); + + constructor() {} + + connect(collectionViewer: CollectionViewer): Observable> { + return this.columnsSubject.asObservable(); + } + + disconnect(collectionViewer: CollectionViewer): void { + this.columnsSubject.complete(); + } + + setColumns(columns: CsvColumnParam[]) { + this.columnsSubject.next(columns); + } + +} 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 01274a9555..10981de3a4 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 @@ -162,7 +162,7 @@ export class WidgetComponentService { } else { fetchQueue = new Array>(); this.widgetsInfoFetchQueue.set(key, fetchQueue); - this.widgetService.getWidgetType(bundleAlias, widgetTypeAlias, isSystem, true, false).subscribe( + this.widgetService.getWidgetType(bundleAlias, widgetTypeAlias, isSystem, {ignoreErrors: true}).subscribe( (widgetType) => { this.loadWidget(widgetType, bundleAlias, isSystem, widgetInfoSubject); }, diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts index e0cdc00076..b5981e5700 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts @@ -705,8 +705,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont {entityType: entity.entityType, id: entity.id}, query, dataKeyType, - true, - true + {ignoreLoading: true, ignoreErrors: true} ).pipe( map((keys) => { const dataKeys: Array = []; diff --git a/ui-ngx/src/app/modules/home/dialogs/home-dialogs.module.ts b/ui-ngx/src/app/modules/home/dialogs/home-dialogs.module.ts index 92dcf50231..1918df4281 100644 --- a/ui-ngx/src/app/modules/home/dialogs/home-dialogs.module.ts +++ b/ui-ngx/src/app/modules/home/dialogs/home-dialogs.module.ts @@ -19,6 +19,7 @@ import { CommonModule } from '@angular/common'; import { SharedModule } from '@app/shared/shared.module'; import {AssignToCustomerDialogComponent} from '@modules/home/dialogs/assign-to-customer-dialog.component'; import {AddEntitiesToCustomerDialogComponent} from '@modules/home/dialogs/add-entities-to-customer-dialog.component'; +import { HomeDialogsService } from './home-dialogs.service'; @NgModule({ entryComponents: [ @@ -37,6 +38,9 @@ import {AddEntitiesToCustomerDialogComponent} from '@modules/home/dialogs/add-en exports: [ AssignToCustomerDialogComponent, AddEntitiesToCustomerDialogComponent + ], + providers: [ + HomeDialogsService ] }) export class HomeDialogsModule { } diff --git a/ui-ngx/src/app/modules/home/dialogs/home-dialogs.service.ts b/ui-ngx/src/app/modules/home/dialogs/home-dialogs.service.ts new file mode 100644 index 0000000000..674c3aaa5a --- /dev/null +++ b/ui-ngx/src/app/modules/home/dialogs/home-dialogs.service.ts @@ -0,0 +1,57 @@ +/// +/// Copyright © 2016-2019 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 { Injectable } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { AuthService } from '@core/auth/auth.service'; +import { MatDialog } from '@angular/material/dialog'; +import { EntityType } from '@shared/models/entity-type.models'; +import { Observable } from 'rxjs'; +import { + ImportDialogCsvComponent, + ImportDialogCsvData +} from '@home/components/import-export/import-dialog-csv.component'; + +@Injectable() +export class HomeDialogsService { + constructor( + private dialog: MatDialog + ) { + } + + public importEntities(entityType: EntityType): Observable { + switch (entityType) { + case EntityType.DEVICE: + return this.openImportDialogCSV(entityType, 'device.import', 'device.device-file'); + case EntityType.ASSET: + return this.openImportDialogCSV(entityType, 'asset.import', 'asset.asset-file'); + break; + } + } + + private openImportDialogCSV(entityType: EntityType, importTitle: string, importFileLabel: string): Observable { + return this.dialog.open(ImportDialogCsvComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + entityType, + importTitle, + importFileLabel + } + }).afterClosed(); + } +} diff --git a/ui-ngx/src/app/modules/home/home.component.html b/ui-ngx/src/app/modules/home/home.component.html index b29f051677..5db1c33185 100644 --- a/ui-ngx/src/app/modules/home/home.component.html +++ b/ui-ngx/src/app/modules/home/home.component.html @@ -19,7 +19,7 @@ + [opened]="sidenavOpened && !forceFullscreen">
@@ -35,9 +35,12 @@
- +
- + +
+ + +
+
+ + dashboard.state + + + {{ stateItem.value.name }} + + + +
+
+
+ + + +
+ diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/select-target-state-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/states/select-target-state-dialog.component.ts new file mode 100644 index 0000000000..0c5b6842ff --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/select-target-state-dialog.component.ts @@ -0,0 +1,85 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Inject, OnInit, SkipSelf } from '@angular/core'; +import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; +import { Router } from '@angular/router'; +import { DialogComponent } from '@app/shared/components/dialog.component'; +import { UtilsService } from '@core/services/utils.service'; +import { TranslateService } from '@ngx-translate/core'; +import { DashboardState } from '@app/shared/models/dashboard.models'; +import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; +import { DashboardService } from '@core/http/dashboard.service'; + +export interface SelectTargetStateDialogData { + states: {[id: string]: DashboardState }; +} + +@Component({ + selector: 'tb-select-target-state-dialog', + templateUrl: './select-target-state-dialog.component.html', + providers: [{provide: ErrorStateMatcher, useExisting: SelectTargetStateDialogComponent}], + styleUrls: [] +}) +export class SelectTargetStateDialogComponent extends + DialogComponent + implements OnInit, ErrorStateMatcher { + + states: {[id: string]: DashboardState }; + stateFormGroup: FormGroup; + + submitted = false; + + constructor(protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) public data: SelectTargetStateDialogData, + @SkipSelf() private errorStateMatcher: ErrorStateMatcher, + public dialogRef: MatDialogRef, + private fb: FormBuilder, + private dashboardUtils: DashboardUtilsService) { + super(store, router, dialogRef); + + this.states = this.data.states; + + this.stateFormGroup = this.fb.group( + { + stateId: [this.dashboardUtils.getRootStateId(this.states), [Validators.required]] + } + ); + } + + ngOnInit(): void { + } + + isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { + const originalErrorState = this.errorStateMatcher.isErrorState(control, form); + const customErrorState = !!(control && control.invalid && this.submitted); + return originalErrorState || customErrorState; + } + + cancel(): void { + this.dialogRef.close(null); + } + + save(): void { + this.submitted = true; + const stateId: string = this.stateFormGroup.get('stateId').value; + this.dialogRef.close(stateId); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/state-controller.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/states/state-controller.component.ts index cfe3e9bf8f..3c21b014e5 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/states/state-controller.component.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/state-controller.component.ts @@ -60,9 +60,10 @@ export abstract class StateControllerComponent implements IStateControllerCompon set dashboardId(val: string) { if (this.dashboardIdValue !== val) { this.dashboardIdValue = val; - if (this.inited) { +/* if (this.inited) { + this.currentState = this.route.snapshot.queryParamMap.get('state'); this.init(); - } + }*/ } } get dashboardId(): string { @@ -84,8 +85,6 @@ export abstract class StateControllerComponent implements IStateControllerCompon currentState: string; - currentUrl: string; - private rxSubscriptions = new Array(); private inited = false; @@ -96,10 +95,9 @@ export abstract class StateControllerComponent implements IStateControllerCompon } ngOnInit(): void { - this.currentUrl = this.router.url.split('?')[0]; this.rxSubscriptions.push(this.route.queryParamMap.subscribe((paramMap) => { - const newUrl = this.router.url.split('?')[0]; - if (this.currentUrl === newUrl) { + const dashboardId = this.route.snapshot.params.dashboardId; + if (this.dashboardId === dashboardId) { const newState = paramMap.get('state'); if (this.currentState !== newState) { this.currentState = newState; @@ -144,6 +142,11 @@ export abstract class StateControllerComponent implements IStateControllerCompon this.statesControllerService.cleanupPreservedStates(); } + public reInit() { + this.currentState = this.route.snapshot.queryParamMap.get('state'); + this.init(); + } + protected abstract init(); protected abstract onMobileChanged(); diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/state-controller.models.ts b/ui-ngx/src/app/modules/home/pages/dashboard/states/state-controller.models.ts index 2fa0fef100..8a1ce9319d 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/states/state-controller.models.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/state-controller.models.ts @@ -27,4 +27,5 @@ export interface IStateControllerComponent extends IStateController { states: {[id: string]: DashboardState }; dashboardId: string; preservedState: any; + reInit(): void; } diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/states-component.directive.ts b/ui-ngx/src/app/modules/home/pages/dashboard/states/states-component.directive.ts index 48970e36ae..534eb1d6f7 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/states/states-component.directive.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/states-component.directive.ts @@ -72,6 +72,7 @@ export class StatesComponentDirective implements OnInit, OnDestroy, OnChanges { } ngOnChanges(changes: SimpleChanges): void { + let reInitController = false; for (const propName of Object.keys(changes)) { const change = changes[propName]; if (!change.firstChange && change.currentValue !== change.previousValue) { @@ -81,6 +82,7 @@ export class StatesComponentDirective implements OnInit, OnDestroy, OnChanges { this.stateControllerComponent.states = this.states; } else if (propName === 'dashboardId') { this.stateControllerComponent.dashboardId = this.dashboardId; + reInitController = true; } else if (propName === 'isMobile') { this.stateControllerComponent.isMobile = this.isMobile; } else if (propName === 'state') { @@ -88,6 +90,9 @@ export class StatesComponentDirective implements OnInit, OnDestroy, OnChanges { } } } + if (reInitController) { + this.stateControllerComponent.reInit(); + } } private reInit() { diff --git a/ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html b/ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html index a4d260f91b..f0862dbe52 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html @@ -19,6 +19,7 @@ label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab"> @@ -26,6 +27,7 @@ label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab"> 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 b916313ce9..555bbd0499 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 @@ -14,51 +14,53 @@ /// limitations under the License. /// -import {Injectable} from '@angular/core'; +import { Injectable } from '@angular/core'; -import {ActivatedRouteSnapshot, Resolve, Router} from '@angular/router'; +import { ActivatedRouteSnapshot, Resolve, Router } from '@angular/router'; import { CellActionDescriptor, checkBoxCell, DateEntityTableColumn, EntityTableColumn, - EntityTableConfig, GroupActionDescriptor, + EntityTableConfig, + GroupActionDescriptor, HeaderActionDescriptor } from '@home/models/entity/entities-table-config.models'; -import {TranslateService} from '@ngx-translate/core'; -import {DatePipe} from '@angular/common'; -import {EntityType, entityTypeResources, entityTypeTranslations} from '@shared/models/entity-type.models'; -import {EntityAction} from '@home/models/entity/entity-component.models'; -import {Device, DeviceCredentials, DeviceInfo} from '@app/shared/models/device.models'; -import {DeviceComponent} from '@modules/home/pages/device/device.component'; -import {forkJoin, Observable, of} from 'rxjs'; -import {select, Store} from '@ngrx/store'; -import {selectAuthUser} from '@core/auth/auth.selectors'; -import {map, mergeMap, take, tap} from 'rxjs/operators'; -import {AppState} from '@core/core.state'; -import {DeviceService} from '@app/core/http/device.service'; -import {Authority} from '@app/shared/models/authority.enum'; -import {CustomerService} from '@core/http/customer.service'; -import {Customer} from '@app/shared/models/customer.model'; -import {NULL_UUID} from '@shared/models/id/has-uuid'; -import {BroadcastService} from '@core/services/broadcast.service'; -import {DeviceTableHeaderComponent} from '@modules/home/pages/device/device-table-header.component'; -import {MatDialog} from '@angular/material'; +import { TranslateService } from '@ngx-translate/core'; +import { DatePipe } from '@angular/common'; +import { EntityType, entityTypeResources, entityTypeTranslations } from '@shared/models/entity-type.models'; +import { EntityAction } from '@home/models/entity/entity-component.models'; +import { Device, DeviceCredentials, DeviceInfo } from '@app/shared/models/device.models'; +import { DeviceComponent } from '@modules/home/pages/device/device.component'; +import { forkJoin, Observable, of } from 'rxjs'; +import { select, Store } from '@ngrx/store'; +import { selectAuthUser } from '@core/auth/auth.selectors'; +import { map, mergeMap, take, tap } from 'rxjs/operators'; +import { AppState } from '@core/core.state'; +import { DeviceService } from '@app/core/http/device.service'; +import { Authority } from '@app/shared/models/authority.enum'; +import { CustomerService } from '@core/http/customer.service'; +import { Customer } from '@app/shared/models/customer.model'; +import { NULL_UUID } from '@shared/models/id/has-uuid'; +import { BroadcastService } from '@core/services/broadcast.service'; +import { DeviceTableHeaderComponent } from '@modules/home/pages/device/device-table-header.component'; +import { MatDialog } from '@angular/material'; import { DeviceCredentialsDialogComponent, DeviceCredentialsDialogData } from '@modules/home/pages/device/device-credentials-dialog.component'; -import {DialogService} from '@core/services/dialog.service'; +import { DialogService } from '@core/services/dialog.service'; import { AssignToCustomerDialogComponent, AssignToCustomerDialogData } from '@modules/home/dialogs/assign-to-customer-dialog.component'; -import {DeviceId} from '@app/shared/models/id/device-id'; +import { DeviceId } from '@app/shared/models/id/device-id'; import { AddEntitiesToCustomerDialogComponent, AddEntitiesToCustomerDialogData } from '../../dialogs/add-entities-to-customer-dialog.component'; import { DeviceTabsComponent } from '@home/pages/device/device-tabs.component'; +import { HomeDialogsService } from '@home/dialogs/home-dialogs.service'; @Injectable() export class DevicesTableConfigResolver implements Resolve> { @@ -72,6 +74,7 @@ export class DevicesTableConfigResolver implements Resolve { + if (res) { + this.broadcast.broadcast('deviceSaved'); + this.config.table.updateData(); + } + }); } addDevicesToCustomer($event: Event) { diff --git a/ui-ngx/src/app/modules/home/pages/entity-view/entity-view-tabs.component.html b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view-tabs.component.html index 3de07d16e7..33d183389e 100644 --- a/ui-ngx/src/app/modules/home/pages/entity-view/entity-view-tabs.component.html +++ b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view-tabs.component.html @@ -19,6 +19,7 @@ label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab"> @@ -26,6 +27,7 @@ label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab"> diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-tabs.component.html b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-tabs.component.html index 1c5b2b55e0..0118d1f5f2 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-tabs.component.html +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-tabs.component.html @@ -19,6 +19,7 @@ label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab"> @@ -26,6 +27,7 @@ label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab"> diff --git a/ui-ngx/src/app/modules/home/pages/tenant/tenant-tabs.component.html b/ui-ngx/src/app/modules/home/pages/tenant/tenant-tabs.component.html index b48d44b826..38d717742a 100644 --- a/ui-ngx/src/app/modules/home/pages/tenant/tenant-tabs.component.html +++ b/ui-ngx/src/app/modules/home/pages/tenant/tenant-tabs.component.html @@ -19,6 +19,7 @@ label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab"> @@ -26,6 +27,7 @@ label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab"> diff --git a/ui-ngx/src/app/modules/login/pages/login/login.component.ts b/ui-ngx/src/app/modules/login/pages/login/login.component.ts index ca75c1f4b8..b893f729f3 100644 --- a/ui-ngx/src/app/modules/login/pages/login/login.component.ts +++ b/ui-ngx/src/app/modules/login/pages/login/login.component.ts @@ -29,7 +29,10 @@ import { FormBuilder } from '@angular/forms'; }) export class LoginComponent extends PageComponent implements OnInit { - loginFormGroup = this.fb.group(new LoginRequest('', '')); + loginFormGroup = this.fb.group({ + username: '', + password: '' + }); constructor(protected store: Store, private authService: AuthService, diff --git a/ui-ngx/src/app/shared/components/dashboard-autocomplete.component.ts b/ui-ngx/src/app/shared/components/dashboard-autocomplete.component.ts index f4743ab4c5..65b4337c75 100644 --- a/ui-ngx/src/app/shared/components/dashboard-autocomplete.component.ts +++ b/ui-ngx/src/app/shared/components/dashboard-autocomplete.component.ts @@ -192,19 +192,22 @@ export class DashboardAutocompleteComponent implements ControlValueAccessor, OnI const authUser = getCurrentAuthUser(this.store); if (this.dashboardsScope === 'customer' || authUser.authority === Authority.CUSTOMER_USER) { if (this.customerId) { - dashboardsObservable = this.dashboardService.getCustomerDashboards(this.customerId, pageLink, false, true); + dashboardsObservable = this.dashboardService.getCustomerDashboards(this.customerId, pageLink, + {ignoreLoading: true}); } else { dashboardsObservable = of(emptyPageData()); } } else { if (authUser.authority === Authority.SYS_ADMIN) { if (this.tenantId) { - dashboardsObservable = this.dashboardService.getTenantDashboardsByTenantId(this.tenantId, pageLink, false, true); + dashboardsObservable = this.dashboardService.getTenantDashboardsByTenantId(this.tenantId, pageLink, + {ignoreLoading: true}); } else { dashboardsObservable = of(emptyPageData()); } } else { - dashboardsObservable = this.dashboardService.getTenantDashboards(pageLink, false, true); + dashboardsObservable = this.dashboardService.getTenantDashboards(pageLink, + {ignoreLoading: true}); } } return dashboardsObservable; diff --git a/ui-ngx/src/app/shared/components/dashboard-select.component.ts b/ui-ngx/src/app/shared/components/dashboard-select.component.ts index 341dd9e34e..c78ef98cc2 100644 --- a/ui-ngx/src/app/shared/components/dashboard-select.component.ts +++ b/ui-ngx/src/app/shared/components/dashboard-select.component.ts @@ -202,12 +202,13 @@ export class DashboardSelectComponent implements ControlValueAccessor, OnInit { const authUser = getCurrentAuthUser(this.store); if (this.dashboardsScope === 'customer' || authUser.authority === Authority.CUSTOMER_USER) { if (this.customerId && this.customerId !== NULL_UUID) { - dashboardsObservable = this.dashboardService.getCustomerDashboards(this.customerId, pageLink, false, true); + dashboardsObservable = this.dashboardService.getCustomerDashboards(this.customerId, pageLink, + {ignoreLoading: true}); } else { dashboardsObservable = of(emptyPageData()); } } else { - dashboardsObservable = this.dashboardService.getTenantDashboards(pageLink, false, true); + dashboardsObservable = this.dashboardService.getTenantDashboards(pageLink, {ignoreLoading: true}); } return dashboardsObservable; } diff --git a/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts b/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts index 41985ac384..815c3ba8da 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts @@ -230,7 +230,7 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit if (targetEntityType === AliasEntityType.CURRENT_CUSTOMER) { targetEntityType = EntityType.CUSTOMER; } - this.entityService.getEntity(targetEntityType, value, true).subscribe( + this.entityService.getEntity(targetEntityType, value, {ignoreLoading: true}).subscribe( (entity) => { this.modelValue = entity.id.id; this.selectEntityFormGroup.get('entity').patchValue(entity, {emitEvent: false}); @@ -238,7 +238,7 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit ); } else { const targetEntityType = value.entityType as EntityType; - this.entityService.getEntity(targetEntityType, value.id, true).subscribe( + this.entityService.getEntity(targetEntityType, value.id, {ignoreLoading: true}).subscribe( (entity) => { this.modelValue = entity.id.id; this.selectEntityFormGroup.get('entity').patchValue(entity, {emitEvent: false}); @@ -281,7 +281,7 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit targetEntityType = EntityType.CUSTOMER; } return this.entityService.getEntitiesByNameFilter(targetEntityType, searchText, - 50, this.entitySubtypeValue, false, true).pipe( + 50, this.entitySubtypeValue, {ignoreLoading: true}).pipe( map((data) => { if (data) { if (this.excludeEntityIds && this.excludeEntityIds.length) { diff --git a/ui-ngx/src/app/shared/components/entity/entity-keys-list.component.ts b/ui-ngx/src/app/shared/components/entity/entity-keys-list.component.ts index a9aca0725a..dc006ecf57 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-keys-list.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-keys-list.component.ts @@ -198,7 +198,7 @@ export class EntityKeysListComponent implements ControlValueAccessor, OnInit, Af fetchKeys(searchText?: string): Observable> { this.searchText = searchText; return this.entityIdValue ? this.entityService.getEntityKeys(this.entityIdValue, searchText, - this.dataKeyType, false, true).pipe( + this.dataKeyType, {ignoreLoading: true}).pipe( map((data) => data ? data : [])) : of([]); } diff --git a/ui-ngx/src/app/shared/components/entity/entity-list.component.ts b/ui-ngx/src/app/shared/components/entity/entity-list.component.ts index db373c80f0..54cb2f37db 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-list.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-list.component.ts @@ -223,7 +223,7 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV fetchEntities(searchText?: string): Observable>> { this.searchText = searchText; return this.entityService.getEntitiesByNameFilter(this.entityType, searchText, - 50, '', false, true).pipe( + 50, '', {ignoreLoading: true}).pipe( map((data) => data ? data : [])); } diff --git a/ui-ngx/src/app/shared/components/entity/entity-subtype-autocomplete.component.ts b/ui-ngx/src/app/shared/components/entity/entity-subtype-autocomplete.component.ts index 9983254072..7e809471bf 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-subtype-autocomplete.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-subtype-autocomplete.component.ts @@ -205,13 +205,13 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor, let subTypesObservable: Observable>; switch (this.entityType) { case EntityType.ASSET: - subTypesObservable = this.assetService.getAssetTypes(false, true); + subTypesObservable = this.assetService.getAssetTypes({ignoreLoading: true}); break; case EntityType.DEVICE: - subTypesObservable = this.deviceService.getDeviceTypes(false, true); + subTypesObservable = this.deviceService.getDeviceTypes({ignoreLoading: true}); break; case EntityType.ENTITY_VIEW: - subTypesObservable = this.entityViewService.getEntityViewTypes(false, true); + subTypesObservable = this.entityViewService.getEntityViewTypes({ignoreLoading: true}); break; } if (subTypesObservable) { diff --git a/ui-ngx/src/app/shared/components/entity/entity-subtype-list.component.ts b/ui-ngx/src/app/shared/components/entity/entity-subtype-list.component.ts index a98e06210c..58ed2600b5 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-subtype-list.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-subtype-list.component.ts @@ -276,13 +276,13 @@ export class EntitySubTypeListComponent implements ControlValueAccessor, OnInit, let subTypesObservable: Observable>; switch (this.entityType) { case EntityType.ASSET: - subTypesObservable = this.assetService.getAssetTypes(false, true); + subTypesObservable = this.assetService.getAssetTypes({ignoreLoading: true}); break; case EntityType.DEVICE: - subTypesObservable = this.deviceService.getDeviceTypes(false, true); + subTypesObservable = this.deviceService.getDeviceTypes({ignoreLoading: true}); break; case EntityType.ENTITY_VIEW: - subTypesObservable = this.entityViewService.getEntityViewTypes(false, true); + subTypesObservable = this.entityViewService.getEntityViewTypes({ignoreLoading: true}); break; } if (subTypesObservable) { diff --git a/ui-ngx/src/app/shared/components/entity/entity-subtype-select.component.html b/ui-ngx/src/app/shared/components/entity/entity-subtype-select.component.html index 96f0b679c7..9877f9b66f 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-subtype-select.component.html +++ b/ui-ngx/src/app/shared/components/entity/entity-subtype-select.component.html @@ -17,8 +17,7 @@ --> {{ entitySubtypeTitle | translate }} - + {{ displaySubTypeFn(subType) }} diff --git a/ui-ngx/src/app/shared/components/entity/entity-subtype-select.component.ts b/ui-ngx/src/app/shared/components/entity/entity-subtype-select.component.ts index 106f869d80..d446a3e2ca 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-subtype-select.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-subtype-select.component.ts @@ -211,13 +211,13 @@ export class EntitySubTypeSelectComponent implements ControlValueAccessor, OnIni if (!this.subTypes) { switch (this.entityType) { case EntityType.ASSET: - this.subTypes = this.assetService.getAssetTypes(false, true); + this.subTypes = this.assetService.getAssetTypes({ignoreLoading: true}); break; case EntityType.DEVICE: - this.subTypes = this.deviceService.getDeviceTypes(false, true); + this.subTypes = this.deviceService.getDeviceTypes({ignoreLoading: true}); break; case EntityType.ENTITY_VIEW: - this.subTypes = this.entityViewService.getEntityViewTypes(false, true); + this.subTypes = this.entityViewService.getEntityViewTypes({ignoreLoading: true}); break; } if (this.subTypes) { @@ -227,6 +227,20 @@ export class EntitySubTypeSelectComponent implements ControlValueAccessor, OnIni this.subTypesLoaded = true; return allSubtypes; }), + tap((subTypes) => { + const type: EntitySubtype | string = this.subTypeFormGroup.get('subType').value; + const strType = typeof type === 'string' ? type : type.type; + const found = subTypes.find((subType) => { + if (typeof subType === 'string') { + return subType === type; + } else { + return subType.type === strType; + } + }); + if (found) { + this.subTypeFormGroup.get('subType').patchValue(found); + } + }), publishReplay(1), refCount() ); diff --git a/ui-ngx/src/app/shared/components/fullscreen.directive.ts b/ui-ngx/src/app/shared/components/fullscreen.directive.ts index d8b3827036..d21ded3b61 100644 --- a/ui-ngx/src/app/shared/components/fullscreen.directive.ts +++ b/ui-ngx/src/app/shared/components/fullscreen.directive.ts @@ -107,7 +107,9 @@ export class FullscreenDirective implements OnChanges, OnDestroy { if (this.elementRef) { this.elementRef.nativeElement.classList.remove('tb-fullscreen'); } - this.overlayRef.dispose(); + if (this.overlayRef) { + this.overlayRef.dispose(); + } this.fullscreenChanged.emit(false); } } diff --git a/ui-ngx/src/app/shared/models/asset.models.ts b/ui-ngx/src/app/shared/models/asset.models.ts index c7b5c41563..ba29f904f8 100644 --- a/ui-ngx/src/app/shared/models/asset.models.ts +++ b/ui-ngx/src/app/shared/models/asset.models.ts @@ -22,8 +22,8 @@ import {DeviceCredentialsId} from '@shared/models/id/device-credentials-id'; import { EntitySearchQuery } from '@shared/models/relation.models'; export interface Asset extends BaseData { - tenantId: TenantId; - customerId: CustomerId; + tenantId?: TenantId; + customerId?: CustomerId; name: string; type: string; additionalInfo?: any; diff --git a/ui-ngx/src/app/shared/models/constants.ts b/ui-ngx/src/app/shared/models/constants.ts index 31edec6cfe..96a4fe782c 100644 --- a/ui-ngx/src/app/shared/models/constants.ts +++ b/ui-ngx/src/app/shared/models/constants.ts @@ -63,6 +63,7 @@ export const HelpLinks = { devices: helpBaseUrl + '/docs/user-guide/ui/devices', assets: helpBaseUrl + '/docs/user-guide/ui/assets', entityViews: helpBaseUrl + '/docs/user-guide/ui/entity-views', + entitiesImport: helpBaseUrl + '/docs/user-guide/bulk-provisioning', rulechains: helpBaseUrl + '/docs/user-guide/ui/rule-chains', dashboards: helpBaseUrl + '/docs/user-guide/ui/dashboards', widgetsBundles: helpBaseUrl + '/docs/user-guide/ui/widget-library#bundles', diff --git a/ui-ngx/src/app/shared/models/dashboard.models.ts b/ui-ngx/src/app/shared/models/dashboard.models.ts index 6c12e61a44..44d4fb1727 100644 --- a/ui-ngx/src/app/shared/models/dashboard.models.ts +++ b/ui-ngx/src/app/shared/models/dashboard.models.ts @@ -98,7 +98,6 @@ export interface DashboardConfiguration { states?: {[id: string]: DashboardState }; entityAliases?: EntityAliases; [key: string]: any; - // TODO: } export interface Dashboard extends DashboardInfo { diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index 6ddd015a24..3ab95fc73e 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -22,11 +22,11 @@ import {DeviceCredentialsId} from '@shared/models/id/device-credentials-id'; import { EntitySearchQuery } from '@shared/models/relation.models'; export interface Device extends BaseData { - tenantId: TenantId; - customerId: CustomerId; + tenantId?: TenantId; + customerId?: CustomerId; name: string; type: string; - label: string; + label?: string; additionalInfo?: any; } diff --git a/ui-ngx/src/app/shared/models/entity.models.ts b/ui-ngx/src/app/shared/models/entity.models.ts index b1349986fa..aab052f213 100644 --- a/ui-ngx/src/app/shared/models/entity.models.ts +++ b/ui-ngx/src/app/shared/models/entity.models.ts @@ -17,6 +17,7 @@ import { BaseData } from '@shared/models/base-data'; import { EntityType } from '@shared/models/entity-type.models'; import { EntityId } from '@shared/models/id/entity-id'; +import { AttributeData } from './telemetry/telemetry.models'; export interface EntityInfo { origEntity?: BaseData; @@ -26,3 +27,26 @@ export interface EntityInfo { id?: string; entityDescription?: string; } + +export interface ImportEntityData { + name: string; + type: string; + accessToken: string; + attributes: { + server: AttributeData[], + shared: AttributeData[] + }; + timeseries: AttributeData[]; +} + +export interface ImportEntitiesResultInfo { + create?: { + entity: number; + }; + update?: { + entity: number; + }; + error?: { + entity: number; + }; +} diff --git a/ui-ngx/src/app/shared/models/login.models.ts b/ui-ngx/src/app/shared/models/login.models.ts index 9d11359f5a..25905f923d 100644 --- a/ui-ngx/src/app/shared/models/login.models.ts +++ b/ui-ngx/src/app/shared/models/login.models.ts @@ -14,17 +14,16 @@ /// limitations under the License. /// -export class LoginRequest { +export interface LoginRequest { username: string; password: string; +} - constructor(username: string, password: string) { - this.username = username; - this.password = password; - } +export interface PublicLoginRequest { + publicId: string; } -export class LoginResponse { +export interface LoginResponse { token: string; refreshToken: string; } diff --git a/ui-ngx/src/app/shared/models/settings.models.ts b/ui-ngx/src/app/shared/models/settings.models.ts index 147967b3e7..2a1832df27 100644 --- a/ui-ngx/src/app/shared/models/settings.models.ts +++ b/ui-ngx/src/app/shared/models/settings.models.ts @@ -50,3 +50,8 @@ export interface UserPasswordPolicy { export interface SecuritySettings { passwordPolicy: UserPasswordPolicy; } + +export interface UpdateMessage { + message: string; + updateAvailable: boolean; +} diff --git a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts index baacc18006..d0c837332a 100644 --- a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts +++ b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts @@ -20,6 +20,7 @@ import { AggregationType } from '../time/time.models'; import { Observable, ReplaySubject, Subject } from 'rxjs'; import { EntityId } from '@shared/models/id/entity-id'; import { map } from 'rxjs/operators'; +import { NgZone } from '@angular/core'; export enum DataKeyType { timeseries = 'timeseries', @@ -64,7 +65,7 @@ export const isClientSideTelemetryType = new Map( ); export interface AttributeData { - lastUpdateTs: number; + lastUpdateTs?: number; key: string; value: any; } @@ -231,6 +232,8 @@ export class TelemetrySubscriber { private dataSubject = new ReplaySubject(); private reconnectSubject = new Subject(); + private zone: NgZone; + public subscriptionCommands: Array; public data$ = this.dataSubject.asObservable(); @@ -238,7 +241,7 @@ export class TelemetrySubscriber { public static createEntityAttributesSubscription(telemetryService: TelemetryService, entityId: EntityId, attributeScope: TelemetryType, - keys: string[] = null): TelemetrySubscriber { + zone: NgZone, keys: string[] = null): TelemetrySubscriber { let subscriptionCommand: SubscriptionCmd; if (attributeScope === LatestTelemetry.LATEST_TELEMETRY) { subscriptionCommand = new TimeseriesSubscriptionCmd(); @@ -252,6 +255,7 @@ export class TelemetrySubscriber { subscriptionCommand.keys = keys.join(','); } const subscriber = new TelemetrySubscriber(telemetryService); + subscriber.zone = zone; subscriber.subscriptionCommands.push(subscriptionCommand); return subscriber; } @@ -280,7 +284,15 @@ export class TelemetrySubscriber { } } message.prepareData(keys); - this.dataSubject.next(message); + if (this.zone) { + this.zone.run( + () => { + this.dataSubject.next(message); + } + ); + } else { + this.dataSubject.next(message); + } } public onReconnected() { diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index 5e040d8b51..ab42427e58 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -262,7 +262,6 @@ export interface Datasource { entityDescription?: string; generated?: boolean; [key: string]: any; - // TODO: } export type DataSet = [number, any][]; diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index d5d4bd73e4..31200899d8 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -61,6 +61,7 @@ import { RouterModule } from '@angular/router'; import { ShareModule as ShareButtonsModule } from '@ngx-share/core'; import { HotkeyModule } from 'angular2-hotkeys'; import { ColorPickerModule } from 'ngx-color-picker'; +import { NgxHmCarouselModule } from 'ngx-hm-carousel'; import { UserMenuComponent } from '@shared/components/user-menu.component'; import { NospacePipe } from './pipe/nospace.pipe'; import { TranslateModule } from '@ngx-translate/core'; @@ -233,6 +234,7 @@ import { FileInputComponent } from './components/file-input.component'; ShareButtonsModule, HotkeyModule, ColorPickerModule, + NgxHmCarouselModule, NgxFlowModule ], exports: [ @@ -314,6 +316,7 @@ import { FileInputComponent } from './components/file-input.component'; ShareButtonsModule, HotkeyModule, ColorPickerModule, + NgxHmCarouselModule, ColorPickerDialogComponent, MaterialIconsDialogComponent, ColorInputComponent, 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 9f85270dfd..d806f61f42 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -52,7 +52,8 @@ "import": "Import", "export": "Export", "share-via": "Share via {{provider}}", - "continue": "Continue" + "continue": "Continue", + "back": "Back" }, "aggregation": { "aggregation": "Aggregation", From aa1a43fcea729d81fc962fd636da806028fe1d77 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 15 Nov 2019 16:12:24 +0200 Subject: [PATCH 052/133] Rule chain page --- ui-ngx/package-lock.json | 103 +++++++++++++++++- ui-ngx/package.json | 1 + .../rulechain/rulechain-page.component.html | 19 ++++ .../rulechain/rulechain-page.component.ts | 62 +++++++++++ .../rulechain/rulechain-routing.module.ts | 55 +++++++++- .../home/pages/rulechain/rulechain.module.ts | 4 +- .../rulechains-table-config.resolver.ts | 4 +- ui-ngx/src/app/shared/shared.module.ts | 5 +- 8 files changed, 239 insertions(+), 14 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.ts diff --git a/ui-ngx/package-lock.json b/ui-ngx/package-lock.json index 6a21376c02..bc2b30191c 100644 --- a/ui-ngx/package-lock.json +++ b/ui-ngx/package-lock.json @@ -5140,9 +5140,9 @@ "dev": true }, "handlebars": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.1.tgz", - "integrity": "sha512-C29UoFzHe9yM61lOsIlCE5/mQVGrnIOrOq7maQl76L7tYPCgC1og0Ajt6uWnX4ZTxBPnjw+CUvawphwCfJgUnA==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.2.tgz", + "integrity": "sha512-29Zxv/cynYB7mkT1rVWQnV7mGX6v7H/miQ6dbEpYTKq5eJBN7PsRB+ViYJlcT6JINTSu4dVB9kOqEun78h6Exg==", "dev": true, "requires": { "neo-async": "^2.6.0", @@ -7411,6 +7411,97 @@ "resolved": "https://registry.npmjs.org/ngx-color-picker/-/ngx-color-picker-8.2.0.tgz", "integrity": "sha512-rzR+cByjNG9M/UskU5vNoH7cUc6oM8STTDFKOZmnlX4ALOuM1+61CBjsNTGETWfo9a/h5mbGX02oh5/iNAa7vA==" }, + "ngx-flowchart": { + "version": "git://github.com/thingsboard/ngx-flowchart.git#d26ee52089a6d9cf8147c5f162144825fceb3009", + "from": "git://github.com/thingsboard/ngx-flowchart.git#master", + "requires": { + "@angular/animations": "~8.0.0", + "@angular/common": "~8.0.0", + "@angular/compiler": "~8.0.0", + "@angular/core": "~8.0.0", + "@angular/forms": "~8.0.0", + "@angular/platform-browser": "~8.0.0", + "@angular/platform-browser-dynamic": "~8.0.0", + "@angular/router": "~8.0.0", + "rxjs": "~6.4.0", + "tslib": "^1.9.0", + "zone.js": "~0.9.1" + }, + "dependencies": { + "@angular/animations": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-8.0.3.tgz", + "integrity": "sha512-9zciJ4YRR0bodFSYgsgXdYMz8wKKyVjch7XZADGkWubXT8mGuwlpdPMlQ6n9Cwj8Ebu0u52WxMeQsX76K9RlYA==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/common": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-8.0.3.tgz", + "integrity": "sha512-2YLYGVUf9eJZcocRmD3/9UHj4qFHt2t4ftDWJmrFM9zo2PZF+G5O9fASO7qoBbwpx3KFZtQO4dprKl2dFugRjg==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/compiler": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-8.0.3.tgz", + "integrity": "sha512-1/vF8D6l1O6IfWiDtaj6nC+B8CtkVtFgXgooDzLBO6XAkaCuJCnhKT1HnpWG5GtVsGaY9MGoTl1vE9ZMDbRQjg==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/core": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-8.0.3.tgz", + "integrity": "sha512-IIxrtIPNuv2+HudER9J1nmPGiGJ4aRpeiFM9V4lSiSFv50RzuaoG60XqYIpUyuBdgvyKigcrfSbu9+x1vyN0hw==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/forms": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-8.0.3.tgz", + "integrity": "sha512-22s82QDRQ72K4vMYuNh3NAN+da9uanwoydnfKlp2rb9dZAb2QVX9NN6gSoMrkSSr2O9KTP6pWiw6A3/MW8sGRA==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/platform-browser": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-8.0.3.tgz", + "integrity": "sha512-ceAPP2Ijmk2sZ1rnOU/WNlE3DtT6K6ljpjO9oUfXKMoSMdWirJKAraT3m/BAzmYwMSXpPBxA7c3paZjiLL6t5A==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/platform-browser-dynamic": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-8.0.3.tgz", + "integrity": "sha512-ZjQjSYslSQAKzM4llvyMFxnSjFpbhT1U9FOdKwscPe475zAKX0087qsHrP2CRwkJRfwtdcmj9wMUQIPlzMpHLA==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/router": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-8.0.3.tgz", + "integrity": "sha512-CU5pLTfQVUnTN93mdIKJrVjXiNldUkk30DPz4lpdxpZjYOqFGXeeSeQWmToHSofLPodNcAB4kkZ41VyXvlBu7w==", + "requires": { + "tslib": "^1.9.0" + } + }, + "rxjs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", + "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==", + "requires": { + "tslib": "^1.9.0" + } + } + } + }, "ngx-hm-carousel": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/ngx-hm-carousel/-/ngx-hm-carousel-1.7.2.tgz", @@ -10796,9 +10887,9 @@ "dev": true }, "uglify-js": { - "version": "3.6.8", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.8.tgz", - "integrity": "sha512-XhHJ3S3ZyMwP8kY1Gkugqx3CJh2C3O0y8NPiSxtm1tyD/pktLAkFZsFGpuNfTZddKDQ/bbDBLAd2YyA1pbi8HQ==", + "version": "3.6.9", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.9.tgz", + "integrity": "sha512-pcnnhaoG6RtrvHJ1dFncAe8Od6Nuy30oaJ82ts6//sGSXOP5UjBMEthiProjXmMNHOfd93sqlkztifFMcb+4yw==", "dev": true, "optional": true, "requires": { diff --git a/ui-ngx/package.json b/ui-ngx/package.json index 0dab581312..405d18d9f3 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -59,6 +59,7 @@ "moment": "^2.24.0", "ngx-clipboard": "^12.2.0", "ngx-color-picker": "^8.2.0", + "ngx-flowchart": "git://github.com/thingsboard/ngx-flowchart.git#master", "ngx-hm-carousel": "^1.7.2", "ngx-translate-messageformat-compiler": "^4.5.0", "objectpath": "^1.2.2", 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 new file mode 100644 index 0000000000..1ecb483e0a --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.html @@ -0,0 +1,19 @@ + +
Rule chain
+ 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 new file mode 100644 index 0000000000..1434ecb6af --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.ts @@ -0,0 +1,62 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import {Component, OnInit} from '@angular/core'; +import {UserService} from '@core/http/user.service'; +import {User} from '@shared/models/user.model'; +import {Authority} from '@shared/models/authority.enum'; +import {PageComponent} from '@shared/components/page.component'; +import {Store} from '@ngrx/store'; +import {AppState} from '@core/core.state'; +import {FormBuilder, FormGroup, Validators} from '@angular/forms'; +import {HasConfirmForm} from '@core/guards/confirm-on-exit.guard'; +import {ActionAuthUpdateUserDetails} from '@core/auth/auth.actions'; +import {environment as env} from '@env/environment'; +import {TranslateService} from '@ngx-translate/core'; +import {ActionSettingsChangeLanguage} from '@core/settings/settings.actions'; +import {ChangePasswordDialogComponent} from '@modules/home/pages/profile/change-password-dialog.component'; +import {MatDialog} from '@angular/material'; +import {DialogService} from '@core/services/dialog.service'; +import {AuthService} from '@core/auth/auth.service'; +import {ActivatedRoute} from '@angular/router'; +import { Dashboard } from '@shared/models/dashboard.models'; +import { RuleChain } from '@shared/models/rule-chain.models'; + +@Component({ + selector: 'tb-rulechain-page', + templateUrl: './rulechain-page.component.html', + styleUrls: [] +}) +export class RuleChainPageComponent extends PageComponent implements OnInit { + + ruleChain: RuleChain; + + constructor(protected store: Store, + private route: ActivatedRoute, + private userService: UserService, + private authService: AuthService, + private translate: TranslateService, + public dialog: MatDialog, + public dialogService: DialogService, + public fb: FormBuilder) { + super(store); + } + + ngOnInit() { + this.ruleChain = this.route.snapshot.data.ruleChain; + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts index 7c1dfac123..129da15ff1 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts @@ -14,12 +14,44 @@ /// limitations under the License. /// -import {NgModule} from '@angular/core'; -import {RouterModule, Routes} from '@angular/router'; +import { Injectable, NgModule } from '@angular/core'; +import { ActivatedRouteSnapshot, Resolve, RouterModule, Routes } from '@angular/router'; import {EntitiesTableComponent} from '../../components/entity/entities-table.component'; import {Authority} from '@shared/models/authority.enum'; import {RuleChainsTableConfigResolver} from '@modules/home/pages/rulechain/rulechains-table-config.resolver'; +import { Dashboard } from '@shared/models/dashboard.models'; +import { DashboardService } from '@core/http/dashboard.service'; +import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { BreadCrumbConfig, BreadCrumbLabelFunction } from '@shared/components/breadcrumb'; +import { RuleChain } from '@shared/models/rule-chain.models'; +import { RuleChainService } from '@core/http/rule-chain.service'; +import { DashboardPageComponent } from '@home/pages/dashboard/dashboard-page.component'; +import { dashboardBreadcumbLabelFunction, DashboardResolver } from '@home/pages/dashboard/dashboard-routing.module'; +import { RuleChainPageComponent } from '@home/pages/rulechain/rulechain-page.component'; + + +@Injectable() +export class RuleChainResolver implements Resolve { + + constructor(private ruleChainService: RuleChainService) { + } + + resolve(route: ActivatedRouteSnapshot): Observable { + const ruleChainId = route.params.ruleChainId; + return this.ruleChainService.getRuleChain(ruleChainId); + } +} + +export const ruleChainBreadcumbLabelFunction: BreadCrumbLabelFunction = ((route, translate, component) => { + let label: string = component.ruleChain.name; + if (component.ruleChain.root) { + label += ` (${translate.instant('rulechain.root')})`; + } + return label; +}); const routes: Routes = [ { @@ -41,6 +73,22 @@ const routes: Routes = [ resolve: { entitiesTableConfig: RuleChainsTableConfigResolver } + }, + { + path: ':ruleChainId', + component: RuleChainPageComponent, + data: { + breadcrumb: { + labelFunction: ruleChainBreadcumbLabelFunction, + icon: 'settings_ethernet' + } as BreadCrumbConfig, + auth: [Authority.TENANT_ADMIN], + title: 'rulechain.rulechain', + widgetEditMode: false + }, + resolve: { + ruleChain: RuleChainResolver + } } ] } @@ -50,7 +98,8 @@ const routes: Routes = [ imports: [RouterModule.forChild(routes)], exports: [RouterModule], providers: [ - RuleChainsTableConfigResolver + RuleChainsTableConfigResolver, + RuleChainResolver ] }) export class RuleChainRoutingModule { } diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.module.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.module.ts index 63b372afc2..3b87d7d13a 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.module.ts +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.module.ts @@ -21,6 +21,7 @@ import {RuleChainComponent} from '@modules/home/pages/rulechain/rulechain.compon import {RuleChainRoutingModule} from '@modules/home/pages/rulechain/rulechain-routing.module'; import {HomeComponentsModule} from '@modules/home/components/home-components.module'; import { RuleChainTabsComponent } from '@home/pages/rulechain/rulechain-tabs.component'; +import { RuleChainPageComponent } from './rulechain-page.component'; @NgModule({ entryComponents: [ @@ -29,7 +30,8 @@ import { RuleChainTabsComponent } from '@home/pages/rulechain/rulechain-tabs.com ], declarations: [ RuleChainComponent, - RuleChainTabsComponent + RuleChainTabsComponent, + RuleChainPageComponent ], imports: [ CommonModule, diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts index 7920e9c05e..e7c7c4d76a 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts @@ -128,9 +128,7 @@ export class RuleChainsTableConfigResolver implements Resolve Date: Thu, 21 Nov 2019 19:34:49 +0200 Subject: [PATCH 053/133] NgxFlowchart integration --- ui-ngx/package-lock.json | 988 ++++++------------ .../rulechain/rulechain-page.component.html | 11 +- .../rulechain/rulechain-page.component.ts | 111 ++ 3 files changed, 439 insertions(+), 671 deletions(-) diff --git a/ui-ngx/package-lock.json b/ui-ngx/package-lock.json index bc2b30191c..3d0a10bde7 100644 --- a/ui-ngx/package-lock.json +++ b/ui-ngx/package-lock.json @@ -5,13 +5,29 @@ "requires": true, "dependencies": { "@angular-builders/custom-webpack": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@angular-builders/custom-webpack/-/custom-webpack-8.2.0.tgz", - "integrity": "sha512-6654RfyqhTGhCI0edC9YU/iMn1UJnzX01bxYJbDWFgvReCqXdlgy+Fe9tp1MeqKweX6BQ1d0gRroR/WjY1aX0A==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@angular-builders/custom-webpack/-/custom-webpack-8.4.0.tgz", + "integrity": "sha512-I/U0zOwVAzBO1RRcF4zmX7enOruvfnHjXvxv5YQ4SkQaNtMo34xuX/8g+HQ4mMvY1/aH3sfBh8k8qzXqp4xD+A==", "dev": true, "requires": { "lodash": "^4.17.10", + "ts-node": "^8.5.2", "webpack-merge": "^4.2.1" + }, + "dependencies": { + "ts-node": { + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.5.2.tgz", + "integrity": "sha512-W1DK/a6BGoV/D4x/SXXm6TSQx6q3blECUzd5TN+j56YEMX3yPVMpHsICLedUw3DvGF3aTQ8hfdR9AKMaHjIi+A==", + "dev": true, + "requires": { + "arg": "^4.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.6", + "yn": "^3.0.0" + } + } } }, "@angular-devkit/architect": { @@ -97,6 +113,20 @@ "integrity": "sha512-YNZN8lt82XIMLnLirj9MhKDFZHalwzzrL9YLt6eb0T5D0EDl4IQ90IGkua8mHbnxNrkj1d8hbdizMc0Qmg1WnQ==", "dev": true }, + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "parse5": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", @@ -118,6 +148,16 @@ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true }, + "source-map-support": { + "version": "0.5.12", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz", + "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "webpack-merge": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.1.tgz", @@ -203,6 +243,12 @@ "requires": { "tslib": "^1.9.0" } + }, + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true } } }, @@ -228,9 +274,9 @@ } }, "@angular/animations": { - "version": "8.2.11", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-8.2.11.tgz", - "integrity": "sha512-u0PSR2uvSqn3ovgGlw2H8ZueyYN42SLir2Yn3+7sGE+LcYOSTjyJ/GIgjV8jWddvPbx7KYzFRCs6bEMpBsMXYg==", + "version": "8.2.14", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-8.2.14.tgz", + "integrity": "sha512-3Vc9TnNpKdtvKIXcWDFINSsnwgEMiDmLzjceWg1iYKwpeZGQahUXPoesLwQazBMmxJzQiA4HOMj0TTXKZ+Jzkg==", "requires": { "tslib": "^1.9.0" } @@ -300,25 +346,25 @@ } }, "@angular/common": { - "version": "8.2.11", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-8.2.11.tgz", - "integrity": "sha512-AN8KKv13rVN1vwyVszZ1POvuUequ7jNC7Af/IDuxTJXKUDCI44dvjYQI0hbCicDDV+XZgjdkd4CBIiLCZRKnOQ==", + "version": "8.2.14", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-8.2.14.tgz", + "integrity": "sha512-Qmt+aX2quUW54kaNT7QH7WGXnFxr/cC2C6sf5SW5SdkZfDQSiz8IaItvieZfXVQUbBOQKFRJ7TlSkt0jI/yjvw==", "requires": { "tslib": "^1.9.0" } }, "@angular/compiler": { - "version": "8.2.11", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-8.2.11.tgz", - "integrity": "sha512-Qnh07i04s0LZECEKu9/NHwdRutfEs9N3y8f0uMdC+m7Cjy3guvmLtriAMdnxlH3KFFFSnugM6Ng6+gII8tZI6A==", + "version": "8.2.14", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-8.2.14.tgz", + "integrity": "sha512-ABZO4E7eeFA1QyJ2trDezxeQM5ZFa1dXw1Mpl/+1vuXDKNjJgNyWYwKp/NwRkLmrsuV0yv4UDCDe4kJOGbPKnw==", "requires": { "tslib": "^1.9.0" } }, "@angular/compiler-cli": { - "version": "8.2.11", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-8.2.11.tgz", - "integrity": "sha512-g6Ius7ctubz/xFjU/kaX6EwOQbfLtKqlRXWP4YRsKA302idlBrgbZYdMrTbFApf0H00vyS87/gipyHp7iNqyHw==", + "version": "8.2.14", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-8.2.14.tgz", + "integrity": "sha512-XDrTyrlIZM+0NquVT+Kbg5bn48AaWFT+B3bAT288PENrTdkuxuF9AhjFRZj8jnMdmaE4O2rioEkXBtl6z3zptA==", "dev": true, "requires": { "canonical-path": "1.0.0", @@ -997,12 +1043,6 @@ "kind-of": "^3.0.2" } }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", @@ -1035,12 +1075,6 @@ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, "string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", @@ -1103,9 +1137,9 @@ } }, "@angular/core": { - "version": "8.2.11", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-8.2.11.tgz", - "integrity": "sha512-TRfoJPcTjjiFh1dquqDc7LDI4sqD/LPCC9y2HP4r8644xJawhPJ0Ms6gWaeobCeP2k0vKsjrFmbPWeolOig/YA==", + "version": "8.2.14", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-8.2.14.tgz", + "integrity": "sha512-zeePkigi+hPh3rN7yoNENG/YUBUsIvUXdxx+AZq+QPaFeKEA2FBSrKn36ojHFrdJUjKzl0lPMEiGC2b6a6bo6g==", "requires": { "tslib": "^1.9.0" } @@ -1119,17 +1153,17 @@ } }, "@angular/forms": { - "version": "8.2.11", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-8.2.11.tgz", - "integrity": "sha512-9sXLvAgscGwoE7k9kwRPogPSx8B8kT+kbXCvkL3Az+C+HrEuQ3rwMIJEFO+OhOCkRwrdUniKsp7GHLw94jAqtQ==", + "version": "8.2.14", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-8.2.14.tgz", + "integrity": "sha512-zhyKL3CFIqcyHJ/TQF/h1OZztK611a6rxuPHCrt/5Sn1SuBTJJQ1pPTkOYIDy6IrCrtyANc8qB6P17Mao71DNQ==", "requires": { "tslib": "^1.9.0" } }, "@angular/language-service": { - "version": "8.2.11", - "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-8.2.11.tgz", - "integrity": "sha512-jNzVEzJRdDynAAY3ncJBRX9RE52nRNpaKrW7KgZCpAoqgyponi8HBs+lt/ANcRmhdoIkM1VAOnDAb1rE30dLeQ==", + "version": "8.2.14", + "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-8.2.14.tgz", + "integrity": "sha512-7EhN9JJbAJcH2xCa+rIOmekjiEuB0qwPdHuD5qn/wwMfRzMZo+Db4hHbR9KHrLH6H82PTwYKye/LLpDaZqoHOA==", "dev": true }, "@angular/material": { @@ -1141,33 +1175,33 @@ } }, "@angular/platform-browser": { - "version": "8.2.11", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-8.2.11.tgz", - "integrity": "sha512-bKDkC7kpNvgvRQfw4s+84GJWrY7MPFx9d1bUk9OPQzEBAjzRX+E6IFlqfTpZRGy4m5rbSzBk3bpZdqcZSJM98A==", + "version": "8.2.14", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-8.2.14.tgz", + "integrity": "sha512-MtJptptyKzsE37JZ2VB/tI4cvMrdAH+cT9pMBYZd66YSZfKjIj5s+AZo7z8ncoskQSB1o3HMfDjSK7QXGx1mLQ==", "requires": { "tslib": "^1.9.0" } }, "@angular/platform-browser-dynamic": { - "version": "8.2.11", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-8.2.11.tgz", - "integrity": "sha512-0DyO7N2HTQdV0OQJiLy3ZWimvdBgPHPbTP62l/Qc+ieZzA86KU8jWjbtyUOdqXkyEQ9gcpWEpxX9dBH8nW74jg==", + "version": "8.2.14", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-8.2.14.tgz", + "integrity": "sha512-mO2JPR5kLU/A3AQngy9+R/Q5gaF9csMStBQjwsCRI0wNtlItOIGL6+wTYpiTuh/ux+WVN1F2sLcEYU4Zf1ud9A==", "requires": { "tslib": "^1.9.0" } }, "@angular/router": { - "version": "8.2.11", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-8.2.11.tgz", - "integrity": "sha512-US8vp9TiryAVoLZwKZqApG6OvRizlJh6k+YA40/Z+fZBFIFM+Og80yiuUJ+p29unheVqA/tvAHmBdBrLNaliJA==", + "version": "8.2.14", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-8.2.14.tgz", + "integrity": "sha512-DHA2BhODqV7F0g6ZKgFaZgbsqzHHWRcfWchCOrOVKu2rYiKUTwwHVLBgZAhrpNeinq2pWanVYSIhMr7wy+LfEA==", "requires": { "tslib": "^1.9.0" } }, "@auth0/angular-jwt": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@auth0/angular-jwt/-/angular-jwt-3.0.0.tgz", - "integrity": "sha512-Ky8hghnEx+CtCd097YXji08/LvLTG98IAEX/j1UgnutRDhQ31eczOohDn98v3i3MHNfLjfI3HdyxPK1Qc0IkZw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@auth0/angular-jwt/-/angular-jwt-3.0.1.tgz", + "integrity": "sha512-hfWfgbpgtcvyU/agNxQ6cBk81mmASiNxQeZ6xn/3zJo8uLFHk2eQIy2yt2ztktcOQ6V2uc6GlKLRKjVIgyc1Sw==", "requires": { "url": "^0.11.0" } @@ -1182,12 +1216,12 @@ } }, "@babel/generator": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.6.4.tgz", - "integrity": "sha512-jsBuXkFoZxk0yWLyGI9llT9oiQ2FeTASmRFE32U+aaDTfoE92t78eroO7PTpU/OrYq38hlcDM6vbfLDaOLy+7w==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.7.2.tgz", + "integrity": "sha512-WthSArvAjYLz4TcbKOi88me+KmDJdKSlfwwN8CnUYn9jBkzhq0ZEPuBfkAWIvjJ3AdEV1Cf/+eSQTnp3IDJKlQ==", "dev": true, "requires": { - "@babel/types": "^7.6.3", + "@babel/types": "^7.7.2", "jsesc": "^2.5.1", "lodash": "^4.17.13", "source-map": "^0.5.0" @@ -1208,32 +1242,32 @@ } }, "@babel/helper-function-name": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", - "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.7.0.tgz", + "integrity": "sha512-tDsJgMUAP00Ugv8O2aGEua5I2apkaQO7lBGUq1ocwN3G23JE5Dcq0uh3GvFTChPa4b40AWiAsLvCZOA2rdnQ7Q==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.0.0", - "@babel/template": "^7.1.0", - "@babel/types": "^7.0.0" + "@babel/helper-get-function-arity": "^7.7.0", + "@babel/template": "^7.7.0", + "@babel/types": "^7.7.0" } }, "@babel/helper-get-function-arity": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", - "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.0.tgz", + "integrity": "sha512-tLdojOTz4vWcEnHWHCuPN5P85JLZWbm5Fx5ZsMEMPhF3Uoe3O7awrbM2nQ04bDOUToH/2tH/ezKEOR8zEYzqyw==", "dev": true, "requires": { - "@babel/types": "^7.0.0" + "@babel/types": "^7.7.0" } }, "@babel/helper-split-export-declaration": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz", - "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==", + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.0.tgz", + "integrity": "sha512-HgYSI8rH08neWlAH3CcdkFg9qX9YsZysZI5GD8LjhQib/mM0jGOZOVkoUiiV2Hu978fRtjtsGsW6w0pKHUWtqA==", "dev": true, "requires": { - "@babel/types": "^7.4.4" + "@babel/types": "^7.7.0" } }, "@babel/highlight": { @@ -1248,42 +1282,42 @@ } }, "@babel/parser": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.4.tgz", - "integrity": "sha512-D8RHPW5qd0Vbyo3qb+YjO5nvUVRTXFLQ/FsDxJU2Nqz4uB5EnUN0ZQSEYpvTIbRuttig1XbHWU5oMeQwQSAA+A==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.3.tgz", + "integrity": "sha512-bqv+iCo9i+uLVbI0ILzKkvMorqxouI+GbV13ivcARXn9NNEabi2IEz912IgNpT/60BNXac5dgcfjb94NjsF33A==", "dev": true }, "@babel/runtime": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.6.3.tgz", - "integrity": "sha512-kq6anf9JGjW8Nt5rYfEuGRaEAaH1mkv3Bbu6rYvLOpPh/RusSJXuKPEAoZ7L7gybZkchE8+NV5g9vKF4AGAtsA==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.2.tgz", + "integrity": "sha512-JONRbXbTXc9WQE2mAZd1p0Z3DZ/6vaQIkgYMSTP3KjRCyd7rCZCcfhCyX+YjwcKxcZ82UrxbRD358bpExNgrjw==", "requires": { "regenerator-runtime": "^0.13.2" } }, "@babel/template": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.6.0.tgz", - "integrity": "sha512-5AEH2EXD8euCk446b7edmgFdub/qfH1SN6Nii3+fyXP807QRx9Q73A2N5hNwRRslC2H9sNzaFhsPubkS4L8oNQ==", + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.0.tgz", + "integrity": "sha512-OKcwSYOW1mhWbnTBgQY5lvg1Fxg+VyfQGjcBduZFljfc044J5iDlnDSfhQ867O17XHiSCxYHUxHg2b7ryitbUQ==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.6.0", - "@babel/types": "^7.6.0" + "@babel/parser": "^7.7.0", + "@babel/types": "^7.7.0" } }, "@babel/traverse": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.6.3.tgz", - "integrity": "sha512-unn7P4LGsijIxaAJo/wpoU11zN+2IaClkQAxcJWBNCMS6cmVh802IyLHNkAjQ0iYnRS3nnxk5O3fuXW28IMxTw==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.7.2.tgz", + "integrity": "sha512-TM01cXib2+rgIZrGJOLaHV/iZUAxf4A0dt5auY6KNZ+cm6aschuJGqKJM3ROTt3raPUdIDk9siAufIFEleRwtw==", "dev": true, "requires": { "@babel/code-frame": "^7.5.5", - "@babel/generator": "^7.6.3", - "@babel/helper-function-name": "^7.1.0", - "@babel/helper-split-export-declaration": "^7.4.4", - "@babel/parser": "^7.6.3", - "@babel/types": "^7.6.3", + "@babel/generator": "^7.7.2", + "@babel/helper-function-name": "^7.7.0", + "@babel/helper-split-export-declaration": "^7.7.0", + "@babel/parser": "^7.7.2", + "@babel/types": "^7.7.2", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.13" @@ -1313,9 +1347,9 @@ } }, "@babel/types": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.3.tgz", - "integrity": "sha512-CqbcpTxMcpuQTMhjI37ZHVgjBkysg5icREQIEZ0eG1yCNwg3oy+5AaLiOKmjsCj6nqOsa6Hf0ObjRVwokb7srA==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.2.tgz", + "integrity": "sha512-YTf6PXoh3+eZgRCBzzP25Bugd2ngmpQVrk7kXX0i5N9BO7TFBtIgZYs7WtxtOGs8e6A4ZI7ECkbBCEHeXocvOA==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -1372,21 +1406,19 @@ } }, "@material-ui/core": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.5.1.tgz", - "integrity": "sha512-6pyk7diT7bflf4qUpqgPCpKYqjhRHPFwsgEV2Gv71lMqwxuRygFGHE2TdZ+l5T249H66Doj2P/j6fW7yzgxTWw==", + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.6.1.tgz", + "integrity": "sha512-TljDMCJmi1zh7JhAFTp8qjIlbkVACiNftrcitzJJ+hAqpuP9PTO4euEkkAuYjISfUFZl3Z4kaOrBwN1HDrhIOQ==", "requires": { "@babel/runtime": "^7.4.4", - "@material-ui/styles": "^4.5.0", - "@material-ui/system": "^4.5.0", + "@material-ui/styles": "^4.6.0", + "@material-ui/system": "^4.5.2", "@material-ui/types": "^4.1.1", - "@material-ui/utils": "^4.4.0", + "@material-ui/utils": "^4.5.2", "@types/react-transition-group": "^4.2.0", "clsx": "^1.0.2", "convert-css-length": "^2.0.1", - "deepmerge": "^4.0.0", "hoist-non-react-statics": "^3.2.1", - "is-plain-object": "^3.0.0", "normalize-scroll-left": "^0.2.0", "popper.js": "^1.14.1", "prop-types": "^15.7.2", @@ -1402,9 +1434,9 @@ } }, "@material-ui/pickers": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/@material-ui/pickers/-/pickers-3.2.7.tgz", - "integrity": "sha512-dDi8G8TOXssXZQsGCRM4zoDnWMY4O/vvqVCH4ViIHflvS4ek4v30IlFcSONI5jGzL0dmJhNKso2UEn7qS3iZ3g==", + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@material-ui/pickers/-/pickers-3.2.8.tgz", + "integrity": "sha512-uWGIUpfxPXZItCWnnF3ZSWgjv40wnlhceG6QhSQ1ARpqxU/4TEcnflR7FO54ULKOsyjwF+sgWPrqV/rPgGtPXA==", "requires": { "@babel/runtime": "^7.6.0", "@types/styled-jsx": "^2.2.8", @@ -1414,17 +1446,16 @@ } }, "@material-ui/styles": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.5.0.tgz", - "integrity": "sha512-O0NSAECHK9f3DZK6wy56PZzp8b/7KSdfpJs8DSC7vnXUAoMPCTtchBKLzMtUsNlijiJFeJjSxNdQfjWXgyur5A==", + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.6.0.tgz", + "integrity": "sha512-lqqh4UEMdIYcU1Yth4pQyMTah02uAkg3NOT3MirN9FUexdL8pNA6zCHigEgDSfwmvnXyxHhxTkphfy0DRfnt9w==", "requires": { "@babel/runtime": "^7.4.4", "@emotion/hash": "^0.7.1", "@material-ui/types": "^4.1.1", - "@material-ui/utils": "^4.1.0", + "@material-ui/utils": "^4.5.2", "clsx": "^1.0.2", "csstype": "^2.5.2", - "deepmerge": "^4.0.0", "hoist-non-react-statics": "^3.2.1", "jss": "^10.0.0", "jss-plugin-camel-case": "^10.0.0", @@ -1438,12 +1469,12 @@ } }, "@material-ui/system": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.5.1.tgz", - "integrity": "sha512-M72CGz3MYxXTFLet2qWmQDBXZdtF7JKGqYaf7t9MPDYD6WYG6wKM2hUbgUtRKOwls8ZBXQGKsiAX8K4v5pXSPw==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.5.2.tgz", + "integrity": "sha512-h9RWvdM9XKlHHqwiuhyvWdobptQkHli+m2jJFs7i1AI/hmGsIc4reDmS7fInhETgt/Txx7uiAIznfRNIIVHmQw==", "requires": { "@babel/runtime": "^7.4.4", - "deepmerge": "^4.0.0", + "@material-ui/utils": "^4.5.2", "prop-types": "^15.7.2" } }, @@ -1456,9 +1487,9 @@ } }, "@material-ui/utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.4.0.tgz", - "integrity": "sha512-UXoQVwArQEQWXxf2FPs0iJGT+MePQpKr0Qh0CPoLc1OdF0GSMTmQczcqCzwZkeHxHAOq/NkIKM1Pb/ih1Avicg==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.5.2.tgz", + "integrity": "sha512-zhbNfHd1gLa8At6RPDG7uMZubHxbY+LtM6IkSfeWi6Lo4Ax80l62YaN1QmUpO1IvGCkn/j62tQX3yObiQZrJsQ==", "requires": { "@babel/runtime": "^7.4.4", "prop-types": "^15.7.2", @@ -1466,19 +1497,19 @@ } }, "@ngrx/effects": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-8.4.0.tgz", - "integrity": "sha512-LQv+NIYkFehXMSBMT9xL04RvmDmbzSbCbSCXbNSH2hN216TqX83L29u5T4I06oGhzQ3xN+SSQXGWQiZYJvKuEA==" + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-8.5.2.tgz", + "integrity": "sha512-i2rKLmFcfJmPPQRul1PuXXRuBX2q1g7tK6DbWreNabU/46fYlEwqiiW6lU53t3AgJ3yd6UpeLTR6CHTflkzL+w==" }, "@ngrx/store": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@ngrx/store/-/store-8.4.0.tgz", - "integrity": "sha512-Z8+2hfGcynGrzJuU7ixxYxOI6M2E0H8Omni1u01h55vvaZeoTO8bRt6OWqbjxxEsSKQmBBZ1XyOuuZXSWjxvYw==" + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/@ngrx/store/-/store-8.5.2.tgz", + "integrity": "sha512-JPlc23Aw3rlEKt6LCkg3a0zlo0tEgkohH3CDHVbUIYSgg3DWOnmNfwztbz4pa2u2wua5PfFCovC7HKTNmapx/w==" }, "@ngrx/store-devtools": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@ngrx/store-devtools/-/store-devtools-8.4.0.tgz", - "integrity": "sha512-622z0CNdmfmPy17LFcewf/7xU2quhun+G+D7FdF58DlwZDFPJp0UotER1HW+ApGmI8h6hjeQs/7flzX0H12sIg==" + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/@ngrx/store-devtools/-/store-devtools-8.5.2.tgz", + "integrity": "sha512-3GrAAX3/J39u0AcREgWBiUwuNkZhgei+2K6/bulkAu/BHw+PJaZqq5+c+uQFvi0/aq+/8+9wjNhhCWS4Entk/Q==" }, "@ngtools/webpack": { "version": "8.2.2", @@ -1603,9 +1634,9 @@ } }, "@types/jasmine": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.4.4.tgz", - "integrity": "sha512-+/sHcTPyDS1JQacDRRRWb+vNrjBwnD+cKvTaWlxlJ/uOOFvzCkjOwNaqVjYMLfsjzNi0WtDH9RyReDXPG1Cdug==", + "version": "3.4.6", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.4.6.tgz", + "integrity": "sha512-hpQHs+lmZ0uuCrGyqypdI1Ho7jRFolOBT6OkNdZPFziLSSEKvWu+VxWU6bGdNEA/hoV4jV8pdDeNx8EWlmfNAw==", "dev": true }, "@types/jasminewd2": { @@ -1661,18 +1692,18 @@ "dev": true }, "@types/react": { - "version": "16.9.9", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.9.tgz", - "integrity": "sha512-L+AudFJkDukk+ukInYvpoAPyJK5q1GanFOINOJnM0w6tUgITuWvJ4jyoBPFL7z4/L8hGLd+K/6xR5uUjXu0vVg==", + "version": "16.9.11", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.11.tgz", + "integrity": "sha512-UBT4GZ3PokTXSWmdgC/GeCGEJXE5ofWyibCcecRLUVN2ZBpXQGVgQGtG2foS7CrTKFKlQVVswLvf7Js6XA/CVQ==", "requires": { "@types/prop-types": "*", "csstype": "^2.2.0" } }, "@types/react-dom": { - "version": "16.9.2", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.2.tgz", - "integrity": "sha512-hgPbBoI1aTSTvZwo8HYw35UaTldW6n2ETLvHAcfcg1FaOuBV3olmyCe5eMpx2WybWMBPv0MdU2t5GOcQhP+3zA==", + "version": "16.9.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.4.tgz", + "integrity": "sha512-fya9xteU/n90tda0s+FtN5Ym4tbgxpq/hb/Af24dvs6uYnYn+fspaxw5USlw0R8apDNwxsqumdRoCoKitckQqw==", "dev": true, "requires": { "@types/react": "*" @@ -1726,14 +1757,6 @@ "@types/node": "*", "@types/source-list-map": "*", "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } }, "@webassemblyjs/ast": { @@ -1956,9 +1979,9 @@ } }, "ace-builds": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.4.6.tgz", - "integrity": "sha512-JpuzcHQxTO6Nk8Q1Y8fVQAaxpimziAvVe/CX4h39ig0iHTtBNuySOGHUxmno8mNe4IMbjO8RJICmsrQKTdTWpQ==" + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.4.7.tgz", + "integrity": "sha512-gwQGVFewBopRLho08BfahyvRa9FlB43JUig5ItAKTYc9kJJsbA9QNz75p28QtQomoPQ9rJx82ymL21x4ZSZmdg==" }, "acorn": { "version": "6.3.0", @@ -2297,19 +2320,9 @@ "dev": true }, "attr-accept": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-1.1.3.tgz", - "integrity": "sha512-iT40nudw8zmCweivz6j58g+RT33I4KbaIvRUhjNmDwO2WmsQUxFEZZYZ5w3vXe5x5MX9D7mfvA/XaLOZYFR9EQ==", - "requires": { - "core-js": "^2.5.0" - }, - "dependencies": { - "core-js": { - "version": "2.6.10", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.10.tgz", - "integrity": "sha512-I39t74+4t+zau64EN1fE5v2W31Adtc/REhzWN+gWRRXg6WH5qAsZm62DHpQ1+Yhe4047T55jvzz7MUqF/dBBlA==" - } - } + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.0.0.tgz", + "integrity": "sha512-I9SDP4Wvh2ItYYoafEg8hFpsBe96pfQ+eabceShXt3sw2fbIP96+Aoj9zZE0vkZNAkXXzHJATVRuWz+h9FxJxQ==" }, "autoprefixer": { "version": "9.6.1", @@ -2556,12 +2569,6 @@ "is-data-descriptor": "^1.0.0", "kind-of": "^6.0.2" } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true } } }, @@ -2815,9 +2822,9 @@ } }, "buffer": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", - "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", "dev": true, "requires": { "base64-js": "^1.0.2", @@ -2943,14 +2950,6 @@ "to-object-path": "^0.3.0", "union-value": "^1.0.0", "unset-value": "^1.0.0" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } } }, "caller-callsite": { @@ -3025,9 +3024,9 @@ "dev": true }, "chokidar": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.2.2.tgz", - "integrity": "sha512-bw3pm7kZ2Wa6+jQWYP/c7bAZy3i4GwiIiMO2EeRjrE48l8vBqC/WvFhSF0xyM8fQiPEGvwMY/5bqDG7sSEOuhg==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", + "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", "dev": true, "requires": { "anymatch": "~3.1.1", @@ -3102,12 +3101,6 @@ "requires": { "is-descriptor": "^0.1.0" } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true } } }, @@ -3123,14 +3116,6 @@ "dev": true, "requires": { "source-map": "~0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } }, "cli-cursor": { @@ -3202,23 +3187,6 @@ "is-plain-object": "^2.0.4", "kind-of": "^6.0.0", "shallow-clone": "^1.0.0" - }, - "dependencies": { - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } } }, "clsx": { @@ -3401,9 +3369,9 @@ }, "dependencies": { "find-cache-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.0.0.tgz", - "integrity": "sha512-t7ulV1fmbxh5G9l/492O1p5+EBbr3uwpt6odhFTMc+nWyhmbloe+ja9BZ8pIBtqFWhOmCWVjx+pTW4zDkFoclw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.1.0.tgz", + "integrity": "sha512-zw+EFiNBNPgI2NTrKkDd1xd7q0cs6wr/iWnr/oUkI0yF9K9GqQ+riIt4aiyFaaqpaWbxPrJXHI+QvmNUQbX+0Q==", "dev": true, "requires": { "commondir": "^1.0.1", @@ -3516,13 +3484,10 @@ "dev": true }, "console-browserify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", - "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", - "dev": true, - "requires": { - "date-now": "^0.1.4" - } + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", + "dev": true }, "constants-browserify": { "version": "1.0.0", @@ -3551,9 +3516,9 @@ "integrity": "sha512-iGpbcvhLPRKUbBc0Quxx7w/bV14AC3ItuBEGMahA5WTYqB8lq9jH0kTXFheCBASsYnqeMFZhiTruNxr1N59Axg==" }, "convert-source-map": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", - "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", "dev": true, "requires": { "safe-buffer": "~5.1.1" @@ -3612,9 +3577,9 @@ } }, "core-js": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.3.2.tgz", - "integrity": "sha512-S1FfZpeBchkhyoY76YAdFzKS4zz9aOK7EeFaNA2aJlyXyA+sgqz6xdxmLPGXEAf0nF44MVN1kSjrA9Kt3ATDQg==" + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.4.1.tgz", + "integrity": "sha512-KX/dnuY/J8FtEwbnrzmAjUYgLqtk+cxM86hfG60LGiW3MmltIc2yAmDgBgEkfm0blZhUrdr1Zd84J2Y14mLxzg==" }, "core-util-is": { "version": "1.0.2", @@ -3796,12 +3761,6 @@ "integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==", "dev": true }, - "date-now": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", - "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", - "dev": true - }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -3830,9 +3789,9 @@ "dev": true }, "deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.0.tgz", - "integrity": "sha512-ZbfWJq/wN1Z273o7mUSjILYqehAktR2NVoSrOukDkU9kg2v/Uv89yU4Cvz8seJeAmtN5oqiefKq8FPuXOboqLw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", + "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", "requires": { "is-arguments": "^1.0.4", "is-date-object": "^1.0.1", @@ -3848,11 +3807,6 @@ "integrity": "sha1-d9BYPKJKab5LvZrC+uQV1VUj5bA=", "dev": true }, - "deepmerge": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.1.1.tgz", - "integrity": "sha512-+qO5WbNBKBaZez95TffdUDnGIo4+r5kmsX8aOb7PDHvXsTbghAmleuxjs6ytNaf5Eg4FGBXDS5vqO61TRi6BMg==" - }, "default-gateway": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz", @@ -3926,12 +3880,6 @@ "is-data-descriptor": "^1.0.0", "kind-of": "^6.0.2" } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true } } }, @@ -3998,9 +3946,9 @@ "dev": true }, "des.js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", - "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", + "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", "dev": true, "requires": { "inherits": "^2.0.1", @@ -4112,12 +4060,12 @@ "integrity": "sha512-AYZUzLepy05E9bCY4ExoqHrrIlM49PEak9oF93JEFoibqKL0F7w5DLM70/rosLOawerWZ3MlepQcl+EmHskOyw==" }, "dom-helpers": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.1.0.tgz", - "integrity": "sha512-zRRYDhpiKuAJHasOqCm7lBnsd22nrM4+OYI4ASWCxen+ocTMl7BIAKgGag97TlLiTl6rrau5aPe1VGUm9jQBng==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.1.3.tgz", + "integrity": "sha512-nZD1OtwfWGRBWlpANxacBEZrEuLa16o1nh7YopFWeoF68Zt8GGEmzHu6Xv4F3XaFIC+YXtTLrzgqKxFgLEe4jw==", "requires": { - "@babel/runtime": "^7.5.5", - "csstype": "^2.6.6" + "@babel/runtime": "^7.6.3", + "csstype": "^2.6.7" } }, "dom-scroll-into-view": { @@ -4183,9 +4131,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.285", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.285.tgz", - "integrity": "sha512-DYR9KW723sUbGK++DCmCmM95AbNXT4Q0tlCFMcYijFjayhuDqlGYR68OemlP8MJj0gjkwdeItIUfd0oLCgw+4A==", + "version": "1.3.309", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.309.tgz", + "integrity": "sha512-NZd91XD15v2UPLjYXoN/gLnkwIUQjdH4SQLpRCCQiYJH6BBkfgp5pWemBJPr1rZ2dl8Ee3o91O9Sa1QuAfZmog==", "dev": true }, "elliptic": { @@ -4361,9 +4309,9 @@ } }, "es-abstract": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.15.0.tgz", - "integrity": "sha512-bhkEqWJ2t2lMeaJDuk7okMkJWI/yqgH/EoGwpcvv0XW9RWQsRspI4wt6xuyuvMvvQE3gg/D9HXppgk21w78GyQ==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.16.0.tgz", + "integrity": "sha512-xdQnfykZ9JMEiasTAJZJdMWCQ1Vm00NBw79/AWi7ELfZuuPCSOMDZbT9mkOfSctVtfhb+sAAzrm+j//GjjLHLg==", "dev": true, "requires": { "es-to-primitive": "^1.2.0", @@ -4379,9 +4327,9 @@ } }, "es-to-primitive": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", - "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", "dev": true, "requires": { "is-callable": "^1.1.4", @@ -4622,21 +4570,6 @@ "requires": { "is-plain-object": "^2.0.4" } - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true } } }, @@ -5004,9 +4937,9 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.1.tgz", - "integrity": "sha512-4FRPXWETxtigtJW/gxzEDsX1LVbPAM93VleB83kZB+ellqbHMkyt2aJfuzNLRvFPnGi6bcE5SvfxgbXPeKteJw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", + "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", "dev": true, "optional": true }, @@ -5052,9 +4985,9 @@ } }, "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -5123,9 +5056,9 @@ } }, "graceful-fs": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", - "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", + "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", "dev": true }, "hammerjs": { @@ -5140,23 +5073,15 @@ "dev": true }, "handlebars": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.2.tgz", - "integrity": "sha512-29Zxv/cynYB7mkT1rVWQnV7mGX6v7H/miQ6dbEpYTKq5eJBN7PsRB+ViYJlcT6JINTSu4dVB9kOqEun78h6Exg==", + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.3.tgz", + "integrity": "sha512-3yPecJoJHK/4c6aZhSvxOyG4vJKDshV36VHp0iVCDVh7o9w2vwi3NSnL2MMPj3YdduqaBcu7cGbggJQM0br9xA==", "dev": true, "requires": { "neo-async": "^2.6.0", "optimist": "^0.6.1", "source-map": "^0.6.1", "uglify-js": "^3.1.4" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } }, "har-schema": { @@ -5222,9 +5147,9 @@ "dev": true }, "has-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", - "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", "dev": true }, "has-value": { @@ -5236,14 +5161,6 @@ "get-value": "^2.0.6", "has-values": "^1.0.0", "isobject": "^3.0.0" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } } }, "has-values": { @@ -5319,9 +5236,9 @@ } }, "hoist-non-react-statics": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz", - "integrity": "sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-wbg3bpgA/ZqWrZuMOeJi8+SKMhr7X9TesL/rXMjTzh0p0JUBo3II8DHboYbuIXWRlttrUFxwcu/5kygrCw8fJw==", "requires": { "react-is": "^16.7.0" } @@ -5451,9 +5368,9 @@ "dev": true }, "https-proxy-agent": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.3.tgz", - "integrity": "sha512-Ytgnz23gm2DVftnzqRRz2dOXZbGd2uiajSw/95bPp6v53zPRspQjLm/AfBgqbJ2qfeRXWIOMVLpp86+/5yX39Q==", + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", "dev": true, "requires": { "agent-base": "^4.3.0", @@ -5876,11 +5793,12 @@ "dev": true }, "is-plain-object": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.0.tgz", - "integrity": "sha512-tZIpofR+P05k8Aocp7UI/2UTa9lTJSebCXpFFoR9aibpokDj/uXBsJ8luUu0tTVYKkMU6URDUuOfJZ7koewXvg==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, "requires": { - "isobject": "^4.0.0" + "isobject": "^3.0.1" } }, "is-promise": { @@ -5904,12 +5822,12 @@ "dev": true }, "is-symbol": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", - "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", "dev": true, "requires": { - "has-symbols": "^1.0.0" + "has-symbols": "^1.0.1" } }, "is-typedarray": { @@ -5952,9 +5870,10 @@ "dev": true }, "isobject": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz", - "integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true }, "isstream": { "version": "0.1.2", @@ -6159,12 +6078,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true } } }, @@ -6498,12 +6411,6 @@ "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true } } }, @@ -6593,13 +6500,6 @@ "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true } } }, @@ -6673,11 +6573,6 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, - "lodash._getnative": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=" - }, "lodash.clonedeep": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", @@ -6689,31 +6584,11 @@ "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" }, - "lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=" - }, - "lodash.isarray": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=" - }, "lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" }, - "lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", - "requires": { - "lodash._getnative": "^3.0.0", - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0" - } - }, "lodash.tail": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.tail/-/lodash.tail-4.1.1.tgz", @@ -6751,9 +6626,9 @@ } }, "loglevel": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.4.tgz", - "integrity": "sha512-p0b6mOGKcGa+7nnmKbpzR6qloPbrgLcnio++E+14Vo/XffOGwZtRpUhr8dTH/x2oCMmEoIU0Zwm3ZauhvYD17g==", + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.6.tgz", + "integrity": "sha512-Sgr5lbboAUBo3eXCSPL4/KoVz3ROKquOjcctxmHIt+vol2DrqTQe3SwkKKuYhEiWB5kYa13YyopJ69deJ1irzQ==", "dev": true }, "loose-envify": { @@ -6799,16 +6674,16 @@ "dev": true }, "make-fetch-happen": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-5.0.0.tgz", - "integrity": "sha512-nFr/vpL1Jc60etMVKeaLOqfGjMMb3tAHFVJWxHOFCFS04Zmd7kGlMxo0l1tzfhoQje0/UPnd0X8OeGUiXXnfPA==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-5.0.2.tgz", + "integrity": "sha512-07JHC0r1ykIoruKO8ifMXu+xEU8qOXDFETylktdug6vJDACnP+HKevOu3PXyNPzFyTSlz8vrBYlBO1JZRe8Cag==", "dev": true, "requires": { "agentkeepalive": "^3.4.1", "cacache": "^12.0.0", "http-cache-semantics": "^3.8.1", "http-proxy-agent": "^2.1.0", - "https-proxy-agent": "^2.2.1", + "https-proxy-agent": "^2.2.3", "lru-cache": "^5.1.1", "mississippi": "^3.0.0", "node-fetch-npm": "^2.0.2", @@ -7071,12 +6946,6 @@ } } }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, "to-regex-range": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", @@ -7106,18 +6975,18 @@ "dev": true }, "mime-db": { - "version": "1.40.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", - "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", + "version": "1.42.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.42.0.tgz", + "integrity": "sha512-UbfJCR4UAVRNgMpfImz05smAXK7+c+ZntjaA26ANtkXLlOe947Aag5zdIcKQULAiF9Cq4WxBi9jUs5zkA84bYQ==", "dev": true }, "mime-types": { - "version": "2.1.24", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", - "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "version": "2.1.25", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.25.tgz", + "integrity": "sha512-5KhStqB5xpTAeGqKBAMgwaYMnQik7teQN4IAzC7npDv6kzeU6prfkR67bc87J1kWMPGkoaZSq1npmexMgkmEVg==", "dev": true, "requires": { - "mime-db": "1.40.0" + "mime-db": "1.42.0" } }, "mimic-fn": { @@ -7153,11 +7022,6 @@ "version": "2.5.5", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==" - }, - "shallowequal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", - "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" } } }, @@ -7249,21 +7113,6 @@ "requires": { "is-plain-object": "^2.0.4" } - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true } } }, @@ -7412,95 +7261,8 @@ "integrity": "sha512-rzR+cByjNG9M/UskU5vNoH7cUc6oM8STTDFKOZmnlX4ALOuM1+61CBjsNTGETWfo9a/h5mbGX02oh5/iNAa7vA==" }, "ngx-flowchart": { - "version": "git://github.com/thingsboard/ngx-flowchart.git#d26ee52089a6d9cf8147c5f162144825fceb3009", - "from": "git://github.com/thingsboard/ngx-flowchart.git#master", - "requires": { - "@angular/animations": "~8.0.0", - "@angular/common": "~8.0.0", - "@angular/compiler": "~8.0.0", - "@angular/core": "~8.0.0", - "@angular/forms": "~8.0.0", - "@angular/platform-browser": "~8.0.0", - "@angular/platform-browser-dynamic": "~8.0.0", - "@angular/router": "~8.0.0", - "rxjs": "~6.4.0", - "tslib": "^1.9.0", - "zone.js": "~0.9.1" - }, - "dependencies": { - "@angular/animations": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-8.0.3.tgz", - "integrity": "sha512-9zciJ4YRR0bodFSYgsgXdYMz8wKKyVjch7XZADGkWubXT8mGuwlpdPMlQ6n9Cwj8Ebu0u52WxMeQsX76K9RlYA==", - "requires": { - "tslib": "^1.9.0" - } - }, - "@angular/common": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-8.0.3.tgz", - "integrity": "sha512-2YLYGVUf9eJZcocRmD3/9UHj4qFHt2t4ftDWJmrFM9zo2PZF+G5O9fASO7qoBbwpx3KFZtQO4dprKl2dFugRjg==", - "requires": { - "tslib": "^1.9.0" - } - }, - "@angular/compiler": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-8.0.3.tgz", - "integrity": "sha512-1/vF8D6l1O6IfWiDtaj6nC+B8CtkVtFgXgooDzLBO6XAkaCuJCnhKT1HnpWG5GtVsGaY9MGoTl1vE9ZMDbRQjg==", - "requires": { - "tslib": "^1.9.0" - } - }, - "@angular/core": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-8.0.3.tgz", - "integrity": "sha512-IIxrtIPNuv2+HudER9J1nmPGiGJ4aRpeiFM9V4lSiSFv50RzuaoG60XqYIpUyuBdgvyKigcrfSbu9+x1vyN0hw==", - "requires": { - "tslib": "^1.9.0" - } - }, - "@angular/forms": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-8.0.3.tgz", - "integrity": "sha512-22s82QDRQ72K4vMYuNh3NAN+da9uanwoydnfKlp2rb9dZAb2QVX9NN6gSoMrkSSr2O9KTP6pWiw6A3/MW8sGRA==", - "requires": { - "tslib": "^1.9.0" - } - }, - "@angular/platform-browser": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-8.0.3.tgz", - "integrity": "sha512-ceAPP2Ijmk2sZ1rnOU/WNlE3DtT6K6ljpjO9oUfXKMoSMdWirJKAraT3m/BAzmYwMSXpPBxA7c3paZjiLL6t5A==", - "requires": { - "tslib": "^1.9.0" - } - }, - "@angular/platform-browser-dynamic": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-8.0.3.tgz", - "integrity": "sha512-ZjQjSYslSQAKzM4llvyMFxnSjFpbhT1U9FOdKwscPe475zAKX0087qsHrP2CRwkJRfwtdcmj9wMUQIPlzMpHLA==", - "requires": { - "tslib": "^1.9.0" - } - }, - "@angular/router": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-8.0.3.tgz", - "integrity": "sha512-CU5pLTfQVUnTN93mdIKJrVjXiNldUkk30DPz4lpdxpZjYOqFGXeeSeQWmToHSofLPodNcAB4kkZ41VyXvlBu7w==", - "requires": { - "tslib": "^1.9.0" - } - }, - "rxjs": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", - "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==", - "requires": { - "tslib": "^1.9.0" - } - } - } + "version": "git://github.com/thingsboard/ngx-flowchart.git#671b505b2484806a4a1c376344d0a12e5716679a", + "from": "git://github.com/thingsboard/ngx-flowchart.git#master" }, "ngx-hm-carousel": { "version": "1.7.2", @@ -7583,9 +7345,9 @@ } }, "node-releases": { - "version": "1.1.36", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.36.tgz", - "integrity": "sha512-ggXhX6QGyJSjj3r+6ml2LqqC28XOWmKtpb+a15/Zpr9V3yoNazxJNlcQDS9bYaid5FReEWHEgToH1mwoUceWwg==", + "version": "1.1.40", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.40.tgz", + "integrity": "sha512-r4LPcC5b/bS8BdtWH1fbeK88ib/wg9aqmg6/s3ngNLn2Ewkn/8J6Iw3P9RTlfIAdSdvYvQl2thCY5Y+qTAQ2iQ==", "dev": true, "requires": { "semver": "^6.3.0" @@ -7796,9 +7558,9 @@ } }, "object-inspect": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", - "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", "dev": true }, "object-is": { @@ -7818,14 +7580,6 @@ "dev": true, "requires": { "isobject": "^3.0.0" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } } }, "object.getownpropertydescriptors": { @@ -7845,14 +7599,6 @@ "dev": true, "requires": { "isobject": "^3.0.1" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } } }, "objectpath": { @@ -8153,9 +7899,9 @@ } }, "parse5": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz", - "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", "optional": true }, "parseqs": { @@ -8271,9 +8017,9 @@ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "picomatch": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.0.7.tgz", - "integrity": "sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.1.1.tgz", + "integrity": "sha512-OYMyqkKzK7blWO/+XZYP6w8hH0LDvkBvdvKukti+7kqYFCiEAk+gI3DWnryapc0Dau05ugGTy0foQ6mqn4AHYA==", "dev": true }, "pify": { @@ -8365,12 +8111,6 @@ "supports-color": "^6.1.0" }, "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, "supports-color": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", @@ -8862,23 +8602,23 @@ } }, "rc-animate": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/rc-animate/-/rc-animate-2.10.1.tgz", - "integrity": "sha512-yfP3g5fNf8wB5eh85nim2IGrqNu5u7TKrrSh710+1vlUqZvnI2R5YHK99IBCQNgkLCAWjT0sHtkcYdynjly39w==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/rc-animate/-/rc-animate-2.10.2.tgz", + "integrity": "sha512-cE/A7piAzoWFSgUD69NmmMraqCeqVBa51UErod8NS3LUEqWfppSVagHfa0qHAlwPVPiIBg3emRONyny3eiH0Dg==", "requires": { "babel-runtime": "6.x", "classnames": "^2.2.6", "css-animation": "^1.3.2", "prop-types": "15.x", "raf": "^3.4.0", - "rc-util": "^4.8.0", + "rc-util": "^4.15.3", "react-lifecycles-compat": "^3.0.4" } }, "rc-menu": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-7.5.2.tgz", - "integrity": "sha512-vIS4kiVk81+W+gtvh+dyPA4ucMLCOH6NdwAFnum5by5hono/1fGNSR+F25mG8cHblRQ/8S9dJbaiVQUQ+tvtTQ==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-7.5.3.tgz", + "integrity": "sha512-H/jUyGbJxZI/iuVdC6Iu9KHfz7tucoqK0Vn8ahDnv+ppc1PnKb4SkBbXn5LrmUyaj7thCBiaktBxVnUXSmNE2g==", "requires": { "classnames": "2.x", "dom-scroll-into-view": "1.x", @@ -8889,13 +8629,6 @@ "rc-util": "^4.13.0", "resize-observer-polyfill": "^1.5.0", "shallowequal": "^1.1.0" - }, - "dependencies": { - "shallowequal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", - "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" - } } }, "rc-select": { @@ -8932,21 +8665,21 @@ } }, "rc-util": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-4.13.0.tgz", - "integrity": "sha512-rjfPy+afc2n40APHp6GYScXfgwHuUnYLz/4SCEWRaF8CHXKR8xw598LtPA36J3fEXENuMm6liO/CoKBoSrYCDw==", + "version": "4.15.7", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-4.15.7.tgz", + "integrity": "sha512-9pn2NU7IafaP0Hbtll7Ufu9eF9odDOMGMI/WD9lgD4yrm3xR4yCAWZ0cqQGIhKMqdkg3I7et7/dTekrftMeqJQ==", "requires": { "add-dom-event-listener": "^1.1.0", "babel-runtime": "6.x", "prop-types": "^15.5.10", "react-lifecycles-compat": "^3.0.4", - "shallowequal": "^0.2.2" + "shallowequal": "^1.1.0" } }, "react": { - "version": "16.10.2", - "resolved": "https://registry.npmjs.org/react/-/react-16.10.2.tgz", - "integrity": "sha512-MFVIq0DpIhrHFyqLU0S3+4dIcBhhOvBE8bJ/5kHPVOVaGdo0KuiQzpcjCPsf585WvhypqtrMILyoE2th6dT+Lw==", + "version": "16.12.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.12.0.tgz", + "integrity": "sha512-fglqy3k5E+81pA8s+7K0/T3DBCF0ZDOher1elBFzF7O6arXJgzyu/FW+COxFvAWXJoJN9KIZbT2LXlukwphYTA==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", @@ -8966,30 +8699,30 @@ } }, "react-dom": { - "version": "16.10.2", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.10.2.tgz", - "integrity": "sha512-kWGDcH3ItJK4+6Pl9DZB16BXYAZyrYQItU4OMy0jAkv5aNqc+mAKb4TpFtAteI6TJZu+9ZlNhaeNQSVQDHJzkw==", + "version": "16.12.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.12.0.tgz", + "integrity": "sha512-LMxFfAGrcS3kETtQaCkTKjMiifahaMySFDn71fZUNpPHZQEzmk/GiAeIT8JSOrHB23fnuCOMruL2a8NYlw+8Gw==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", "prop-types": "^15.6.2", - "scheduler": "^0.16.2" + "scheduler": "^0.18.0" } }, "react-dropzone": { - "version": "10.1.10", - "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-10.1.10.tgz", - "integrity": "sha512-vcLBdkYo7wgZpw1o4cz7uk8/Mmm+sYHeiTfFSshA/EGthz/TjjrTOrKwvFHm3o1p1LPk+x+KbDDlw5OeIo6eYA==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-10.2.0.tgz", + "integrity": "sha512-VvJtg6GKtM1Xu+SsMcBNBcB2XcOi27xbNLBMDkrpqsk3cSILFiBVoCuW96FSOWkCK1IFeNg67FjKu/c/KuUhkg==", "requires": { - "attr-accept": "^1.1.3", - "file-selector": "^0.1.11", + "attr-accept": "^2.0.0", + "file-selector": "^0.1.12", "prop-types": "^15.7.2" } }, "react-is": { - "version": "16.10.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.10.2.tgz", - "integrity": "sha512-INBT1QEgtcCCgvccr5/86CfD71fw9EPmDxgiJX4I2Ddr6ZsV6iFXsuby+qWJPtmNuMY0zByTsG4468P7nHuNWA==" + "version": "16.12.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz", + "integrity": "sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==" }, "react-lifecycles-compat": { "version": "3.0.4", @@ -9418,9 +9151,9 @@ "dev": true }, "scheduler": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.16.2.tgz", - "integrity": "sha512-BqYVWqwz6s1wZMhjFvLfVR5WXP7ZY32M/wYPo04CcuPM7XZEbV2TBNW7Z0UkguPTl0dWMA59VbNXxK6q+pHItg==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.18.0.tgz", + "integrity": "sha512-agTSHR1Nbfi6ulI0kYNK0203joW2Y5W4po4l+v03tOoiJKpTBbxpNhWDvqc/4IcOw+KLmSiQLTasZ4cab2/UWQ==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" @@ -9637,21 +9370,6 @@ "requires": { "is-extendable": "^0.1.0" } - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true } } }, @@ -9697,12 +9415,9 @@ } }, "shallowequal": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-0.2.2.tgz", - "integrity": "sha1-HjL9W8q2rWiKSBLLDMBO/HXHAU4=", - "requires": { - "lodash.keys": "^3.1.2" - } + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" }, "shebang-command": { "version": "1.2.0", @@ -9737,9 +9452,9 @@ "dev": true }, "smart-buffer": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.0.2.tgz", - "integrity": "sha512-JDhEpTKzXusOqXZ0BUIdH+CjFdO/CR3tLlf5CN34IypI+xMmXW1uB16OOY8z3cICbJlDAVJzNbwBhNO0wt9OAw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.1.0.tgz", + "integrity": "sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw==", "dev": true }, "snapdragon": { @@ -9832,12 +9547,6 @@ "is-data-descriptor": "^1.0.0", "kind-of": "^6.0.2" } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true } } }, @@ -10016,13 +9725,13 @@ } }, "socks": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.3.2.tgz", - "integrity": "sha512-pCpjxQgOByDHLlNqlnh/mNSAxIUkyBBuwwhTcV+enZGbDaClPvHdvm6uvOwZfFJkam7cGhBNbb4JxiP8UZkRvQ==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.3.3.tgz", + "integrity": "sha512-o5t52PCNtVdiOvzMry7wU4aOqYWL0PeCXRWBEiJow4/i/wr+wpsJQ9awEu1EonLIqsfGd5qSgDdxEOvCdmBEpA==", "dev": true, "requires": { - "ip": "^1.1.5", - "smart-buffer": "4.0.2" + "ip": "1.1.5", + "smart-buffer": "^4.1.0" } }, "socks-proxy-agent": { @@ -10062,9 +9771,9 @@ "dev": true }, "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, "source-map-loader": { @@ -10102,21 +9811,13 @@ } }, "source-map-support": { - "version": "0.5.12", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz", - "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==", + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", + "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", "dev": true, "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } }, "source-map-url": { @@ -10582,22 +10283,14 @@ } }, "terser": { - "version": "4.3.9", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.3.9.tgz", - "integrity": "sha512-NFGMpHjlzmyOtPL+fDw3G7+6Ueh/sz4mkaUYa4lJCxOPTNzd0Uj0aZJOmsDYoSQyfuVoWDMSWTPU3huyOm2zdA==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.4.0.tgz", + "integrity": "sha512-oDG16n2WKm27JO8h4y/w3iqBGAOSCtq7k8dRmrn4Wf9NouL0b2WpMHGChFGZq4nFAQy1FsNJrVQHfurXOSTmOA==", "dev": true, "requires": { "commander": "^2.20.0", "source-map": "~0.6.1", "source-map-support": "~0.5.12" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } }, "terser-webpack-plugin": { @@ -10616,14 +10309,6 @@ "terser": "^4.0.0", "webpack-sources": "^1.3.0", "worker-farm": "^1.7.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } }, "through": { @@ -10895,15 +10580,6 @@ "requires": { "commander": "~2.20.3", "source-map": "~0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - } } }, "ultron": { @@ -11019,12 +10695,6 @@ "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true } } }, @@ -11183,9 +10853,9 @@ } }, "vm-browserify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.0.tgz", - "integrity": "sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", + "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", "dev": true }, "void-elements": { @@ -11865,12 +11535,6 @@ "kind-of": "^3.0.2" } }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", @@ -12708,12 +12372,6 @@ "kind-of": "^3.0.2" } }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", @@ -12794,14 +12452,6 @@ "requires": { "source-list-map": "^2.0.0", "source-map": "~0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } }, "webpack-subresource-integrity": { 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 1ecb483e0a..1cdb167ddf 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 @@ -15,5 +15,12 @@ limitations under the License. --> -
Rule chain
- + + 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 1434ecb6af..a604312074 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 @@ -34,6 +34,7 @@ import {AuthService} from '@core/auth/auth.service'; import {ActivatedRoute} from '@angular/router'; import { Dashboard } from '@shared/models/dashboard.models'; import { RuleChain } from '@shared/models/rule-chain.models'; +import { FcModel, FlowchartConstants } from 'ngx-flowchart/dist/ngx-flowchart'; @Component({ selector: 'tb-rulechain-page', @@ -44,6 +45,13 @@ export class RuleChainPageComponent extends PageComponent implements OnInit { ruleChain: RuleChain; + flowchartConstants = FlowchartConstants; + selected = []; + model: FcModel = { + nodes: [], + edges: [] + }; + constructor(protected store: Store, private route: ActivatedRoute, private userService: UserService, @@ -57,6 +65,109 @@ export class RuleChainPageComponent extends PageComponent implements OnInit { ngOnInit() { this.ruleChain = this.route.snapshot.data.ruleChain; + this.testData(); + } + + onModelChanged() { + console.log('Model changed!'); + } + + private testData() { + this.model.nodes.push(... + [ + { + name: 'Node 1', + readonly: true, + id: '2', + x: 300, + y: 100, + color: '#000', + borderColor: '#000', + connectors: [ + { + type: FlowchartConstants.leftConnectorType, + id: '1' + }, + { + type: FlowchartConstants.rightConnectorType, + id: '2' + } + ] + }, + { + name: 'Node 2', + id: '3', + x: 600, + y: 100, + color: '#F15B26', + connectors: [ + { + type: FlowchartConstants.leftConnectorType, + id: '3' + }, + { + type: FlowchartConstants.rightConnectorType, + id: '4' + } + ] + }, + { + name: 'Node 3', + id: '4', + x: 1000, + y: 100, + color: '#000', + borderColor: '#000', + connectors: [ + { + type: FlowchartConstants.leftConnectorType, + id: '5' + }, + { + type: FlowchartConstants.rightConnectorType, + id: '6' + } + ] + }, + { + name: 'Node 4', + id: '5', + x: 1300, + y: 100, + color: '#000', + borderColor: '#000', + connectors: [ + { + type: FlowchartConstants.leftConnectorType, + id: '7' + }, + { + type: FlowchartConstants.rightConnectorType, + id: '8' + } + ] + } + ] + ); + this.model.edges.push(... + [ + { + source: '2', + destination: '3', + label: 'label1' + }, + { + source: '4', + destination: '5', + label: 'label2' + }, + { + source: '6', + destination: '7', + label: 'label3' + } + ] + ); } } From cabdc68a429c5db9c852979d1486a5ad22f47092 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 22 Nov 2019 17:58:13 +0200 Subject: [PATCH 054/133] RuleChain page implementation. --- ui-ngx/proxy.conf.json | 4 + .../core/http/component-descriptor.service.ts | 103 +++++ .../src/app/core/http/rule-chain.service.ts | 189 +++++++- .../rulechain/rulechain-page.component.html | 4 +- .../rulechain/rulechain-page.component.ts | 425 +++++++++++++----- .../pages/rulechain/rulechain-page.models.ts | 49 ++ .../rulechain/rulechain-routing.module.ts | 80 +++- .../models/component-descriptor.models.ts | 39 ++ .../app/shared/models/rule-chain.models.ts | 89 +++- .../src/app/shared/models/rule-node.models.ts | 131 +++++- 10 files changed, 972 insertions(+), 141 deletions(-) create mode 100644 ui-ngx/src/app/core/http/component-descriptor.service.ts create mode 100644 ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.models.ts create mode 100644 ui-ngx/src/app/shared/models/component-descriptor.models.ts diff --git a/ui-ngx/proxy.conf.json b/ui-ngx/proxy.conf.json index 5c77d20da8..a558aee24e 100644 --- a/ui-ngx/proxy.conf.json +++ b/ui-ngx/proxy.conf.json @@ -3,6 +3,10 @@ "target": "http://localhost:8080", "secure": false }, + "/static/rulenode": { + "target": "http://localhost:8080", + "secure": false + }, "/api/ws": { "target": "ws://localhost:8080", "ws": true diff --git a/ui-ngx/src/app/core/http/component-descriptor.service.ts b/ui-ngx/src/app/core/http/component-descriptor.service.ts new file mode 100644 index 0000000000..6893a0cd04 --- /dev/null +++ b/ui-ngx/src/app/core/http/component-descriptor.service.ts @@ -0,0 +1,103 @@ +/// +/// Copyright © 2016-2019 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 { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { ComponentDescriptor, ComponentType } from '@shared/models/component-descriptor.models'; +import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils'; +import { Observable, of } from 'rxjs'; +import { RuleChainMetaData } from '@shared/models/rule-chain.models'; +import { tap, map } from 'rxjs/operators'; +import { RuleNodeType } from '@shared/models/rule-node.models'; + +@Injectable({ + providedIn: 'root' +}) +export class ComponentDescriptorService { + + private componentsByType: Map> = + new Map>(); + private componentsByClazz: Map = new Map(); + + constructor( + private http: HttpClient + ) { + } + + public getComponentDescriptorsByType(componentType: ComponentType, config?: RequestConfig): Observable> { + const existing = this.componentsByType.get(componentType); + if (existing) { + return of(existing); + } else { + return this.http.get>(`/api/components/${componentType}`, defaultHttpOptionsFromConfig(config)).pipe( + map((componentDescriptors) => { + this.componentsByType.set(componentType, componentDescriptors); + componentDescriptors.forEach((componentDescriptor) => { + this.componentsByClazz.set(componentDescriptor.clazz, componentDescriptor); + }); + return componentDescriptors; + }) + ); + } + } + + public getComponentDescriptorsByTypes(componentTypes: Array, + config?: RequestConfig): Observable> { + let result: ComponentDescriptor[] = []; + for (let i = componentTypes.length - 1; i >= 0; i--) { + const componentType = componentTypes[i]; + const componentDescriptors = this.componentsByType.get(componentType); + if (componentDescriptors) { + result = result.concat(componentDescriptors); + componentTypes.splice(i, 1); + } + } + if (!componentTypes.length) { + return of(result); + } else { + return this.http.get>(`/api/components?componentTypes=${componentTypes.join(',')}`, + defaultHttpOptionsFromConfig(config)).pipe( + map((componentDescriptors) => { + componentDescriptors.forEach((componentDescriptor) => { + let componentsList = this.componentsByType.get(componentDescriptor.type); + if (!componentsList) { + componentsList = new Array(); + this.componentsByType.set(componentDescriptor.type, componentsList); + } + componentsList.push(componentDescriptor); + this.componentsByClazz.set(componentDescriptor.clazz, componentDescriptor); + }); + result = result.concat(componentDescriptors); + return result; + }) + ); + } + } + + public getComponentDescriptorByClazz(componentDescriptorClazz: string, config?: RequestConfig): Observable { + const existing = this.componentsByClazz.get(componentDescriptorClazz); + if (existing) { + return of(existing); + } else { + return this.http.get(`/api/component/${componentDescriptorClazz}`, defaultHttpOptionsFromConfig(config)).pipe( + map((componentDescriptor) => { + this.componentsByClazz.set(componentDescriptorClazz, componentDescriptor); + return componentDescriptor; + }) + ); + } + } +} diff --git a/ui-ngx/src/app/core/http/rule-chain.service.ts b/ui-ngx/src/app/core/http/rule-chain.service.ts index f1379c8e6a..a6ff059659 100644 --- a/ui-ngx/src/app/core/http/rule-chain.service.ts +++ b/ui-ngx/src/app/core/http/rule-chain.service.ts @@ -14,21 +14,39 @@ /// limitations under the License. /// -import {Injectable} from '@angular/core'; -import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; -import {Observable} from 'rxjs/index'; -import {HttpClient} from '@angular/common/http'; -import {PageLink} from '@shared/models/page/page-link'; -import {PageData} from '@shared/models/page/page-data'; -import {RuleChain} from '@shared/models/rule-chain.models'; +import { Injectable } from '@angular/core'; +import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; +import { forkJoin, Observable, of } from 'rxjs/index'; +import { HttpClient } from '@angular/common/http'; +import { PageLink } from '@shared/models/page/page-link'; +import { PageData } from '@shared/models/page/page-data'; +import { + ResolvedRuleChainMetaData, + RuleChain, RuleChainConnectionInfo, + RuleChainMetaData, + ruleChainNodeComponent, + ruleNodeTypeComponentTypes, unknownNodeComponent +} from '@shared/models/rule-chain.models'; +import { ComponentDescriptorService } from './component-descriptor.service'; +import { RuleNodeComponentDescriptor } from '@app/shared/models/rule-node.models'; +import { ResourcesService } from '../services/resources.service'; +import { catchError, map, mergeMap } from 'rxjs/operators'; +import { TranslateService } from '@ngx-translate/core'; +import { EntityType } from '@shared/models/entity-type.models'; +import { deepClone } from '@core/utils'; @Injectable({ providedIn: 'root' }) export class RuleChainService { + private ruleNodeComponents: Array; + constructor( - private http: HttpClient + private http: HttpClient, + private componentDescriptorService: ComponentDescriptorService, + private resourcesService: ResourcesService, + private translate: TranslateService ) { } public getRuleChains(pageLink: PageLink, config?: RequestConfig): Observable> { @@ -52,4 +70,159 @@ export class RuleChainService { return this.http.post(`/api/ruleChain/${ruleChainId}/root`, null, defaultHttpOptionsFromConfig(config)); } + public getRuleChainMetadata(ruleChainId: string, config?: RequestConfig): Observable { + return this.http.get(`/api/ruleChain/${ruleChainId}/metadata`, defaultHttpOptionsFromConfig(config)); + } + + public getResolvedRuleChainMetadata(ruleChainId: string, config?: RequestConfig): Observable { + return this.getRuleChainMetadata(ruleChainId, config).pipe( + mergeMap((ruleChainMetaData) => { + return this.resolveTargetRuleChains(ruleChainMetaData.ruleChainConnections).pipe( + map((targetRuleChainsMap) => { + const resolvedRuleChainMetadata: ResolvedRuleChainMetaData = {...ruleChainMetaData, targetRuleChainsMap}; + return resolvedRuleChainMetadata; + }) + ); + }) + ); + } + + public saveRuleChainMetadata(ruleChainMetaData: RuleChainMetaData, config?: RequestConfig): Observable { + return this.http.post('/api/ruleChain/metadata', ruleChainMetaData, defaultHttpOptionsFromConfig(config)); + } + + public saveAndGetResolvedRuleChainMetadata(ruleChainMetaData: RuleChainMetaData, + config?: RequestConfig): Observable { + return this.saveRuleChainMetadata(ruleChainMetaData, config).pipe( + mergeMap((savedRuleChainMetaData) => { + return this.resolveTargetRuleChains(savedRuleChainMetaData.ruleChainConnections).pipe( + map((targetRuleChainsMap) => { + const resolvedRuleChainMetadata: ResolvedRuleChainMetaData = {...savedRuleChainMetaData, targetRuleChainsMap}; + return resolvedRuleChainMetadata; + }) + ); + }) + ); + } + + public getRuleNodeComponents(config?: RequestConfig): Observable> { + if (this.ruleNodeComponents) { + return of(this.ruleNodeComponents); + } else { + return this.loadRuleNodeComponents(config).pipe( + mergeMap((components) => { + return this.resolveRuleNodeComponentsUiResources(components).pipe( + map((ruleNodeComponents) => { + this.ruleNodeComponents = ruleNodeComponents; + this.ruleNodeComponents.push(ruleChainNodeComponent); + this.ruleNodeComponents.sort( + (comp1, comp2) => { + let result = comp1.type.toString().localeCompare(comp2.type.toString()); + if (result === 0) { + result = comp1.name.localeCompare(comp2.name); + } + return result; + } + ); + return this.ruleNodeComponents; + }) + ); + }) + ); + } + } + + public getRuleNodeComponentByClazz(clazz: string): RuleNodeComponentDescriptor { + const found = this.ruleNodeComponents.filter((component) => component.clazz === clazz); + if (found && found.length) { + return found[0]; + } else { + const unknownComponent = deepClone(unknownNodeComponent); + unknownComponent.clazz = clazz; + unknownComponent.configurationDescriptor.nodeDefinition.details = 'Unknown Rule Node class: ' + clazz; + return unknownComponent; + } + } + + private resolveTargetRuleChains(ruleChainConnections: Array): Observable<{[ruleChainId: string]: RuleChain}> { + if (ruleChainConnections && ruleChainConnections.length) { + const tasks: Observable[] = []; + ruleChainConnections.forEach((connection) => { + tasks.push(this.resolveRuleChain(connection.targetRuleChainId.id)); + }); + return forkJoin(tasks).pipe( + map((ruleChains) => { + const ruleChainsMap: {[ruleChainId: string]: RuleChain} = {}; + ruleChains.forEach((ruleChain) => { + ruleChainsMap[ruleChain.id.id] = ruleChain; + }); + return ruleChainsMap; + }) + ); + } else { + return of({} as {[ruleChainId: string]: RuleChain}); + } + } + + private loadRuleNodeComponents(config?: RequestConfig): Observable> { + return this.componentDescriptorService.getComponentDescriptorsByTypes(ruleNodeTypeComponentTypes, config).pipe( + map((components) => { + const ruleNodeComponents: RuleNodeComponentDescriptor[] = []; + components.forEach((component) => { + ruleNodeComponents.push(component as RuleNodeComponentDescriptor); + }); + return ruleNodeComponents; + }) + ); + } + + private resolveRuleNodeComponentsUiResources(components: Array): + Observable> { + const tasks: Observable[] = []; + components.forEach((component) => { + tasks.push(this.resolveRuleNodeComponentUiResources(component)); + }); + return forkJoin(tasks).pipe( + catchError((err) => { + return of(components); + }) + ); + } + + private resolveRuleNodeComponentUiResources(component: RuleNodeComponentDescriptor): Observable { + const uiResources = component.configurationDescriptor.nodeDefinition.uiResources; + if (uiResources && uiResources.length) { + const tasks: Observable[] = []; + uiResources.forEach((uiResource) => { + tasks.push(this.resourcesService.loadResource(uiResource)); + }); + return forkJoin(tasks).pipe( + map((res) => { + return component; + }), + catchError(() => { + component.configurationDescriptor.nodeDefinition.uiResourceLoadError = this.translate.instant('rulenode.ui-resources-load-error'); + return of(component); + }) + ); + } else { + return of(component); + } + } + + private resolveRuleChain(ruleChainId: string): Observable { + return this.getRuleChain(ruleChainId, {ignoreErrors: true}).pipe( + map(ruleChain => ruleChain), + catchError((err) => { + const ruleChain = { + id: { + entityType: EntityType.RULE_CHAIN, + id: ruleChainId + } + } as RuleChain; + return of(ruleChain); + }) + ); + } + } 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 1cdb167ddf..827115dc38 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 @@ -17,9 +17,9 @@ --> 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 a604312074..7f073551c0 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 @@ -14,160 +14,341 @@ /// limitations under the License. /// -import {Component, OnInit} from '@angular/core'; -import {UserService} from '@core/http/user.service'; -import {User} from '@shared/models/user.model'; -import {Authority} from '@shared/models/authority.enum'; -import {PageComponent} from '@shared/components/page.component'; -import {Store} from '@ngrx/store'; -import {AppState} from '@core/core.state'; -import {FormBuilder, FormGroup, Validators} from '@angular/forms'; -import {HasConfirmForm} from '@core/guards/confirm-on-exit.guard'; -import {ActionAuthUpdateUserDetails} from '@core/auth/auth.actions'; -import {environment as env} from '@env/environment'; -import {TranslateService} from '@ngx-translate/core'; -import {ActionSettingsChangeLanguage} from '@core/settings/settings.actions'; -import {ChangePasswordDialogComponent} from '@modules/home/pages/profile/change-password-dialog.component'; -import {MatDialog} from '@angular/material'; -import {DialogService} from '@core/services/dialog.service'; -import {AuthService} from '@core/auth/auth.service'; -import {ActivatedRoute} from '@angular/router'; -import { Dashboard } from '@shared/models/dashboard.models'; -import { RuleChain } from '@shared/models/rule-chain.models'; -import { FcModel, FlowchartConstants } from 'ngx-flowchart/dist/ngx-flowchart'; +import { Component, OnInit } from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { FormBuilder } from '@angular/forms'; +import { HasDirtyFlag } from '@core/guards/confirm-on-exit.guard'; +import { TranslateService } from '@ngx-translate/core'; +import { MatDialog } from '@angular/material'; +import { DialogService } from '@core/services/dialog.service'; +import { AuthService } from '@core/auth/auth.service'; +import { ActivatedRoute, Router } from '@angular/router'; +import { + inputNodeComponent, + ResolvedRuleChainMetaData, + RuleChain, + ruleChainNodeComponent +} from '@shared/models/rule-chain.models'; +import { FlowchartConstants } from 'ngx-flowchart/dist/ngx-flowchart'; +import { RuleNodeComponentDescriptor, RuleNodeType, ruleNodeTypeDescriptors } from '@shared/models/rule-node.models'; +import { FcRuleEdge, FcRuleNode, FcRuleNodeModel, FcRuleNodeType, FcRuleNodeTypeModel } from './rulechain-page.models'; +import { RuleChainService } from '@core/http/rule-chain.service'; @Component({ selector: 'tb-rulechain-page', templateUrl: './rulechain-page.component.html', styleUrls: [] }) -export class RuleChainPageComponent extends PageComponent implements OnInit { +export class RuleChainPageComponent extends PageComponent implements OnInit, HasDirtyFlag { + + get isDirty(): boolean { + return this.isDirtyValue || this.isImport; + } + + isImport: boolean; + isDirtyValue: boolean; + + errorTooltips = {}; + isFullscreen = false; + + editingRuleNode = null; + isEditingRuleNode = false; + + editingRuleNodeLink = null; + isEditingRuleNodeLink = false; + + isLibraryOpen = true; + + ruleNodeSearch = ''; ruleChain: RuleChain; + ruleChainMetaData: ResolvedRuleChainMetaData; - flowchartConstants = FlowchartConstants; - selected = []; - model: FcModel = { + ruleChainModel: FcRuleNodeModel = { nodes: [], edges: [] }; + selectedObjects = []; + nextNodeID: number; + nextConnectorID: number; + inputConnectorId: number; + + ruleNodeTypesModel: {[type: string]: {model: FcRuleNodeTypeModel, selectedObjects: any[]}} = {}; + + ruleNodeComponents: Array; + + flowchartConstants = FlowchartConstants; constructor(protected store: Store, private route: ActivatedRoute, - private userService: UserService, + private router: Router, + private ruleChainService: RuleChainService, private authService: AuthService, private translate: TranslateService, public dialog: MatDialog, public dialogService: DialogService, public fb: FormBuilder) { super(store); + this.init(); } ngOnInit() { + } + + private init() { this.ruleChain = this.route.snapshot.data.ruleChain; - this.testData(); + if (this.route.snapshot.data.import && !this.ruleChain) { + this.router.navigateByUrl('ruleChains'); + return; + } + this.isImport = this.route.snapshot.data.import; + this.ruleChainMetaData = this.route.snapshot.data.ruleChainMetaData; + this.ruleNodeComponents = this.route.snapshot.data.ruleNodeComponents; + for (const type of Object.keys(RuleNodeType)) { + const desc = ruleNodeTypeDescriptors.get(RuleNodeType[type]); + if (!desc.special) { + this.ruleNodeTypesModel[type] = { + model: { + nodes: [], + edges: [] + }, + selectedObjects: [] + }; + } + } + this.loadRuleChainLibrary(this.ruleNodeComponents); + this.createRuleChainModel(); } - onModelChanged() { - console.log('Model changed!'); + private loadRuleChainLibrary(ruleNodeComponents: Array) { + for (const componentType of Object.keys(this.ruleNodeTypesModel)) { + this.ruleNodeTypesModel[componentType].model.nodes.length = 0; + } + ruleNodeComponents.forEach((ruleNodeComponent) => { + const componentType = ruleNodeComponent.type; + const model = this.ruleNodeTypesModel[componentType].model; + const desc = ruleNodeTypeDescriptors.get(RuleNodeType[componentType]); + let icon = desc.icon; + let iconUrl = null; + if (ruleNodeComponent.configurationDescriptor.nodeDefinition.icon) { + icon = ruleNodeComponent.configurationDescriptor.nodeDefinition.icon; + } + if (ruleNodeComponent.configurationDescriptor.nodeDefinition.iconUrl) { + iconUrl = ruleNodeComponent.configurationDescriptor.nodeDefinition.iconUrl; + } + const node: FcRuleNodeType = { + id: 'node-lib-' + componentType + '-' + model.nodes.length, + component: ruleNodeComponent, + name: '', + nodeClass: desc.nodeClass, + icon, + iconUrl, + x: 30, + y: 10 + 50 * model.nodes.length, + connectors: [] + }; + if (ruleNodeComponent.configurationDescriptor.nodeDefinition.inEnabled) { + node.connectors.push( + { + type: FlowchartConstants.leftConnectorType, + id: (model.nodes.length * 2) + '' + } + ); + } + if (ruleNodeComponent.configurationDescriptor.nodeDefinition.outEnabled) { + node.connectors.push( + { + type: FlowchartConstants.rightConnectorType, + id: (model.nodes.length * 2 + 1) + '' + } + ); + } + model.nodes.push(node); + }); } - private testData() { - this.model.nodes.push(... - [ - { - name: 'Node 1', - readonly: true, - id: '2', - x: 300, - y: 100, - color: '#000', - borderColor: '#000', - connectors: [ - { - type: FlowchartConstants.leftConnectorType, - id: '1' - }, - { - type: FlowchartConstants.rightConnectorType, - id: '2' - } - ] - }, - { - name: 'Node 2', - id: '3', - x: 600, - y: 100, - color: '#F15B26', - connectors: [ - { - type: FlowchartConstants.leftConnectorType, - id: '3' - }, - { - type: FlowchartConstants.rightConnectorType, - id: '4' - } - ] - }, - { - name: 'Node 3', - id: '4', - x: 1000, - y: 100, - color: '#000', - borderColor: '#000', - connectors: [ - { - type: FlowchartConstants.leftConnectorType, - id: '5' - }, - { - type: FlowchartConstants.rightConnectorType, - id: '6' - } - ] - }, - { - name: 'Node 4', - id: '5', - x: 1300, - y: 100, - color: '#000', - borderColor: '#000', - connectors: [ - { - type: FlowchartConstants.leftConnectorType, - id: '7' - }, - { - type: FlowchartConstants.rightConnectorType, - id: '8' + private createRuleChainModel() { + this.nextNodeID = 1; + this.nextConnectorID = 1; + + this.selectedObjects.length = 0; + this.ruleChainModel.nodes.length = 0; + this.ruleChainModel.edges.length = 0; + + this.inputConnectorId = this.nextConnectorID++; + this.ruleChainModel.nodes.push( + { + id: 'rule-chain-node-' + this.nextNodeID++, + component: inputNodeComponent, + name: '', + nodeClass: ruleNodeTypeDescriptors.get(RuleNodeType.INPUT).nodeClass, + icon: ruleNodeTypeDescriptors.get(RuleNodeType.INPUT).icon, + readonly: true, + x: 50, + y: 150, + connectors: [ + { + type: FlowchartConstants.rightConnectorType, + id: this.inputConnectorId + '' + }, + ] + + } + ); + const nodes: FcRuleNode[] = []; + this.ruleChainMetaData.nodes.forEach((ruleNode) => { + const component = this.ruleChainService.getRuleNodeComponentByClazz(ruleNode.type); + const descriptor = ruleNodeTypeDescriptors.get(component.type); + let icon = descriptor.icon; + let iconUrl = null; + if (component.configurationDescriptor.nodeDefinition.icon) { + icon = component.configurationDescriptor.nodeDefinition.icon; + } + if (component.configurationDescriptor.nodeDefinition.iconUrl) { + iconUrl = component.configurationDescriptor.nodeDefinition.iconUrl; + } + const node: FcRuleNode = { + id: 'rule-chain-node-' + this.nextNodeID++, + ruleNodeId: ruleNode.id, + additionalInfo: ruleNode.additionalInfo, + configuration: ruleNode.configuration, + debugMode: ruleNode.debugMode, + x: Math.round(ruleNode.additionalInfo.layoutX), + y: Math.round(ruleNode.additionalInfo.layoutY), + component, + name: ruleNode.name, + nodeClass: descriptor.nodeClass, + icon, + iconUrl, + connectors: [] + }; + if (component.configurationDescriptor.nodeDefinition.inEnabled) { + node.connectors.push( + { + type: FlowchartConstants.leftConnectorType, + id: (this.nextConnectorID++) + '' + } + ); + } + if (component.configurationDescriptor.nodeDefinition.outEnabled) { + node.connectors.push( + { + type: FlowchartConstants.rightConnectorType, + id: (this.nextConnectorID++) + '' + } + ); + } + nodes.push(node); + this.ruleChainModel.nodes.push(node); + }); + if (this.ruleChainMetaData.firstNodeIndex > -1) { + const destNode = nodes[this.ruleChainMetaData.firstNodeIndex]; + if (destNode) { + const connectors = destNode.connectors.filter(connector => connector.type === FlowchartConstants.leftConnectorType); + if (connectors && connectors.length) { + const edge: FcRuleEdge = { + source: this.inputConnectorId + '', + destination: connectors[0].id + }; + this.ruleChainModel.edges.push(edge); + } + } + } + if (this.ruleChainMetaData.connections) { + const edgeMap: {[edgeKey: string]: FcRuleEdge} = {}; + this.ruleChainMetaData.connections.forEach((connection) => { + const sourceNode = nodes[connection.fromIndex]; + const destNode = nodes[connection.toIndex]; + if (sourceNode && destNode) { + const sourceConnectors = sourceNode.connectors.filter(connector => connector.type === FlowchartConstants.rightConnectorType); + const destConnectors = destNode.connectors.filter(connector => connector.type === FlowchartConstants.leftConnectorType); + if (sourceConnectors && sourceConnectors.length && destConnectors && destConnectors.length) { + const sourceId = sourceConnectors[0].id; + const destId = destConnectors[0].id; + const edgeKey = sourceId + '_' + destId; + let edge = edgeMap[edgeKey]; + if (!edge) { + edge = { + source: sourceId, + destination: destId, + label: connection.type, + labels: [connection.type] + }; + edgeMap[edgeKey] = edge; + this.ruleChainModel.edges.push(edge); + } else { + edge.label += ' / ' + connection.type; + edge.labels.push(connection.type); } - ] + } } - ] - ); - this.model.edges.push(... - [ - { - source: '2', - destination: '3', - label: 'label1' - }, - { - source: '4', - destination: '5', - label: 'label2' - }, - { - source: '6', - destination: '7', - label: 'label3' + }); + } + if (this.ruleChainMetaData.ruleChainConnections) { + const ruleChainsMap = this.ruleChainMetaData.targetRuleChainsMap; + const ruleChainNodesMap: {[ruleChainNodeId: string]: FcRuleNode} = {}; + const ruleChainEdgeMap: {[edgeKey: string]: FcRuleEdge} = {}; + this.ruleChainMetaData.ruleChainConnections.forEach((ruleChainConnection) => { + const ruleChain = ruleChainsMap[ruleChainConnection.targetRuleChainId.id]; + if (ruleChainConnection.additionalInfo && ruleChainConnection.additionalInfo.ruleChainNodeId) { + let ruleChainNode = ruleChainNodesMap[ruleChainConnection.additionalInfo.ruleChainNodeId]; + if (!ruleChainNode) { + ruleChainNode = { + id: 'rule-chain-node-' + this.nextNodeID++, + name: ruleChain.name ? name : 'Unresolved', + targetRuleChainId: ruleChain.name ? ruleChainConnection.targetRuleChainId.id : null, + error: ruleChain.name ? undefined : this.translate.instant('rulenode.invalid-target-rulechain'), + additionalInfo: ruleChainConnection.additionalInfo, + x: Math.round(ruleChainConnection.additionalInfo.layoutX), + y: Math.round(ruleChainConnection.additionalInfo.layoutY), + component: ruleChainNodeComponent, + nodeClass: ruleNodeTypeDescriptors.get(RuleNodeType.RULE_CHAIN).nodeClass, + icon: ruleNodeTypeDescriptors.get(RuleNodeType.RULE_CHAIN).icon, + connectors: [ + { + type: FlowchartConstants.leftConnectorType, + id: (this.nextConnectorID++) + '' + } + ] + }; + ruleChainNodesMap[ruleChainConnection.additionalInfo.ruleChainNodeId] = ruleChainNode; + this.ruleChainModel.nodes.push(ruleChainNode); + } + const sourceNode = nodes[ruleChainConnection.fromIndex]; + if (sourceNode) { + const connectors = sourceNode.connectors.filter(connector => connector.type === FlowchartConstants.rightConnectorType); + if (connectors && connectors.length) { + const sourceId = connectors[0].id; + const destId = ruleChainNode.connectors[0].id; + const edgeKey = sourceId + '_' + destId; + let ruleChainEdge = ruleChainEdgeMap[edgeKey]; + if (!ruleChainEdge) { + ruleChainEdge = { + source: sourceId, + destination: destId, + label: ruleChainConnection.type, + labels: [ruleChainConnection.type] + }; + ruleChainEdgeMap[edgeKey] = ruleChainEdge; + this.ruleChainModel.edges.push(ruleChainEdge); + } else { + ruleChainEdge.label += ' / ' + ruleChainConnection.type; + ruleChainEdge.labels.push(ruleChainConnection.type); + } + } + } } - ] - ); + }); + } + this.isDirtyValue = false; + } + + onModelChanged() { + console.log('Model changed!'); + this.isDirtyValue = true; } + } diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.models.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.models.ts new file mode 100644 index 0000000000..c7e972625b --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.models.ts @@ -0,0 +1,49 @@ +/// +/// Copyright © 2016-2019 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 { FcNode, FcEdge, FcModel } from 'ngx-flowchart/dist/ngx-flowchart'; +import { RuleNodeComponentDescriptor, RuleNodeConfiguration } from '@shared/models/rule-node.models'; +import { RuleNodeId } from '@app/shared/models/id/rule-node-id'; +import { RuleChainId } from '@shared/models/id/rule-chain-id'; + +export interface FcRuleNodeType extends FcNode { + component: RuleNodeComponentDescriptor; + nodeClass: string; + icon: string; + iconUrl?: string; +} + +export interface FcRuleNodeTypeModel extends FcModel { + nodes: Array; +} + +export interface FcRuleNode extends FcRuleNodeType { + ruleNodeId?: RuleNodeId; + additionalInfo?: any; + configuration?: RuleNodeConfiguration; + debugMode?: boolean; + targetRuleChainId?: string; + error?: string; +} + +export interface FcRuleEdge extends FcEdge { + labels?: string[]; +} + +export interface FcRuleNodeModel extends FcModel { + nodes: Array; + edges: Array; +} diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts index 129da15ff1..93f25ecc7f 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts @@ -26,11 +26,18 @@ import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { BreadCrumbConfig, BreadCrumbLabelFunction } from '@shared/components/breadcrumb'; -import { RuleChain } from '@shared/models/rule-chain.models'; +import { + RuleChain, + RuleChainMetaData, + RuleChainImport, + ResolvedRuleChainMetaData +} from '@shared/models/rule-chain.models'; import { RuleChainService } from '@core/http/rule-chain.service'; import { DashboardPageComponent } from '@home/pages/dashboard/dashboard-page.component'; import { dashboardBreadcumbLabelFunction, DashboardResolver } from '@home/pages/dashboard/dashboard-routing.module'; import { RuleChainPageComponent } from '@home/pages/rulechain/rulechain-page.component'; +import { RuleNodeComponentDescriptor } from '@shared/models/rule-node.models'; +import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard'; @Injectable() @@ -45,6 +52,29 @@ export class RuleChainResolver implements Resolve { } } +@Injectable() +export class ResolvedRuleChainMetaDataResolver implements Resolve { + + constructor(private ruleChainService: RuleChainService) { + } + + resolve(route: ActivatedRouteSnapshot): Observable { + const ruleChainId = route.params.ruleChainId; + return this.ruleChainService.getResolvedRuleChainMetadata(ruleChainId); + } +} + +@Injectable() +export class RuleNodeComponentsResolver implements Resolve> { + + constructor(private ruleChainService: RuleChainService) { + } + + resolve(route: ActivatedRouteSnapshot): Observable> { + return this.ruleChainService.getRuleNodeComponents(); + } +} + export const ruleChainBreadcumbLabelFunction: BreadCrumbLabelFunction = ((route, translate, component) => { let label: string = component.ruleChain.name; if (component.ruleChain.root) { @@ -53,6 +83,10 @@ export const ruleChainBreadcumbLabelFunction: BreadCrumbLabelFunction = ((route, return label; }); +export const importRuleChainBreadcumbLabelFunction: BreadCrumbLabelFunction = ((route, translate, component) => { + return `${translate.instant('rulechain.import')}: ${component.ruleChain.name}`; +}); + const routes: Routes = [ { path: 'ruleChains', @@ -77,6 +111,7 @@ const routes: Routes = [ { path: ':ruleChainId', component: RuleChainPageComponent, + canDeactivate: [ConfirmOnExitGuard], data: { breadcrumb: { labelFunction: ruleChainBreadcumbLabelFunction, @@ -84,10 +119,31 @@ const routes: Routes = [ } as BreadCrumbConfig, auth: [Authority.TENANT_ADMIN], title: 'rulechain.rulechain', - widgetEditMode: false + import: false }, resolve: { - ruleChain: RuleChainResolver + ruleChain: RuleChainResolver, + ruleChainMetaData: ResolvedRuleChainMetaDataResolver, + ruleNodeComponents: RuleNodeComponentsResolver + } + }, + { + path: 'ruleChain/import', + component: RuleChainPageComponent, + canDeactivate: [ConfirmOnExitGuard], + data: { + breadcrumb: { + labelFunction: importRuleChainBreadcumbLabelFunction, + icon: 'settings_ethernet' + } as BreadCrumbConfig, + auth: [Authority.TENANT_ADMIN], + title: 'rulechain.rulechain', + import: true + }, + resolve: { + ruleChain: 'importRuleChain', + ruleChainMetaData: 'importRuleChainMetadata', + ruleNodeComponents: RuleNodeComponentsResolver } } ] @@ -99,7 +155,23 @@ const routes: Routes = [ exports: [RouterModule], providers: [ RuleChainsTableConfigResolver, - RuleChainResolver + RuleChainResolver, + ResolvedRuleChainMetaDataResolver, + RuleNodeComponentsResolver, + { + provide: 'importRuleChain', + useValue: (route: ActivatedRouteSnapshot) => { + const ruleChainImport: RuleChainImport = route.params.ruleChainImport; + return ruleChainImport.ruleChain; + } + }, + { + provide: 'importRuleChainMetadata', + useValue: (route: ActivatedRouteSnapshot) => { + const ruleChainImport: RuleChainImport = route.params.ruleChainImport; + return ruleChainImport.metadata; + } + } ] }) export class RuleChainRoutingModule { } diff --git a/ui-ngx/src/app/shared/models/component-descriptor.models.ts b/ui-ngx/src/app/shared/models/component-descriptor.models.ts new file mode 100644 index 0000000000..47f81f96b6 --- /dev/null +++ b/ui-ngx/src/app/shared/models/component-descriptor.models.ts @@ -0,0 +1,39 @@ +/// +/// Copyright © 2016-2019 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 { RuleNodeType } from '@shared/models/rule-node.models'; + +export enum ComponentType { + ENRICHMENT = 'ENRICHMENT', + FILTER = 'FILTER', + TRANSFORMATION = 'TRANSFORMATION', + ACTION = 'ACTION', + EXTERNAL = 'EXTERNAL' +} + +export enum ComponentScope { + SYSTEM = 'SYSTEM', + TENANT = 'TENANT' +} + +export interface ComponentDescriptor { + type: ComponentType | RuleNodeType; + scope?: ComponentScope; + name: string; + clazz: string; + configurationDescriptor?: any; + actions?: string; +} diff --git a/ui-ngx/src/app/shared/models/rule-chain.models.ts b/ui-ngx/src/app/shared/models/rule-chain.models.ts index 100214a8cc..3f6c205e08 100644 --- a/ui-ngx/src/app/shared/models/rule-chain.models.ts +++ b/ui-ngx/src/app/shared/models/rule-chain.models.ts @@ -14,10 +14,12 @@ /// limitations under the License. /// -import {BaseData} from '@shared/models/base-data'; -import {TenantId} from '@shared/models/id/tenant-id'; -import {RuleChainId} from '@shared/models/id/rule-chain-id'; -import {RuleNodeId} from '@shared/models/id/rule-node-id'; +import { BaseData } from '@shared/models/base-data'; +import { TenantId } from '@shared/models/id/tenant-id'; +import { RuleChainId } from '@shared/models/id/rule-chain-id'; +import { RuleNodeId } from '@shared/models/id/rule-node-id'; +import { RuleNode, RuleNodeComponentDescriptor, RuleNodeType } from '@shared/models/rule-node.models'; +import { ComponentType } from '@shared/models/component-descriptor.models'; export interface RuleChain extends BaseData { tenantId: TenantId; @@ -28,3 +30,82 @@ export interface RuleChain extends BaseData { configuration?: any; additionalInfo?: any; } + +export interface RuleChainMetaData { + ruleChainId: RuleChainId; + firstNodeIndex: number; + nodes: Array; + connections: Array; + ruleChainConnections: Array; +} + +export interface ResolvedRuleChainMetaData extends RuleChainMetaData { + targetRuleChainsMap: {[ruleChainId: string]: RuleChain}; +} + +export interface RuleChainImport { + ruleChain: RuleChain; + metadata: ResolvedRuleChainMetaData; +} + +export interface NodeConnectionInfo { + fromIndex: number; + toIndex: number; + type: string; +} + +export interface RuleChainConnectionInfo { + fromIndex: number; + targetRuleChainId: RuleChainId; + additionalInfo: any; + type: string; +} + +export const ruleNodeTypeComponentTypes: ComponentType[] = + [ + ComponentType.FILTER, + ComponentType.ENRICHMENT, + ComponentType.TRANSFORMATION, + ComponentType.ACTION, + ComponentType.EXTERNAL + ]; + +export const ruleChainNodeComponent: RuleNodeComponentDescriptor = { + type: RuleNodeType.RULE_CHAIN, + name: 'rule chain', + clazz: 'tb.internal.RuleChain', + configurationDescriptor: { + nodeDefinition: { + description: '', + details: 'Forwards incoming messages to specified Rule Chain', + inEnabled: true, + outEnabled: false, + relationTypes: [], + customRelations: false, + defaultConfiguration: {} + } + } +}; + +export const unknownNodeComponent: RuleNodeComponentDescriptor = { + type: RuleNodeType.UNKNOWN, + name: 'unknown', + clazz: 'tb.internal.Unknown', + configurationDescriptor: { + nodeDefinition: { + description: '', + details: '', + inEnabled: true, + outEnabled: true, + relationTypes: [], + customRelations: false, + defaultConfiguration: {} + } + } +}; + +export const inputNodeComponent: RuleNodeComponentDescriptor = { + type: RuleNodeType.INPUT, + name: 'Input', + clazz: 'tb.internal.Input' +}; 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 360c8099dc..fa75276e96 100644 --- a/ui-ngx/src/app/shared/models/rule-node.models.ts +++ b/ui-ngx/src/app/shared/models/rule-node.models.ts @@ -20,6 +20,8 @@ import {TenantId} from '@shared/models/id/tenant-id'; import {CustomerId} from '@shared/models/id/customer-id'; import {RuleChainId} from '@shared/models/id/rule-chain-id'; import {RuleNodeId} from '@shared/models/id/rule-node-id'; +import { ComponentDescriptor, ComponentType } from '@shared/models/component-descriptor.models'; +import { EntityType, EntityTypeResource } from '@shared/models/entity-type.models'; export enum MsgDataType { JSON = 'JSON', @@ -28,7 +30,7 @@ export enum MsgDataType { } export interface RuleNodeConfiguration { - todo: Array; + [key: string]: any; // TODO: } @@ -40,3 +42,130 @@ export interface RuleNode extends BaseData { configuration: RuleNodeConfiguration; additionalInfo?: any; } + +export interface RuleNodeConfigurationDescriptor { + nodeDefinition: { + description: string; + details: string; + inEnabled: boolean; + outEnabled: boolean; + relationTypes: string[]; + customRelations: boolean; + defaultConfiguration: any; + icon?: string; + iconUrl?: string; + uiResources?: string[]; + uiResourceLoadError?: string; + }; +} + +export enum RuleNodeType { + FILTER = 'FILTER', + ENRICHMENT = 'ENRICHMENT', + TRANSFORMATION = 'TRANSFORMATION', + ACTION = 'ACTION', + EXTERNAL = 'EXTERNAL', + RULE_CHAIN = 'RULE_CHAIN', + UNKNOWN = 'UNKNOWN', + INPUT = 'INPUT' +} + +export interface RuleNodeTypeDescriptor { + value: RuleNodeType; + name: string; + details: string; + nodeClass: string; + icon: string; + special?: boolean; +} + +export const ruleNodeTypeDescriptors = new Map( + [ + [ + RuleNodeType.FILTER, + { + value: RuleNodeType.FILTER, + name: 'rulenode.type-filter', + details: 'rulenode.type-filter-details', + nodeClass: 'tb-filter-type', + icon: 'filter_list' + } + ], + [ + RuleNodeType.ENRICHMENT, + { + value: RuleNodeType.ENRICHMENT, + name: 'rulenode.type-enrichment', + details: 'rulenode.type-enrichment-details', + nodeClass: 'tb-enrichment-type', + icon: 'playlist_add' + } + ], + [ + RuleNodeType.TRANSFORMATION, + { + value: RuleNodeType.TRANSFORMATION, + name: 'rulenode.type-transformation', + details: 'rulenode.type-transformation-details', + nodeClass: 'tb-transformation-type', + icon: 'transform' + } + ], + [ + RuleNodeType.ACTION, + { + value: RuleNodeType.ACTION, + name: 'rulenode.type-action', + details: 'rulenode.type-action-details', + nodeClass: 'tb-action-type', + icon: 'flash_on' + } + ], + [ + RuleNodeType.EXTERNAL, + { + value: RuleNodeType.EXTERNAL, + name: 'rulenode.type-external', + details: 'rulenode.type-external-details', + nodeClass: 'tb-external-type', + icon: 'cloud_upload' + } + ], + [ + RuleNodeType.RULE_CHAIN, + { + value: RuleNodeType.RULE_CHAIN, + name: 'rulenode.type-rule-chain', + details: 'rulenode.type-rule-chain-details', + nodeClass: 'tb-rule-chain-type', + icon: 'settings_ethernet' + } + ], + [ + RuleNodeType.INPUT, + { + value: RuleNodeType.INPUT, + name: 'rulenode.type-input', + details: 'rulenode.type-input-details', + nodeClass: 'tb-input-type', + icon: 'input', + special: true + } + ], + [ + RuleNodeType.UNKNOWN, + { + value: RuleNodeType.UNKNOWN, + name: 'rulenode.type-unknown', + details: 'rulenode.type-unknown-details', + nodeClass: 'tb-unknown-type', + icon: 'help_outline' + } + ] + ] +); + +export interface RuleNodeComponentDescriptor extends ComponentDescriptor { + type: RuleNodeType; + configurationDescriptor?: RuleNodeConfigurationDescriptor; +} From 74114de4d3d69a6109d211ba0f577517d48839b8 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 9 Dec 2019 20:14:50 +0200 Subject: [PATCH 055/133] Rulenode component --- msa/js-executor/package-lock.json | 23 ++- .../rulechain/rulechain-page.component.scss | 96 ++++++++++ .../rulechain/rulechain-page.component.ts | 2 +- .../home/pages/rulechain/rulechain.module.ts | 16 +- .../pages/rulechain/rulenode.component.html | 56 ++++++ .../pages/rulechain/rulenode.component.scss | 177 ++++++++++++++++++ .../pages/rulechain/rulenode.component.ts | 31 +++ 7 files changed, 392 insertions(+), 9 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/rulechain/rulenode.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/rulechain/rulenode.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/rulechain/rulenode.component.ts diff --git a/msa/js-executor/package-lock.json b/msa/js-executor/package-lock.json index c61ef011a2..bc9fdf512d 100644 --- a/msa/js-executor/package-lock.json +++ b/msa/js-executor/package-lock.json @@ -1407,12 +1407,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1427,17 +1429,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -1554,7 +1559,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -1566,6 +1572,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -1580,6 +1587,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -1691,7 +1699,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -1703,6 +1712,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -1824,6 +1834,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", 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 new file mode 100644 index 0000000000..2754b0ce17 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.scss @@ -0,0 +1,96 @@ +/** + * Copyright © 2016-2019 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 ::ng-deep { + .fc-canvas { + .fc-node { + border-radius: 8px; + &.fc-selected { + &:not(.fc-edit) { + margin: -3px; + border: solid 3px #f00; + } + } + } + + .fc-edit { + .fc-nodeedit, + .fc-nodedelete { + border: solid 2px #fff; + background: #f83e05; + outline: none; + } + } + + .fc-arrow-marker { + polygon { + fill: #808080; + stroke: #808080; + } + } + + .fc-arrow-marker-selected { + polygon { + fill: #f00; + stroke: #f00; + } + } + + .fc-edge { + outline: none; + stroke: #808080; + + &.fc-selected { + stroke: #f00; + } + + &.fc-hover { + stroke: #808080; + } + } + + .edge-endpoint { + fill: #808080; + } + + .fc-edge-label { + opacity: 1 !important; + &:focus { + outline: 0; + } + + &.fc-selected { + .fc-edge-label-text { + span { + color: #fff !important; + background-color: #f00 !important; + border: solid #f00 !important; + } + } + } + + .fc-edge-label-text { + font-size: 14px !important; + font-weight: 600 !important; + + span { + background-color: #fff !important; + color: #003a79 !important; + border: solid 2px #003a79 !important; + } + } + } + } +} 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 7f073551c0..0a3d7f2f24 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 @@ -39,7 +39,7 @@ import { RuleChainService } from '@core/http/rule-chain.service'; @Component({ selector: 'tb-rulechain-page', templateUrl: './rulechain-page.component.html', - styleUrls: [] + styleUrls: ['./rulechain-page.component.scss'] }) export class RuleChainPageComponent extends PageComponent implements OnInit, HasDirtyFlag { diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.module.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.module.ts index 3b87d7d13a..1b961517fa 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.module.ts +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.module.ts @@ -22,16 +22,28 @@ import {RuleChainRoutingModule} from '@modules/home/pages/rulechain/rulechain-ro import {HomeComponentsModule} from '@modules/home/components/home-components.module'; import { RuleChainTabsComponent } from '@home/pages/rulechain/rulechain-tabs.component'; import { RuleChainPageComponent } from './rulechain-page.component'; +import { RuleNodeComponent } from '@home/pages/rulechain/rulenode.component'; +import { FC_NODE_COMPONENT_CONFIG } from 'ngx-flowchart/dist/ngx-flowchart'; @NgModule({ entryComponents: [ RuleChainComponent, - RuleChainTabsComponent + RuleChainTabsComponent, + RuleNodeComponent ], declarations: [ RuleChainComponent, RuleChainTabsComponent, - RuleChainPageComponent + RuleChainPageComponent, + RuleNodeComponent + ], + providers: [ + { + provide: FC_NODE_COMPONENT_CONFIG, + useValue: { + nodeComponentType: RuleNodeComponent + } + } ], imports: [ CommonModule, diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulenode.component.html b/ui-ngx/src/app/modules/home/pages/rulechain/rulenode.component.html new file mode 100644 index 0000000000..e08b8e5cd2 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulenode.component.html @@ -0,0 +1,56 @@ + +
+
+
+ {{node.icon}} + +
+ {{ node.component.name }} + {{ node.name }} +
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ × +
+
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 new file mode 100644 index 0000000000..55587dd5db --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulenode.component.scss @@ -0,0 +1,177 @@ +/** + * Copyright © 2016-2019 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 { + + .fc-node-overlay { + position: absolute; + pointer-events: none; + left: 0; + top: 0; + right: 0; + bottom: 0; + background-color: #000; + border-radius: 5px; + opacity: 0; + } + + :host-context(.fc-hover) .fc-node-overlay { + opacity: 0.25; + transition: opacity .2s; + } + + :host-context(.fc-selected) .fc-node-overlay { + opacity: 0.25; + } + + :host-context(.fc-edit) { + .fc-nodeedit, + .fc-nodedelete { + border: solid 2px #fff; + background: #f83e05; + outline: none; + } + } + + .tb-rule-node { + display: flex; + flex-direction: row; + min-width: 150px; + max-width: 150px; + height: 32px; + min-height: 32px; + max-height: 32px; + padding: 5px 10px; + font-size: 12px; + line-height: 16px; + color: #333; + pointer-events: none; + background-color: #f15b26; + border: solid 1px #777; + border-radius: 5px; + + &.tb-filter-type { + background-color: #f1e861; + } + + &.tb-enrichment-type { + background-color: #cdf14e; + } + + &.tb-transformation-type { + background-color: #79cef1; + } + + &.tb-action-type { + background-color: #f1928f; + } + + &.tb-external-type { + background-color: #fbc766; + } + + &.tb-rule-chain-type { + background-color: #d6c4f1; + } + + &.tb-unknown-type { + background-color: #f16c29; + } + + &.tb-rule-node-highlighted:not(.tb-rule-node-invalid) { + box-shadow: 0 0 10px 6px #51cbee; + + .tb-node-title { + font-weight: 700; + text-decoration: underline; + } + } + + &.tb-rule-node-invalid { + box-shadow: 0 0 10px 6px #ff5c50; + } + + &.tb-input-type { + user-select: none; + background-color: #a3eaa9; + } + + mat-icon, img { + margin: auto; + width: 20px; + min-width: 20px; + height: 20px; + min-height: 20px; + padding-right: 4px; + font-size: 20px; + } + + .tb-node-title { + font-weight: 500; + } + + .tb-node-type, + .tb-node-title { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .fc-leftConnectors, + .fc-rightConnectors { + position: absolute; + top: 0; + + z-index: 0; + + display: flex; + flex-direction: column; + height: 100%; + + .fc-magnet { + align-items: center; + } + } + + .fc-leftConnectors { + left: -20px; + } + + .fc-rightConnectors { + right: -20px; + } + + .fc-magnet { + display: flex; + flex-grow: 1; + justify-content: center; + height: 60px; + } + + .fc-connector { + width: 14px; + height: 14px; + margin: 10px; + pointer-events: all; + background-color: #ccc; + border: 1px solid #333; + border-radius: 5px; + } + + .fc-connector.fc-hover { + background-color: #000; + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulenode.component.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rulenode.component.ts new file mode 100644 index 0000000000..eaa85f30ab --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulenode.component.ts @@ -0,0 +1,31 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { FcNodeComponent } from 'ngx-flowchart/dist/ngx-flowchart'; + +@Component({ + selector: 'rule-node', + templateUrl: './rulenode.component.html', + styleUrls: ['./rulenode.component.scss'] +}) +export class RuleNodeComponent extends FcNodeComponent { + + constructor() { + super(); + } + +} From e85c47aebf5373dbe5630b9c2fa24d81f90f832d Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 10 Dec 2019 11:04:14 +0200 Subject: [PATCH 056/133] RuleChain UI --- .../rulechain/rulechain-page.component.html | 76 ++++++++++++++-- .../rulechain/rulechain-page.component.scss | 87 +++++++++++++++++++ 2 files changed, 154 insertions(+), 9 deletions(-) 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 827115dc38..8c9cecaaa1 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 @@ -15,12 +15,70 @@ limitations under the License. --> - - +
+
+
+
+ +
+ + + +
+ + + + + + + +
+
+
+ +
+ + +
+
+
+
+
+
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 2754b0ce17..20f7bb1b3d 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 @@ -13,7 +13,94 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +:host { + width: 100%; + height: 100%; + .tb-rulechain { + width: 100%; + height: 100%; + section.tb-header-buttons.tb-library-open { + position: absolute; + top: 0; + left: 0; + z-index: 2; + pointer-events: none; + + .mat-button.tb-btn-open-library { + top: 0; + left: 0; + width: 36px; + height: 36px; + margin: 4px 0 0 4px; + line-height: 36px; + opacity: .5; + } + } + .tb-rulechain-library { + z-index: 1; + width: 250px; + min-width: 250px; + + .mat-toolbar { + height: 48px; + min-height: 48px; + padding: 0; + + .mat-toolbar-tools { + height: 48px; + padding: 0 6px; + font-size: 14px; + + .mat-button.mat-icon-button { + margin: 0; + + &.tb-small { + width: 32px; + height: 32px; + min-height: 32px; + padding: 6px; + line-height: 20px; + mat-icon { + width: 20px; + min-width: 20px; + height: 20px; + min-height: 20px; + font-size: 20px; + line-height: 20px; + } + } + } + } + } + } + .tb-rulechain-graph { + z-index: 0; + } + } +} + :host ::ng-deep { + .tb-rulechain { + section.tb-header-buttons.tb-library-open { + .mat-fab { + .mat-button-wrapper { + padding: 0; + } + } + } + .tb-rulechain-library { + .mat-toolbar { + .mat-toolbar-tools { + .mat-form-field { + .mat-form-field-infix { + width: auto; + } + } + } + } + } + } .fc-canvas { .fc-node { border-radius: 8px; From cf1684572b9b1e6c64a30848931d6a0d274062ed Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 12 Dec 2019 19:55:17 +0200 Subject: [PATCH 057/133] RuleChain page --- msa/js-executor/package-lock.json | 23 +- ui-ngx/angular.json | 4 + ui-ngx/package-lock.json | 3668 ++++++++++++++--- ui-ngx/package.json | 101 +- ui-ngx/src/app/core/services/utils.service.ts | 2 +- .../manage-widget-actions.component.models.ts | 6 +- .../widget/dynamic-widget.component.ts | 6 +- .../src/app/modules/home/home.component.html | 36 +- .../src/app/modules/home/home.component.scss | 5 + ui-ngx/src/app/modules/home/home.component.ts | 71 +- .../models/searchable-component.models.ts | 23 + .../rulechain/rulechain-page.component.html | 40 +- .../rulechain/rulechain-page.component.scss | 31 + .../rulechain/rulechain-page.component.ts | 230 +- .../pages/rulechain/rulechain-page.models.ts | 1 + .../rulechain/rulechain-page.tooltipster.scss | 70 + .../pages/rulechain/rulenode.component.html | 4 +- .../pages/rulechain/rulenode.component.ts | 16 +- .../src/app/shared/models/rule-node.models.ts | 9 + ui-ngx/src/tsconfig.app.json | 2 +- 20 files changed, 3637 insertions(+), 711 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/models/searchable-component.models.ts create mode 100644 ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.tooltipster.scss diff --git a/msa/js-executor/package-lock.json b/msa/js-executor/package-lock.json index bc9fdf512d..c61ef011a2 100644 --- a/msa/js-executor/package-lock.json +++ b/msa/js-executor/package-lock.json @@ -1407,14 +1407,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1429,20 +1427,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -1559,8 +1554,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -1572,7 +1566,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -1587,7 +1580,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -1699,8 +1691,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -1712,7 +1703,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -1834,7 +1824,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", diff --git a/ui-ngx/angular.json b/ui-ngx/angular.json index 7f6dccf2c0..a01dbb4b75 100644 --- a/ui-ngx/angular.json +++ b/ui-ngx/angular.json @@ -33,7 +33,10 @@ "styles": [ "src/styles.scss", "node_modules/jquery.terminal/css/jquery.terminal.min.css", + "node_modules/tooltipster/dist/css/tooltipster.bundle.min.css", + "node_modules/tooltipster/dist/css/plugins/tooltipster/sideTip/themes/tooltipster-sideTip-shadow.min.css", "src/app/shared/components/json-form/react/json-form.scss", + "src/app/modules/home/pages/rulechain/rulechain-page.tooltipster.scss", "node_modules/rc-select/assets/index.css" ], "stylePreprocessorOptions": { @@ -54,6 +57,7 @@ "node_modules/flot/src/plugins/jquery.flot.stack.js", "node_modules/flot.curvedlines/curvedLines.js", "node_modules/tinycolor2/dist/tinycolor-min.js", + "node_modules/tooltipster/dist/js/tooltipster.bundle.min.js", "node_modules/split.js/dist/split.js", "node_modules/js-beautify/js/lib/beautify.js", "node_modules/js-beautify/js/lib/beautify-css.js", diff --git a/ui-ngx/package-lock.json b/ui-ngx/package-lock.json index 3d0a10bde7..c435a8bec2 100644 --- a/ui-ngx/package-lock.json +++ b/ui-ngx/package-lock.json @@ -5,38 +5,23 @@ "requires": true, "dependencies": { "@angular-builders/custom-webpack": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@angular-builders/custom-webpack/-/custom-webpack-8.4.0.tgz", - "integrity": "sha512-I/U0zOwVAzBO1RRcF4zmX7enOruvfnHjXvxv5YQ4SkQaNtMo34xuX/8g+HQ4mMvY1/aH3sfBh8k8qzXqp4xD+A==", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/@angular-builders/custom-webpack/-/custom-webpack-8.4.1.tgz", + "integrity": "sha512-FbBt4mFbAxETdYLb6tTX869pIpm8nMiCpT34jROejuqLtsljymdqXhSCEWogWlel8ULAYus6BNdzZyRLyAkfqQ==", "dev": true, "requires": { "lodash": "^4.17.10", "ts-node": "^8.5.2", "webpack-merge": "^4.2.1" - }, - "dependencies": { - "ts-node": { - "version": "8.5.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.5.2.tgz", - "integrity": "sha512-W1DK/a6BGoV/D4x/SXXm6TSQx6q3blECUzd5TN+j56YEMX3yPVMpHsICLedUw3DvGF3aTQ8hfdR9AKMaHjIi+A==", - "dev": true, - "requires": { - "arg": "^4.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "source-map-support": "^0.5.6", - "yn": "^3.0.0" - } - } } }, "@angular-devkit/architect": { - "version": "0.802.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.802.2.tgz", - "integrity": "sha512-bMMo8BejHi3+n4xqewgcfat5+OYDmQQCLxWQ2W+qr7/u08vmTQTix3Q/wClp0nxgN0Zc9/1gSPaeudHLAlEizg==", + "version": "0.803.20", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.803.20.tgz", + "integrity": "sha512-NjyDJ61i9kh8J+qXt0E2j+P5Xsmi2mPasBzwcQyrZZGiho4zC0IFxcdxyzcsXFEupmilJKkjdt2g4QQRC5rUDQ==", "dev": true, "requires": { - "@angular-devkit/core": "8.2.2", + "@angular-devkit/core": "8.3.20", "rxjs": "6.4.0" }, "dependencies": { @@ -52,31 +37,36 @@ } }, "@angular-devkit/build-angular": { - "version": "0.802.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.802.2.tgz", - "integrity": "sha512-48WCSX5IpSbVe/cG9+KrcL6f93JwHicKfYLyrrGhywSENlBYVNLNfbJHz/AuaxjmsiCmiI9gLnRb/W5JoVxuMA==", - "dev": true, - "requires": { - "@angular-devkit/architect": "0.802.2", - "@angular-devkit/build-optimizer": "0.802.2", - "@angular-devkit/build-webpack": "0.802.2", - "@angular-devkit/core": "8.2.2", - "@ngtools/webpack": "8.2.2", + "version": "0.803.20", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.803.20.tgz", + "integrity": "sha512-JDZkZjOqPbOtCMsSKxQf9C+uSTZ7fQGlKGsCpJMzfa4iQ0WrmrhZvnRKQeEpMTTZTpuou/HQeQjyDV+Sx3yumw==", + "dev": true, + "requires": { + "@angular-devkit/architect": "0.803.20", + "@angular-devkit/build-optimizer": "0.803.20", + "@angular-devkit/build-webpack": "0.803.20", + "@angular-devkit/core": "8.3.20", + "@babel/core": "7.5.5", + "@babel/preset-env": "7.5.5", + "@ngtools/webpack": "8.3.20", "ajv": "6.10.2", "autoprefixer": "9.6.1", "browserslist": "4.6.6", - "caniuse-lite": "1.0.30000986", - "circular-dependency-plugin": "5.0.2", + "cacache": "12.0.2", + "caniuse-lite": "1.0.30000989", + "circular-dependency-plugin": "5.2.0", "clean-css": "4.2.1", "copy-webpack-plugin": "5.0.4", - "core-js": "3.1.4", - "file-loader": "4.1.0", + "core-js": "3.2.1", + "file-loader": "4.2.0", + "find-cache-dir": "3.0.0", "glob": "7.1.4", "istanbul-instrumenter-loader": "3.0.1", + "jest-worker": "24.9.0", "karma-source-map-support": "1.4.0", "less": "3.9.0", "less-loader": "5.0.0", - "license-webpack-plugin": "2.1.1", + "license-webpack-plugin": "2.1.2", "loader-utils": "1.2.3", "mini-css-extract-plugin": "0.8.0", "minimatch": "3.0.4", @@ -85,32 +75,58 @@ "postcss": "7.0.17", "postcss-import": "12.0.1", "postcss-loader": "3.0.0", - "raw-loader": "1.0.0", + "raw-loader": "3.1.0", + "regenerator-runtime": "0.13.3", "rxjs": "6.4.0", - "sass": "1.22.7", - "sass-loader": "7.1.0", + "sass": "1.22.9", + "sass-loader": "7.2.0", "semver": "6.3.0", + "source-map": "0.7.3", "source-map-loader": "0.2.4", - "source-map-support": "0.5.12", + "source-map-support": "0.5.13", "speed-measure-webpack-plugin": "1.3.1", - "style-loader": "0.23.1", + "style-loader": "1.0.0", "stylus": "0.54.5", "stylus-loader": "3.0.2", - "terser-webpack-plugin": "1.3.0", + "terser": "4.3.9", + "terser-webpack-plugin": "1.4.1", "tree-kill": "1.2.1", - "webpack": "4.38.0", - "webpack-dev-middleware": "3.7.0", - "webpack-dev-server": "3.7.2", + "webpack": "4.39.2", + "webpack-dev-middleware": "3.7.2", + "webpack-dev-server": "3.9.0", "webpack-merge": "4.2.1", - "webpack-sources": "1.3.0", + "webpack-sources": "1.4.3", "webpack-subresource-integrity": "1.1.0-rc.6", - "worker-plugin": "3.1.0" + "worker-plugin": "3.2.0" }, "dependencies": { + "@angular-devkit/architect": { + "version": "0.803.20", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.803.20.tgz", + "integrity": "sha512-NjyDJ61i9kh8J+qXt0E2j+P5Xsmi2mPasBzwcQyrZZGiho4zC0IFxcdxyzcsXFEupmilJKkjdt2g4QQRC5rUDQ==", + "dev": true, + "requires": { + "@angular-devkit/core": "8.3.20", + "rxjs": "6.4.0" + } + }, + "@angular-devkit/core": { + "version": "8.3.20", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-8.3.20.tgz", + "integrity": "sha512-UCfW/BJBJnioJU34QennQhA4o+rLoCXWiSrI2LM7yw8/MEM9I8KbqRETP1My3HjHkQnvP+Qh3noedpcu3Nnt8A==", + "dev": true, + "requires": { + "ajv": "6.10.2", + "fast-json-stable-stringify": "2.0.0", + "magic-string": "0.25.3", + "rxjs": "6.4.0", + "source-map": "0.7.3" + } + }, "core-js": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.1.4.tgz", - "integrity": "sha512-YNZN8lt82XIMLnLirj9MhKDFZHalwzzrL9YLt6eb0T5D0EDl4IQ90IGkua8mHbnxNrkj1d8hbdizMc0Qmg1WnQ==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.2.1.tgz", + "integrity": "sha512-Qa5XSVefSVPRxy2XfUC13WbvqkxhkwB3ve+pgCQveNgYzbM/UxZeu1dcOX/xr4UmfUd+muuvsaxilQzCyUurMw==", "dev": true }, "glob": { @@ -133,6 +149,28 @@ "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", "dev": true }, + "raw-loader": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-3.1.0.tgz", + "integrity": "sha512-lzUVMuJ06HF4rYveaz9Tv0WRlUMxJ0Y1hgSkkgg+50iEdaI0TthyEDe08KIHb0XsF6rn8WYTqPCaGTZg3sX+qA==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "schema-utils": "^2.0.1" + }, + "dependencies": { + "schema-utils": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.1.tgz", + "integrity": "sha512-0WXHDs1VDJyo+Zqs9TKLKyD/h7yDpHUhEFsM2CzkICFdoX1av+GBq/J2xRTFfsQO5kBfhZzANf2VcIm84jqDbg==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "ajv-keywords": "^3.4.1" + } + } + } + }, "rxjs": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", @@ -148,14 +186,64 @@ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true }, + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true + }, "source-map-support": { - "version": "0.5.12", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz", - "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==", + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", "dev": true, "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "terser-webpack-plugin": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.1.tgz", + "integrity": "sha512-ZXmmfiwtCLfz8WKZyYUuuHf3dMYEjg8NrjHMb0JqHVHVOSkzp3cW2/XG1fP3tRhqEqSzMwzzRQGtAPbs4Cncxg==", + "dev": true, + "requires": { + "cacache": "^12.0.2", + "find-cache-dir": "^2.1.0", + "is-wsl": "^1.1.0", + "schema-utils": "^1.0.0", + "serialize-javascript": "^1.7.0", + "source-map": "^0.6.1", + "terser": "^4.1.2", + "webpack-sources": "^1.4.0", + "worker-farm": "^1.7.0" + }, + "dependencies": { + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "webpack-merge": { @@ -166,42 +254,106 @@ "requires": { "lodash": "^4.17.5" } + }, + "webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "dev": true, + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } } } }, "@angular-devkit/build-optimizer": { - "version": "0.802.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.802.2.tgz", - "integrity": "sha512-0QkTxMgCr2YiysdRVY64smtogDnWz0eyqhmUJbd9kEq1xxDDfuvs+6OT1Lk6xU7tcucVf33DKB9jK/3n3LZIpw==", + "version": "0.803.20", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.803.20.tgz", + "integrity": "sha512-Vzxf1g1EuzaPBoScDYUhyxemi5chlgnpWmObNo5dzVAVzjxo5gJeDIGpiyDqHvr6LBkprqb6XHcZhMWqIcdIHg==", "dev": true, "requires": { "loader-utils": "1.2.3", - "source-map": "0.5.6", + "source-map": "0.7.3", "tslib": "1.10.0", "typescript": "3.5.3", - "webpack-sources": "1.3.0" + "webpack-sources": "1.4.3" }, "dependencies": { "source-map": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", - "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", "dev": true + }, + "typescript": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.3.tgz", + "integrity": "sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==", + "dev": true + }, + "webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "dev": true, + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } } } }, "@angular-devkit/build-webpack": { - "version": "0.802.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.802.2.tgz", - "integrity": "sha512-odsY7hkqUBsRgqTCcGXFuIBd6NJYSCduFHheoDpqwK0SIAlAZ6Q9pB6jv9J0FTwKUJBsVsHk+cXUuaeZhUQcIg==", + "version": "0.803.20", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.803.20.tgz", + "integrity": "sha512-35af8kD3KG/cIv7AB09YNER5HIPlx55ipBxdVk8D+X3MuUcTmD6fFvqXcV0EPlD1vQephthfzSgtNpvuPv4xuA==", "dev": true, "requires": { - "@angular-devkit/architect": "0.802.2", - "@angular-devkit/core": "8.2.2", - "rxjs": "6.4.0", - "webpack-merge": "4.2.1" + "@angular-devkit/architect": "0.803.20", + "@angular-devkit/core": "8.3.20", + "rxjs": "6.4.0" }, "dependencies": { + "@angular-devkit/architect": { + "version": "0.803.20", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.803.20.tgz", + "integrity": "sha512-NjyDJ61i9kh8J+qXt0E2j+P5Xsmi2mPasBzwcQyrZZGiho4zC0IFxcdxyzcsXFEupmilJKkjdt2g4QQRC5rUDQ==", + "dev": true, + "requires": { + "@angular-devkit/core": "8.3.20", + "rxjs": "6.4.0" + } + }, + "@angular-devkit/core": { + "version": "8.3.20", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-8.3.20.tgz", + "integrity": "sha512-UCfW/BJBJnioJU34QennQhA4o+rLoCXWiSrI2LM7yw8/MEM9I8KbqRETP1My3HjHkQnvP+Qh3noedpcu3Nnt8A==", + "dev": true, + "requires": { + "ajv": "6.10.2", + "fast-json-stable-stringify": "2.0.0", + "magic-string": "0.25.3", + "rxjs": "6.4.0", + "source-map": "0.7.3" + } + }, "rxjs": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", @@ -211,21 +363,18 @@ "tslib": "^1.9.0" } }, - "webpack-merge": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.1.tgz", - "integrity": "sha512-4p8WQyS98bUJcCvFMbdGZyZmsKuWjWVnVHnAS3FFg0HDaRVrPbkivx2RYCre8UiemD67RsiFFLfn4JhLAin8Vw==", - "dev": true, - "requires": { - "lodash": "^4.17.5" - } + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true } } }, "@angular-devkit/core": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-8.2.2.tgz", - "integrity": "sha512-qA1lK/OQhNptCxoEGbTryn6yeFS1F/e/EiUTwgU/j4DkBwPyYGE8iqWBd/cgI9AVqQaRSLLhVWXtDPxoNL0TKg==", + "version": "8.3.20", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-8.3.20.tgz", + "integrity": "sha512-UCfW/BJBJnioJU34QennQhA4o+rLoCXWiSrI2LM7yw8/MEM9I8KbqRETP1My3HjHkQnvP+Qh3noedpcu3Nnt8A==", "dev": true, "requires": { "ajv": "6.10.2", @@ -253,12 +402,12 @@ } }, "@angular-devkit/schematics": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-8.2.2.tgz", - "integrity": "sha512-wAbP+IriWgTSLR4prezuFlLbxMZMGXiN0FNH2i/v8MfxNXCBiEvD4YtD/8s8YRsZs+IW7sp3bErSD/EIlS4DyQ==", + "version": "8.3.20", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-8.3.20.tgz", + "integrity": "sha512-sDHZakh4e3A5WenR9zr1x6Va9GNRqQlRhqT3xcbkG88v2M0YqEt7dHB7YwnOhm7zSxiWQM8PdWEQHiQ4iu9NyQ==", "dev": true, "requires": { - "@angular-devkit/core": "8.2.2", + "@angular-devkit/core": "8.3.20", "rxjs": "6.4.0" }, "dependencies": { @@ -291,25 +440,27 @@ } }, "@angular/cli": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-8.2.2.tgz", - "integrity": "sha512-iQvNVbegNXvnuAo8Pal6hjwK8joGcaCTcIa3jh1GLZ9JT4fZk2p9D/8Kay8C0jLm2KytV3f4eSlPAuX5V6p/XQ==", + "version": "8.3.20", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-8.3.20.tgz", + "integrity": "sha512-bCo8zVFZ6iPc1EnHmVCmKvIcV7YkvalBKGNU7LtVHq6qZBI+ZmFtuyL5obKvFg1vJcminjKcY/UcMr9uGcAQrQ==", "dev": true, "requires": { - "@angular-devkit/architect": "0.802.2", - "@angular-devkit/core": "8.2.2", - "@angular-devkit/schematics": "8.2.2", - "@schematics/angular": "8.2.2", - "@schematics/update": "0.802.2", + "@angular-devkit/architect": "0.803.20", + "@angular-devkit/core": "8.3.20", + "@angular-devkit/schematics": "8.3.20", + "@schematics/angular": "8.3.20", + "@schematics/update": "0.803.20", "@yarnpkg/lockfile": "1.1.0", "ansi-colors": "4.1.1", "debug": "^4.1.1", "ini": "1.3.5", - "inquirer": "6.5.0", + "inquirer": "6.5.1", "npm-package-arg": "6.1.0", + "npm-pick-manifest": "3.0.2", "open": "6.4.0", - "pacote": "9.5.4", + "pacote": "9.5.5", "read-package-tree": "5.3.1", + "rimraf": "3.0.0", "semver": "6.3.0", "symbol-observable": "1.2.0", "universal-analytics": "^0.4.20", @@ -337,6 +488,15 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "rimraf": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.0.tgz", + "integrity": "sha512-NDGVxTsjqfunkds7CqsOiEnxln4Bo7Nddl3XhS4pXg5OzwkLqJ971ZVAAnB+DDLnF76N+VnDEiBHaVV8I06SUg==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -1171,121 +1331,1854 @@ "resolved": "https://registry.npmjs.org/@angular/material/-/material-8.2.3.tgz", "integrity": "sha512-SOczkIaqes+r+9XF/UUiokidfFKBpHkOPIaFK857sFD0FBNPvPEpOr5oHKCG3feERRwAFqHS7Wo2ohVEWypb5A==", "requires": { - "tslib": "^1.7.1" + "tslib": "^1.7.1" + } + }, + "@angular/platform-browser": { + "version": "8.2.14", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-8.2.14.tgz", + "integrity": "sha512-MtJptptyKzsE37JZ2VB/tI4cvMrdAH+cT9pMBYZd66YSZfKjIj5s+AZo7z8ncoskQSB1o3HMfDjSK7QXGx1mLQ==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/platform-browser-dynamic": { + "version": "8.2.14", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-8.2.14.tgz", + "integrity": "sha512-mO2JPR5kLU/A3AQngy9+R/Q5gaF9csMStBQjwsCRI0wNtlItOIGL6+wTYpiTuh/ux+WVN1F2sLcEYU4Zf1ud9A==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/router": { + "version": "8.2.14", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-8.2.14.tgz", + "integrity": "sha512-DHA2BhODqV7F0g6ZKgFaZgbsqzHHWRcfWchCOrOVKu2rYiKUTwwHVLBgZAhrpNeinq2pWanVYSIhMr7wy+LfEA==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@auth0/angular-jwt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@auth0/angular-jwt/-/angular-jwt-3.0.1.tgz", + "integrity": "sha512-hfWfgbpgtcvyU/agNxQ6cBk81mmASiNxQeZ6xn/3zJo8uLFHk2eQIy2yt2ztktcOQ6V2uc6GlKLRKjVIgyc1Sw==", + "requires": { + "url": "^0.11.0" + } + }, + "@babel/code-frame": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", + "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/core": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.5.5.tgz", + "integrity": "sha512-i4qoSr2KTtce0DmkuuQBV4AuQgGPUcPXMr9L5MyYAtk06z068lQ10a4O009fe5OB/DfNV+h+qqT7ddNV8UnRjg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.5.5", + "@babel/generator": "^7.5.5", + "@babel/helpers": "^7.5.5", + "@babel/parser": "^7.5.5", + "@babel/template": "^7.4.4", + "@babel/traverse": "^7.5.5", + "@babel/types": "^7.5.5", + "convert-source-map": "^1.1.0", + "debug": "^4.1.0", + "json5": "^2.1.0", + "lodash": "^4.17.13", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "json5": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.1.tgz", + "integrity": "sha512-l+3HXD0GEI3huGq1njuqtzYK8OYJyXMkOLtQ53pjWh89tvWS2h6l+1zMkYWqlb57+SiQodKZyvMEFb2X+KrFhQ==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.7.2.tgz", + "integrity": "sha512-WthSArvAjYLz4TcbKOi88me+KmDJdKSlfwwN8CnUYn9jBkzhq0ZEPuBfkAWIvjJ3AdEV1Cf/+eSQTnp3IDJKlQ==", + "dev": true, + "requires": { + "@babel/types": "^7.7.2", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.7.4.tgz", + "integrity": "sha512-2BQmQgECKzYKFPpiycoF9tlb5HA4lrVyAmLLVK177EcQAqjVLciUb2/R+n1boQ9y5ENV3uz2ZqiNw7QMBBw1Og==", + "dev": true, + "requires": { + "@babel/types": "^7.7.4" + }, + "dependencies": { + "@babel/types": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", + "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.7.4.tgz", + "integrity": "sha512-Biq/d/WtvfftWZ9Uf39hbPBYDUo986m5Bb4zhkeYDGUllF43D+nUe5M6Vuo6/8JDK/0YX/uBdeoQpyaNhNugZQ==", + "dev": true, + "requires": { + "@babel/helper-explode-assignable-expression": "^7.7.4", + "@babel/types": "^7.7.4" + }, + "dependencies": { + "@babel/types": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", + "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/helper-call-delegate": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.7.4.tgz", + "integrity": "sha512-8JH9/B7J7tCYJ2PpWVpw9JhPuEVHztagNVuQAFBVFYluRMlpG7F1CgKEgGeL6KFqcsIa92ZYVj6DSc0XwmN1ZA==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.7.4", + "@babel/traverse": "^7.7.4", + "@babel/types": "^7.7.4" + }, + "dependencies": { + "@babel/generator": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.7.4.tgz", + "integrity": "sha512-m5qo2WgdOJeyYngKImbkyQrnUN1mPceaG5BV+G0E3gWsa4l/jCSryWJdM2x8OuGAOyh+3d5pVYfZWCiNFtynxg==", + "dev": true, + "requires": { + "@babel/types": "^7.7.4", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.7.4.tgz", + "integrity": "sha512-AnkGIdiBhEuiwdoMnKm7jfPfqItZhgRaZfMg1XX3bS25INOnLPjPG1Ppnajh8eqgt5kPJnfqrRHqFqmjKDZLzQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.7.4", + "@babel/template": "^7.7.4", + "@babel/types": "^7.7.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.4.tgz", + "integrity": "sha512-QTGKEdCkjgzgfJ3bAyRwF4yyT3pg+vDgan8DSivq1eS0gwi+KGKE5x8kRcbeFTb/673mkO5SN1IZfmCfA5o+EA==", + "dev": true, + "requires": { + "@babel/types": "^7.7.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.4.tgz", + "integrity": "sha512-guAg1SXFcVr04Guk9eq0S4/rWS++sbmyqosJzVs8+1fH5NI+ZcmkaSkc7dmtAFbHFva6yRJnjW3yAcGxjueDug==", + "dev": true, + "requires": { + "@babel/types": "^7.7.4" + } + }, + "@babel/parser": { + "version": "7.7.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.5.tgz", + "integrity": "sha512-KNlOe9+/nk4i29g0VXgl8PEXIRms5xKLJeuZ6UptN0fHv+jDiriG+y94X6qAgWTR0h3KaoM1wK5G5h7MHFRSig==", + "dev": true + }, + "@babel/template": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.4.tgz", + "integrity": "sha512-qUzihgVPguAzXCK7WXw8pqs6cEwi54s3E+HrejlkuWO6ivMKx9hZl3Y2fSXp9i5HgyWmj7RKP+ulaYnKM4yYxw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.7.4", + "@babel/types": "^7.7.4" + } + }, + "@babel/traverse": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.7.4.tgz", + "integrity": "sha512-P1L58hQyupn8+ezVA2z5KBm4/Zr4lCC8dwKCMYzsa5jFMDMQAzaBNy9W5VjB+KAmBjb40U7a/H6ao+Xo+9saIw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.5.5", + "@babel/generator": "^7.7.4", + "@babel/helper-function-name": "^7.7.4", + "@babel/helper-split-export-declaration": "^7.7.4", + "@babel/parser": "^7.7.4", + "@babel/types": "^7.7.4", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + } + }, + "@babel/types": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", + "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/helper-create-regexp-features-plugin": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.7.4.tgz", + "integrity": "sha512-Mt+jBKaxL0zfOIWrfQpnfYCN7/rS6GKx6CCCfuoqVVd+17R8zNDlzVYmIi9qyb2wOk002NsmSTDymkIygDUH7A==", + "dev": true, + "requires": { + "@babel/helper-regex": "^7.4.4", + "regexpu-core": "^4.6.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + }, + "regexpu-core": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.6.0.tgz", + "integrity": "sha512-YlVaefl8P5BnFYOITTNzDvan1ulLOiXJzCNZxduTIosN17b87h3bvG9yHMoHaRuo88H4mQ06Aodj5VtYGGGiTg==", + "dev": true, + "requires": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^8.1.0", + "regjsgen": "^0.5.0", + "regjsparser": "^0.6.0", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.1.0" + } + }, + "regjsgen": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.1.tgz", + "integrity": "sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg==", + "dev": true + }, + "regjsparser": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.0.tgz", + "integrity": "sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ==", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + } + } + } + }, + "@babel/helper-define-map": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.7.4.tgz", + "integrity": "sha512-v5LorqOa0nVQUvAUTUF3KPastvUt/HzByXNamKQ6RdJRTV7j8rLL+WB5C/MzzWAwOomxDhYFb1wLLxHqox86lg==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.7.4", + "@babel/types": "^7.7.4", + "lodash": "^4.17.13" + }, + "dependencies": { + "@babel/helper-function-name": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.7.4.tgz", + "integrity": "sha512-AnkGIdiBhEuiwdoMnKm7jfPfqItZhgRaZfMg1XX3bS25INOnLPjPG1Ppnajh8eqgt5kPJnfqrRHqFqmjKDZLzQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.7.4", + "@babel/template": "^7.7.4", + "@babel/types": "^7.7.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.4.tgz", + "integrity": "sha512-QTGKEdCkjgzgfJ3bAyRwF4yyT3pg+vDgan8DSivq1eS0gwi+KGKE5x8kRcbeFTb/673mkO5SN1IZfmCfA5o+EA==", + "dev": true, + "requires": { + "@babel/types": "^7.7.4" + } + }, + "@babel/parser": { + "version": "7.7.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.5.tgz", + "integrity": "sha512-KNlOe9+/nk4i29g0VXgl8PEXIRms5xKLJeuZ6UptN0fHv+jDiriG+y94X6qAgWTR0h3KaoM1wK5G5h7MHFRSig==", + "dev": true + }, + "@babel/template": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.4.tgz", + "integrity": "sha512-qUzihgVPguAzXCK7WXw8pqs6cEwi54s3E+HrejlkuWO6ivMKx9hZl3Y2fSXp9i5HgyWmj7RKP+ulaYnKM4yYxw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.7.4", + "@babel/types": "^7.7.4" + } + }, + "@babel/types": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", + "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/helper-explode-assignable-expression": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.7.4.tgz", + "integrity": "sha512-2/SicuFrNSXsZNBxe5UGdLr+HZg+raWBLE9vC98bdYOKX/U6PY0mdGlYUJdtTDPSU0Lw0PNbKKDpwYHJLn2jLg==", + "dev": true, + "requires": { + "@babel/traverse": "^7.7.4", + "@babel/types": "^7.7.4" + }, + "dependencies": { + "@babel/generator": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.7.4.tgz", + "integrity": "sha512-m5qo2WgdOJeyYngKImbkyQrnUN1mPceaG5BV+G0E3gWsa4l/jCSryWJdM2x8OuGAOyh+3d5pVYfZWCiNFtynxg==", + "dev": true, + "requires": { + "@babel/types": "^7.7.4", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.7.4.tgz", + "integrity": "sha512-AnkGIdiBhEuiwdoMnKm7jfPfqItZhgRaZfMg1XX3bS25INOnLPjPG1Ppnajh8eqgt5kPJnfqrRHqFqmjKDZLzQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.7.4", + "@babel/template": "^7.7.4", + "@babel/types": "^7.7.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.4.tgz", + "integrity": "sha512-QTGKEdCkjgzgfJ3bAyRwF4yyT3pg+vDgan8DSivq1eS0gwi+KGKE5x8kRcbeFTb/673mkO5SN1IZfmCfA5o+EA==", + "dev": true, + "requires": { + "@babel/types": "^7.7.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.4.tgz", + "integrity": "sha512-guAg1SXFcVr04Guk9eq0S4/rWS++sbmyqosJzVs8+1fH5NI+ZcmkaSkc7dmtAFbHFva6yRJnjW3yAcGxjueDug==", + "dev": true, + "requires": { + "@babel/types": "^7.7.4" + } + }, + "@babel/parser": { + "version": "7.7.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.5.tgz", + "integrity": "sha512-KNlOe9+/nk4i29g0VXgl8PEXIRms5xKLJeuZ6UptN0fHv+jDiriG+y94X6qAgWTR0h3KaoM1wK5G5h7MHFRSig==", + "dev": true + }, + "@babel/template": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.4.tgz", + "integrity": "sha512-qUzihgVPguAzXCK7WXw8pqs6cEwi54s3E+HrejlkuWO6ivMKx9hZl3Y2fSXp9i5HgyWmj7RKP+ulaYnKM4yYxw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.7.4", + "@babel/types": "^7.7.4" + } + }, + "@babel/traverse": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.7.4.tgz", + "integrity": "sha512-P1L58hQyupn8+ezVA2z5KBm4/Zr4lCC8dwKCMYzsa5jFMDMQAzaBNy9W5VjB+KAmBjb40U7a/H6ao+Xo+9saIw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.5.5", + "@babel/generator": "^7.7.4", + "@babel/helper-function-name": "^7.7.4", + "@babel/helper-split-export-declaration": "^7.7.4", + "@babel/parser": "^7.7.4", + "@babel/types": "^7.7.4", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + } + }, + "@babel/types": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", + "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/helper-function-name": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.7.0.tgz", + "integrity": "sha512-tDsJgMUAP00Ugv8O2aGEua5I2apkaQO7lBGUq1ocwN3G23JE5Dcq0uh3GvFTChPa4b40AWiAsLvCZOA2rdnQ7Q==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.7.0", + "@babel/template": "^7.7.0", + "@babel/types": "^7.7.0" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.0.tgz", + "integrity": "sha512-tLdojOTz4vWcEnHWHCuPN5P85JLZWbm5Fx5ZsMEMPhF3Uoe3O7awrbM2nQ04bDOUToH/2tH/ezKEOR8zEYzqyw==", + "dev": true, + "requires": { + "@babel/types": "^7.7.0" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.7.4.tgz", + "integrity": "sha512-wQC4xyvc1Jo/FnLirL6CEgPgPCa8M74tOdjWpRhQYapz5JC7u3NYU1zCVoVAGCE3EaIP9T1A3iW0WLJ+reZlpQ==", + "dev": true, + "requires": { + "@babel/types": "^7.7.4" + }, + "dependencies": { + "@babel/types": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", + "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.7.4.tgz", + "integrity": "sha512-9KcA1X2E3OjXl/ykfMMInBK+uVdfIVakVe7W7Lg3wfXUNyS3Q1HWLFRwZIjhqiCGbslummPDnmb7vIekS0C1vw==", + "dev": true, + "requires": { + "@babel/types": "^7.7.4" + }, + "dependencies": { + "@babel/types": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", + "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/helper-module-imports": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.7.4.tgz", + "integrity": "sha512-dGcrX6K9l8258WFjyDLJwuVKxR4XZfU0/vTUgOQYWEnRD8mgr+p4d6fCUMq/ys0h4CCt/S5JhbvtyErjWouAUQ==", + "dev": true, + "requires": { + "@babel/types": "^7.7.4" + }, + "dependencies": { + "@babel/types": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", + "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/helper-module-transforms": { + "version": "7.7.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.7.5.tgz", + "integrity": "sha512-A7pSxyJf1gN5qXVcidwLWydjftUN878VkalhXX5iQDuGyiGK3sOrrKKHF4/A4fwHtnsotv/NipwAeLzY4KQPvw==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.7.4", + "@babel/helper-simple-access": "^7.7.4", + "@babel/helper-split-export-declaration": "^7.7.4", + "@babel/template": "^7.7.4", + "@babel/types": "^7.7.4", + "lodash": "^4.17.13" + }, + "dependencies": { + "@babel/helper-split-export-declaration": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.4.tgz", + "integrity": "sha512-guAg1SXFcVr04Guk9eq0S4/rWS++sbmyqosJzVs8+1fH5NI+ZcmkaSkc7dmtAFbHFva6yRJnjW3yAcGxjueDug==", + "dev": true, + "requires": { + "@babel/types": "^7.7.4" + } + }, + "@babel/parser": { + "version": "7.7.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.5.tgz", + "integrity": "sha512-KNlOe9+/nk4i29g0VXgl8PEXIRms5xKLJeuZ6UptN0fHv+jDiriG+y94X6qAgWTR0h3KaoM1wK5G5h7MHFRSig==", + "dev": true + }, + "@babel/template": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.4.tgz", + "integrity": "sha512-qUzihgVPguAzXCK7WXw8pqs6cEwi54s3E+HrejlkuWO6ivMKx9hZl3Y2fSXp9i5HgyWmj7RKP+ulaYnKM4yYxw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.7.4", + "@babel/types": "^7.7.4" + } + }, + "@babel/types": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", + "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.7.4.tgz", + "integrity": "sha512-VB7gWZ2fDkSuqW6b1AKXkJWO5NyNI3bFL/kK79/30moK57blr6NbH8xcl2XcKCwOmJosftWunZqfO84IGq3ZZg==", + "dev": true, + "requires": { + "@babel/types": "^7.7.4" + }, + "dependencies": { + "@babel/types": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", + "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/helper-plugin-utils": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz", + "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==", + "dev": true + }, + "@babel/helper-regex": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.5.5.tgz", + "integrity": "sha512-CkCYQLkfkiugbRDO8eZn6lRuR8kzZoGXCg3149iTk5se7g6qykSpy3+hELSwquhu+TgHn8nkLiBwHvNX8Hofcw==", + "dev": true, + "requires": { + "lodash": "^4.17.13" + } + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.7.4.tgz", + "integrity": "sha512-Sk4xmtVdM9sA/jCI80f+KS+Md+ZHIpjuqmYPk1M7F/upHou5e4ReYmExAiu6PVe65BhJPZA2CY9x9k4BqE5klw==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.7.4", + "@babel/helper-wrap-function": "^7.7.4", + "@babel/template": "^7.7.4", + "@babel/traverse": "^7.7.4", + "@babel/types": "^7.7.4" + }, + "dependencies": { + "@babel/generator": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.7.4.tgz", + "integrity": "sha512-m5qo2WgdOJeyYngKImbkyQrnUN1mPceaG5BV+G0E3gWsa4l/jCSryWJdM2x8OuGAOyh+3d5pVYfZWCiNFtynxg==", + "dev": true, + "requires": { + "@babel/types": "^7.7.4", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.7.4.tgz", + "integrity": "sha512-AnkGIdiBhEuiwdoMnKm7jfPfqItZhgRaZfMg1XX3bS25INOnLPjPG1Ppnajh8eqgt5kPJnfqrRHqFqmjKDZLzQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.7.4", + "@babel/template": "^7.7.4", + "@babel/types": "^7.7.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.4.tgz", + "integrity": "sha512-QTGKEdCkjgzgfJ3bAyRwF4yyT3pg+vDgan8DSivq1eS0gwi+KGKE5x8kRcbeFTb/673mkO5SN1IZfmCfA5o+EA==", + "dev": true, + "requires": { + "@babel/types": "^7.7.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.4.tgz", + "integrity": "sha512-guAg1SXFcVr04Guk9eq0S4/rWS++sbmyqosJzVs8+1fH5NI+ZcmkaSkc7dmtAFbHFva6yRJnjW3yAcGxjueDug==", + "dev": true, + "requires": { + "@babel/types": "^7.7.4" + } + }, + "@babel/parser": { + "version": "7.7.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.5.tgz", + "integrity": "sha512-KNlOe9+/nk4i29g0VXgl8PEXIRms5xKLJeuZ6UptN0fHv+jDiriG+y94X6qAgWTR0h3KaoM1wK5G5h7MHFRSig==", + "dev": true + }, + "@babel/template": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.4.tgz", + "integrity": "sha512-qUzihgVPguAzXCK7WXw8pqs6cEwi54s3E+HrejlkuWO6ivMKx9hZl3Y2fSXp9i5HgyWmj7RKP+ulaYnKM4yYxw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.7.4", + "@babel/types": "^7.7.4" + } + }, + "@babel/traverse": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.7.4.tgz", + "integrity": "sha512-P1L58hQyupn8+ezVA2z5KBm4/Zr4lCC8dwKCMYzsa5jFMDMQAzaBNy9W5VjB+KAmBjb40U7a/H6ao+Xo+9saIw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.5.5", + "@babel/generator": "^7.7.4", + "@babel/helper-function-name": "^7.7.4", + "@babel/helper-split-export-declaration": "^7.7.4", + "@babel/parser": "^7.7.4", + "@babel/types": "^7.7.4", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + } + }, + "@babel/types": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", + "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/helper-replace-supers": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.7.4.tgz", + "integrity": "sha512-pP0tfgg9hsZWo5ZboYGuBn/bbYT/hdLPVSS4NMmiRJdwWhP0IznPwN9AE1JwyGsjSPLC364I0Qh5p+EPkGPNpg==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.7.4", + "@babel/helper-optimise-call-expression": "^7.7.4", + "@babel/traverse": "^7.7.4", + "@babel/types": "^7.7.4" + }, + "dependencies": { + "@babel/generator": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.7.4.tgz", + "integrity": "sha512-m5qo2WgdOJeyYngKImbkyQrnUN1mPceaG5BV+G0E3gWsa4l/jCSryWJdM2x8OuGAOyh+3d5pVYfZWCiNFtynxg==", + "dev": true, + "requires": { + "@babel/types": "^7.7.4", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.7.4.tgz", + "integrity": "sha512-AnkGIdiBhEuiwdoMnKm7jfPfqItZhgRaZfMg1XX3bS25INOnLPjPG1Ppnajh8eqgt5kPJnfqrRHqFqmjKDZLzQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.7.4", + "@babel/template": "^7.7.4", + "@babel/types": "^7.7.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.4.tgz", + "integrity": "sha512-QTGKEdCkjgzgfJ3bAyRwF4yyT3pg+vDgan8DSivq1eS0gwi+KGKE5x8kRcbeFTb/673mkO5SN1IZfmCfA5o+EA==", + "dev": true, + "requires": { + "@babel/types": "^7.7.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.4.tgz", + "integrity": "sha512-guAg1SXFcVr04Guk9eq0S4/rWS++sbmyqosJzVs8+1fH5NI+ZcmkaSkc7dmtAFbHFva6yRJnjW3yAcGxjueDug==", + "dev": true, + "requires": { + "@babel/types": "^7.7.4" + } + }, + "@babel/parser": { + "version": "7.7.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.5.tgz", + "integrity": "sha512-KNlOe9+/nk4i29g0VXgl8PEXIRms5xKLJeuZ6UptN0fHv+jDiriG+y94X6qAgWTR0h3KaoM1wK5G5h7MHFRSig==", + "dev": true + }, + "@babel/template": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.4.tgz", + "integrity": "sha512-qUzihgVPguAzXCK7WXw8pqs6cEwi54s3E+HrejlkuWO6ivMKx9hZl3Y2fSXp9i5HgyWmj7RKP+ulaYnKM4yYxw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.7.4", + "@babel/types": "^7.7.4" + } + }, + "@babel/traverse": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.7.4.tgz", + "integrity": "sha512-P1L58hQyupn8+ezVA2z5KBm4/Zr4lCC8dwKCMYzsa5jFMDMQAzaBNy9W5VjB+KAmBjb40U7a/H6ao+Xo+9saIw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.5.5", + "@babel/generator": "^7.7.4", + "@babel/helper-function-name": "^7.7.4", + "@babel/helper-split-export-declaration": "^7.7.4", + "@babel/parser": "^7.7.4", + "@babel/types": "^7.7.4", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + } + }, + "@babel/types": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", + "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/helper-simple-access": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.7.4.tgz", + "integrity": "sha512-zK7THeEXfan7UlWsG2A6CI/L9jVnI5+xxKZOdej39Y0YtDYKx9raHk5F2EtK9K8DHRTihYwg20ADt9S36GR78A==", + "dev": true, + "requires": { + "@babel/template": "^7.7.4", + "@babel/types": "^7.7.4" + }, + "dependencies": { + "@babel/parser": { + "version": "7.7.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.5.tgz", + "integrity": "sha512-KNlOe9+/nk4i29g0VXgl8PEXIRms5xKLJeuZ6UptN0fHv+jDiriG+y94X6qAgWTR0h3KaoM1wK5G5h7MHFRSig==", + "dev": true + }, + "@babel/template": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.4.tgz", + "integrity": "sha512-qUzihgVPguAzXCK7WXw8pqs6cEwi54s3E+HrejlkuWO6ivMKx9hZl3Y2fSXp9i5HgyWmj7RKP+ulaYnKM4yYxw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.7.4", + "@babel/types": "^7.7.4" + } + }, + "@babel/types": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", + "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.0.tgz", + "integrity": "sha512-HgYSI8rH08neWlAH3CcdkFg9qX9YsZysZI5GD8LjhQib/mM0jGOZOVkoUiiV2Hu978fRtjtsGsW6w0pKHUWtqA==", + "dev": true, + "requires": { + "@babel/types": "^7.7.0" + } + }, + "@babel/helper-wrap-function": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.7.4.tgz", + "integrity": "sha512-VsfzZt6wmsocOaVU0OokwrIytHND55yvyT4BPB9AIIgwr8+x7617hetdJTsuGwygN5RC6mxA9EJztTjuwm2ofg==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.7.4", + "@babel/template": "^7.7.4", + "@babel/traverse": "^7.7.4", + "@babel/types": "^7.7.4" + }, + "dependencies": { + "@babel/generator": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.7.4.tgz", + "integrity": "sha512-m5qo2WgdOJeyYngKImbkyQrnUN1mPceaG5BV+G0E3gWsa4l/jCSryWJdM2x8OuGAOyh+3d5pVYfZWCiNFtynxg==", + "dev": true, + "requires": { + "@babel/types": "^7.7.4", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.7.4.tgz", + "integrity": "sha512-AnkGIdiBhEuiwdoMnKm7jfPfqItZhgRaZfMg1XX3bS25INOnLPjPG1Ppnajh8eqgt5kPJnfqrRHqFqmjKDZLzQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.7.4", + "@babel/template": "^7.7.4", + "@babel/types": "^7.7.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.4.tgz", + "integrity": "sha512-QTGKEdCkjgzgfJ3bAyRwF4yyT3pg+vDgan8DSivq1eS0gwi+KGKE5x8kRcbeFTb/673mkO5SN1IZfmCfA5o+EA==", + "dev": true, + "requires": { + "@babel/types": "^7.7.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.4.tgz", + "integrity": "sha512-guAg1SXFcVr04Guk9eq0S4/rWS++sbmyqosJzVs8+1fH5NI+ZcmkaSkc7dmtAFbHFva6yRJnjW3yAcGxjueDug==", + "dev": true, + "requires": { + "@babel/types": "^7.7.4" + } + }, + "@babel/parser": { + "version": "7.7.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.5.tgz", + "integrity": "sha512-KNlOe9+/nk4i29g0VXgl8PEXIRms5xKLJeuZ6UptN0fHv+jDiriG+y94X6qAgWTR0h3KaoM1wK5G5h7MHFRSig==", + "dev": true + }, + "@babel/template": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.4.tgz", + "integrity": "sha512-qUzihgVPguAzXCK7WXw8pqs6cEwi54s3E+HrejlkuWO6ivMKx9hZl3Y2fSXp9i5HgyWmj7RKP+ulaYnKM4yYxw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.7.4", + "@babel/types": "^7.7.4" + } + }, + "@babel/traverse": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.7.4.tgz", + "integrity": "sha512-P1L58hQyupn8+ezVA2z5KBm4/Zr4lCC8dwKCMYzsa5jFMDMQAzaBNy9W5VjB+KAmBjb40U7a/H6ao+Xo+9saIw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.5.5", + "@babel/generator": "^7.7.4", + "@babel/helper-function-name": "^7.7.4", + "@babel/helper-split-export-declaration": "^7.7.4", + "@babel/parser": "^7.7.4", + "@babel/types": "^7.7.4", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + } + }, + "@babel/types": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", + "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/helpers": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.7.4.tgz", + "integrity": "sha512-ak5NGZGJ6LV85Q1Zc9gn2n+ayXOizryhjSUBTdu5ih1tlVCJeuQENzc4ItyCVhINVXvIT/ZQ4mheGIsfBkpskg==", + "dev": true, + "requires": { + "@babel/template": "^7.7.4", + "@babel/traverse": "^7.7.4", + "@babel/types": "^7.7.4" + }, + "dependencies": { + "@babel/generator": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.7.4.tgz", + "integrity": "sha512-m5qo2WgdOJeyYngKImbkyQrnUN1mPceaG5BV+G0E3gWsa4l/jCSryWJdM2x8OuGAOyh+3d5pVYfZWCiNFtynxg==", + "dev": true, + "requires": { + "@babel/types": "^7.7.4", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.7.4.tgz", + "integrity": "sha512-AnkGIdiBhEuiwdoMnKm7jfPfqItZhgRaZfMg1XX3bS25INOnLPjPG1Ppnajh8eqgt5kPJnfqrRHqFqmjKDZLzQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.7.4", + "@babel/template": "^7.7.4", + "@babel/types": "^7.7.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.4.tgz", + "integrity": "sha512-QTGKEdCkjgzgfJ3bAyRwF4yyT3pg+vDgan8DSivq1eS0gwi+KGKE5x8kRcbeFTb/673mkO5SN1IZfmCfA5o+EA==", + "dev": true, + "requires": { + "@babel/types": "^7.7.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.4.tgz", + "integrity": "sha512-guAg1SXFcVr04Guk9eq0S4/rWS++sbmyqosJzVs8+1fH5NI+ZcmkaSkc7dmtAFbHFva6yRJnjW3yAcGxjueDug==", + "dev": true, + "requires": { + "@babel/types": "^7.7.4" + } + }, + "@babel/parser": { + "version": "7.7.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.5.tgz", + "integrity": "sha512-KNlOe9+/nk4i29g0VXgl8PEXIRms5xKLJeuZ6UptN0fHv+jDiriG+y94X6qAgWTR0h3KaoM1wK5G5h7MHFRSig==", + "dev": true + }, + "@babel/template": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.4.tgz", + "integrity": "sha512-qUzihgVPguAzXCK7WXw8pqs6cEwi54s3E+HrejlkuWO6ivMKx9hZl3Y2fSXp9i5HgyWmj7RKP+ulaYnKM4yYxw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.7.4", + "@babel/types": "^7.7.4" + } + }, + "@babel/traverse": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.7.4.tgz", + "integrity": "sha512-P1L58hQyupn8+ezVA2z5KBm4/Zr4lCC8dwKCMYzsa5jFMDMQAzaBNy9W5VjB+KAmBjb40U7a/H6ao+Xo+9saIw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.5.5", + "@babel/generator": "^7.7.4", + "@babel/helper-function-name": "^7.7.4", + "@babel/helper-split-export-declaration": "^7.7.4", + "@babel/parser": "^7.7.4", + "@babel/types": "^7.7.4", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + } + }, + "@babel/types": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", + "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/highlight": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz", + "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.3.tgz", + "integrity": "sha512-bqv+iCo9i+uLVbI0ILzKkvMorqxouI+GbV13ivcARXn9NNEabi2IEz912IgNpT/60BNXac5dgcfjb94NjsF33A==", + "dev": true + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.7.4.tgz", + "integrity": "sha512-1ypyZvGRXriY/QP668+s8sFr2mqinhkRDMPSQLNghCQE+GAkFtp+wkHVvg2+Hdki8gwP+NFzJBJ/N1BfzCCDEw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-remap-async-to-generator": "^7.7.4", + "@babel/plugin-syntax-async-generators": "^7.7.4" + } + }, + "@babel/plugin-proposal-dynamic-import": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.7.4.tgz", + "integrity": "sha512-StH+nGAdO6qDB1l8sZ5UBV8AC3F2VW2I8Vfld73TMKyptMU9DY5YsJAS8U81+vEtxcH3Y/La0wG0btDrhpnhjQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-dynamic-import": "^7.7.4" + } + }, + "@babel/plugin-proposal-json-strings": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.7.4.tgz", + "integrity": "sha512-wQvt3akcBTfLU/wYoqm/ws7YOAQKu8EVJEvHip/mzkNtjaclQoCCIqKXFP5/eyfnfbQCDV3OLRIK3mIVyXuZlw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-json-strings": "^7.7.4" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.7.4.tgz", + "integrity": "sha512-rnpnZR3/iWKmiQyJ3LKJpSwLDcX/nSXhdLk4Aq/tXOApIvyu7qoabrige0ylsAJffaUC51WiBu209Q0U+86OWQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-object-rest-spread": "^7.7.4" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.7.4.tgz", + "integrity": "sha512-DyM7U2bnsQerCQ+sejcTNZh8KQEUuC3ufzdnVnSiUv/qoGJp2Z3hanKL18KDhsBT5Wj6a7CMT5mdyCNJsEaA9w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.7.4" + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.7.4.tgz", + "integrity": "sha512-cHgqHgYvffluZk85dJ02vloErm3Y6xtH+2noOBOJ2kXOJH3aVCDnj5eR/lVNlTnYu4hndAPJD3rTFjW3qee0PA==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.7.4", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.7.4.tgz", + "integrity": "sha512-Li4+EjSpBgxcsmeEF8IFcfV/+yJGxHXDirDkEoyFjumuwbmfCVHUt0HuowD/iGM7OhIRyXJH9YXxqiH6N815+g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.7.4.tgz", + "integrity": "sha512-jHQW0vbRGvwQNgyVxwDh4yuXu4bH1f5/EICJLAhl1SblLs2CDhrsmCk+v5XLdE9wxtAFRyxx+P//Iw+a5L/tTg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.7.4.tgz", + "integrity": "sha512-QpGupahTQW1mHRXddMG5srgpHWqRLwJnJZKXTigB9RPFCCGbDGCgBeM/iC82ICXp414WeYx/tD54w7M2qRqTMg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.7.4.tgz", + "integrity": "sha512-mObR+r+KZq0XhRVS2BrBKBpr5jqrqzlPvS9C9vuOf5ilSwzloAl7RPWLrgKdWS6IreaVrjHxTjtyqFiOisaCwg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.7.4.tgz", + "integrity": "sha512-4ZSuzWgFxqHRE31Glu+fEr/MirNZOMYmD/0BhBWyLyOOQz/gTAl7QmWm2hX1QxEIXsr2vkdlwxIzTyiYRC4xcQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.7.4.tgz", + "integrity": "sha512-zUXy3e8jBNPiffmqkHRNDdZM2r8DWhCB7HhcoyZjiK1TxYEluLHAvQuYnTT+ARqRpabWqy/NHkO6e3MsYB5YfA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.7.4.tgz", + "integrity": "sha512-zpUTZphp5nHokuy8yLlyafxCJ0rSlFoSHypTUWgpdwoDXWQcseaect7cJ8Ppk6nunOM6+5rPMkod4OYKPR5MUg==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.7.4", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-remap-async-to-generator": "^7.7.4" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.7.4.tgz", + "integrity": "sha512-kqtQzwtKcpPclHYjLK//3lH8OFsCDuDJBaFhVwf8kqdnF6MN4l618UDlcA7TfRs3FayrHj+svYnSX8MC9zmUyQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.7.4.tgz", + "integrity": "sha512-2VBe9u0G+fDt9B5OV5DQH4KBf5DoiNkwFKOz0TCvBWvdAN2rOykCTkrL+jTLxfCAm76l9Qo5OqL7HBOx2dWggg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "lodash": "^4.17.13" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.7.4.tgz", + "integrity": "sha512-sK1mjWat7K+buWRuImEzjNf68qrKcrddtpQo3swi9j7dUcG6y6R6+Di039QN2bD1dykeswlagupEmpOatFHHUg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.7.4", + "@babel/helper-define-map": "^7.7.4", + "@babel/helper-function-name": "^7.7.4", + "@babel/helper-optimise-call-expression": "^7.7.4", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.7.4", + "@babel/helper-split-export-declaration": "^7.7.4", + "globals": "^11.1.0" + }, + "dependencies": { + "@babel/helper-function-name": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.7.4.tgz", + "integrity": "sha512-AnkGIdiBhEuiwdoMnKm7jfPfqItZhgRaZfMg1XX3bS25INOnLPjPG1Ppnajh8eqgt5kPJnfqrRHqFqmjKDZLzQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.7.4", + "@babel/template": "^7.7.4", + "@babel/types": "^7.7.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.4.tgz", + "integrity": "sha512-QTGKEdCkjgzgfJ3bAyRwF4yyT3pg+vDgan8DSivq1eS0gwi+KGKE5x8kRcbeFTb/673mkO5SN1IZfmCfA5o+EA==", + "dev": true, + "requires": { + "@babel/types": "^7.7.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.4.tgz", + "integrity": "sha512-guAg1SXFcVr04Guk9eq0S4/rWS++sbmyqosJzVs8+1fH5NI+ZcmkaSkc7dmtAFbHFva6yRJnjW3yAcGxjueDug==", + "dev": true, + "requires": { + "@babel/types": "^7.7.4" + } + }, + "@babel/parser": { + "version": "7.7.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.5.tgz", + "integrity": "sha512-KNlOe9+/nk4i29g0VXgl8PEXIRms5xKLJeuZ6UptN0fHv+jDiriG+y94X6qAgWTR0h3KaoM1wK5G5h7MHFRSig==", + "dev": true + }, + "@babel/template": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.4.tgz", + "integrity": "sha512-qUzihgVPguAzXCK7WXw8pqs6cEwi54s3E+HrejlkuWO6ivMKx9hZl3Y2fSXp9i5HgyWmj7RKP+ulaYnKM4yYxw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.7.4", + "@babel/types": "^7.7.4" + } + }, + "@babel/types": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", + "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.7.4.tgz", + "integrity": "sha512-bSNsOsZnlpLLyQew35rl4Fma3yKWqK3ImWMSC/Nc+6nGjC9s5NFWAer1YQ899/6s9HxO2zQC1WoFNfkOqRkqRQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.7.4.tgz", + "integrity": "sha512-4jFMXI1Cu2aXbcXXl8Lr6YubCn6Oc7k9lLsu8v61TZh+1jny2BWmdtvY9zSUlLdGUvcy9DMAWyZEOqjsbeg/wA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.7.4.tgz", + "integrity": "sha512-mk0cH1zyMa/XHeb6LOTXTbG7uIJ8Rrjlzu91pUx/KS3JpcgaTDwMS8kM+ar8SLOvlL2Lofi4CGBAjCo3a2x+lw==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.7.4", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.7.4.tgz", + "integrity": "sha512-g1y4/G6xGWMD85Tlft5XedGaZBCIVN+/P0bs6eabmcPP9egFleMAo65OOjlhcz1njpwagyY3t0nsQC9oTFegJA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.7.4.tgz", + "integrity": "sha512-MCqiLfCKm6KEA1dglf6Uqq1ElDIZwFuzz1WH5mTf8k2uQSxEJMbOIEh7IZv7uichr7PMfi5YVSrr1vz+ipp7AQ==", + "dev": true, + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.7.4", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.7.4.tgz", + "integrity": "sha512-zZ1fD1B8keYtEcKF+M1TROfeHTKnijcVQm0yO/Yu1f7qoDoxEIc/+GX6Go430Bg84eM/xwPFp0+h4EbZg7epAA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.7.4.tgz", + "integrity": "sha512-E/x09TvjHNhsULs2IusN+aJNRV5zKwxu1cpirZyRPw+FyyIKEHPXTsadj48bVpc1R5Qq1B5ZkzumuFLytnbT6g==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.7.4", + "@babel/helper-plugin-utils": "^7.0.0" + }, + "dependencies": { + "@babel/helper-function-name": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.7.4.tgz", + "integrity": "sha512-AnkGIdiBhEuiwdoMnKm7jfPfqItZhgRaZfMg1XX3bS25INOnLPjPG1Ppnajh8eqgt5kPJnfqrRHqFqmjKDZLzQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.7.4", + "@babel/template": "^7.7.4", + "@babel/types": "^7.7.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.4.tgz", + "integrity": "sha512-QTGKEdCkjgzgfJ3bAyRwF4yyT3pg+vDgan8DSivq1eS0gwi+KGKE5x8kRcbeFTb/673mkO5SN1IZfmCfA5o+EA==", + "dev": true, + "requires": { + "@babel/types": "^7.7.4" + } + }, + "@babel/parser": { + "version": "7.7.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.5.tgz", + "integrity": "sha512-KNlOe9+/nk4i29g0VXgl8PEXIRms5xKLJeuZ6UptN0fHv+jDiriG+y94X6qAgWTR0h3KaoM1wK5G5h7MHFRSig==", + "dev": true + }, + "@babel/template": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.4.tgz", + "integrity": "sha512-qUzihgVPguAzXCK7WXw8pqs6cEwi54s3E+HrejlkuWO6ivMKx9hZl3Y2fSXp9i5HgyWmj7RKP+ulaYnKM4yYxw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.7.4", + "@babel/types": "^7.7.4" + } + }, + "@babel/types": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", + "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/plugin-transform-literals": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.7.4.tgz", + "integrity": "sha512-X2MSV7LfJFm4aZfxd0yLVFrEXAgPqYoDG53Br/tCKiKYfX0MjVjQeWPIhPHHsCqzwQANq+FLN786fF5rgLS+gw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.7.4.tgz", + "integrity": "sha512-9VMwMO7i69LHTesL0RdGy93JU6a+qOPuvB4F4d0kR0zyVjJRVJRaoaGjhtki6SzQUu8yen/vxPKN6CWnCUw6bA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.7.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.7.5.tgz", + "integrity": "sha512-CT57FG4A2ZUNU1v+HdvDSDrjNWBrtCmSH6YbbgN3Lrf0Di/q/lWRxZrE72p3+HCCz9UjfZOEBdphgC0nzOS6DQ==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.7.5", + "@babel/helper-plugin-utils": "^7.0.0", + "babel-plugin-dynamic-import-node": "^2.3.0" } }, - "@angular/platform-browser": { - "version": "8.2.14", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-8.2.14.tgz", - "integrity": "sha512-MtJptptyKzsE37JZ2VB/tI4cvMrdAH+cT9pMBYZd66YSZfKjIj5s+AZo7z8ncoskQSB1o3HMfDjSK7QXGx1mLQ==", + "@babel/plugin-transform-modules-commonjs": { + "version": "7.7.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.7.5.tgz", + "integrity": "sha512-9Cq4zTFExwFhQI6MT1aFxgqhIsMWQWDVwOgLzl7PTWJHsNaqFvklAU+Oz6AQLAS0dJKTwZSOCo20INwktxpi3Q==", + "dev": true, "requires": { - "tslib": "^1.9.0" + "@babel/helper-module-transforms": "^7.7.5", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-simple-access": "^7.7.4", + "babel-plugin-dynamic-import-node": "^2.3.0" } }, - "@angular/platform-browser-dynamic": { - "version": "8.2.14", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-8.2.14.tgz", - "integrity": "sha512-mO2JPR5kLU/A3AQngy9+R/Q5gaF9csMStBQjwsCRI0wNtlItOIGL6+wTYpiTuh/ux+WVN1F2sLcEYU4Zf1ud9A==", + "@babel/plugin-transform-modules-systemjs": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.7.4.tgz", + "integrity": "sha512-y2c96hmcsUi6LrMqvmNDPBBiGCiQu0aYqpHatVVu6kD4mFEXKjyNxd/drc18XXAf9dv7UXjrZwBVmTTGaGP8iw==", + "dev": true, "requires": { - "tslib": "^1.9.0" + "@babel/helper-hoist-variables": "^7.7.4", + "@babel/helper-plugin-utils": "^7.0.0", + "babel-plugin-dynamic-import-node": "^2.3.0" } }, - "@angular/router": { - "version": "8.2.14", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-8.2.14.tgz", - "integrity": "sha512-DHA2BhODqV7F0g6ZKgFaZgbsqzHHWRcfWchCOrOVKu2rYiKUTwwHVLBgZAhrpNeinq2pWanVYSIhMr7wy+LfEA==", + "@babel/plugin-transform-modules-umd": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.7.4.tgz", + "integrity": "sha512-u2B8TIi0qZI4j8q4C51ktfO7E3cQ0qnaXFI1/OXITordD40tt17g/sXqgNNCcMTcBFKrUPcGDx+TBJuZxLx7tw==", + "dev": true, "requires": { - "tslib": "^1.9.0" + "@babel/helper-module-transforms": "^7.7.4", + "@babel/helper-plugin-utils": "^7.0.0" } }, - "@auth0/angular-jwt": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@auth0/angular-jwt/-/angular-jwt-3.0.1.tgz", - "integrity": "sha512-hfWfgbpgtcvyU/agNxQ6cBk81mmASiNxQeZ6xn/3zJo8uLFHk2eQIy2yt2ztktcOQ6V2uc6GlKLRKjVIgyc1Sw==", + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.7.4.tgz", + "integrity": "sha512-jBUkiqLKvUWpv9GLSuHUFYdmHg0ujC1JEYoZUfeOOfNydZXp1sXObgyPatpcwjWgsdBGsagWW0cdJpX/DO2jMw==", + "dev": true, "requires": { - "url": "^0.11.0" + "@babel/helper-create-regexp-features-plugin": "^7.7.4" } }, - "@babel/code-frame": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", - "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", + "@babel/plugin-transform-new-target": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.7.4.tgz", + "integrity": "sha512-CnPRiNtOG1vRodnsyGX37bHQleHE14B9dnnlgSeEs3ek3fHN1A1SScglTCg1sfbe7sRQ2BUcpgpTpWSfMKz3gg==", "dev": true, "requires": { - "@babel/highlight": "^7.0.0" + "@babel/helper-plugin-utils": "^7.0.0" } }, - "@babel/generator": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.7.2.tgz", - "integrity": "sha512-WthSArvAjYLz4TcbKOi88me+KmDJdKSlfwwN8CnUYn9jBkzhq0ZEPuBfkAWIvjJ3AdEV1Cf/+eSQTnp3IDJKlQ==", + "@babel/plugin-transform-object-super": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.7.4.tgz", + "integrity": "sha512-ho+dAEhC2aRnff2JCA0SAK7V2R62zJd/7dmtoe7MHcso4C2mS+vZjn1Pb1pCVZvJs1mgsvv5+7sT+m3Bysb6eg==", "dev": true, "requires": { - "@babel/types": "^7.7.2", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0" + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.7.4" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.7.4.tgz", + "integrity": "sha512-VJwhVePWPa0DqE9vcfptaJSzNDKrWU/4FbYCjZERtmqEs05g3UMXnYMZoXja7JAJ7Y7sPZipwm/pGApZt7wHlw==", + "dev": true, + "requires": { + "@babel/helper-call-delegate": "^7.7.4", + "@babel/helper-get-function-arity": "^7.7.4", + "@babel/helper-plugin-utils": "^7.0.0" }, "dependencies": { - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true + "@babel/helper-get-function-arity": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.4.tgz", + "integrity": "sha512-QTGKEdCkjgzgfJ3bAyRwF4yyT3pg+vDgan8DSivq1eS0gwi+KGKE5x8kRcbeFTb/673mkO5SN1IZfmCfA5o+EA==", + "dev": true, + "requires": { + "@babel/types": "^7.7.4" + } }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true + "@babel/types": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", + "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } } } }, - "@babel/helper-function-name": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.7.0.tgz", - "integrity": "sha512-tDsJgMUAP00Ugv8O2aGEua5I2apkaQO7lBGUq1ocwN3G23JE5Dcq0uh3GvFTChPa4b40AWiAsLvCZOA2rdnQ7Q==", + "@babel/plugin-transform-property-literals": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.7.4.tgz", + "integrity": "sha512-MatJhlC4iHsIskWYyawl53KuHrt+kALSADLQQ/HkhTjX954fkxIEh4q5slL4oRAnsm/eDoZ4q0CIZpcqBuxhJQ==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.7.0", - "@babel/template": "^7.7.0", - "@babel/types": "^7.7.0" + "@babel/helper-plugin-utils": "^7.0.0" } }, - "@babel/helper-get-function-arity": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.0.tgz", - "integrity": "sha512-tLdojOTz4vWcEnHWHCuPN5P85JLZWbm5Fx5ZsMEMPhF3Uoe3O7awrbM2nQ04bDOUToH/2tH/ezKEOR8zEYzqyw==", + "@babel/plugin-transform-regenerator": { + "version": "7.7.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.7.5.tgz", + "integrity": "sha512-/8I8tPvX2FkuEyWbjRCt4qTAgZK0DVy8QRguhA524UH48RfGJy94On2ri+dCuwOpcerPRl9O4ebQkRcVzIaGBw==", "dev": true, "requires": { - "@babel/types": "^7.7.0" + "regenerator-transform": "^0.14.0" } }, - "@babel/helper-split-export-declaration": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.0.tgz", - "integrity": "sha512-HgYSI8rH08neWlAH3CcdkFg9qX9YsZysZI5GD8LjhQib/mM0jGOZOVkoUiiV2Hu978fRtjtsGsW6w0pKHUWtqA==", + "@babel/plugin-transform-reserved-words": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.7.4.tgz", + "integrity": "sha512-OrPiUB5s5XvkCO1lS7D8ZtHcswIC57j62acAnJZKqGGnHP+TIc/ljQSrgdX/QyOTdEK5COAhuc820Hi1q2UgLQ==", "dev": true, "requires": { - "@babel/types": "^7.7.0" + "@babel/helper-plugin-utils": "^7.0.0" } }, - "@babel/highlight": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz", - "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==", + "@babel/plugin-transform-shorthand-properties": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.7.4.tgz", + "integrity": "sha512-q+suddWRfIcnyG5YiDP58sT65AJDZSUhXQDZE3r04AuqD6d/XLaQPPXSBzP2zGerkgBivqtQm9XKGLuHqBID6Q==", "dev": true, "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" + "@babel/helper-plugin-utils": "^7.0.0" } }, - "@babel/parser": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.3.tgz", - "integrity": "sha512-bqv+iCo9i+uLVbI0ILzKkvMorqxouI+GbV13ivcARXn9NNEabi2IEz912IgNpT/60BNXac5dgcfjb94NjsF33A==", - "dev": true + "@babel/plugin-transform-spread": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.7.4.tgz", + "integrity": "sha512-8OSs0FLe5/80cndziPlg4R0K6HcWSM0zyNhHhLsmw/Nc5MaA49cAsnoJ/t/YZf8qkG7fD+UjTRaApVDB526d7Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.7.4.tgz", + "integrity": "sha512-Ls2NASyL6qtVe1H1hXts9yuEeONV2TJZmplLONkMPUG158CtmnrzW5Q5teibM5UVOFjG0D3IC5mzXR6pPpUY7A==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.0.0" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.7.4.tgz", + "integrity": "sha512-sA+KxLwF3QwGj5abMHkHgshp9+rRz+oY9uoRil4CyLtgEuE/88dpkeWgNk5qKVsJE9iSfly3nvHapdRiIS2wnQ==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.7.4", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.7.4.tgz", + "integrity": "sha512-KQPUQ/7mqe2m0B8VecdyaW5XcQYaePyl9R7IsKd+irzj6jvbhoGnRE+M0aNkyAzI07VfUQ9266L5xMARitV3wg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.7.4.tgz", + "integrity": "sha512-N77UUIV+WCvE+5yHw+oks3m18/umd7y392Zv7mYTpFqHtkpcc+QUz+gLJNTWVlWROIWeLqY0f3OjZxV5TcXnRw==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.7.4", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/preset-env": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.5.5.tgz", + "integrity": "sha512-GMZQka/+INwsMz1A5UEql8tG015h5j/qjptpKY2gJ7giy8ohzU710YciJB5rcKsWGWHiW3RUnHib0E5/m3Tp3A==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-async-generator-functions": "^7.2.0", + "@babel/plugin-proposal-dynamic-import": "^7.5.0", + "@babel/plugin-proposal-json-strings": "^7.2.0", + "@babel/plugin-proposal-object-rest-spread": "^7.5.5", + "@babel/plugin-proposal-optional-catch-binding": "^7.2.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-syntax-async-generators": "^7.2.0", + "@babel/plugin-syntax-dynamic-import": "^7.2.0", + "@babel/plugin-syntax-json-strings": "^7.2.0", + "@babel/plugin-syntax-object-rest-spread": "^7.2.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.2.0", + "@babel/plugin-transform-arrow-functions": "^7.2.0", + "@babel/plugin-transform-async-to-generator": "^7.5.0", + "@babel/plugin-transform-block-scoped-functions": "^7.2.0", + "@babel/plugin-transform-block-scoping": "^7.5.5", + "@babel/plugin-transform-classes": "^7.5.5", + "@babel/plugin-transform-computed-properties": "^7.2.0", + "@babel/plugin-transform-destructuring": "^7.5.0", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/plugin-transform-duplicate-keys": "^7.5.0", + "@babel/plugin-transform-exponentiation-operator": "^7.2.0", + "@babel/plugin-transform-for-of": "^7.4.4", + "@babel/plugin-transform-function-name": "^7.4.4", + "@babel/plugin-transform-literals": "^7.2.0", + "@babel/plugin-transform-member-expression-literals": "^7.2.0", + "@babel/plugin-transform-modules-amd": "^7.5.0", + "@babel/plugin-transform-modules-commonjs": "^7.5.0", + "@babel/plugin-transform-modules-systemjs": "^7.5.0", + "@babel/plugin-transform-modules-umd": "^7.2.0", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.4.5", + "@babel/plugin-transform-new-target": "^7.4.4", + "@babel/plugin-transform-object-super": "^7.5.5", + "@babel/plugin-transform-parameters": "^7.4.4", + "@babel/plugin-transform-property-literals": "^7.2.0", + "@babel/plugin-transform-regenerator": "^7.4.5", + "@babel/plugin-transform-reserved-words": "^7.2.0", + "@babel/plugin-transform-shorthand-properties": "^7.2.0", + "@babel/plugin-transform-spread": "^7.2.0", + "@babel/plugin-transform-sticky-regex": "^7.2.0", + "@babel/plugin-transform-template-literals": "^7.4.4", + "@babel/plugin-transform-typeof-symbol": "^7.2.0", + "@babel/plugin-transform-unicode-regex": "^7.4.4", + "@babel/types": "^7.5.5", + "browserslist": "^4.6.0", + "core-js-compat": "^3.1.1", + "invariant": "^2.2.2", + "js-levenshtein": "^1.1.3", + "semver": "^5.5.0" + } }, "@babel/runtime": { "version": "7.7.2", @@ -1295,6 +3188,16 @@ "regenerator-runtime": "^0.13.2" } }, + "@babel/runtime-corejs3": { + "version": "7.7.6", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.7.6.tgz", + "integrity": "sha512-NrRUehqG0sMSCaP+0XV/vOvvjNl4BQOWq3Qys1Q2KTEm5tGMo9h0dHnIzeKerj0a7SIB8LP5kYg/T1raE3FoKQ==", + "dev": true, + "requires": { + "core-js-pure": "^3.0.0", + "regenerator-runtime": "^0.13.2" + } + }, "@babel/template": { "version": "7.7.0", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.0.tgz", @@ -1366,16 +3269,16 @@ } }, "@date-io/core": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/@date-io/core/-/core-1.3.11.tgz", - "integrity": "sha512-Yxf2ei0vjU38Fizswr/Uwub5QeRiLOHiTRiHUuTdg+biVB+1EUk+h5szas9SEWA2pZDlSo73F5TPuu+zKqOIBQ==" + "version": "1.3.13", + "resolved": "https://registry.npmjs.org/@date-io/core/-/core-1.3.13.tgz", + "integrity": "sha512-AlEKV7TxjeK+jxWVKcCFrfYAk8spX9aCyiToFIiLPtfQbsjmRGLIhb5VZgptQcJdHtLXo7+m0DuurwFgUToQuA==" }, "@date-io/date-fns": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/@date-io/date-fns/-/date-fns-1.3.11.tgz", - "integrity": "sha512-6Pvk4gwCU4L19XYzDUrro861JCQjZkJQjugxAA+M8wsDTW75A5rmSZGa6g2rQQXfg6ox4B7HBx9p6JYDsSPX0g==", + "version": "1.3.13", + "resolved": "https://registry.npmjs.org/@date-io/date-fns/-/date-fns-1.3.13.tgz", + "integrity": "sha512-yXxGzcRUPcogiMj58wVgFjc9qUYrCnnU9eLcyNbsQCmae4jPuZCDoIBR21j8ZURsM7GRtU62VOw5yNd4dDHunA==", "requires": { - "@date-io/core": "^1.3.11" + "@date-io/core": "^1.3.13" } }, "@emotion/hash": { @@ -1406,15 +3309,15 @@ } }, "@material-ui/core": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.6.1.tgz", - "integrity": "sha512-TljDMCJmi1zh7JhAFTp8qjIlbkVACiNftrcitzJJ+hAqpuP9PTO4euEkkAuYjISfUFZl3Z4kaOrBwN1HDrhIOQ==", + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.7.2.tgz", + "integrity": "sha512-ZbeO6xshTEHcMU2jMNjBY26u9p5ILQFj0y7HvOPZ9WT6POaN6qNKYX2PdXnnRDE1MpN8W2K1cxM4KKkiYWNkCQ==", "requires": { "@babel/runtime": "^7.4.4", - "@material-ui/styles": "^4.6.0", - "@material-ui/system": "^4.5.2", + "@material-ui/styles": "^4.7.1", + "@material-ui/system": "^4.7.1", "@material-ui/types": "^4.1.1", - "@material-ui/utils": "^4.5.2", + "@material-ui/utils": "^4.7.1", "@types/react-transition-group": "^4.2.0", "clsx": "^1.0.2", "convert-css-length": "^2.0.1", @@ -1422,6 +3325,7 @@ "normalize-scroll-left": "^0.2.0", "popper.js": "^1.14.1", "prop-types": "^15.7.2", + "react-is": "^16.8.0", "react-transition-group": "^4.3.0" } }, @@ -1446,14 +3350,14 @@ } }, "@material-ui/styles": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.6.0.tgz", - "integrity": "sha512-lqqh4UEMdIYcU1Yth4pQyMTah02uAkg3NOT3MirN9FUexdL8pNA6zCHigEgDSfwmvnXyxHhxTkphfy0DRfnt9w==", + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.7.1.tgz", + "integrity": "sha512-BBfxVThaPrglqHmKtSdrZJxnbFGJqKdZ5ZvDarj3HsmkteGCXsP1ohrDi5TWoa5JEJFo9S6q6NywqsENZn9rZA==", "requires": { "@babel/runtime": "^7.4.4", "@emotion/hash": "^0.7.1", "@material-ui/types": "^4.1.1", - "@material-ui/utils": "^4.5.2", + "@material-ui/utils": "^4.7.1", "clsx": "^1.0.2", "csstype": "^2.5.2", "hoist-non-react-statics": "^3.2.1", @@ -1469,12 +3373,12 @@ } }, "@material-ui/system": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.5.2.tgz", - "integrity": "sha512-h9RWvdM9XKlHHqwiuhyvWdobptQkHli+m2jJFs7i1AI/hmGsIc4reDmS7fInhETgt/Txx7uiAIznfRNIIVHmQw==", + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.7.1.tgz", + "integrity": "sha512-zH02p+FOimXLSKOW/OT2laYkl9bB3dD1AvnZqsHYoseUaq0aVrpbl2BGjQi+vJ5lg8w73uYlt9zOWzb3+1UdMQ==", "requires": { "@babel/runtime": "^7.4.4", - "@material-ui/utils": "^4.5.2", + "@material-ui/utils": "^4.7.1", "prop-types": "^15.7.2" } }, @@ -1487,13 +3391,13 @@ } }, "@material-ui/utils": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.5.2.tgz", - "integrity": "sha512-zhbNfHd1gLa8At6RPDG7uMZubHxbY+LtM6IkSfeWi6Lo4Ax80l62YaN1QmUpO1IvGCkn/j62tQX3yObiQZrJsQ==", + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.7.1.tgz", + "integrity": "sha512-+ux0SlLdlehvzCk2zdQ3KiS3/ylWvuo/JwAGhvb8dFVvwR21K28z0PU9OQW2PGogrMEdvX3miEI5tGxTwwWiwQ==", "requires": { "@babel/runtime": "^7.4.4", "prop-types": "^15.7.2", - "react-is": "^16.8.6" + "react-is": "^16.8.0" } }, "@ngrx/effects": { @@ -1512,18 +3416,31 @@ "integrity": "sha512-3GrAAX3/J39u0AcREgWBiUwuNkZhgei+2K6/bulkAu/BHw+PJaZqq5+c+uQFvi0/aq+/8+9wjNhhCWS4Entk/Q==" }, "@ngtools/webpack": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-8.2.2.tgz", - "integrity": "sha512-ksPFlZbH0+Rj+0qTGmkbtU3GHLjQKF4nN047AZn8Q4QnPynKqItHskSlyVi0CMnKfJxOr2VTxlSkiKN+pUb0sA==", + "version": "8.3.20", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-8.3.20.tgz", + "integrity": "sha512-2e9Kat6PQEzqtNsZZpnOIvoDzyGwMELiuBYBa9keZeaXOD6TxjSyCRzHHXAldAXqvh4Uj2qjTid54Sy14CxtsQ==", "dev": true, "requires": { - "@angular-devkit/core": "8.2.2", + "@angular-devkit/core": "8.3.20", "enhanced-resolve": "4.1.0", "rxjs": "6.4.0", "tree-kill": "1.2.1", - "webpack-sources": "1.3.0" + "webpack-sources": "1.4.3" }, "dependencies": { + "@angular-devkit/core": { + "version": "8.3.20", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-8.3.20.tgz", + "integrity": "sha512-UCfW/BJBJnioJU34QennQhA4o+rLoCXWiSrI2LM7yw8/MEM9I8KbqRETP1My3HjHkQnvP+Qh3noedpcu3Nnt8A==", + "dev": true, + "requires": { + "ajv": "6.10.2", + "fast-json-stable-stringify": "2.0.0", + "magic-string": "0.25.3", + "rxjs": "6.4.0", + "source-map": "0.7.3" + } + }, "rxjs": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", @@ -1532,6 +3449,30 @@ "requires": { "tslib": "^1.9.0" } + }, + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true + }, + "webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "dev": true, + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } } } }, @@ -1560,26 +3501,26 @@ } }, "@schematics/angular": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-8.2.2.tgz", - "integrity": "sha512-0kZoGXwYRDLREwMYT+m0MyGenpPidLEulrWxgYWoLhsJAFKax7lTy2YYljtFTd+AlZYyB3PTpDsDip8uT743tA==", + "version": "8.3.20", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-8.3.20.tgz", + "integrity": "sha512-Y20pSJhQ0KQd8Tk2kPQlmpRDNDaoIKMeOOGLT2FgCFrumxZXuIbBgN9fGDgW40iI2sq80bccOeo24RKkn3QpcA==", "dev": true, "requires": { - "@angular-devkit/core": "8.2.2", - "@angular-devkit/schematics": "8.2.2" + "@angular-devkit/core": "8.3.20", + "@angular-devkit/schematics": "8.3.20" } }, "@schematics/update": { - "version": "0.802.2", - "resolved": "https://registry.npmjs.org/@schematics/update/-/update-0.802.2.tgz", - "integrity": "sha512-ohwdxf0+uQ0aCTk27evs1l04rJ1nB3S95ihDr3rSQOl0WWizdto6TbXURtQ4PubORehjqvhrqqKGVp+QL2npGw==", + "version": "0.803.20", + "resolved": "https://registry.npmjs.org/@schematics/update/-/update-0.803.20.tgz", + "integrity": "sha512-MseLreuHdnSLUEnRxZFVSHKKK+3mGXH12SgOSeirwATIL22Df74+Q5BYvsge/Kd2k6s9ak/NCuRXG7FAo8mkMA==", "dev": true, "requires": { - "@angular-devkit/core": "8.2.2", - "@angular-devkit/schematics": "8.2.2", + "@angular-devkit/core": "8.3.20", + "@angular-devkit/schematics": "8.3.20", "@yarnpkg/lockfile": "1.1.0", "ini": "1.3.5", - "pacote": "9.5.4", + "pacote": "9.5.5", "rxjs": "6.4.0", "semver": "6.3.0", "semver-intersect": "1.4.0" @@ -1634,9 +3575,9 @@ } }, "@types/jasmine": { - "version": "3.4.6", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.4.6.tgz", - "integrity": "sha512-hpQHs+lmZ0uuCrGyqypdI1Ho7jRFolOBT6OkNdZPFziLSSEKvWu+VxWU6bGdNEA/hoV4jV8pdDeNx8EWlmfNAw==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.5.0.tgz", + "integrity": "sha512-kGCRI9oiCxFS6soGKlyzhMzDydfcPix9PpTkr7h11huxOxhWwP37Tg7DYBaQ18eQTNreZEuLkhpbGSqVNZPnnw==", "dev": true }, "@types/jasminewd2": { @@ -1652,7 +3593,6 @@ "version": "3.3.31", "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.3.31.tgz", "integrity": "sha512-Lz4BAJihoFw5nRzKvg4nawXPzutkv7wmfQ5121avptaSIXlDNJCUuxZxX/G+9EVidZGuO0UBlk+YjKbwRKJigg==", - "dev": true, "requires": { "@types/sizzle": "*" } @@ -1675,9 +3615,9 @@ "integrity": "sha512-13gmo3M2qVvjQrWNseqM3+cR6S2Ss3grbR2NZltgMq94wOwqJYQdgn8qzwDshzgXqMlSUtyPZjysImmktu22ew==" }, "@types/node": { - "version": "10.14.22", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.22.tgz", - "integrity": "sha512-9taxKC944BqoTVjE+UT3pQH0nHZlTvITwfsOZqyc+R3sfJuxaTtxWjfn1K2UlxyPcKHf0rnaXcVFrS9F9vf0bw==", + "version": "12.12.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.17.tgz", + "integrity": "sha512-Is+l3mcHvs47sKy+afn2O1rV4ldZFU7W8101cNlOd+MRbjM4Onida8jSZnJdTe/0Pcf25g9BNIUsuugmE6puHA==", "dev": true }, "@types/prop-types": { @@ -1692,9 +3632,9 @@ "dev": true }, "@types/react": { - "version": "16.9.11", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.11.tgz", - "integrity": "sha512-UBT4GZ3PokTXSWmdgC/GeCGEJXE5ofWyibCcecRLUVN2ZBpXQGVgQGtG2foS7CrTKFKlQVVswLvf7Js6XA/CVQ==", + "version": "16.9.16", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.16.tgz", + "integrity": "sha512-dQ3wlehuBbYlfvRXfF5G+5TbZF3xqgkikK7DWAsQXe2KnzV+kjD4W2ea+ThCrKASZn9h98bjjPzoTYzfRqyBkw==", "requires": { "@types/prop-types": "*", "csstype": "^2.2.0" @@ -1748,6 +3688,15 @@ "integrity": "sha512-PeHg/AtdW6aaIO2a+98Xj7rWY4KC1E6yOy7AFknJQ7VXUGNrMlyxDFxJo7HqLtjQms/ZhhQX52mLVW/EX3JGOw==", "dev": true }, + "@types/tooltipster": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/tooltipster/-/tooltipster-0.0.29.tgz", + "integrity": "sha512-qDghalzudZcsX21l42N6heDCEhqlRxIsfyICLffEM3qH9hpHPAxwj4XmrMywaX5JgixRFuucVZRA6fV4XmSUVg==", + "dev": true, + "requires": { + "@types/jquery": "*" + } + }, "@types/webpack-sources": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-0.1.5.tgz", @@ -1984,9 +3933,9 @@ "integrity": "sha512-gwQGVFewBopRLho08BfahyvRa9FlB43JUig5ItAKTYc9kJJsbA9QNz75p28QtQomoPQ9rJx82ymL21x4ZSZmdg==" }, "acorn": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.3.0.tgz", - "integrity": "sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.0.tgz", + "integrity": "sha512-gac8OEcQ2Li1dxIEWGZzsp2BitJxwkwcOm0zHAJLcPJaVvm58FRnk6RkuLRpU1EujipU2ZFODv2P9DLMfnV8mw==", "dev": true }, "add-dom-event-listener": { @@ -2027,6 +3976,16 @@ "humanize-ms": "^1.2.1" } }, + "aggregate-error": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz", + "integrity": "sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, "ajv": { "version": "6.10.2", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", @@ -2081,10 +4040,13 @@ "dev": true }, "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", - "dev": true + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.0.tgz", + "integrity": "sha512-EiYhwo0v255HUL6eDyuLrXEkTi7WwVCLAw+SeOQ7M7qdun1z1pum4DEm/nuqIVbPvi9RPPc9k9LbyBv6H0DwVg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } }, "ansi-html": { "version": "0.0.7", @@ -2139,9 +4101,9 @@ "dev": true }, "arg": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.1.tgz", - "integrity": "sha512-SlmP3fEA88MBv0PypnXZ8ZfJhwmDeIE3SP71j37AiXQBXYosPV0x6uISAaHYSlSVhmHOVkomen0tbGk6Anlebw==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.2.tgz", + "integrity": "sha512-+ytCkGcBtHZ3V2r2Z06AncYO8jz46UEamcspGoU8lHcEbpn6J77QK0vdWvChsclg/tM5XIJC5tnjmPp7Eq6Obg==", "dev": true }, "argparse": { @@ -2352,12 +4314,24 @@ "dev": true }, "axobject-query": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.2.tgz", - "integrity": "sha512-MCeek8ZH7hKyO1rWUbKNQBbl4l2eY0ntk7OGi+q0RlafrCnfPxC06WZA+uebCfmYp4mNU9jRBP1AhGyf8+W3ww==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.1.1.tgz", + "integrity": "sha512-lF98xa/yvy6j3fBHAgQXIYl+J4eZadOSqsPojemUqClzNbBV38wWGpUbQbVEyf4eUF5yF7eHmGgGA2JiHyjeqw==", "dev": true, "requires": { - "ast-types-flow": "0.0.7" + "@babel/runtime": "^7.7.4", + "@babel/runtime-corejs3": "^7.7.4" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.7.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.6.tgz", + "integrity": "sha512-BWAJxpNVa0QlE5gZdWjSxXtemZyZ9RmrmVozxt3NUXeZhVIJ5ANyqmMc0JDrivBZyxUuQvFxlvH4OWWOogGfUw==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.2" + } + } } }, "babel-code-frame": { @@ -2420,6 +4394,12 @@ "trim-right": "^1.0.1" }, "dependencies": { + "jsesc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", + "dev": true + }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -2437,6 +4417,15 @@ "babel-runtime": "^6.22.0" } }, + "babel-plugin-dynamic-import-node": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz", + "integrity": "sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ==", + "dev": true, + "requires": { + "object.assign": "^4.1.0" + } + }, "babel-runtime": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", @@ -2486,6 +4475,14 @@ "globals": "^9.18.0", "invariant": "^2.2.2", "lodash": "^4.17.4" + }, + "dependencies": { + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + } } }, "babel-types": { @@ -2498,6 +4495,14 @@ "esutils": "^2.0.2", "lodash": "^4.17.4", "to-fast-properties": "^1.0.3" + }, + "dependencies": { + "to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "dev": true + } } }, "babylon": { @@ -2897,9 +4902,9 @@ "dev": true }, "cacache": { - "version": "11.3.3", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.3.tgz", - "integrity": "sha512-p8WcneCytvzPxhDvYp31PD039vi77I12W+/KfR9S8AZbaiARFBCpsPJS+9uhWfeBfeAtW7o/4vt3MUqLkbY6nA==", + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.2.tgz", + "integrity": "sha512-ifKgxH2CKhJEg6tNdAwziu6Q33EvuG26tYcda6PT3WKisZcYDXsnEdnRv67Po3yCzFfaSoMjGZzJyD2c3DT1dg==", "dev": true, "requires": { "bluebird": "^3.5.5", @@ -2907,6 +4912,7 @@ "figgy-pudding": "^3.5.1", "glob": "^7.1.4", "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", "lru-cache": "^5.1.1", "mississippi": "^3.0.0", "mkdirp": "^0.5.1", @@ -2989,9 +4995,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30000986", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000986.tgz", - "integrity": "sha512-pM+LnkoAX0+QnIH3tpW5EnkmfpEoqOD8FAcoBvsl3Xh6DXkgctiCxeCbXphP/k3XJtJzm+zOAJbi6U6IVkpWZQ==", + "version": "1.0.30000989", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000989.tgz", + "integrity": "sha512-vrMcvSuMz16YY6GSVZ0dWDTJP8jqk3iFQ/Aq5iqblPwxSVVZI+zxDyTX0VPqtQsDnfdrBDcsmhgTEOh5R8Lbpw==", "dev": true }, "canonical-path": { @@ -3076,9 +5082,9 @@ } }, "circular-dependency-plugin": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/circular-dependency-plugin/-/circular-dependency-plugin-5.0.2.tgz", - "integrity": "sha512-oC7/DVAyfcY3UWKm0sN/oVoDedQDQiw/vIiAnuTWTpE5s0zWf7l3WY417Xw/Fbi/QbAjctAkxgMiS9P0s3zkmA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/circular-dependency-plugin/-/circular-dependency-plugin-5.2.0.tgz", + "integrity": "sha512-7p4Kn/gffhQaavNfyDFg7LS5S/UT1JAjyGd4UqR2+jzoYF02eDkj0Ec3+48TsIa4zghjLY87nQHIh/ecK9qLdw==", "dev": true }, "class-utils": { @@ -3118,13 +5124,19 @@ "source-map": "~0.6.0" } }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", "dev": true, "requires": { - "restore-cursor": "^2.0.0" + "restore-cursor": "^3.1.0" } }, "cli-width": { @@ -3178,15 +5190,14 @@ "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" }, "clone-deep": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-2.0.2.tgz", - "integrity": "sha512-SZegPTKjCgpQH63E+eN6mVEEPdQBOUzjyJm5Pora4lrwWRFS8I0QAxV/KD6vV/i0WuijHZWQC1fMsPEdxfdVCQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", "dev": true, "requires": { - "for-own": "^1.0.0", "is-plain-object": "^2.0.4", - "kind-of": "^6.0.0", - "shallow-clone": "^1.0.0" + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" } }, "clsx": { @@ -3207,9 +5218,9 @@ "dev": true }, "codelyzer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/codelyzer/-/codelyzer-5.1.2.tgz", - "integrity": "sha512-1z7mtpwxcz5uUqq0HLO0ifj/tz2dWEmeaK+8c5TEZXAwwVxrjjg0118ODCOCCOcpfYaaEHxStNCaWVYo9FUPXw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/codelyzer/-/codelyzer-5.2.0.tgz", + "integrity": "sha512-izfUfhEOOgAizszPlEDxo71DK/C4wprZw0vkY6UWcOSTQvN1JyfXf9DXwaV7WX+/JC+hH0ShXfdtGLA9Rca7LA==", "dev": true, "requires": { "app-root-path": "^2.2.1", @@ -3355,23 +5366,49 @@ } }, "compression-webpack-plugin": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/compression-webpack-plugin/-/compression-webpack-plugin-3.0.0.tgz", - "integrity": "sha512-ls+oKw4eRbvaSv/hj9NmctihhBcR26j76JxV0bLRLcWhrUBdQFgd06z/Kgg7exyQvtWWP484wZxs0gIUX3NO0Q==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/compression-webpack-plugin/-/compression-webpack-plugin-3.0.1.tgz", + "integrity": "sha512-FOwoBVzDiwSdJDnZTKXDpAjJU90k8SbChgxnoiYwTo15xjIDJkSC8wFKuc13DymXjgasPEqzS5+2RUgSKXdKKA==", "dev": true, "requires": { - "cacache": "^11.2.0", + "cacache": "^13.0.1", "find-cache-dir": "^3.0.0", "neo-async": "^2.5.0", - "schema-utils": "^1.0.0", - "serialize-javascript": "^1.4.0", + "schema-utils": "^2.6.1", + "serialize-javascript": "^2.1.2", "webpack-sources": "^1.0.1" }, "dependencies": { + "cacache": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-13.0.1.tgz", + "integrity": "sha512-5ZvAxd05HDDU+y9BVvcqYu2LLXmPnQ0hW62h32g4xBTgL/MppR4/04NHfj/ycM2y6lmTnbw6HVi+1eN0Psba6w==", + "dev": true, + "requires": { + "chownr": "^1.1.2", + "figgy-pudding": "^3.5.1", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.2", + "infer-owner": "^1.0.4", + "lru-cache": "^5.1.1", + "minipass": "^3.0.0", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "p-map": "^3.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^2.7.1", + "ssri": "^7.0.0", + "unique-filename": "^1.1.1" + } + }, "find-cache-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.1.0.tgz", - "integrity": "sha512-zw+EFiNBNPgI2NTrKkDd1xd7q0cs6wr/iWnr/oUkI0yF9K9GqQ+riIt4aiyFaaqpaWbxPrJXHI+QvmNUQbX+0Q==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.2.0.tgz", + "integrity": "sha512-1JKclkYYsf1q9WIJKLZa9S9muC+08RIjzAlLrK4QcYLJMS6mk9yombQ9qf+zJ7H9LS800k0s44L4sDq9VYzqyg==", "dev": true, "requires": { "commondir": "^1.0.1", @@ -3389,6 +5426,15 @@ "path-exists": "^4.0.0" } }, + "fs-minipass": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.0.0.tgz", + "integrity": "sha512-40Qz+LFXmd9tzYVnnBmZvFfvAADfUA14TXPK1s7IfElJTIZ97rA8w4Kin7Wt5JBrC3ShnnFJO/5vPjPEeJIq9A==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -3398,6 +5444,23 @@ "p-locate": "^4.1.0" } }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + }, + "dependencies": { + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } + } + }, "make-dir": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.0.tgz", @@ -3407,6 +5470,15 @@ "semver": "^6.0.0" } }, + "minipass": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.1.tgz", + "integrity": "sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, "p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", @@ -3416,6 +5488,15 @@ "p-limit": "^2.2.0" } }, + "p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -3431,10 +5512,42 @@ "find-up": "^4.0.0" } }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "schema-utils": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.1.tgz", + "integrity": "sha512-0WXHDs1VDJyo+Zqs9TKLKyD/h7yDpHUhEFsM2CzkICFdoX1av+GBq/J2xRTFfsQO5kBfhZzANf2VcIm84jqDbg==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "ajv-keywords": "^3.4.1" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "serialize-javascript": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz", + "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==", + "dev": true + }, + "ssri": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-7.1.0.tgz", + "integrity": "sha512-77/WrDZUWocK0mvA5NTRQyveUf+wsrIc6vyrxpS8tVvYBcX215QbafrJR3KtkpskIzoFLqqNuuYQvxaMjXJ/0g==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1", + "minipass": "^3.1.1" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true } } @@ -3574,12 +5687,103 @@ "schema-utils": "^1.0.0", "serialize-javascript": "^1.7.0", "webpack-log": "^2.0.0" + }, + "dependencies": { + "cacache": { + "version": "11.3.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.3.tgz", + "integrity": "sha512-p8WcneCytvzPxhDvYp31PD039vi77I12W+/KfR9S8AZbaiARFBCpsPJS+9uhWfeBfeAtW7o/4vt3MUqLkbY6nA==", + "dev": true, + "requires": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } } }, "core-js": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.4.1.tgz", - "integrity": "sha512-KX/dnuY/J8FtEwbnrzmAjUYgLqtk+cxM86hfG60LGiW3MmltIc2yAmDgBgEkfm0blZhUrdr1Zd84J2Y14mLxzg==" + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.5.0.tgz", + "integrity": "sha512-Ifh3kj78gzQ7NAoJXeTu+XwzDld0QRIwjBLRqAMhuLhP3d2Av5wmgE9ycfnvK6NAEjTkQ1sDPeoEZAWO3Hx1Uw==" + }, + "core-js-compat": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.5.0.tgz", + "integrity": "sha512-E7iJB72svRjJTnm9HDvujzNVMCm3ZcDYEedkJ/sDTNsy/0yooCd9Cg7GSzE7b4e0LfIkjijdB1tqg0pGwxWeWg==", + "dev": true, + "requires": { + "browserslist": "^4.8.2", + "semver": "^6.3.0" + }, + "dependencies": { + "browserslist": { + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.2.tgz", + "integrity": "sha512-+M4oeaTplPm/f1pXDw84YohEv7B1i/2Aisei8s4s6k3QsoSHa7i5sz8u/cGQkkatCPxMASKxPualR4wwYgVboA==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001015", + "electron-to-chromium": "^1.3.322", + "node-releases": "^1.1.42" + } + }, + "caniuse-lite": { + "version": "1.0.30001015", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001015.tgz", + "integrity": "sha512-/xL2AbW/XWHNu1gnIrO8UitBGoFthcsDgU9VLK1/dpsoxbaD5LscHozKze05R6WLsBvLhqv78dAPozMFQBYLbQ==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "core-js-pure": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.5.0.tgz", + "integrity": "sha512-wB0QtKAofWigiISuT1Tej3hKgq932fB//Lf1VoPbiLpTYlHY0nIDhgF+q1na0DAKFHH5wGCirkAknOmDN8ijXA==", + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -3751,9 +5955,9 @@ } }, "date-fns": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.1.0.tgz", - "integrity": "sha512-eKeLk3sLCnxB/0PN4t1+zqDtSs4jb4mXRSTZ2okmx/myfWyDqeO4r5nnmA5LClJiCwpuTMeK2v5UQPuE4uMaxA==" + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.8.1.tgz", + "integrity": "sha512-EL/C8IHvYRwAHYgFRse4MGAPSqlJVlOrhVYZ75iQBKrnv+ZedmYsgwH3t+BCDuZDXpoo07+q9j4qgSSOa7irJg==" }, "date-format": { "version": "2.1.0", @@ -4131,15 +6335,15 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.309", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.309.tgz", - "integrity": "sha512-NZd91XD15v2UPLjYXoN/gLnkwIUQjdH4SQLpRCCQiYJH6BBkfgp5pWemBJPr1rZ2dl8Ee3o91O9Sa1QuAfZmog==", + "version": "1.3.322", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.322.tgz", + "integrity": "sha512-Tc8JQEfGQ1MzfSzI/bTlSr7btJv/FFO7Yh6tanqVmIWOuNCu6/D1MilIEgLtmWqIrsv+o4IjpLAhgMBr/ncNAA==", "dev": true }, "elliptic": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.1.tgz", - "integrity": "sha512-xvJINNLbTeWQjrl6X+7eQCrIy/YPv5XCpKW6kB5mKvtnGILoLDcySuwomfdzt0BMdLNVnuRNTuzKNHj0bva1Cg==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz", + "integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==", "dev": true, "requires": { "bn.js": "^4.4.0", @@ -4689,18 +6893,18 @@ "dev": true }, "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.1.0.tgz", + "integrity": "sha512-ravh8VRXqHuMvZt/d8GblBeqDMkdJMBdv/2KntFH+ra5MXkO7nxNKpzQ3n6QD/2da1kH0aWmNISdvhM7gl2gVg==", "dev": true, "requires": { "escape-string-regexp": "^1.0.5" } }, "file-loader": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-4.1.0.tgz", - "integrity": "sha512-ajDk1nlByoalZAGR4b0H6oD+EGlWnyW1qbSxzaUc7RFiqmn+RbXQQRbTc72jsiUIlVusJ4Et58ltds8ZwTfnAw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-4.2.0.tgz", + "integrity": "sha512-+xZnaK5R8kBJrHK0/6HRlrKNamvVS5rjyuju+rnyxRGuwUJwpAMsVzUl5dz6rK8brkzjV6JpcFNjp6NqV0g1OQ==", "dev": true, "requires": { "loader-utils": "^1.2.3", @@ -4708,9 +6912,9 @@ }, "dependencies": { "schema-utils": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.5.0.tgz", - "integrity": "sha512-32ISrwW2scPXHUSusP8qMg5dLUawKkyV+/qIEV9JdXKx+rsM6mi8vZY8khg2M69Qom16rtroWXD3Ybtiws38gQ==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.1.tgz", + "integrity": "sha512-0WXHDs1VDJyo+Zqs9TKLKyD/h7yDpHUhEFsM2CzkICFdoX1av+GBq/J2xRTFfsQO5kBfhZzANf2VcIm84jqDbg==", "dev": true, "requires": { "ajv": "^6.10.2", @@ -4762,14 +6966,74 @@ } }, "find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.0.0.tgz", + "integrity": "sha512-t7ulV1fmbxh5G9l/492O1p5+EBbr3uwpt6odhFTMc+nWyhmbloe+ja9BZ8pIBtqFWhOmCWVjx+pTW4zDkFoclw==", "dev": true, "requires": { "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" + "make-dir": "^3.0.0", + "pkg-dir": "^4.1.0" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "make-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.0.tgz", + "integrity": "sha512-grNJDhb8b1Jm1qeqW5R/O63wUo4UXo2v2HMic6YT9i/HBlF93S8jkMgH7yugvY9ABDShH4VZMn8I+U8+fCNegw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } } }, "find-up": { @@ -4842,15 +7106,6 @@ "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", "dev": true }, - "for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", - "dev": true, - "requires": { - "for-in": "^1.0.1" - } - }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -5019,9 +7274,9 @@ } }, "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true }, "globby": { @@ -5501,6 +7756,12 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, "indexof": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", @@ -5533,32 +7794,66 @@ "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" }, "inquirer": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.0.tgz", - "integrity": "sha512-scfHejeG/lVZSpvCXpsB4j/wQNPM5JC8kiElOI0OUTwmc1RTpXr4H32/HOlQHcZiYl2z2VElwuCVDRG8vFmbnA==", + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.1.tgz", + "integrity": "sha512-uxNHBeQhRXIoHWTSNYUFhQVrHYFThIt6IVo2fFmSe8aBwdR3/w6b58hJpiL/fMukFkvGzjg+hSxFtwvVmKZmXw==", "dev": true, "requires": { - "ansi-escapes": "^3.2.0", + "ansi-escapes": "^4.2.1", "chalk": "^2.4.2", - "cli-cursor": "^2.1.0", + "cli-cursor": "^3.1.0", "cli-width": "^2.0.0", "external-editor": "^3.0.3", - "figures": "^2.0.0", - "lodash": "^4.17.12", - "mute-stream": "0.0.7", + "figures": "^3.0.0", + "lodash": "^4.17.15", + "mute-stream": "0.0.8", "run-async": "^2.2.0", "rxjs": "^6.4.0", - "string-width": "^2.1.0", + "string-width": "^4.1.0", "strip-ansi": "^5.1.0", "through": "^2.3.6" }, "dependencies": { "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, "strip-ansi": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", @@ -5566,6 +7861,14 @@ "dev": true, "requires": { "ansi-regex": "^4.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + } } } } @@ -5613,6 +7916,12 @@ "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==", "dev": true }, + "is-absolute-url": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", + "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==", + "dev": true + }, "is-accessor-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", @@ -6110,9 +8419,9 @@ } }, "jasmine-core": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.4.0.tgz", - "integrity": "sha512-HU/YxV4i6GcmiH4duATwAbJQMlE0MsDIR5XmSVxURxKHn3aGAdbY1/ZJFmVRbKtnLwIxxMJD7gYaPsypcbYimg==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.5.0.tgz", + "integrity": "sha512-nCeAiw37MIMA9w9IXso7bRaLl+c/ef3wnxsoSAlYrzS+Ot0zTG6nU8G/cIfGkqpkjX2wNaIW9RFG0TwIFnG6bA==", "dev": true }, "jasmine-spec-reporter": { @@ -6135,30 +8444,41 @@ "resolved": "https://registry.npmjs.org/javascript-detect-element-resize/-/javascript-detect-element-resize-0.5.3.tgz", "integrity": "sha1-GnHNUd/lZZB/KZAS/nOilBBAJd4=" }, + "jest-worker": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz", + "integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==", + "dev": true, + "requires": { + "merge-stream": "^2.0.0", + "supports-color": "^6.1.0" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "jquery": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.4.1.tgz", "integrity": "sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw==" }, "jquery.terminal": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/jquery.terminal/-/jquery.terminal-2.8.0.tgz", - "integrity": "sha512-veyI105Vvro7MEInnfm7ZivToJCtFl6t2wSiV26CODl+1yv+zkbzibbYqAXQIG9Cpye2DvH0+aOUfSjnzCBV/A==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/jquery.terminal/-/jquery.terminal-2.9.0.tgz", + "integrity": "sha512-MK+/Or+9fcZDUZNvTXs4/PRYx4wYsyG65oxUzr8oK36UBrbTIIeHtg2SKW1jvWdR+jgP4V2rcmjcotHPUmkh5w==", "requires": { - "@types/jquery": "3.3.29", + "@types/jquery": "^3.3.29", "jquery": "~3", "prismjs": "^1.16.0", "wcwidth": "^1.0.1" - }, - "dependencies": { - "@types/jquery": { - "version": "3.3.29", - "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.3.29.tgz", - "integrity": "sha512-FhJvBninYD36v3k6c+bVk1DSZwh7B5Dpb/Pyk3HKVsiohn0nhbefZZ+3JXbWQhFyt0MxSl2jRDdGQPHeOHFXrQ==", - "requires": { - "@types/sizzle": "*" - } - } } }, "js-beautify": { @@ -6173,6 +8493,12 @@ "nopt": "~4.0.1" } }, + "js-levenshtein": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", + "dev": true + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -6195,9 +8521,9 @@ "dev": true }, "jsesc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", - "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true }, "json-parse-better-errors": { @@ -6372,9 +8698,9 @@ } }, "karma": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/karma/-/karma-4.2.0.tgz", - "integrity": "sha512-fmCuxN1rwJxTdZfOXK5LjlmS4Ana/OvzNMpkyLL/TLE8hmgSkpVpMYQ7RTVa8TNKRVQDZNl5W1oF5cfKfgIMlA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/karma/-/karma-4.4.1.tgz", + "integrity": "sha512-L5SIaXEYqzrh6b1wqYC42tNsFMx2PWuxky84pK9coK09MvmL7mxii3G3bZBh/0rvD27lqDd0le9jyhzvwif73A==", "dev": true, "requires": { "bluebird": "^3.3.0", @@ -6383,7 +8709,6 @@ "chokidar": "^3.0.0", "colors": "^1.1.0", "connect": "^3.6.0", - "core-js": "^3.1.3", "di": "^0.0.1", "dom-serialize": "^2.2.0", "flatted": "^2.0.0", @@ -6391,7 +8716,7 @@ "graceful-fs": "^4.1.2", "http-proxy": "^1.13.0", "isbinaryfile": "^3.0.0", - "lodash": "^4.17.11", + "lodash": "^4.17.14", "log4js": "^4.0.0", "mime": "^2.3.1", "minimatch": "^3.0.2", @@ -6415,18 +8740,18 @@ } }, "karma-chrome-launcher": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.0.0.tgz", - "integrity": "sha512-u/PnVgDOP97AUe/gJeABlC6Wa6aQ83MZsm0JgsJQ5bGQ9XcXON/7b2aRhl59A62Zom+q3PFveBkczc7E1RT7TA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.1.0.tgz", + "integrity": "sha512-3dPs/n7vgz1rxxtynpzZTvb9y/GIaW8xjAwcIGttLbycqoFtI7yo1NGnQi6oFTherRE+GIhCAHZC4vEqWGhNvg==", "dev": true, "requires": { "which": "^1.2.1" } }, "karma-coverage-istanbul-reporter": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-2.1.0.tgz", - "integrity": "sha512-UH0mXPJFJyK5uiK7EkwGtQ8f30lCBAfqRResnZ4pzLJ04SOp4SPlYkmwbbZ6iVJ6sQFVzlDUXlntBEsLRdgZpg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-2.1.1.tgz", + "integrity": "sha512-CH8lTi8+kKXGvrhy94+EkEMldLCiUA0xMOiL31vvli9qK0T+qcXJAwWBRVJWnVWxYkTmyWar8lPz63dxX6/z1A==", "dev": true, "requires": { "istanbul-api": "^2.1.6", @@ -6523,9 +8848,9 @@ } }, "license-webpack-plugin": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-2.1.1.tgz", - "integrity": "sha512-TiarZIg5vkQ2rGdYJn2+5YxO/zqlqjpK5IVglr7OfmrN1sBCakS+PQrsP2uC5gtve1ZDb9WMSUMlmHDQ0FoW4w==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-2.1.2.tgz", + "integrity": "sha512-7poZHRla+ae0eEButlwMrPpkXyhNVBf2EHePYWT0jyLnI6311/OXJkTI2sOIRungRpQgU2oDMpro5bSFPT5F0A==", "dev": true, "requires": { "@types/webpack-sources": "^0.1.5", @@ -6589,12 +8914,6 @@ "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" }, - "lodash.tail": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.tail/-/lodash.tail-4.1.1.tgz", - "integrity": "sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=", - "dev": true - }, "log4js": { "version": "4.5.1", "resolved": "https://registry.npmjs.org/log4js/-/log4js-4.5.1.tgz", @@ -6677,44 +8996,21 @@ "version": "5.0.2", "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-5.0.2.tgz", "integrity": "sha512-07JHC0r1ykIoruKO8ifMXu+xEU8qOXDFETylktdug6vJDACnP+HKevOu3PXyNPzFyTSlz8vrBYlBO1JZRe8Cag==", - "dev": true, - "requires": { - "agentkeepalive": "^3.4.1", - "cacache": "^12.0.0", - "http-cache-semantics": "^3.8.1", - "http-proxy-agent": "^2.1.0", - "https-proxy-agent": "^2.2.3", - "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", - "node-fetch-npm": "^2.0.2", - "promise-retry": "^1.1.1", - "socks-proxy-agent": "^4.0.0", - "ssri": "^6.0.0" - }, - "dependencies": { - "cacache": { - "version": "12.0.3", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.3.tgz", - "integrity": "sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw==", - "dev": true, - "requires": { - "bluebird": "^3.5.5", - "chownr": "^1.1.1", - "figgy-pudding": "^3.5.1", - "glob": "^7.1.4", - "graceful-fs": "^4.1.15", - "infer-owner": "^1.0.3", - "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "promise-inflight": "^1.0.1", - "rimraf": "^2.6.3", - "ssri": "^6.0.1", - "unique-filename": "^1.1.1", - "y18n": "^4.0.0" - } - }, + "dev": true, + "requires": { + "agentkeepalive": "^3.4.1", + "cacache": "^12.0.0", + "http-cache-semantics": "^3.8.1", + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "node-fetch-npm": "^2.0.2", + "promise-retry": "^1.1.1", + "socks-proxy-agent": "^4.0.0", + "ssri": "^6.0.0" + }, + "dependencies": { "lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -6827,6 +9123,12 @@ "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", "dev": true }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, "messageformat": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/messageformat/-/messageformat-2.3.0.tgz", @@ -7068,6 +9370,84 @@ } } }, + "minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.1.tgz", + "integrity": "sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.1.tgz", + "integrity": "sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "minipass-pipeline": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.2.tgz", + "integrity": "sha512-3JS5A2DKhD2g0Gg8x3yamO0pj7YeKGwVlDS90pF++kxptwx/F+B//roxf9SqYil5tQo65bijy+dAuAFZmYOouA==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.1.tgz", + "integrity": "sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, "minizlib": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", @@ -7116,24 +9496,6 @@ } } }, - "mixin-object": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", - "integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=", - "dev": true, - "requires": { - "for-in": "^0.1.3", - "is-extendable": "^0.1.1" - }, - "dependencies": { - "for-in": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", - "integrity": "sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=", - "dev": true - } - } - }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", @@ -7194,9 +9556,9 @@ "integrity": "sha512-gciOLNN8Vsf7YzcqRjKzlAJ6y7e+B86u7i3KXes0xfxx/nfLmozlW1Vn+Sc9x3tPIePFgc1AeIFhtRgkqTjzDQ==" }, "mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, "nan": { @@ -7247,9 +9609,9 @@ } }, "ngx-clipboard": { - "version": "12.2.1", - "resolved": "https://registry.npmjs.org/ngx-clipboard/-/ngx-clipboard-12.2.1.tgz", - "integrity": "sha512-9TzgVUKcVHGMYRa/DIit05+uVieiQhd8UEo7f97HTDiDeC1iXFgJjdHSGYyVWfVEQ5WuoryXmk6uYkgugf0y2g==", + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/ngx-clipboard/-/ngx-clipboard-12.3.0.tgz", + "integrity": "sha512-ToSsuDv9I1L0g+TcthePcZ4B859/MpoarlHVr2KnHWy3pR8SxfJlNyP2i9STYRQkJ5bSEg65RFErW4tx52lHYQ==", "requires": { "ngx-window-token": "^2.0.0", "tslib": "^1.9.0" @@ -7345,9 +9707,9 @@ } }, "node-releases": { - "version": "1.1.40", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.40.tgz", - "integrity": "sha512-r4LPcC5b/bS8BdtWH1fbeK88ib/wg9aqmg6/s3ngNLn2Ewkn/8J6Iw3P9RTlfIAdSdvYvQl2thCY5Y+qTAQ2iQ==", + "version": "1.1.42", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.42.tgz", + "integrity": "sha512-OQ/ESmUqGawI2PRX+XIRao44qWYBBfN54ImQYdWVTQqUckuejOg76ysSqDBK8NG3zwySRVnX36JwDQ6x+9GxzA==", "dev": true, "requires": { "semver": "^6.3.0" @@ -7412,9 +9774,18 @@ } }, "npm-bundled": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.6.tgz", - "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", + "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", + "dev": true, + "requires": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-normalize-package-bin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", "dev": true }, "npm-package-arg": { @@ -7430,9 +9801,9 @@ } }, "npm-packlist": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.6.tgz", - "integrity": "sha512-u65uQdb+qwtGvEJh/DgQgW1Xg7sqeNbmxYyrvlNznaVTjV3E5P6F/EFjM+BVHXl7JJlsdG8A64M0XI8FI/IOlg==", + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.7.tgz", + "integrity": "sha512-vAj7dIkp5NhieaGZxBJB8fF4R0078rqsmhJcAfXZ6O7JJhjhPK96n5Ry1oZcfLXgfun0GWTZPOxaEyqv8GBykQ==", "dev": true, "requires": { "ignore-walk": "^3.0.1", @@ -7440,9 +9811,9 @@ } }, "npm-pick-manifest": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-2.2.3.tgz", - "integrity": "sha512-+IluBC5K201+gRU85vFlUwX3PFShZAbAgDNp2ewJdWMVSppdo/Zih0ul2Ecky/X7b51J7LrrUAP+XOmOCvYZqA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-3.0.2.tgz", + "integrity": "sha512-wNprTNg+X5nf+tDi+hbjdHhM4bX+mKqv6XmPh7B5eG+QY9VARfQPfCEH013H5GqfNj6ee8Ij2fg8yk0mzps1Vw==", "dev": true, "requires": { "figgy-pudding": "^3.5.1", @@ -7582,6 +9953,18 @@ "isobject": "^3.0.0" } }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, "object.getownpropertydescriptors": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", @@ -7636,20 +10019,12 @@ } }, "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", "dev": true, "requires": { - "mimic-fn": "^1.0.0" - }, - "dependencies": { - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "dev": true - } + "mimic-fn": "^2.1.0" } }, "open": { @@ -7783,16 +10158,17 @@ "dev": true }, "pacote": { - "version": "9.5.4", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-9.5.4.tgz", - "integrity": "sha512-nWr0ari6E+apbdoN0hToTKZElO5h4y8DGFa2pyNA5GQIdcP0imC96bA0bbPw1gpeguVIiUgHHaAlq/6xfPp8Qw==", + "version": "9.5.5", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-9.5.5.tgz", + "integrity": "sha512-jAEP+Nqj4kyMWyNpfTU/Whx1jA7jEc5cCOlurm0/0oL+v8TAp1QSsK83N7bYe+2bEdFzMAtPG5TBebjzzGV0cA==", "dev": true, "requires": { "bluebird": "^3.5.3", - "cacache": "^12.0.0", + "cacache": "^12.0.2", "figgy-pudding": "^3.5.1", "get-stream": "^4.1.0", "glob": "^7.1.3", + "infer-owner": "^1.0.4", "lru-cache": "^5.1.1", "make-fetch-happen": "^5.0.0", "minimatch": "^3.0.4", @@ -7817,29 +10193,6 @@ "which": "^1.3.1" }, "dependencies": { - "cacache": { - "version": "12.0.3", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.3.tgz", - "integrity": "sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw==", - "dev": true, - "requires": { - "bluebird": "^3.5.5", - "chownr": "^1.1.1", - "figgy-pudding": "^3.5.1", - "glob": "^7.1.4", - "graceful-fs": "^4.1.15", - "infer-owner": "^1.0.3", - "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "promise-inflight": "^1.0.1", - "rimraf": "^2.6.3", - "ssri": "^6.0.1", - "unique-filename": "^1.1.1", - "y18n": "^4.0.0" - } - }, "lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -7849,6 +10202,17 @@ "yallist": "^3.0.2" } }, + "npm-pick-manifest": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-2.2.3.tgz", + "integrity": "sha512-+IluBC5K201+gRU85vFlUwX3PFShZAbAgDNp2ewJdWMVSppdo/Zih0ul2Ecky/X7b51J7LrrUAP+XOmOCvYZqA==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1", + "npm-package-arg": "^6.0.0", + "semver": "^5.4.1" + } + }, "yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -8184,6 +10548,12 @@ "clipboard": "^2.0.0" } }, + "private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "dev": true + }, "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -8580,16 +10950,6 @@ } } }, - "raw-loader": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-1.0.0.tgz", - "integrity": "sha512-Uqy5AqELpytJTRxYT4fhltcKPj0TyaEpzJDcGz7DFJi+pQOOi3GjR/DOdxTkTsF+NzhnldIoG6TORaBlInUuqA==", - "dev": true, - "requires": { - "loader-utils": "^1.1.0", - "schema-utils": "^1.0.0" - } - }, "rc-align": { "version": "2.4.5", "resolved": "https://registry.npmjs.org/rc-align/-/rc-align-2.4.5.tgz", @@ -8710,9 +11070,9 @@ } }, "react-dropzone": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-10.2.0.tgz", - "integrity": "sha512-VvJtg6GKtM1Xu+SsMcBNBcB2XcOi27xbNLBMDkrpqsk3cSILFiBVoCuW96FSOWkCK1IFeNg67FjKu/c/KuUhkg==", + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-10.2.1.tgz", + "integrity": "sha512-Me5nOu8hK9/Xyg5easpdfJ6SajwUquqYR/2YTdMotsCUgJ1pHIIwNsv0n+qcIno0tWR2V2rVQtj2r/hXYs2TnQ==", "requires": { "attr-accept": "^2.0.0", "file-selector": "^0.1.12", @@ -8766,16 +11126,16 @@ } }, "read-package-json": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.1.0.tgz", - "integrity": "sha512-KLhu8M1ZZNkMcrq1+0UJbR8Dii8KZUqB0Sha4mOx/bknfKI/fyrQVrG/YIt2UOtG667sD8+ee4EXMM91W9dC+A==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.1.1.tgz", + "integrity": "sha512-dAiqGtVc/q5doFz6096CcnXhpYk0ZN8dEKVkGLU0CsASt8SrgF6SF7OTKAYubfvFhWaqofl+Y8HK19GR8jwW+A==", "dev": true, "requires": { "glob": "^7.1.1", "graceful-fs": "^4.1.2", "json-parse-better-errors": "^1.0.1", "normalize-package-data": "^2.0.0", - "slash": "^1.0.0" + "npm-normalize-package-bin": "^1.0.0" } }, "read-package-tree": { @@ -8837,11 +11197,29 @@ "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", "dev": true }, + "regenerate-unicode-properties": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz", + "integrity": "sha512-LGZzkgtLY79GeXLm8Dp0BVLdQlWICzBnJz/ipWUgo59qBaZ+BHtq51P2q1uVZlppMuUAT37SDk39qUbjTWB7bA==", + "dev": true, + "requires": { + "regenerate": "^1.4.0" + } + }, "regenerator-runtime": { "version": "0.13.3", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==" }, + "regenerator-transform": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.1.tgz", + "integrity": "sha512-flVuee02C3FKRISbxhXl9mGzdbWUVHubl1SMaknjxkFB1/iqpJhArQUvRxOOPEc/9tAiX0BaQ28FJH10E4isSQ==", + "dev": true, + "requires": { + "private": "^0.1.6" + } + }, "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", @@ -9003,12 +11381,12 @@ "dev": true }, "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", "dev": true, "requires": { - "onetime": "^2.0.0", + "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, @@ -9105,34 +11483,25 @@ "dev": true }, "sass": { - "version": "1.22.7", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.22.7.tgz", - "integrity": "sha512-ahREi0AdG7RTovSv14+yd1prQSfIvFcrDpOsth5EQf1+RM7SvOxsSttzNQaFmK1aa/k/3vyYwlYF5l0Xl+6c+g==", + "version": "1.22.9", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.22.9.tgz", + "integrity": "sha512-FzU1X2V8DlnqabrL4u7OBwD2vcOzNMongEJEx3xMEhWY/v26FFR3aG0hyeu2T965sfR0E9ufJwmG+Qjz78vFPQ==", "dev": true, "requires": { "chokidar": ">=2.0.0 <4.0.0" } }, "sass-loader": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-7.1.0.tgz", - "integrity": "sha512-+G+BKGglmZM2GUSfT9TLuEp6tzehHPjAMoRRItOojWIqIGPloVCMhNIQuG639eJ+y033PaGTSjLaTHts8Kw79w==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-7.2.0.tgz", + "integrity": "sha512-h8yUWaWtsbuIiOCgR9fd9c2lRXZ2uG+h8Dzg/AGNj+Hg/3TO8+BBAW9mEP+mh8ei+qBKqSJ0F1FLlYjNBc61OA==", "dev": true, "requires": { - "clone-deep": "^2.0.1", + "clone-deep": "^4.0.1", "loader-utils": "^1.0.1", - "lodash.tail": "^4.1.1", "neo-async": "^2.5.0", - "pify": "^3.0.0", + "pify": "^4.0.1", "semver": "^5.5.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - } } }, "saucelabs": { @@ -9179,9 +11548,9 @@ } }, "screenfull": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/screenfull/-/screenfull-4.2.1.tgz", - "integrity": "sha512-PLSp6f5XdhvjCCCO8OjavRfzkSGL3Qmdm7P82bxyU8HDDDBhDV3UckRaYcRa/NDNTYt8YBpzjoLWHUAejmOjLg==" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/screenfull/-/screenfull-5.0.0.tgz", + "integrity": "sha512-yShzhaIoE9OtOhWVyBBffA6V98CDCoyHTsp8228blmqYy1Z5bddzE/4FPiJKlr8DVR4VBiiUyfPzIQPIYDkeMA==" }, "select": { "version": "1.1.2", @@ -9396,22 +11765,12 @@ } }, "shallow-clone": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-1.0.0.tgz", - "integrity": "sha512-oeXreoKR/SyNJtRJMAKPDSvd28OqEwG4eR/xc856cRGBII7gX9lvAqDxusPm0846z/w/hWYjI1NpKwJ00NHzRA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", "dev": true, "requires": { - "is-extendable": "^0.1.1", - "kind-of": "^5.0.0", - "mixin-object": "^2.0.1" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } + "kind-of": "^6.0.2" } }, "shallowequal": { @@ -9596,9 +11955,9 @@ } }, "socket.io-adapter": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz", - "integrity": "sha1-KoBeihTWNyEk3ZFZrUUC+MsH8Gs=", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz", + "integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==", "dev": true }, "socket.io-client": { @@ -9685,9 +12044,9 @@ } }, "sockjs-client": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.3.0.tgz", - "integrity": "sha512-R9jxEzhnnrdxLCNln0xg5uGHqMnkhPSTzUZH2eXcR03S/On9Yvoq2wyUZILRUhZCNVu2PmwWVoyuiPz8th8zbg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.4.0.tgz", + "integrity": "sha512-5zaLyO8/nri5cua0VtOrFXBPK1jbL4+1cebT/mmKA1E1ZXOvJrII75bPu0l0k843G/+iAbhEqzyKr0w/eCCj7g==", "dev": true, "requires": { "debug": "^3.2.5", @@ -10179,13 +12538,25 @@ "dev": true }, "style-loader": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.23.1.tgz", - "integrity": "sha512-XK+uv9kWwhZMZ1y7mysB+zoihsEj4wneFWAS5qoiLwzW0WzSqMrrsIy+a3zkQJq0ipFtBpX5W3MqyRIBF/WFGg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-1.0.0.tgz", + "integrity": "sha512-B0dOCFwv7/eY31a5PCieNwMgMhVGFe9w+rh7s/Bx8kfFkrth9zfTZquoYvdw8URgiqxObQKcpW51Ugz1HjfdZw==", "dev": true, "requires": { - "loader-utils": "^1.1.0", - "schema-utils": "^1.0.0" + "loader-utils": "^1.2.3", + "schema-utils": "^2.0.1" + }, + "dependencies": { + "schema-utils": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.1.tgz", + "integrity": "sha512-0WXHDs1VDJyo+Zqs9TKLKyD/h7yDpHUhEFsM2CzkICFdoX1av+GBq/J2xRTFfsQO5kBfhZzANf2VcIm84jqDbg==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "ajv-keywords": "^3.4.1" + } + } } }, "stylus": { @@ -10283,9 +12654,9 @@ } }, "terser": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.4.0.tgz", - "integrity": "sha512-oDG16n2WKm27JO8h4y/w3iqBGAOSCtq7k8dRmrn4Wf9NouL0b2WpMHGChFGZq4nFAQy1FsNJrVQHfurXOSTmOA==", + "version": "4.3.9", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.3.9.tgz", + "integrity": "sha512-NFGMpHjlzmyOtPL+fDw3G7+6Ueh/sz4mkaUYa4lJCxOPTNzd0Uj0aZJOmsDYoSQyfuVoWDMSWTPU3huyOm2zdA==", "dev": true, "requires": { "commander": "^2.20.0", @@ -10294,21 +12665,49 @@ } }, "terser-webpack-plugin": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.3.0.tgz", - "integrity": "sha512-W2YWmxPjjkUcOWa4pBEv4OP4er1aeQJlSo2UhtCFQCuRXEHjOFscO8VyWHj9JLlA0RzQb8Y2/Ta78XZvT54uGg==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz", + "integrity": "sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==", "dev": true, "requires": { - "cacache": "^11.3.2", - "find-cache-dir": "^2.0.0", + "cacache": "^12.0.2", + "find-cache-dir": "^2.1.0", "is-wsl": "^1.1.0", - "loader-utils": "^1.2.3", "schema-utils": "^1.0.0", - "serialize-javascript": "^1.7.0", + "serialize-javascript": "^2.1.2", "source-map": "^0.6.1", - "terser": "^4.0.0", - "webpack-sources": "^1.3.0", + "terser": "^4.1.2", + "webpack-sources": "^1.4.0", "worker-farm": "^1.7.0" + }, + "dependencies": { + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, + "serialize-javascript": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz", + "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==", + "dev": true + }, + "webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "dev": true, + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } + } } }, "through": { @@ -10380,9 +12779,9 @@ "dev": true }, "to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", "dev": true }, "to-object-path": { @@ -10432,6 +12831,11 @@ "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", "dev": true }, + "tooltipster": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/tooltipster/-/tooltipster-4.2.7.tgz", + "integrity": "sha512-W4tY3LG2eyPY2VQZRH3JcsNuRl3jPCEGmKBPOMTP/05E3+1kOJjASzPRRkcpP+uf9vqX7+896ivU86f6B8Esgw==" + }, "tough-cookie": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", @@ -10463,9 +12867,9 @@ "dev": true }, "ts-node": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.3.0.tgz", - "integrity": "sha512-dyNS/RqyVTDcmNM4NIBAeDMpsAdaQ+ojdf0GOLqE6nwJOgzEkdRNzJywhDfwnuvB10oa6NLVG1rUJQCpRN7qoQ==", + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.5.4.tgz", + "integrity": "sha512-izbVCRV68EasEPQ8MSIGBNK9dc/4sYJJKYA+IarMQct1RtEot6Xp0bXuClsbUSnKpg50ho+aOAx8en5c+y4OFw==", "dev": true, "requires": { "arg": "^4.1.0", @@ -10481,16 +12885,16 @@ "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" }, "tslint": { - "version": "5.18.0", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.18.0.tgz", - "integrity": "sha512-Q3kXkuDEijQ37nXZZLKErssQVnwCV/+23gFEMROi8IlbaBG6tXqLPQJ5Wjcyt/yHPKBC+hD5SzuGaMora+ZS6w==", + "version": "5.20.1", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.20.1.tgz", + "integrity": "sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "builtin-modules": "^1.1.1", "chalk": "^2.3.0", "commander": "^2.12.1", - "diff": "^3.2.0", + "diff": "^4.0.1", "glob": "^7.1.1", "js-yaml": "^3.13.1", "minimatch": "^3.0.4", @@ -10499,14 +12903,6 @@ "semver": "^5.3.0", "tslib": "^1.8.0", "tsutils": "^2.29.0" - }, - "dependencies": { - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - } } }, "tsutils": { @@ -10544,6 +12940,12 @@ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", "dev": true }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + }, "type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -10572,9 +12974,9 @@ "dev": true }, "uglify-js": { - "version": "3.6.9", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.9.tgz", - "integrity": "sha512-pcnnhaoG6RtrvHJ1dFncAe8Od6Nuy30oaJ82ts6//sGSXOP5UjBMEthiProjXmMNHOfd93sqlkztifFMcb+4yw==", + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.7.2.tgz", + "integrity": "sha512-uhRwZcANNWVLrxLfNFEdltoPNhECUR3lc+UdJoG9CBpMcSnKyWA94tc3eAujB1GcMY5Uwq8ZMp4qWpxWYDQmaA==", "dev": true, "optional": true, "requires": { @@ -10588,6 +12990,34 @@ "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", "dev": true }, + "unicode-canonical-property-names-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", + "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==", + "dev": true + }, + "unicode-match-property-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", + "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", + "dev": true, + "requires": { + "unicode-canonical-property-names-ecmascript": "^1.0.4", + "unicode-property-aliases-ecmascript": "^1.0.4" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.1.0.tgz", + "integrity": "sha512-hDTHvaBk3RmFzvSl0UVrUmC3PuW9wKVnpoUDYH0JDkSIovzw+J5viQmeYHxVSBptubnr7PbH2e0fnpDRQnQl5g==", + "dev": true + }, + "unicode-property-aliases-ecmascript": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz", + "integrity": "sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw==", + "dev": true + }, "union-value": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", @@ -11595,34 +14025,46 @@ } }, "webpack": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.38.0.tgz", - "integrity": "sha512-lbuFsVOq8PZY+1Ytz/mYOvYOo+d4IJ31hHk/7iyoeWtwN33V+5HYotSH+UIb9tq914ey0Hot7z6HugD+je3sWw==", + "version": "4.39.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.39.2.tgz", + "integrity": "sha512-AKgTfz3xPSsEibH00JfZ9sHXGUwIQ6eZ9tLN8+VLzachk1Cw2LVmy+4R7ZiwTa9cZZ15tzySjeMui/UnSCAZhA==", "dev": true, "requires": { "@webassemblyjs/ast": "1.8.5", "@webassemblyjs/helper-module-context": "1.8.5", "@webassemblyjs/wasm-edit": "1.8.5", "@webassemblyjs/wasm-parser": "1.8.5", - "acorn": "^6.2.0", - "ajv": "^6.1.0", - "ajv-keywords": "^3.1.0", - "chrome-trace-event": "^1.0.0", + "acorn": "^6.2.1", + "ajv": "^6.10.2", + "ajv-keywords": "^3.4.1", + "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^4.1.0", - "eslint-scope": "^4.0.0", + "eslint-scope": "^4.0.3", "json-parse-better-errors": "^1.0.2", - "loader-runner": "^2.3.0", - "loader-utils": "^1.1.0", - "memory-fs": "~0.4.1", - "micromatch": "^3.1.8", - "mkdirp": "~0.5.0", - "neo-async": "^2.5.0", - "node-libs-browser": "^2.0.0", + "loader-runner": "^2.4.0", + "loader-utils": "^1.2.3", + "memory-fs": "^0.4.1", + "micromatch": "^3.1.10", + "mkdirp": "^0.5.1", + "neo-async": "^2.6.1", + "node-libs-browser": "^2.2.1", "schema-utils": "^1.0.0", - "tapable": "^1.1.0", - "terser-webpack-plugin": "^1.1.0", - "watchpack": "^1.5.0", - "webpack-sources": "^1.3.0" + "tapable": "^1.1.3", + "terser-webpack-plugin": "^1.4.1", + "watchpack": "^1.6.0", + "webpack-sources": "^1.4.1" + }, + "dependencies": { + "webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "dev": true, + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } + } } }, "webpack-core": { @@ -11653,13 +14095,14 @@ } }, "webpack-dev-middleware": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.0.tgz", - "integrity": "sha512-qvDesR1QZRIAZHOE3iQ4CXLZZSQ1lAUsSpnQmlB1PBfoN/xdRjmge3Dok0W4IdaVLJOGJy3sGI4sZHwjRU0PCA==", + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz", + "integrity": "sha512-1xC42LxbYoqLNAhV6YzTYacicgMZQTqRd27Sim9wn5hJrX3I5nxYy1SxSd4+gjUFsz1dQFj+yEe6zEVmSkeJjw==", "dev": true, "requires": { "memory-fs": "^0.4.1", - "mime": "^2.4.2", + "mime": "^2.4.4", + "mkdirp": "^0.5.1", "range-parser": "^1.2.1", "webpack-log": "^2.0.0" }, @@ -11673,41 +14116,43 @@ } }, "webpack-dev-server": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.7.2.tgz", - "integrity": "sha512-mjWtrKJW2T9SsjJ4/dxDC2fkFVUw8jlpemDERqV0ZJIkjjjamR2AbQlr3oz+j4JLhYCHImHnXZK5H06P2wvUew==", + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.9.0.tgz", + "integrity": "sha512-E6uQ4kRrTX9URN9s/lIbqTAztwEPdvzVrcmHE8EQ9YnuT9J8Es5Wrd8n9BKg1a0oZ5EgEke/EQFgUsp18dSTBw==", "dev": true, "requires": { "ansi-html": "0.0.7", "bonjour": "^3.5.0", - "chokidar": "^2.1.6", + "chokidar": "^2.1.8", "compression": "^1.7.4", "connect-history-api-fallback": "^1.6.0", "debug": "^4.1.1", "del": "^4.1.1", "express": "^4.17.1", "html-entities": "^1.2.1", - "http-proxy-middleware": "^0.19.1", + "http-proxy-middleware": "0.19.1", "import-local": "^2.0.0", "internal-ip": "^4.3.0", "ip": "^1.1.5", + "is-absolute-url": "^3.0.3", "killable": "^1.0.1", - "loglevel": "^1.6.3", + "loglevel": "^1.6.4", "opn": "^5.5.0", "p-retry": "^3.0.1", - "portfinder": "^1.0.20", + "portfinder": "^1.0.25", "schema-utils": "^1.0.0", - "selfsigned": "^1.10.4", - "semver": "^6.1.1", + "selfsigned": "^1.10.7", + "semver": "^6.3.0", "serve-index": "^1.9.1", "sockjs": "0.3.19", - "sockjs-client": "1.3.0", - "spdy": "^4.0.0", + "sockjs-client": "1.4.0", + "spdy": "^4.0.1", "strip-ansi": "^3.0.1", "supports-color": "^6.1.0", "url": "^0.11.0", - "webpack-dev-middleware": "^3.7.0", + "webpack-dev-middleware": "^3.7.2", "webpack-log": "^2.0.0", + "ws": "^6.2.1", "yargs": "12.0.5" }, "dependencies": { @@ -12422,6 +14867,15 @@ "is-number": "^3.0.0", "repeat-string": "^1.6.1" } + }, + "ws": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", + "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0" + } } } }, @@ -12517,9 +14971,9 @@ } }, "worker-plugin": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/worker-plugin/-/worker-plugin-3.1.0.tgz", - "integrity": "sha512-iQ9KTTmmN5fhfc2KMR7CcDblvcrg1QQ4pXymqZ3cRZF8L0890YLBcEqlIsGPdxoFwghyN8RA1pCEhCKuTF4Lkw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/worker-plugin/-/worker-plugin-3.2.0.tgz", + "integrity": "sha512-W5nRkw7+HlbsEt3qRP6MczwDDISjiRj2GYt9+bpe8A2La00TmJdwzG5bpdMXhRt1qcWmwAvl1TiKaHRa+XDS9Q==", "dev": true, "requires": { "loader-utils": "^1.1.0" diff --git a/ui-ngx/package.json b/ui-ngx/package.json index 405d18d9f3..db02a1ca6a 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -12,52 +12,52 @@ }, "private": true, "dependencies": { - "@angular/animations": "~8.2.11", + "@angular/animations": "~8.2.14", "@angular/cdk": "~8.2.3", - "@angular/common": "~8.2.11", - "@angular/compiler": "~8.2.11", - "@angular/core": "~8.2.11", - "@angular/flex-layout": "^8.0.0-beta.26", - "@angular/forms": "~8.2.11", + "@angular/common": "~8.2.14", + "@angular/compiler": "~8.2.14", + "@angular/core": "~8.2.14", + "@angular/flex-layout": "^8.0.0-beta.27", + "@angular/forms": "~8.2.14", "@angular/material": "^8.2.3", - "@angular/platform-browser": "~8.2.11", - "@angular/platform-browser-dynamic": "~8.2.11", - "@angular/router": "~8.2.11", - "@auth0/angular-jwt": "^3.0.0", - "@date-io/date-fns": "^1.3.11", + "@angular/platform-browser": "~8.2.14", + "@angular/platform-browser-dynamic": "~8.2.14", + "@angular/router": "~8.2.14", + "@auth0/angular-jwt": "^3.0.1", + "@date-io/date-fns": "^1.3.13", "@flowjs/flow.js": "^2.13.2", "@flowjs/ngx-flow": "^0.4.3", "@mat-datetimepicker/core": "^2.0.1", - "@material-ui/core": "^4.5.1", + "@material-ui/core": "^4.7.2", "@material-ui/icons": "^4.5.1", - "@material-ui/pickers": "^3.2.7", - "@ngrx/effects": "^8.2.0", - "@ngrx/store": "^8.2.0", - "@ngrx/store-devtools": "^8.2.0", - "@ngx-share/core": "^7.1.2", + "@material-ui/pickers": "^3.2.8", + "@ngrx/effects": "^8.5.2", + "@ngrx/store": "^8.5.2", + "@ngrx/store-devtools": "^8.5.2", + "@ngx-share/core": "^7.1.4", "@ngx-translate/core": "^11.0.1", "@ngx-translate/http-loader": "^4.0.0", - "ace-builds": "^1.4.5", - "angular-gridster2": "^8.1.0", + "ace-builds": "^1.4.7", + "angular-gridster2": "^8.2.0", "angular2-hotkeys": "^2.1.5", "base64-js": "^1.3.1", "compass-sass-mixins": "^0.12.7", - "core-js": "^3.1.4", - "date-fns": "2.1.0", - "deep-equal": "^1.0.1", + "core-js": "^3.5.0", + "date-fns": "2.8.1", + "deep-equal": "^1.1.1", "flot": "git://github.com/thingsboard/flot.git#0.9-work", "flot.curvedlines": "git://github.com/MichaelZinsmaier/CurvedLines.git#master", "font-awesome": "^4.7.0", "hammerjs": "^2.0.8", "javascript-detect-element-resize": "^0.5.3", "jquery": "^3.4.1", - "jquery.terminal": "^2.8.0", + "jquery.terminal": "^2.9.0", "js-beautify": "^1.10.2", "json-schema-defaults": "^0.4.0", "material-design-icons": "^3.0.1", "messageformat": "^2.3.0", "moment": "^2.24.0", - "ngx-clipboard": "^12.2.0", + "ngx-clipboard": "^12.3.0", "ngx-color-picker": "^8.2.0", "ngx-flowchart": "git://github.com/thingsboard/ngx-flowchart.git#master", "ngx-hm-carousel": "^1.7.2", @@ -65,50 +65,55 @@ "objectpath": "^1.2.2", "prop-types": "^15.7.2", "rc-select": "^9.2.1", - "react": "^16.10.2", + "react": "^16.12.0", "react-ace": "^8.0.0", - "react-dom": "^16.10.2", - "react-dropzone": "^10.1.10", + "react-dom": "^16.12.0", + "react-dropzone": "^10.2.1", "reactcss": "^1.2.3", - "rxjs": "~6.5.2", + "rxjs": "~6.5.3", "schema-inspector": "^1.6.8", - "screenfull": "^4.2.1", + "screenfull": "^5.0.0", "split.js": "^1.5.11", "tinycolor2": "^1.4.1", + "tooltipster": "^4.2.7", "tslib": "^1.10.0", "tv4": "^1.3.0", "typeface-roboto": "^0.0.75", "zone.js": "~0.9.1" }, "devDependencies": { - "@angular-builders/custom-webpack": "^8.2.0", - "@angular-devkit/build-angular": "^0.802.0", - "@angular/cli": "~8.2.2", - "@angular/compiler-cli": "~8.2.11", - "@angular/language-service": "~8.2.11", + "@angular-builders/custom-webpack": "^8.4.1", + "@angular-devkit/build-angular": "^0.803.20", + "@angular/cli": "~8.3.20", + "@angular/compiler-cli": "~8.2.14", + "@angular/language-service": "~8.2.14", "@types/flot": "0.0.31", - "@types/jasmine": "~3.4.0", - "@types/jasminewd2": "~2.0.6", + "@types/jasmine": "~3.5.0", + "@types/jasminewd2": "~2.0.8", "@types/jquery": "^3.3.31", "@types/js-beautify": "^1.8.1", - "@types/node": "~10.14.15", - "@types/react": "^16.9.9", - "@types/react-dom": "^16.9.2", + "@types/node": "~12.12.17", + "@types/react": "^16.9.16", + "@types/react-dom": "^16.9.4", "@types/tinycolor2": "^1.4.2", - "codelyzer": "~5.1.0", - "compression-webpack-plugin": "^3.0.0", - "directory-tree": "^2.2.3", - "jasmine-core": "~3.4.0", + "@types/tooltipster": "0.0.29", + "codelyzer": "~5.2.0", + "compression-webpack-plugin": "^3.0.1", + "directory-tree": "^2.2.4", + "jasmine-core": "~3.5.0", "jasmine-spec-reporter": "~4.2.1", - "karma": "~4.2.0", - "karma-chrome-launcher": "~3.0.0", - "karma-coverage-istanbul-reporter": "~2.1.0", + "karma": "~4.4.1", + "karma-chrome-launcher": "~3.1.0", + "karma-coverage-istanbul-reporter": "~2.1.1", "karma-jasmine": "~2.0.1", "karma-jasmine-html-reporter": "^1.4.2", "ngrx-store-freeze": "^0.2.4", "protractor": "~5.4.2", - "ts-node": "~8.3.0", - "tslint": "~5.18.0", + "ts-node": "~8.5.4", + "tslint": "~5.20.1", "typescript": "~3.5.3" + }, + "resolutions": { + "serialize-javascript": "^2.1.1" } } diff --git a/ui-ngx/src/app/core/services/utils.service.ts b/ui-ngx/src/app/core/services/utils.service.ts index fd9e64c2d9..bdb3999c8f 100644 --- a/ui-ngx/src/app/core/services/utils.service.ts +++ b/ui-ngx/src/app/core/services/utils.service.ts @@ -28,7 +28,7 @@ import { alarmFields } 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'; -import * as materialIconsCodepoints from '!raw-loader!material-design-icons/iconfont/codepoints'; +import materialIconsCodepoints from '!raw-loader!material-design-icons/iconfont/codepoints'; import { Observable, of, ReplaySubject } from 'rxjs'; const varsRegex = /\$\{([^}]*)\}/g; diff --git a/ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.models.ts b/ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.models.ts index bd790c6d7f..27a7d4346a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.models.ts @@ -30,9 +30,9 @@ import { entityTypeTranslations } from '@shared/models/entity-type.models'; import { UtilsService } from '@core/services/utils.service'; import { deepClone, isUndefined } from '@core/utils'; -import * as customSampleJs from '!raw-loader!./custom-sample-js.raw'; -import * as customSampleCss from '!raw-loader!./custom-sample-css.raw'; -import * as customSampleHtml from '!raw-loader!./custom-sample-html.raw'; +import customSampleJs from '!raw-loader!./custom-sample-js.raw'; +import customSampleCss from '!raw-loader!./custom-sample-css.raw'; +import customSampleHtml from '!raw-loader!./custom-sample-html.raw'; export interface WidgetActionCallbacks { fetchDashboardStates: (query: string) => Array; diff --git a/ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts index f2db6714a5..9b56a5c806 100644 --- a/ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts @@ -15,7 +15,7 @@ /// import { PageComponent } from '@shared/components/page.component'; -import { Input, OnDestroy, OnInit } from '@angular/core'; +import { Inject, Input, OnDestroy, OnInit } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { WidgetContext, IDynamicWidgetComponent } from '@home/models/widget-component.models'; @@ -38,8 +38,8 @@ export class DynamicWidgetComponent extends PageComponent implements IDynamicWid [key: string]: any; - constructor(public raf: RafService, - protected store: Store) { + constructor(@Inject(RafService) public raf: RafService, + @Inject(Store) protected store: Store) { super(store); } diff --git a/ui-ngx/src/app/modules/home/home.component.html b/ui-ngx/src/app/modules/home/home.component.html index 5db1c33185..df432c4aa2 100644 --- a/ui-ngx/src/app/modules/home/home.component.html +++ b/ui-ngx/src/app/modules/home/home.component.html @@ -35,24 +35,48 @@
- - -
+ +
- + - +
- +
diff --git a/ui-ngx/src/app/modules/home/home.component.scss b/ui-ngx/src/app/modules/home/home.component.scss index 7d26343517..08852bb9fe 100644 --- a/ui-ngx/src/app/modules/home/home.component.scss +++ b/ui-ngx/src/app/modules/home/home.component.scss @@ -17,6 +17,11 @@ display: flex; width: 100%; height: 100%; + + .tb-invisible { + display: none !important; + } + mat-sidenav-container { flex: 1; } diff --git a/ui-ngx/src/app/modules/home/home.component.ts b/ui-ngx/src/app/modules/home/home.component.ts index 5ff321f208..daf22ee81a 100644 --- a/ui-ngx/src/app/modules/home/home.component.ts +++ b/ui-ngx/src/app/modules/home/home.component.ts @@ -14,10 +14,10 @@ /// limitations under the License. /// -import { Component, Inject, OnInit, ViewChild } from '@angular/core'; -import { Observable } from 'rxjs'; +import { AfterViewInit, Component, ElementRef, Inject, OnInit, ViewChild } from '@angular/core'; +import { fromEvent, Observable } from 'rxjs'; import { select, Store } from '@ngrx/store'; -import { map, mergeMap, take } from 'rxjs/operators'; +import { debounceTime, distinctUntilChanged, map, mergeMap, take, tap } from 'rxjs/operators'; import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; import { User } from '@shared/models/user.model'; @@ -34,19 +34,21 @@ import * as screenfull from 'screenfull'; import { MatSidenav } from '@angular/material'; import { AuthState } from '@core/auth/auth.models'; import { WINDOW } from '@core/services/window.service'; +import { ISearchableComponent, instanceOfSearchableComponent } from '@home/models/searchable-component.models'; @Component({ selector: 'tb-home', templateUrl: './home.component.html', styleUrls: ['./home.component.scss'] }) -export class HomeComponent extends PageComponent implements OnInit { +export class HomeComponent extends PageComponent implements AfterViewInit, OnInit { authState: AuthState = getCurrentAuthState(this.store); forceFullscreen = this.authState.forceFullscreen; activeComponent: any; + searchableComponent: ISearchableComponent; sidenavMode = 'side'; sidenavOpened = true; @@ -56,6 +58,8 @@ export class HomeComponent extends PageComponent implements OnInit { @ViewChild('sidenav', {static: false}) sidenav: MatSidenav; + @ViewChild('searchInput', {static: false}) searchInputField: ElementRef; + // @ts-ignore fullscreenEnabled = screenfull.enabled; @@ -63,6 +67,10 @@ export class HomeComponent extends PageComponent implements OnInit { userDetails$: Observable; userDetailsString: Observable; + searchEnabled = false; + showSearch = false; + searchText = ''; + constructor(protected store: Store, @Inject(WINDOW) private window: Window, private authService: AuthService, @@ -98,6 +106,18 @@ export class HomeComponent extends PageComponent implements OnInit { ); } + ngAfterViewInit() { + fromEvent(this.searchInputField.nativeElement, 'keyup') + .pipe( + debounceTime(150), + distinctUntilChanged(), + tap(() => { + this.searchTextUpdated(); + }) + ) + .subscribe(); + } + sidenavClicked() { if (this.sidenavMode === 'over') { this.sidenav.toggle(); @@ -120,4 +140,47 @@ export class HomeComponent extends PageComponent implements OnInit { goBack() { this.window.history.back(); } + + activeComponentChanged(activeComponent: any) { + this.showSearch = false; + this.searchText = ''; + this.activeComponent = activeComponent; + if (this.activeComponent && instanceOfSearchableComponent(this.activeComponent)) { + this.searchEnabled = true; + this.searchableComponent = this.activeComponent; + } else { + this.searchEnabled = false; + this.searchableComponent = null; + } + } + + displaySearchMode(): boolean { + return this.searchEnabled && this.showSearch; + } + + openSearch() { + if (this.searchEnabled) { + this.showSearch = true; + setTimeout(() => { + this.searchInputField.nativeElement.focus(); + this.searchInputField.nativeElement.setSelectionRange(0, 0); + }, 10); + } + } + + closeSearch() { + if (this.searchEnabled) { + this.showSearch = false; + if (this.searchText.length) { + this.searchText = ''; + this.searchTextUpdated(); + } + } + } + + private searchTextUpdated() { + if (this.searchableComponent) { + this.searchableComponent.onSearchTextUpdated(this.searchText); + } + } } diff --git a/ui-ngx/src/app/modules/home/models/searchable-component.models.ts b/ui-ngx/src/app/modules/home/models/searchable-component.models.ts new file mode 100644 index 0000000000..caca6283c5 --- /dev/null +++ b/ui-ngx/src/app/modules/home/models/searchable-component.models.ts @@ -0,0 +1,23 @@ +/// +/// Copyright © 2016-2019 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 interface ISearchableComponent { + onSearchTextUpdated(searchText: string); +} + +export function instanceOfSearchableComponent(object: any): object is ISearchableComponent { + return 'onSearchTextUpdated' in object; +} 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 8c9cecaaa1..2525745ec8 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 @@ -30,7 +30,7 @@ - -
+
+ + + + {{ ruleNodeTypeDescriptorsMap.get(ruleNodeType).icon }} +
{{ ruleNodeTypeDescriptorsMap.get(ruleNodeType).name }}
+
+
+ + +
+
- + [nodeWidth]="170" + [nodeHeight]="50" + [dragAnimation]="flowchartConstants.dragAnimationRepaint" + [userCallbacks]="editCallbacks">
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 20f7bb1b3d..9743609b04 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 @@ -73,6 +73,32 @@ } } } + .tb-rulechain-library-panel-group { + overflow-x: hidden; + overflow-y: auto; + .mat-expansion-panel { + border-radius: 0px; + &:last-child { + margin-bottom: 5px; + } + .mat-expansion-panel-header { + background: #e6e6e6; + } + &.mat-expanded { + .mat-expansion-panel-header { + border-bottom: 1px solid; + border-color: #909090; + } + } + } + .tb-panel-title { + min-width: 140px; + user-select: none; + } + .fc-canvas { + background: #f9f9f9; + } + } } .tb-rulechain-graph { z-index: 0; @@ -99,6 +125,11 @@ } } } + .tb-rulechain-library-panel-group { + .mat-expansion-panel-body { + padding: 0; + } + } } } .fc-canvas { 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 0a3d7f2f24..9e05502f19 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 @@ -14,14 +14,14 @@ /// limitations under the License. /// -import { Component, OnInit } from '@angular/core'; +import { AfterViewInit, Component, ElementRef, OnInit, QueryList, ViewChild, ViewChildren } from '@angular/core'; import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { FormBuilder } from '@angular/forms'; import { HasDirtyFlag } from '@core/guards/confirm-on-exit.guard'; import { TranslateService } from '@ngx-translate/core'; -import { MatDialog } from '@angular/material'; +import { MatDialog, MatExpansionPanel } from '@angular/material'; import { DialogService } from '@core/services/dialog.service'; import { AuthService } from '@core/auth/auth.service'; import { ActivatedRoute, Router } from '@angular/router'; @@ -31,26 +31,46 @@ import { RuleChain, ruleChainNodeComponent } from '@shared/models/rule-chain.models'; -import { FlowchartConstants } from 'ngx-flowchart/dist/ngx-flowchart'; -import { RuleNodeComponentDescriptor, RuleNodeType, ruleNodeTypeDescriptors } from '@shared/models/rule-node.models'; +import { FlowchartConstants, UserCallbacks, NgxFlowchartComponent } from 'ngx-flowchart/dist/ngx-flowchart'; +import { + RuleNodeComponentDescriptor, + RuleNodeType, + ruleNodeTypeDescriptors, + ruleNodeTypesLibrary +} from '@shared/models/rule-node.models'; import { FcRuleEdge, FcRuleNode, FcRuleNodeModel, FcRuleNodeType, FcRuleNodeTypeModel } from './rulechain-page.models'; import { RuleChainService } from '@core/http/rule-chain.service'; +import { fromEvent, of } from 'rxjs'; +import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators'; +import Timeout = NodeJS.Timeout; +import { ISearchableComponent } from '../../models/searchable-component.models'; @Component({ selector: 'tb-rulechain-page', templateUrl: './rulechain-page.component.html', styleUrls: ['./rulechain-page.component.scss'] }) -export class RuleChainPageComponent extends PageComponent implements OnInit, HasDirtyFlag { +export class RuleChainPageComponent extends PageComponent + implements AfterViewInit, OnInit, HasDirtyFlag, ISearchableComponent { get isDirty(): boolean { return this.isDirtyValue || this.isImport; } + @ViewChild('ruleNodeSearchInput', {static: false}) ruleNodeSearchInputField: ElementRef; + + @ViewChild('ruleChainCanvas', {static: true}) ruleChainCanvas: NgxFlowchartComponent; + + @ViewChildren('ruleNodeTypeExpansionPanels', + {read: MatExpansionPanel}) expansionPanels: QueryList; + + ruleNodeTypeDescriptorsMap = ruleNodeTypeDescriptors; + ruleNodeTypesLibraryArray = ruleNodeTypesLibrary; + isImport: boolean; isDirtyValue: boolean; - errorTooltips = {}; + errorTooltips: {[nodeId: string]: JQueryTooltipster.ITooltipsterInstance} = {}; isFullscreen = false; editingRuleNode = null; @@ -62,6 +82,7 @@ export class RuleChainPageComponent extends PageComponent implements OnInit, Has isLibraryOpen = true; ruleNodeSearch = ''; + ruleNodeTypeSearch = ''; ruleChain: RuleChain; ruleChainMetaData: ResolvedRuleChainMetaData; @@ -71,16 +92,58 @@ export class RuleChainPageComponent extends PageComponent implements OnInit, Has edges: [] }; selectedObjects = []; + + editCallbacks: UserCallbacks = { + edgeDoubleClick: (event, edge) => { + console.log('TODO'); + }, + edgeEdit: (event, edge) => { + console.log('TODO'); + }, + nodeCallbacks: { + doubleClick: (event, node) => { + console.log('TODO'); + }, + nodeEdit: (event, node) => { + console.log('TODO'); + }, + mouseEnter: this.displayNodeDescriptionTooltip.bind(this), + mouseLeave: this.destroyTooltips.bind(this), + mouseDown: this.destroyTooltips.bind(this) + }, + isValidEdge: (source, destination) => { + return source.type === FlowchartConstants.rightConnectorType && destination.type === FlowchartConstants.leftConnectorType; + }, + createEdge: (event, edge) => { + console.log('TODO'); + return of(edge); + }, + dropNode: (event, node) => { + console.log('TODO dropNode'); + console.log(node); + } + }; + nextNodeID: number; nextConnectorID: number; inputConnectorId: number; ruleNodeTypesModel: {[type: string]: {model: FcRuleNodeTypeModel, selectedObjects: any[]}} = {}; + nodeLibCallbacks: UserCallbacks = { + nodeCallbacks: { + mouseEnter: this.displayLibNodeDescriptionTooltip.bind(this), + mouseLeave: this.destroyTooltips.bind(this), + mouseDown: this.destroyTooltips.bind(this) + } + }; + ruleNodeComponents: Array; flowchartConstants = FlowchartConstants; + private tooltipTimeout: Timeout; + constructor(protected store: Store, private route: ActivatedRoute, private router: Router, @@ -97,6 +160,23 @@ export class RuleChainPageComponent extends PageComponent implements OnInit, Has ngOnInit() { } + ngAfterViewInit() { + fromEvent(this.ruleNodeSearchInputField.nativeElement, 'keyup') + .pipe( + debounceTime(150), + distinctUntilChanged(), + tap(() => { + this.updateRuleChainLibrary(); + }) + ) + .subscribe(); + } + + onSearchTextUpdated(searchText: string) { + this.ruleNodeSearch = searchText; + this.updateRuleNodesHighlight(); + } + private init() { this.ruleChain = this.route.snapshot.data.ruleChain; if (this.route.snapshot.data.import && !this.ruleChain) { @@ -106,8 +186,8 @@ export class RuleChainPageComponent extends PageComponent implements OnInit, Has this.isImport = this.route.snapshot.data.import; this.ruleChainMetaData = this.route.snapshot.data.ruleChainMetaData; this.ruleNodeComponents = this.route.snapshot.data.ruleNodeComponents; - for (const type of Object.keys(RuleNodeType)) { - const desc = ruleNodeTypeDescriptors.get(RuleNodeType[type]); + for (const type of ruleNodeTypesLibrary) { + const desc = ruleNodeTypeDescriptors.get(type); if (!desc.special) { this.ruleNodeTypesModel[type] = { model: { @@ -118,10 +198,17 @@ export class RuleChainPageComponent extends PageComponent implements OnInit, Has }; } } - this.loadRuleChainLibrary(this.ruleNodeComponents); + this.updateRuleChainLibrary(); this.createRuleChainModel(); } + private updateRuleChainLibrary() { + const search = this.ruleNodeTypeSearch.toUpperCase(); + const res = this.ruleNodeComponents.filter( + (ruleNodeComponent) => ruleNodeComponent.name.toUpperCase().includes(search)); + this.loadRuleChainLibrary(res); + } + private loadRuleChainLibrary(ruleNodeComponents: Array) { for (const componentType of Object.keys(this.ruleNodeTypesModel)) { this.ruleNodeTypesModel[componentType].model.nodes.length = 0; @@ -167,6 +254,21 @@ export class RuleChainPageComponent extends PageComponent implements OnInit, Has } model.nodes.push(node); }); + if (this.expansionPanels) { + for (let i = 0; i < ruleNodeTypesLibrary.length; i++) { + const panel = this.expansionPanels.find((item, index) => { + return index === i; + }); + if (panel) { + const type = ruleNodeTypesLibrary[i]; + if (!this.ruleNodeTypesModel[type].model.nodes.length) { + panel.close(); + } else { + panel.open(); + } + } + } + } } private createRuleChainModel() { @@ -350,5 +452,115 @@ export class RuleChainPageComponent extends PageComponent implements OnInit, Has this.isDirtyValue = true; } + typeHeaderMouseEnter(event: MouseEvent, ruleNodeType: RuleNodeType) { + const type = ruleNodeTypeDescriptors.get(ruleNodeType); + this.displayTooltip(event, + '
' + + '
' + + '
' + this.translate.instant(type.name) + '
' + + '
' + this.translate.instant(type.details) + '
' + + '
' + + '
' + ); + } + + displayLibNodeDescriptionTooltip(event: MouseEvent, node: FcRuleNodeType) { + this.displayTooltip(event, + '
' + + '
' + + '
' + node.component.name + '
' + + '
' + node.component.configurationDescriptor.nodeDefinition.description + '
' + + '
' + node.component.configurationDescriptor.nodeDefinition.details + '
' + + '
' + + '
' + ); + } + + displayNodeDescriptionTooltip(event: MouseEvent, node: FcRuleNode) { + if (!this.errorTooltips[node.id]) { + let name: string; + let desc: string; + let details: string; + if (node.component.type === RuleNodeType.INPUT) { + name = this.translate.instant(ruleNodeTypeDescriptors.get(RuleNodeType.INPUT).name); + desc = this.translate.instant(ruleNodeTypeDescriptors.get(RuleNodeType.INPUT).details); + } else { + name = node.name; + desc = this.translate.instant(ruleNodeTypeDescriptors.get(node.component.type).name) + ' - ' + node.component.name; + if (node.additionalInfo) { + details = node.additionalInfo.description; + } + } + let tooltipContent = '
' + + '
' + + '
' + name + '
' + + '
' + desc + '
'; + if (details) { + tooltipContent += '
' + details + '
'; + } + tooltipContent += '
' + + '
'; + this.displayTooltip(event, tooltipContent); + } + } + + destroyTooltips() { + if (this.tooltipTimeout) { + clearTimeout(this.tooltipTimeout); + this.tooltipTimeout = null; + } + const instances = $.tooltipster.instances(); + instances.forEach((instance) => { + if (!instance.isErrorTooltip) { + instance.destroy(); + } + }); + } + + updateRuleNodesHighlight() { + for (const ruleNode of this.ruleChainModel.nodes) { + ruleNode.highlighted = false; + } + if (this.ruleNodeSearch) { + const search = this.ruleNodeSearch.toUpperCase(); + const res = this.ruleChainModel.nodes.filter(node => node.name.toUpperCase().includes(search)); + if (res) { + for (const ruleNode of res) { + ruleNode.highlighted = true; + } + } + } + this.ruleChainCanvas.modelService.detectChanges(); + } + + private displayTooltip(event: MouseEvent, content: string) { + this.destroyTooltips(); + this.tooltipTimeout = setTimeout(() => { + const element = $(event.target); + element.tooltipster( + { + theme: 'tooltipster-shadow', + delay: 100, + trigger: 'custom', + triggerOpen: { + click: false, + tap: false + }, + triggerClose: { + click: true, + tap: true, + scroll: true + }, + side: 'right', + trackOrigin: true + } + ); + const contentElement = $(content); + const tooltip = element.tooltipster('instance'); + tooltip.content(contentElement); + tooltip.open(); + }, 500); + } + } diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.models.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.models.ts index c7e972625b..20cb4d1dba 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.models.ts +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.models.ts @@ -37,6 +37,7 @@ export interface FcRuleNode extends FcRuleNodeType { debugMode?: boolean; targetRuleChainId?: string; error?: string; + highlighted?: boolean; } export interface FcRuleEdge extends FcEdge { diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.tooltipster.scss b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.tooltipster.scss new file mode 100644 index 0000000000..afb53cdeff --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.tooltipster.scss @@ -0,0 +1,70 @@ +/** + * Copyright © 2016-2019 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. + */ +.tooltipster-base { + .tb-rule-node-tooltip, + .tb-rule-node-help { + color: #333; + } + + .tb-rule-node-tooltip { + max-width: 300px; + font-size: 14px; + + &.tb-lib-tooltip { + width: 300px; + } + } + + .tb-rule-node-help { + font-size: 16px; + } + + .tb-rule-node-error-tooltip { + font-size: 16px; + color: #ea0d0d; + } + + .tb-rule-node-tooltip, + .tb-rule-node-error-tooltip, + .tb-rule-node-help { + #tb-node-content { + .tb-node-title { + font-weight: 600; + } + + .tb-node-description { + font-style: italic; + color: #555; + } + + .tb-node-details { + padding-top: 10px; + padding-bottom: 10px; + } + + code { + padding: 0 3px 2px 3px; + margin: 1px; + font-size: 12px; + color: #ad1625; + white-space: nowrap; + background-color: #f7f7f9; + border: 1px solid #e1e1e8; + border-radius: 2px; + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulenode.component.html b/ui-ngx/src/app/modules/home/pages/rulechain/rulenode.component.html index e08b8e5cd2..65ca7d13b4 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulenode.component.html +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulenode.component.html @@ -22,8 +22,8 @@ (mouseleave)="userNodeCallbacks.mouseLeave($event, node)">
- {{node.icon}} - + {{node.icon}} +
{{ node.component.name }} {{ node.name }} diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulenode.component.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rulenode.component.ts index eaa85f30ab..d6bb3cc588 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulenode.component.ts +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulenode.component.ts @@ -14,7 +14,8 @@ /// limitations under the License. /// -import { Component } from '@angular/core'; +import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'; +import { Component, OnInit } from '@angular/core'; import { FcNodeComponent } from 'ngx-flowchart/dist/ngx-flowchart'; @Component({ @@ -22,10 +23,19 @@ import { FcNodeComponent } from 'ngx-flowchart/dist/ngx-flowchart'; templateUrl: './rulenode.component.html', styleUrls: ['./rulenode.component.scss'] }) -export class RuleNodeComponent extends FcNodeComponent { +export class RuleNodeComponent extends FcNodeComponent implements OnInit { - constructor() { + iconUrl: SafeResourceUrl; + + constructor(private sanitizer: DomSanitizer) { super(); } + ngOnInit(): void { + super.ngOnInit(); + if (this.node.iconUrl) { + this.iconUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.node.iconUrl); + } + } + } 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 fa75276e96..2aa77791f6 100644 --- a/ui-ngx/src/app/shared/models/rule-node.models.ts +++ b/ui-ngx/src/app/shared/models/rule-node.models.ts @@ -70,6 +70,15 @@ export enum RuleNodeType { INPUT = 'INPUT' } +export const ruleNodeTypesLibrary = [ + RuleNodeType.FILTER, + RuleNodeType.ENRICHMENT, + RuleNodeType.TRANSFORMATION, + RuleNodeType.ACTION, + RuleNodeType.EXTERNAL, + RuleNodeType.RULE_CHAIN, +]; + export interface RuleNodeTypeDescriptor { value: RuleNodeType; name: string; diff --git a/ui-ngx/src/tsconfig.app.json b/ui-ngx/src/tsconfig.app.json index 4691de678a..d32504308a 100644 --- a/ui-ngx/src/tsconfig.app.json +++ b/ui-ngx/src/tsconfig.app.json @@ -2,7 +2,7 @@ "extends": "../tsconfig.json", "compilerOptions": { "outDir": "../out-tsc/app", - "types": ["node", "jquery", "flot", "tinycolor2", "js-beautify", "react", "react-dom"] + "types": ["node", "jquery", "flot", "tooltipster", "tinycolor2", "js-beautify", "react", "react-dom"] }, "exclude": [ "test.ts", From 27984f85cdaed250306be2713ee2c673b105a1df Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 13 Dec 2019 15:49:46 +0200 Subject: [PATCH 058/133] RuleChain UI --- .../rule-node-details.component.html | 46 ++++ .../rulechain/rule-node-details.component.ts | 70 +++++ .../rulechain/rulechain-page.component.html | 106 +++++++- .../rulechain/rulechain-page.component.scss | 244 +++++++++++++----- .../rulechain/rulechain-page.component.ts | 121 ++++++++- 5 files changed, 497 insertions(+), 90 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.ts 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 new file mode 100644 index 0000000000..4770b90fe3 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.html @@ -0,0 +1,46 @@ + +
+
+
+
+
+ + rulenode.name + + + {{ 'rulenode.name-required' | translate }} + + + + {{ 'rulenode.debug-mode' | translate }} + +
+ TODO: tb-rule-node-config +
+ + rulenode.description + + +
+
+
+ + +
+ + rulenode.description + + +
+
+
+
+
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 new file mode 100644 index 0000000000..b3995a5f09 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.ts @@ -0,0 +1,70 @@ +import { + AfterViewInit, + Component, + EventEmitter, + Input, + OnInit, + Output, + ViewChild, + ViewEncapsulation +} from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { HasDirtyFlag } from '@core/guards/confirm-on-exit.guard'; +import { ISearchableComponent } from '@home/models/searchable-component.models'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { FormBuilder, FormGroup, NgForm } from '@angular/forms'; +import { FcRuleNode } from './rulechain-page.models'; +import { RuleNodeType } from '@shared/models/rule-node.models'; +import { EntityType } from '@shared/models/entity-type.models'; + +@Component({ + selector: 'tb-rule-node', + templateUrl: './rule-node-details.component.html', + styleUrls: [] +}) +export class RuleNodeDetailsComponent extends PageComponent implements OnInit { + + @ViewChild('ruleNodeForm', {static: true}) ruleNodeForm: NgForm; + + ruleNodeValue: FcRuleNode; + + @Input() + set ruleNode(ruleNode: FcRuleNode) { + this.ruleNodeValue = ruleNode; + } + + @Input() + ruleChainId: string; + + @Input() + isEdit: boolean; + + @Input() + isReadOnly: boolean; + + @Output() + deleteRuleNode = new EventEmitter(); + + ruleNodeType = RuleNodeType; + entityType = EntityType; + + ruleNodeFormGroup: FormGroup; + + constructor(protected store: Store, + private fb: FormBuilder) { + super(store); + this.ruleNodeFormGroup = this.fb.group({}); + } + + private buildForm() { + } + + ngOnInit(): void { + } + + onDeleteRuleNode(event) { + this.deleteRuleNode.emit(); + } + +} 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 2525745ec8..0b0a7507cc 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 @@ -17,6 +17,14 @@ -->
+
-
- - -
+ + +
+ + +
+ +
+ + +
+
+
+ + + + + + +
+
+
{{editingRuleNode.component.name}}
+
 
+
{{editingRuleNode.component.configurationDescriptor.nodeDefinition.description}}
+
 
+
+
+
+
+
+
+
+
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 9743609b04..4a8fbeb404 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 @@ -14,99 +14,203 @@ * limitations under the License. */ -:host { +.tb-rulechain { width: 100%; height: 100%; - .tb-rulechain { - width: 100%; - height: 100%; - section.tb-header-buttons.tb-library-open { - position: absolute; - top: 0; - left: 0; - z-index: 2; - pointer-events: none; + button.mat-button.mat-icon-button.tb-fullscreen-button { + position: absolute; + top: 10px; + right: 10px; + background: #ccc; + color: #666; + opacity: .85; + margin: 0 6px; + z-index: 2; + } + section.tb-header-buttons.tb-library-open { + position: absolute; + top: 0; + left: 0; + z-index: 2; + pointer-events: none; - .mat-button.tb-btn-open-library { - top: 0; - left: 0; - width: 36px; - height: 36px; - margin: 4px 0 0 4px; - line-height: 36px; - opacity: .5; + .mat-fab { + .mat-button-wrapper { + padding: 0; } } - .tb-rulechain-library { - z-index: 1; - width: 250px; - min-width: 250px; - .mat-toolbar { + .mat-button.tb-btn-open-library { + top: 0; + left: 0; + width: 36px; + height: 36px; + margin: 4px 0 0 4px; + line-height: 36px; + opacity: .5; + } + } + + .tb-rulechain-library { + z-index: 1; + width: 250px; + min-width: 250px; + + .mat-toolbar { + height: 48px; + min-height: 48px; + padding: 0; + + .mat-toolbar-tools { height: 48px; - min-height: 48px; - padding: 0; + padding: 0 6px; + font-size: 14px; - .mat-toolbar-tools { - height: 48px; - padding: 0 6px; - font-size: 14px; - - .mat-button.mat-icon-button { - margin: 0; - - &.tb-small { - width: 32px; - height: 32px; - min-height: 32px; - padding: 6px; + .mat-form-field { + .mat-form-field-infix { + width: auto; + } + } + + .mat-button.mat-icon-button { + margin: 0; + + &.tb-small { + width: 32px; + height: 32px; + min-height: 32px; + padding: 6px; + line-height: 20px; + mat-icon { + width: 20px; + min-width: 20px; + height: 20px; + min-height: 20px; + font-size: 20px; line-height: 20px; - mat-icon { - width: 20px; - min-width: 20px; - height: 20px; - min-height: 20px; - font-size: 20px; - line-height: 20px; - } } } } } - .tb-rulechain-library-panel-group { - overflow-x: hidden; - overflow-y: auto; - .mat-expansion-panel { - border-radius: 0px; - &:last-child { - margin-bottom: 5px; + } + .tb-rulechain-library-panel-group { + overflow-x: hidden; + overflow-y: auto; + .mat-expansion-panel { + border-radius: 0px; + &:last-child { + margin-bottom: 5px; + } + .mat-expansion-panel-header { + background: #e6e6e6; + &:hover { + background: #dadada; } + } + &.mat-expanded { .mat-expansion-panel-header { - background: #e6e6e6; + border-bottom: 1px solid; + border-color: #909090; } - &.mat-expanded { - .mat-expansion-panel-header { - border-bottom: 1px solid; - border-color: #909090; - } - } - } - .tb-panel-title { - min-width: 140px; - user-select: none; } - .fc-canvas { - background: #f9f9f9; + } + .mat-expansion-panel-body { + padding: 0; + } + .tb-panel-title { + min-width: 140px; + user-select: none; + } + .fc-canvas { + background: #f9f9f9; + } + } + } + .tb-rulechain-graph { + z-index: 0; + } + .fc-canvas { + .fc-node { + border-radius: 8px; + &.fc-selected { + &:not(.fc-edit) { + margin: -3px; + border: solid 3px #f00; } } } - .tb-rulechain-graph { - z-index: 0; + + .fc-edit { + .fc-nodeedit, + .fc-nodedelete { + border: solid 2px #fff; + background: #f83e05; + outline: none; + } + } + + .fc-arrow-marker { + polygon { + fill: #808080; + stroke: #808080; + } + } + + .fc-arrow-marker-selected { + polygon { + fill: #f00; + stroke: #f00; + } + } + + .fc-edge { + outline: none; + stroke: #808080; + + &.fc-selected { + stroke: #f00; + } + + &.fc-hover { + stroke: #808080; + } + } + + .edge-endpoint { + fill: #808080; + } + + .fc-edge-label { + opacity: 1 !important; + &:focus { + outline: 0; + } + + &.fc-selected { + .fc-edge-label-text { + span { + color: #fff !important; + background-color: #f00 !important; + border: solid #f00 !important; + } + } + } + + .fc-edge-label-text { + font-size: 14px !important; + font-weight: 600 !important; + + span { + background-color: #fff !important; + color: #003a79 !important; + border: solid 2px #003a79 !important; + } + } } } } -:host ::ng-deep { +/*:host ::ng-deep { .tb-rulechain { section.tb-header-buttons.tb-library-open { .mat-fab { @@ -211,4 +315,4 @@ } } } -} +}*/ 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 9e05502f19..3d1d0d8712 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 @@ -14,7 +14,17 @@ /// limitations under the License. /// -import { AfterViewInit, Component, ElementRef, OnInit, QueryList, ViewChild, ViewChildren } from '@angular/core'; +import { + AfterViewInit, + Component, + ElementRef, + OnInit, + QueryList, + ViewChild, + ViewChildren, + ViewEncapsulation, + HostBinding +} from '@angular/core'; import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; @@ -31,7 +41,7 @@ import { RuleChain, ruleChainNodeComponent } from '@shared/models/rule-chain.models'; -import { FlowchartConstants, UserCallbacks, NgxFlowchartComponent } from 'ngx-flowchart/dist/ngx-flowchart'; +import { FlowchartConstants, NgxFlowchartComponent, UserCallbacks } from 'ngx-flowchart/dist/ngx-flowchart'; import { RuleNodeComponentDescriptor, RuleNodeType, @@ -42,13 +52,14 @@ import { FcRuleEdge, FcRuleNode, FcRuleNodeModel, FcRuleNodeType, FcRuleNodeType import { RuleChainService } from '@core/http/rule-chain.service'; import { fromEvent, of } from 'rxjs'; import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators'; -import Timeout = NodeJS.Timeout; import { ISearchableComponent } from '../../models/searchable-component.models'; +import Timeout = NodeJS.Timeout; @Component({ selector: 'tb-rulechain-page', templateUrl: './rulechain-page.component.html', - styleUrls: ['./rulechain-page.component.scss'] + styleUrls: ['./rulechain-page.component.scss'], + encapsulation: ViewEncapsulation.None }) export class RuleChainPageComponent extends PageComponent implements AfterViewInit, OnInit, HasDirtyFlag, ISearchableComponent { @@ -57,6 +68,9 @@ export class RuleChainPageComponent extends PageComponent return this.isDirtyValue || this.isImport; } + @HostBinding('style.width') width = '100%'; + @HostBinding('style.height') height = '100%'; + @ViewChild('ruleNodeSearchInput', {static: false}) ruleNodeSearchInputField: ElementRef; @ViewChild('ruleChainCanvas', {static: true}) ruleChainCanvas: NgxFlowchartComponent; @@ -69,6 +83,7 @@ export class RuleChainPageComponent extends PageComponent isImport: boolean; isDirtyValue: boolean; + isInvalid = false; errorTooltips: {[nodeId: string]: JQueryTooltipster.ITooltipsterInstance} = {}; isFullscreen = false; @@ -445,11 +460,14 @@ export class RuleChainPageComponent extends PageComponent }); } this.isDirtyValue = false; + this.updateRuleNodesHighlight(); + this.validate(); } onModelChanged() { console.log('Model changed!'); this.isDirtyValue = true; + this.validate(); } typeHeaderMouseEnter(event: MouseEvent, ruleNodeType: RuleNodeType) { @@ -517,7 +535,7 @@ export class RuleChainPageComponent extends PageComponent }); } - updateRuleNodesHighlight() { + private updateRuleNodesHighlight() { for (const ruleNode of this.ruleChainModel.nodes) { ruleNode.highlighted = false; } @@ -530,7 +548,98 @@ export class RuleChainPageComponent extends PageComponent } } } - this.ruleChainCanvas.modelService.detectChanges(); + if (this.ruleChainCanvas) { + this.ruleChainCanvas.modelService.detectChanges(); + } + } + + objectsSelected(): boolean { + return this.ruleChainCanvas.modelService.nodes.getSelectedNodes().length > 0 || + this.ruleChainCanvas.modelService.edges.getSelectedEdges().length > 0; + } + + deleteSelected() { + this.ruleChainCanvas.modelService.deleteSelected(); + } + + isDebugModeEnabled(): boolean { + const res = this.ruleChainModel.nodes.find((node) => node.debugMode); + return typeof res !== 'undefined'; + } + + resetDebugModeInAllNodes() { + this.ruleChainModel.nodes.forEach((node) => { + if (node.component.type !== RuleNodeType.INPUT && node.component.type !== RuleNodeType.RULE_CHAIN) { + node.debugMode = false; + } + }); + } + + validate() { + setTimeout(() => { + this.isInvalid = false; + this.ruleChainModel.nodes.forEach((node) => { + if (node.error) { + this.isInvalid = true; + } + this.updateNodeErrorTooltip(node); + }); + }, 0); + } + + saveRuleChain() { + // TODO: + } + + revertRuleChain() { + this.createRuleChainModel(); + } + + private updateNodeErrorTooltip(node: FcRuleNode) { + if (node.error) { + const element = $('#' + node.id); + let tooltip = this.errorTooltips[node.id]; + if (!tooltip || !element.hasClass('tooltipstered')) { + element.tooltipster( + { + theme: 'tooltipster-shadow', + delay: 0, + animationDuration: 0, + trigger: 'custom', + triggerOpen: { + click: false, + tap: false + }, + triggerClose: { + click: false, + tap: false, + scroll: false + }, + side: 'top', + trackOrigin: true + } + ); + const content = '
' + + '
' + + '
' + node.error + '
' + + '
' + + '
'; + const contentElement = $(content); + tooltip = element.tooltipster('instance'); + tooltip.isErrorTooltip = true; + tooltip.content(contentElement); + this.errorTooltips[node.id] = tooltip; + } + setTimeout(() => { + tooltip.open(); + }, 0); + } else { + if (this.errorTooltips[node.id]) { + const tooltip = this.errorTooltips[node.id]; + tooltip.destroy(); + delete this.errorTooltips[node.id]; + } + } } private displayTooltip(event: MouseEvent, content: string) { From 70e81e84ebe2a2748cff09d9a1967a0eb074f7b9 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 17 Dec 2019 20:16:40 +0200 Subject: [PATCH 059/133] RuleChain UI --- msa/js-executor/package-lock.json | 23 +- ui-ngx/angular.json | 1 - .../src/app/core/http/rule-chain.service.ts | 18 +- .../components/details-panel.component.html | 6 +- .../components/details-panel.component.scss | 6 + .../add-rule-node-link-dialog.component.html | 56 ++++ .../add-rule-node-link-dialog.component.scss | 22 ++ .../rulechain/link-labels.component.html | 65 +++++ .../pages/rulechain/link-labels.conponent.ts | 271 ++++++++++++++++++ .../rule-node-details.component.html | 25 +- .../rulechain/rule-node-details.component.ts | 111 +++++-- .../rulechain/rule-node-link.component.html | 28 ++ .../rulechain/rule-node-link.component.ts | 135 +++++++++ .../rulechain/rulechain-page.component.html | 209 ++++++++------ .../rulechain/rulechain-page.component.scss | 143 +++------ .../rulechain/rulechain-page.component.ts | 246 ++++++++++++++-- .../rulechain/rulechain-page.tooltipster.scss | 70 ----- .../home/pages/rulechain/rulechain.module.ts | 14 +- ui-ngx/src/app/shared/models/constants.ts | 34 +++ .../src/app/shared/models/rule-node.models.ts | 57 ++++ 20 files changed, 1217 insertions(+), 323 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/pages/rulechain/add-rule-node-link-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/rulechain/add-rule-node-link-dialog.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/rulechain/link-labels.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/rulechain/link-labels.conponent.ts create mode 100644 ui-ngx/src/app/modules/home/pages/rulechain/rule-node-link.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/rulechain/rule-node-link.component.ts delete mode 100644 ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.tooltipster.scss diff --git a/msa/js-executor/package-lock.json b/msa/js-executor/package-lock.json index c61ef011a2..bc9fdf512d 100644 --- a/msa/js-executor/package-lock.json +++ b/msa/js-executor/package-lock.json @@ -1407,12 +1407,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1427,17 +1429,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -1554,7 +1559,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -1566,6 +1572,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -1580,6 +1587,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -1691,7 +1699,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -1703,6 +1712,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -1824,6 +1834,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", diff --git a/ui-ngx/angular.json b/ui-ngx/angular.json index a01dbb4b75..c68e36a90b 100644 --- a/ui-ngx/angular.json +++ b/ui-ngx/angular.json @@ -36,7 +36,6 @@ "node_modules/tooltipster/dist/css/tooltipster.bundle.min.css", "node_modules/tooltipster/dist/css/plugins/tooltipster/sideTip/themes/tooltipster-sideTip-shadow.min.css", "src/app/shared/components/json-form/react/json-form.scss", - "src/app/modules/home/pages/rulechain/rulechain-page.tooltipster.scss", "node_modules/rc-select/assets/index.css" ], "stylePreprocessorOptions": { diff --git a/ui-ngx/src/app/core/http/rule-chain.service.ts b/ui-ngx/src/app/core/http/rule-chain.service.ts index a6ff059659..8619c0e3f6 100644 --- a/ui-ngx/src/app/core/http/rule-chain.service.ts +++ b/ui-ngx/src/app/core/http/rule-chain.service.ts @@ -28,7 +28,7 @@ import { ruleNodeTypeComponentTypes, unknownNodeComponent } from '@shared/models/rule-chain.models'; import { ComponentDescriptorService } from './component-descriptor.service'; -import { RuleNodeComponentDescriptor } from '@app/shared/models/rule-node.models'; +import { LinkLabel, RuleNodeComponentDescriptor } from '@app/shared/models/rule-node.models'; import { ResourcesService } from '../services/resources.service'; import { catchError, map, mergeMap } from 'rxjs/operators'; import { TranslateService } from '@ngx-translate/core'; @@ -144,6 +144,22 @@ export class RuleChainService { } } + public getRuleNodeSupportedLinks(component: RuleNodeComponentDescriptor): {[label: string]: LinkLabel} { + const relationTypes = component.configurationDescriptor.nodeDefinition.relationTypes; + const linkLabels: {[label: string]: LinkLabel} = {}; + relationTypes.forEach((label) => { + linkLabels[label] = { + name: label, + value: label + }; + }); + return linkLabels; + } + + public ruleNodeAllowCustomLinks(component: RuleNodeComponentDescriptor): boolean { + return component.configurationDescriptor.nodeDefinition.customRelations; + } + private resolveTargetRuleChains(ruleChainConnections: Array): Observable<{[ruleChainId: string]: RuleChain}> { if (ruleChainConnections && ruleChainConnections.length) { const tasks: Observable[] = []; 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 069f9d5aff..d489ccef2a 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 @@ -16,9 +16,9 @@ -->
- -
-
+ +
+
{{ 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 b8367a6777..65119c89c7 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 @@ -20,6 +20,9 @@ height: 100%; display: flex; flex-direction: column; + .mat-toolbar.details-toolbar { + padding: 0; + } } :host ::ng-deep { @@ -27,6 +30,9 @@ height: 100%; min-height: 100px; max-height: 120px; + &.tb-details-title-header { + min-width: 0; + } } .tb-details-title { width: inherit; diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/add-rule-node-link-dialog.component.html b/ui-ngx/src/app/modules/home/pages/rulechain/add-rule-node-link-dialog.component.html new file mode 100644 index 0000000000..832ecbd9ff --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/rulechain/add-rule-node-link-dialog.component.html @@ -0,0 +1,56 @@ + +
+ +

rulenode.add-link

+ +
+ +
+ + +
+
+ + +
+
+
+ + + +
+
diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/add-rule-node-link-dialog.component.scss b/ui-ngx/src/app/modules/home/pages/rulechain/add-rule-node-link-dialog.component.scss new file mode 100644 index 0000000000..5433dd0be9 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/rulechain/add-rule-node-link-dialog.component.scss @@ -0,0 +1,22 @@ +/** + * Copyright © 2016-2019 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 ::ng-deep { + tb-rule-node-link { + form { + overflow: hidden !important; + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/link-labels.component.html b/ui-ngx/src/app/modules/home/pages/rulechain/link-labels.component.html new file mode 100644 index 0000000000..5dd617479c --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/rulechain/link-labels.component.html @@ -0,0 +1,65 @@ + + + rulenode.link-labels + + + {{label.name}} + close + + + + + + + + +
+
+ rulenode.no-link-labels-found +
+ + + {{ translate.get('rulenode.no-link-label-matching', + {label: truncate.transform(searchText, true, 6, '...')}) | async }} + + + + rulenode.create-new-link-label + +
+
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/link-labels.conponent.ts b/ui-ngx/src/app/modules/home/pages/rulechain/link-labels.conponent.ts new file mode 100644 index 0000000000..c7303f145f --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/rulechain/link-labels.conponent.ts @@ -0,0 +1,271 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + AfterViewInit, + Component, ElementRef, + EventEmitter, forwardRef, + 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'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, NgForm, Validators } from '@angular/forms'; +import { FcRuleNode, FcRuleEdge } from './rulechain-page.models'; +import { RuleNodeType, LinkLabel } from '@shared/models/rule-node.models'; +import { EntityType } from '@shared/models/entity-type.models'; +import { Observable, of, Subscription } from 'rxjs'; +import { RuleChainService } from '@core/http/rule-chain.service'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { deepClone } from '@core/utils'; +import { EntityAlias } from '@shared/models/alias.models'; +import { TruncatePipe } from '@shared/pipe/truncate.pipe'; +import { MatChipList, MatAutocomplete, MatChipInputEvent, MatAutocompleteSelectedEvent } from '@angular/material'; +import { TranslateService } from '@ngx-translate/core'; +import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes'; +import { map, mergeMap, share, startWith } from 'rxjs/operators'; + +@Component({ + selector: 'tb-link-labels', + templateUrl: './link-labels.component.html', + styleUrls: [], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => LinkLabelsComponent), + multi: true + }] +}) +export class LinkLabelsComponent implements ControlValueAccessor, OnInit, OnChanges { + + @ViewChild('chipList', {static: false}) chipList: MatChipList; + @ViewChild('labelAutocomplete', {static: false}) matAutocomplete: MatAutocomplete; + @ViewChild('labelInput', {static: false}) labelInput: ElementRef; + + private requiredValue: boolean; + get required(): boolean { + return this.requiredValue; + } + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + @Input() + disabled: boolean; + + private allowCustomValue: boolean; + get allowCustom(): boolean { + return this.allowCustomValue; + } + @Input() + set allowCustom(value: boolean) { + this.allowCustomValue = coerceBooleanProperty(value); + this.separatorKeysCodes = this.allowCustomValue ? [ENTER, COMMA, SEMICOLON] : []; + } + + @Input() + allowedLabels: {[label: string]: LinkLabel}; + + linksFormGroup: FormGroup; + + modelValue: Array; + + private labelsList: Array = []; + + separatorKeysCodes: number[] = []; + + filteredLabels: Observable>; + + private labels: Array = []; + + private searchText = ''; + + private propagateChange = (v: any) => { }; + + constructor(private fb: FormBuilder, + public truncate: TruncatePipe, + public translate: TranslateService) { + this.linksFormGroup = this.fb.group({ + label: [null] + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + + ngOnInit(): void { + this.filteredLabels = this.linksFormGroup.get('label').valueChanges + .pipe( + startWith(''), + map((value) => value ? value : ''), + mergeMap(name => this.fetchLabels(name) ), + share() + ); + } + + ngOnChanges(changes: SimpleChanges): void { + let reloadLabels = false; + for (const propName of Object.keys(changes)) { + const change = changes[propName]; + if (change.currentValue !== change.previousValue) { + if (['allowCustom', 'allowedLabels'].includes(propName)) { + reloadLabels = true; + } + } + } + if (reloadLabels) { + this.linksFormGroup.get('label').patchValue('', {emitEvent: true}); + } + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.linksFormGroup.disable({emitEvent: false}); + } else { + this.linksFormGroup.enable({emitEvent: false}); + } + } + + writeValue(value: string[]): void { + this.searchText = ''; + this.labelsList.length = 0; + this.labels.length = 0; + this.modelValue = value; + for (const label of Object.keys(this.allowedLabels)) { + this.labelsList.push({name: this.allowedLabels[label].name, value: this.allowedLabels[label].value}); + } + if (value) { + value.forEach((label) => { + if (this.allowedLabels[label]) { + this.labels.push(deepClone(this.allowedLabels[label])); + } else { + this.labels.push({ + name: label, + value: label + }); + } + }); + } + if (this.chipList && this.required) { + this.chipList.errorState = this.labels.length ? false : true; + } + this.linksFormGroup.get('label').patchValue('', {emitEvent: true}); + } + + displayLabelFn(label?: LinkLabel): string | undefined { + return label ? label.name : undefined; + } + + textIsNotEmpty(text: string): boolean { + return (text && text != null && text.length > 0) ? true : false; + } + + createLinkLabel($event: Event, value: string) { + $event.preventDefault(); + this.transformLinkLabel(value); + } + + add(event: MatChipInputEvent): void { + if (!this.matAutocomplete.isOpen) { + this.transformLinkLabel(event.value); + } + } + + private fetchLabels(searchText?: string): Observable> { + this.searchText = searchText; + if (this.searchText && this.searchText.length) { + const search = this.searchText.toUpperCase(); + return of(this.labelsList.filter(label => label.name.toUpperCase().includes(search))); + } else { + return of(this.labelsList); + } + } + + private transformLinkLabel(value: string) { + if ((value || '').trim()) { + let newLabel: LinkLabel = null; + const labelName = value.trim(); + const existingLabel = this.labelsList.find(label => label.name === labelName); + if (existingLabel) { + newLabel = deepClone(existingLabel); + } else if (this.allowCustom) { + newLabel = { + name: labelName, + value: labelName + }; + } + if (newLabel) { + this.addLabel(newLabel); + } + } + this.clear(''); + } + + remove(label: LinkLabel) { + const index = this.labels.indexOf(label); + if (index >= 0) { + this.labels.splice(index, 1); + if (!this.labels.length) { + if (this.required) { + this.chipList.errorState = true; + } + } + this.updateModel(); + } + } + + selected(event: MatAutocompleteSelectedEvent): void { + this.addLabel(event.option.value); + this.clear(''); + } + + addLabel(label: LinkLabel): void { + const index = this.labels.findIndex(existinglabel => existinglabel.value === label.value); + if (index === -1) { + this.labels.push(label); + if (this.required) { + this.chipList.errorState = false; + } + } + this.updateModel(); + } + + clear(value: string = '') { + this.labelInput.nativeElement.value = value; + this.linksFormGroup.get('label').patchValue(null, {emitEvent: true}); + setTimeout(() => { + this.labelInput.nativeElement.blur(); + this.labelInput.nativeElement.focus(); + }, 0); + } + + private updateModel() { + const labels = this.labels.map((label => label.value)); + this.propagateChange(labels); + } +} + 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 4770b90fe3..1d27fcf7f5 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 @@ -1,15 +1,26 @@ - +
- + rulenode.name 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 b3995a5f09..8a3f654824 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 @@ -1,38 +1,41 @@ -import { - AfterViewInit, - Component, - EventEmitter, - Input, - OnInit, - Output, - ViewChild, - ViewEncapsulation -} from '@angular/core'; +/// +/// Copyright © 2016-2019 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, OnChanges, OnInit, Output, SimpleChanges, ViewChild } from '@angular/core'; import { PageComponent } from '@shared/components/page.component'; -import { HasDirtyFlag } from '@core/guards/confirm-on-exit.guard'; -import { ISearchableComponent } from '@home/models/searchable-component.models'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { FormBuilder, FormGroup, NgForm } from '@angular/forms'; +import { FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms'; import { FcRuleNode } from './rulechain-page.models'; import { RuleNodeType } from '@shared/models/rule-node.models'; import { EntityType } from '@shared/models/entity-type.models'; +import { Subscription } from 'rxjs'; +import { RuleChainService } from '@core/http/rule-chain.service'; @Component({ selector: 'tb-rule-node', templateUrl: './rule-node-details.component.html', styleUrls: [] }) -export class RuleNodeDetailsComponent extends PageComponent implements OnInit { +export class RuleNodeDetailsComponent extends PageComponent implements OnInit, OnChanges { @ViewChild('ruleNodeForm', {static: true}) ruleNodeForm: NgForm; - ruleNodeValue: FcRuleNode; - @Input() - set ruleNode(ruleNode: FcRuleNode) { - this.ruleNodeValue = ruleNode; - } + ruleNode: FcRuleNode; @Input() ruleChainId: string; @@ -43,28 +46,84 @@ export class RuleNodeDetailsComponent extends PageComponent implements OnInit { @Input() isReadOnly: boolean; - @Output() - deleteRuleNode = new EventEmitter(); - ruleNodeType = RuleNodeType; entityType = EntityType; ruleNodeFormGroup: FormGroup; + private ruleNodeFormSubscription: Subscription; + constructor(protected store: Store, - private fb: FormBuilder) { + private fb: FormBuilder, + private ruleChainService: RuleChainService) { super(store); this.ruleNodeFormGroup = this.fb.group({}); } private buildForm() { + if (this.ruleNodeFormSubscription) { + this.ruleNodeFormSubscription.unsubscribe(); + this.ruleNodeFormSubscription = null; + } + if (this.ruleNode) { + if (this.ruleNode.component.type !== RuleNodeType.RULE_CHAIN) { + this.ruleNodeFormGroup = this.fb.group({ + name: [this.ruleNode.name, [Validators.required]], + debugMode: [this.ruleNode.debugMode, []], + additionalInfo: this.fb.group( + { + description: [this.ruleNode.additionalInfo ? this.ruleNode.additionalInfo.description : ''], + } + ) + }); + } else { + this.ruleNodeFormGroup = this.fb.group({ + targetRuleChainId: [this.ruleNode.targetRuleChainId, [Validators.required]], + additionalInfo: this.fb.group( + { + description: [this.ruleNode.additionalInfo ? this.ruleNode.additionalInfo.description : ''], + } + ) + }); + } + this.ruleNodeFormSubscription = this.ruleNodeFormGroup.valueChanges.subscribe(() => { + this.updateRuleNode(); + }); + } else { + this.ruleNodeFormGroup = this.fb.group({}); + } } - ngOnInit(): void { + private updateRuleNode() { + const formValue = this.ruleNodeFormGroup.value || {}; + if (this.ruleNode.component.type === RuleNodeType.RULE_CHAIN) { + const targetRuleChainId: string = formValue.targetRuleChainId; + if (this.ruleNode.targetRuleChainId !== targetRuleChainId && targetRuleChainId) { + this.ruleChainService.getRuleChain(targetRuleChainId).subscribe( + (ruleChain) => { + this.ruleNode.name = ruleChain.name; + Object.assign(this.ruleNode, formValue); + } + ); + } else { + Object.assign(this.ruleNode, formValue); + } + } else { + Object.assign(this.ruleNode, formValue); + } } - onDeleteRuleNode(event) { - this.deleteRuleNode.emit(); + ngOnInit(): void { } + ngOnChanges(changes: SimpleChanges): void { + for (const propName of Object.keys(changes)) { + const change = changes[propName]; + if (change.currentValue !== change.previousValue) { + if (propName === 'ruleNode') { + this.buildForm(); + } + } + } + } } diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-link.component.html b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-link.component.html new file mode 100644 index 0000000000..4b54c92e6b --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-link.component.html @@ -0,0 +1,28 @@ + +
+ + + +
diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-link.component.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-link.component.ts new file mode 100644 index 0000000000..7b3f6ead9d --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-link.component.ts @@ -0,0 +1,135 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + AfterViewInit, + Component, ElementRef, + EventEmitter, forwardRef, + 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'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, NgForm, Validators } from '@angular/forms'; +import { FcRuleNode, FcRuleEdge } from './rulechain-page.models'; +import { RuleNodeType, LinkLabel } from '@shared/models/rule-node.models'; +import { EntityType } from '@shared/models/entity-type.models'; +import { Observable, of, Subscription } from 'rxjs'; +import { RuleChainService } from '@core/http/rule-chain.service'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { deepClone } from '@core/utils'; +import { EntityAlias } from '@shared/models/alias.models'; +import { TruncatePipe } from '@shared/pipe/truncate.pipe'; +import { MatChipList, MatAutocomplete, MatChipInputEvent, MatAutocompleteSelectedEvent } from '@angular/material'; +import { TranslateService } from '@ngx-translate/core'; +import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes'; +import { map, mergeMap, share } from 'rxjs/operators'; + +@Component({ + selector: 'tb-rule-node-link', + templateUrl: './rule-node-link.component.html', + styleUrls: [], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => RuleNodeLinkComponent), + multi: true + }] +}) +export class RuleNodeLinkComponent implements ControlValueAccessor, OnInit { + + @ViewChild('ruleNodeLinkForm', {static: true}) ruleNodeLinkForm: NgForm; + + private requiredValue: boolean; + get required(): boolean { + return this.requiredValue; + } + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + @Input() + disabled: boolean; + + private allowCustomValue: boolean; + get allowCustom(): boolean { + return this.allowCustomValue; + } + @Input() + set allowCustom(value: boolean) { + this.allowCustomValue = coerceBooleanProperty(value); + } + + @Input() + allowedLabels: {[label: string]: LinkLabel}; + + ruleNodeLinkFormGroup: FormGroup; + + modelValue: FcRuleEdge; + + private propagateChange = (v: any) => { }; + + constructor(private fb: FormBuilder, + public truncate: TruncatePipe, + public translate: TranslateService) { + this.ruleNodeLinkFormGroup = this.fb.group({ + labels: [[], Validators.required] + }); + this.ruleNodeLinkFormGroup.get('labels').valueChanges.subscribe( + (labels: string[]) => this.updateModel(labels) + ); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + ngOnInit(): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.ruleNodeLinkFormGroup.disable({emitEvent: false}); + } else { + this.ruleNodeLinkFormGroup.enable({emitEvent: false}); + } + } + + writeValue(value: FcRuleEdge): void { + this.modelValue = value; + const labels = this.modelValue && this.modelValue.labels ? this.modelValue.labels : []; + this.ruleNodeLinkFormGroup.get('labels').patchValue(labels, {emitEvent: false}); + } + + private updateModel(labels: string[]) { + if (labels && labels.length) { + this.modelValue.labels = labels; + this.modelValue.label = labels.join(' / '); + this.propagateChange(this.modelValue); + } else { + this.propagateChange(this.required ? null : this.modelValue); + } + } +} 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 0b0a7507cc..8e86f155ea 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 @@ -17,14 +17,6 @@ -->
-
menu
- +
- - - -
- - -
- -
- - -
-
+ + +
+
+
+ + + + + + + + +
+
+
{{editingRuleNode.component.name}}
+
 
+
{{editingRuleNode.component.configurationDescriptor.nodeDefinition.description}}
+
 
+
+
- - - - - - -
-
-
{{editingRuleNode.component.name}}
-
 
-
{{editingRuleNode.component.configurationDescriptor.nodeDefinition.description}}
-
 
-
-
-
-
-
-
-
- + + + + +
+
+
+ + +
+ + + +
+ + +
+
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 4a8fbeb404..e161ded881 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 @@ -126,8 +126,12 @@ } } } - .tb-rulechain-graph { - z-index: 0; + .mat-drawer-content.tb-rulechain-graph-content { + overflow: hidden; + .tb-rulechain-graph { + z-index: 0; + overflow: auto; + } } .fc-canvas { .fc-node { @@ -210,109 +214,56 @@ } } -/*:host ::ng-deep { - .tb-rulechain { - section.tb-header-buttons.tb-library-open { - .mat-fab { - .mat-button-wrapper { - padding: 0; - } - } - } - .tb-rulechain-library { - .mat-toolbar { - .mat-toolbar-tools { - .mat-form-field { - .mat-form-field-infix { - width: auto; - } - } - } - } - .tb-rulechain-library-panel-group { - .mat-expansion-panel-body { - padding: 0; - } - } - } - } - .fc-canvas { - .fc-node { - border-radius: 8px; - &.fc-selected { - &:not(.fc-edit) { - margin: -3px; - border: solid 3px #f00; - } - } - } +.tb-rule-node-tooltip, +.tb-rule-node-help { + color: #333; +} - .fc-edit { - .fc-nodeedit, - .fc-nodedelete { - border: solid 2px #fff; - background: #f83e05; - outline: none; - } - } +.tb-rule-node-tooltip { + max-width: 300px; + font-size: 14px; - .fc-arrow-marker { - polygon { - fill: #808080; - stroke: #808080; - } - } - - .fc-arrow-marker-selected { - polygon { - fill: #f00; - stroke: #f00; - } - } + &.tb-lib-tooltip { + width: 300px; + } +} - .fc-edge { - outline: none; - stroke: #808080; +.tb-rule-node-help { + font-size: 16px; +} - &.fc-selected { - stroke: #f00; - } +.tb-rule-node-error-tooltip { + font-size: 16px; + color: #ea0d0d; +} - &.fc-hover { - stroke: #808080; - } +.tb-rule-node-tooltip, +.tb-rule-node-error-tooltip, +.tb-rule-node-help { + #tb-node-content { + .tb-node-title { + font-weight: 600; } - .edge-endpoint { - fill: #808080; + .tb-node-description { + font-style: italic; + color: #555; } - .fc-edge-label { - opacity: 1 !important; - &:focus { - outline: 0; - } - - &.fc-selected { - .fc-edge-label-text { - span { - color: #fff !important; - background-color: #f00 !important; - border: solid #f00 !important; - } - } - } - - .fc-edge-label-text { - font-size: 14px !important; - font-weight: 600 !important; + .tb-node-details { + padding-top: 10px; + padding-bottom: 10px; + } - span { - background-color: #fff !important; - color: #003a79 !important; - border: solid 2px #003a79 !important; - } - } + code { + padding: 0 3px 2px 3px; + margin: 1px; + font-size: 12px; + color: #ad1625; + white-space: nowrap; + background-color: #f7f7f9; + border: 1px solid #e1e1e8; + border-radius: 2px; } } -}*/ +} 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 3d1d0d8712..04e86b6090 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 @@ -18,20 +18,20 @@ import { AfterViewInit, Component, ElementRef, + HostBinding, Inject, OnInit, - QueryList, + QueryList, SkipSelf, ViewChild, ViewChildren, - ViewEncapsulation, - HostBinding + ViewEncapsulation } from '@angular/core'; import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { FormBuilder } from '@angular/forms'; +import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; import { HasDirtyFlag } from '@core/guards/confirm-on-exit.guard'; import { TranslateService } from '@ngx-translate/core'; -import { MatDialog, MatExpansionPanel } from '@angular/material'; +import { MatDialog, MatExpansionPanel, ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; import { DialogService } from '@core/services/dialog.service'; import { AuthService } from '@core/auth/auth.service'; import { ActivatedRoute, Router } from '@angular/router'; @@ -43,6 +43,8 @@ import { } from '@shared/models/rule-chain.models'; import { FlowchartConstants, NgxFlowchartComponent, UserCallbacks } from 'ngx-flowchart/dist/ngx-flowchart'; import { + getRuleNodeHelpLink, + LinkLabel, RuleNodeComponentDescriptor, RuleNodeType, ruleNodeTypeDescriptors, @@ -50,10 +52,21 @@ import { } from '@shared/models/rule-node.models'; import { FcRuleEdge, FcRuleNode, FcRuleNodeModel, FcRuleNodeType, FcRuleNodeTypeModel } from './rulechain-page.models'; import { RuleChainService } from '@core/http/rule-chain.service'; -import { fromEvent, of } from 'rxjs'; -import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators'; +import { fromEvent, never, of, throwError, NEVER, Observable } from 'rxjs'; +import { debounceTime, distinctUntilChanged, map, tap, mergeMap } from 'rxjs/operators'; import { ISearchableComponent } from '../../models/searchable-component.models'; +import { deepClone, isDefined, isString } from '@core/utils'; +import { RuleNodeDetailsComponent } from '@home/pages/rulechain/rule-node-details.component'; +import { RuleNodeLinkComponent } from './rule-node-link.component'; import Timeout = NodeJS.Timeout; +import { Dashboard } from '@shared/models/dashboard.models'; +import { IAliasController } from '@core/api/widget-api.models'; +import { Widget, widgetTypesData } from '@shared/models/widget.models'; +import { WidgetConfigComponentData, WidgetInfo } from '@home/models/widget-component.models'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { UtilsService } from '@core/services/utils.service'; +import { EntityService } from '@core/http/entity.service'; +import { AddWidgetDialogComponent, AddWidgetDialogData } from '@home/pages/dashboard/add-widget-dialog.component'; @Component({ selector: 'tb-rulechain-page', @@ -88,11 +101,19 @@ export class RuleChainPageComponent extends PageComponent errorTooltips: {[nodeId: string]: JQueryTooltipster.ITooltipsterInstance} = {}; isFullscreen = false; - editingRuleNode = null; + selectedRuleNodeTabIndex = 0; + editingRuleNode: FcRuleNode = null; isEditingRuleNode = false; + editingRuleNodeIndex = -1; + editingRuleNodeAllowCustomLabels = false; + editingRuleNodeLinkLabels: {[label: string]: LinkLabel}; - editingRuleNodeLink = null; + @ViewChild('tbRuleNode', {static: false}) ruleNodeComponent: RuleNodeDetailsComponent; + @ViewChild('tbRuleNodeLink', {static: false}) ruleNodeLinkComponent: RuleNodeLinkComponent; + + editingRuleNodeLink: FcRuleEdge = null; isEditingRuleNodeLink = false; + editingRuleNodeLinkIndex = -1; isLibraryOpen = true; @@ -110,17 +131,17 @@ export class RuleChainPageComponent extends PageComponent editCallbacks: UserCallbacks = { edgeDoubleClick: (event, edge) => { - console.log('TODO'); + this.openLinkDetails(edge); }, edgeEdit: (event, edge) => { - console.log('TODO'); + this.openLinkDetails(edge); }, nodeCallbacks: { - doubleClick: (event, node) => { - console.log('TODO'); + doubleClick: (event, node: FcRuleNode) => { + this.openNodeDetails(node); }, - nodeEdit: (event, node) => { - console.log('TODO'); + nodeEdit: (event, node: FcRuleNode) => { + this.openNodeDetails(node); }, mouseEnter: this.displayNodeDescriptionTooltip.bind(this), mouseLeave: this.destroyTooltips.bind(this), @@ -129,9 +150,40 @@ export class RuleChainPageComponent extends PageComponent isValidEdge: (source, destination) => { return source.type === FlowchartConstants.rightConnectorType && destination.type === FlowchartConstants.leftConnectorType; }, - createEdge: (event, edge) => { + createEdge: (event, edge: FcRuleEdge) => { console.log('TODO'); - return of(edge); + const sourceNode = this.ruleChainCanvas.modelService.nodes.getNodeByConnectorId(edge.source) as FcRuleNode; + if (sourceNode.component.type === RuleNodeType.INPUT) { + const destNode = this.ruleChainCanvas.modelService.nodes.getNodeByConnectorId(edge.destination) as FcRuleNode; + if (destNode.component.type === RuleNodeType.RULE_CHAIN) { + return NEVER; + } else { + const found = this.ruleChainModel.edges.find(theEdge => theEdge.source === (this.inputConnectorId + '')); + if (found) { + this.ruleChainCanvas.modelService.edges.delete(found); + } + return of(edge); + } + } else { + if (edge.label) { + if (!edge.labels) { + edge.labels = edge.label.split(' / '); + } + return of(edge); + } else { + const labels = this.ruleChainService.getRuleNodeSupportedLinks(sourceNode.component); + const allowCustomLabels = this.ruleChainService.ruleNodeAllowCustomLinks(sourceNode.component); + return this.addRuleNodeLink(edge, labels, allowCustomLabels).pipe( + mergeMap((res) => { + if (res) { + return of(res); + } else { + return NEVER; + } + }) + ); + } + } }, dropNode: (event, node) => { console.log('TODO dropNode'); @@ -470,6 +522,88 @@ export class RuleChainPageComponent extends PageComponent this.validate(); } + helpLinkIdForRuleNodeType(): string { + let component: RuleNodeComponentDescriptor = null; + if (this.editingRuleNode) { + component = this.editingRuleNode.component; + } + return getRuleNodeHelpLink(component); + } + + openNodeDetails(node: FcRuleNode) { + if (node.component.type !== RuleNodeType.INPUT) { + this.isEditingRuleNodeLink = false; + this.editingRuleNodeLink = null; + this.isEditingRuleNode = true; + this.editingRuleNodeIndex = this.ruleChainModel.nodes.indexOf(node); + this.editingRuleNode = deepClone(node); + setTimeout(() => { + this.ruleNodeComponent.ruleNodeFormGroup.markAsPristine(); + }, 0); + } + } + + openLinkDetails(edge: FcRuleEdge) { + const sourceNode: FcRuleNode = this.ruleChainCanvas.modelService.nodes.getNodeByConnectorId(edge.source) as FcRuleNode; + if (sourceNode.component.type !== RuleNodeType.INPUT) { + this.isEditingRuleNode = false; + this.editingRuleNode = null; + this.editingRuleNodeLinkLabels = this.ruleChainService.getRuleNodeSupportedLinks(sourceNode.component); + this.editingRuleNodeAllowCustomLabels = this.ruleChainService.ruleNodeAllowCustomLinks(sourceNode.component); + this.isEditingRuleNodeLink = true; + this.editingRuleNodeLinkIndex = this.ruleChainModel.edges.indexOf(edge); + this.editingRuleNodeLink = deepClone(edge); + setTimeout(() => { + this.ruleNodeLinkComponent.ruleNodeLinkFormGroup.markAsPristine(); + }, 0); + } + } + + onDetailsDrawerClosed() { + this.onEditRuleNodeClosed(); + this.onEditRuleNodeLinkClosed(); + } + + onEditRuleNodeClosed() { + this.editingRuleNode = null; + this.isEditingRuleNode = false; + } + + onEditRuleNodeLinkClosed() { + this.editingRuleNodeLink = null; + this.isEditingRuleNodeLink = false; + } + + onRevertRuleNodeEdit() { + this.ruleNodeComponent.ruleNodeFormGroup.markAsPristine(); + const node = this.ruleChainModel.nodes[this.editingRuleNodeIndex]; + this.editingRuleNode = deepClone(node); + } + + onRevertRuleNodeLinkEdit() { + this.ruleNodeLinkComponent.ruleNodeLinkFormGroup.markAsPristine(); + const edge = this.ruleChainModel.edges[this.editingRuleNodeLinkIndex]; + this.editingRuleNodeLink = deepClone(edge); + } + + saveRuleNode() { + this.ruleNodeComponent.ruleNodeFormGroup.markAsPristine(); + if (this.editingRuleNode.error) { + delete this.editingRuleNode.error; + } + this.ruleChainModel.nodes[this.editingRuleNodeIndex] = this.editingRuleNode; + this.editingRuleNode = deepClone(this.editingRuleNode); + this.onModelChanged(); + this.updateRuleNodesHighlight(); + } + + saveRuleNodeLink() { + this.ruleNodeLinkComponent.ruleNodeLinkFormGroup.markAsPristine(); + this.ruleChainModel.edges[this.editingRuleNodeLinkIndex] = this.editingRuleNodeLink; + this.editingRuleNodeLink = deepClone(this.editingRuleNodeLink); + this.onModelChanged(); + } + typeHeaderMouseEnter(event: MouseEvent, ruleNodeType: RuleNodeType) { const type = ruleNodeTypeDescriptors.get(ruleNodeType); this.displayTooltip(event, @@ -568,11 +702,16 @@ export class RuleChainPageComponent extends PageComponent } resetDebugModeInAllNodes() { + let changed = false; this.ruleChainModel.nodes.forEach((node) => { if (node.component.type !== RuleNodeType.INPUT && node.component.type !== RuleNodeType.RULE_CHAIN) { + changed = changed || node.debugMode; node.debugMode = false; } }); + if (changed) { + this.onModelChanged(); + } } validate() { @@ -595,6 +734,19 @@ export class RuleChainPageComponent extends PageComponent this.createRuleChainModel(); } + addRuleNodeLink(link: FcRuleEdge, labels: {[label: string]: LinkLabel}, allowCustomLabels: boolean): Observable { + return this.dialog.open(AddRuleNodeLinkDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + link, + labels, + allowCustomLabels + } + }).afterClosed(); + } + private updateNodeErrorTooltip(node: FcRuleNode) { if (node.error) { const element = $('#' + node.id); @@ -670,6 +822,66 @@ export class RuleChainPageComponent extends PageComponent tooltip.open(); }, 500); } +} + +export interface AddRuleNodeLinkDialogData { + link: FcRuleEdge; + labels: {[label: string]: LinkLabel}; + allowCustomLabels: boolean; +} + +@Component({ + selector: 'tb-add-rule-node-link-dialog', + templateUrl: './add-rule-node-link-dialog.component.html', + providers: [{provide: ErrorStateMatcher, useExisting: AddRuleNodeLinkDialogComponent}], + styleUrls: ['./add-rule-node-link-dialog.component.scss'] +}) +export class AddRuleNodeLinkDialogComponent extends DialogComponent + implements OnInit, ErrorStateMatcher { + + ruleNodeLinkFormGroup: FormGroup; + + link: FcRuleEdge; + labels: {[label: string]: LinkLabel}; + allowCustomLabels: boolean; + + submitted = false; + + constructor(protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) public data: AddRuleNodeLinkDialogData, + @SkipSelf() private errorStateMatcher: ErrorStateMatcher, + public dialogRef: MatDialogRef, + private fb: FormBuilder) { + super(store, router, dialogRef); + + this.link = this.data.link; + this.labels = this.data.labels; + this.allowCustomLabels = this.data.allowCustomLabels; + + this.ruleNodeLinkFormGroup = this.fb.group({ + link: [deepClone(this.link), [Validators.required]] + } + ); + } + + ngOnInit(): void { + } + isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { + const originalErrorState = this.errorStateMatcher.isErrorState(control, form); + const customErrorState = !!(control && control.invalid && this.submitted); + return originalErrorState || customErrorState; + } + + cancel(): void { + this.dialogRef.close(null); + } + add(): void { + this.submitted = true; + const link: FcRuleEdge = this.ruleNodeLinkFormGroup.get('link').value; + this.link = {...this.link, ...link}; + this.dialogRef.close(this.link); + } } diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.tooltipster.scss b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.tooltipster.scss deleted file mode 100644 index afb53cdeff..0000000000 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.tooltipster.scss +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Copyright © 2016-2019 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. - */ -.tooltipster-base { - .tb-rule-node-tooltip, - .tb-rule-node-help { - color: #333; - } - - .tb-rule-node-tooltip { - max-width: 300px; - font-size: 14px; - - &.tb-lib-tooltip { - width: 300px; - } - } - - .tb-rule-node-help { - font-size: 16px; - } - - .tb-rule-node-error-tooltip { - font-size: 16px; - color: #ea0d0d; - } - - .tb-rule-node-tooltip, - .tb-rule-node-error-tooltip, - .tb-rule-node-help { - #tb-node-content { - .tb-node-title { - font-weight: 600; - } - - .tb-node-description { - font-style: italic; - color: #555; - } - - .tb-node-details { - padding-top: 10px; - padding-bottom: 10px; - } - - code { - padding: 0 3px 2px 3px; - margin: 1px; - font-size: 12px; - color: #ad1625; - white-space: nowrap; - background-color: #f7f7f9; - border: 1px solid #e1e1e8; - border-radius: 2px; - } - } - } -} diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.module.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.module.ts index 1b961517fa..665d287320 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.module.ts +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.module.ts @@ -21,21 +21,29 @@ import {RuleChainComponent} from '@modules/home/pages/rulechain/rulechain.compon import {RuleChainRoutingModule} from '@modules/home/pages/rulechain/rulechain-routing.module'; import {HomeComponentsModule} from '@modules/home/components/home-components.module'; import { RuleChainTabsComponent } from '@home/pages/rulechain/rulechain-tabs.component'; -import { RuleChainPageComponent } from './rulechain-page.component'; +import { RuleChainPageComponent, AddRuleNodeLinkDialogComponent } from './rulechain-page.component'; import { RuleNodeComponent } from '@home/pages/rulechain/rulenode.component'; import { FC_NODE_COMPONENT_CONFIG } from 'ngx-flowchart/dist/ngx-flowchart'; +import { RuleNodeDetailsComponent } from './rule-node-details.component'; +import { RuleNodeLinkComponent } from './rule-node-link.component'; +import { LinkLabelsComponent } from '@home/pages/rulechain/link-labels.conponent'; @NgModule({ entryComponents: [ RuleChainComponent, RuleChainTabsComponent, - RuleNodeComponent + RuleNodeComponent, + AddRuleNodeLinkDialogComponent ], declarations: [ RuleChainComponent, RuleChainTabsComponent, RuleChainPageComponent, - RuleNodeComponent + RuleNodeComponent, + RuleNodeDetailsComponent, + LinkLabelsComponent, + RuleNodeLinkComponent, + AddRuleNodeLinkDialogComponent ], providers: [ { diff --git a/ui-ngx/src/app/shared/models/constants.ts b/ui-ngx/src/app/shared/models/constants.ts index 96a4fe782c..978cec592d 100644 --- a/ui-ngx/src/app/shared/models/constants.ts +++ b/ui-ngx/src/app/shared/models/constants.ts @@ -57,6 +57,40 @@ export const HelpLinks = { linksMap: { outgoingMailSettings: helpBaseUrl + '/docs/user-guide/ui/mail-settings', securitySettings: helpBaseUrl + '/docs/user-guide/ui/security-settings', + ruleEngine: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/overview/', + ruleNodeCheckRelation: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/filter-nodes/#check-relation-filter-node', + ruleNodeJsFilter: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/filter-nodes/#script-filter-node', + ruleNodeJsSwitch: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/filter-nodes/#switch-node', + ruleNodeMessageTypeFilter: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/filter-nodes/#message-type-filter-node', + ruleNodeMessageTypeSwitch: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/filter-nodes/#message-type-switch-node', + ruleNodeOriginatorTypeFilter: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/filter-nodes/#originator-type-filter-node', + ruleNodeOriginatorTypeSwitch: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/filter-nodes/#originator-type-switch-node', + ruleNodeOriginatorAttributes: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/enrichment-nodes/#originator-attributes', + ruleNodeOriginatorFields: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/enrichment-nodes/#originator-fields', + ruleNodeCustomerAttributes: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/enrichment-nodes/#customer-attributes', + ruleNodeDeviceAttributes: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/enrichment-nodes/#device-attributes', + ruleNodeRelatedAttributes: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/enrichment-nodes/#related-attributes', + ruleNodeTenantAttributes: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/enrichment-nodes/#tenant-attributes', + ruleNodeChangeOriginator: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/transformation-nodes/#change-originator', + ruleNodeTransformMsg: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/transformation-nodes/#script-transformation-node', + ruleNodeMsgToEmail: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/transformation-nodes/#to-email-node', + ruleNodeClearAlarm: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/action-nodes/#clear-alarm-node', + ruleNodeCreateAlarm: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/action-nodes/#create-alarm-node', + ruleNodeMsgDelay: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/action-nodes/#delay-node', + ruleNodeMsgGenerator: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/action-nodes/#generator-node', + ruleNodeLog: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/action-nodes/#log-node', + ruleNodeRpcCallReply: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/action-nodes/#rpc-call-reply-node', + ruleNodeRpcCallRequest: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/action-nodes/#rpc-call-request-node', + ruleNodeSaveAttributes: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/action-nodes/#save-attributes-node', + ruleNodeSaveTimeseries: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/action-nodes/#save-timeseries-node', + ruleNodeRuleChain: helpBaseUrl + '/docs/user-guide/ui/rule-chains/', + ruleNodeAwsSns: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/external-nodes/#aws-sns-node', + ruleNodeAwsSqs: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/external-nodes/#aws-sqs-node', + ruleNodeKafka: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/external-nodes/#kafka-node', + ruleNodeMqtt: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/external-nodes/#mqtt-node', + ruleNodeRabbitMq: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/external-nodes/#rabbitmq-node', + ruleNodeRestApiCall: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/external-nodes/#rest-api-call-node', + ruleNodeSendEmail: helpBaseUrl + '/docs/user-guide/rule-engine-2-0/external-nodes/#send-email-node', tenants: helpBaseUrl + '/docs/user-guide/ui/tenants', customers: helpBaseUrl + '/docs/user-guide/customers', users: helpBaseUrl + '/docs/user-guide/ui/users', 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 2aa77791f6..9d122bb709 100644 --- a/ui-ngx/src/app/shared/models/rule-node.models.ts +++ b/ui-ngx/src/app/shared/models/rule-node.models.ts @@ -43,6 +43,11 @@ export interface RuleNode extends BaseData { additionalInfo?: any; } +export interface LinkLabel { + name: string; + value: string; +} + export interface RuleNodeConfigurationDescriptor { nodeDefinition: { description: string; @@ -54,6 +59,7 @@ export interface RuleNodeConfigurationDescriptor { defaultConfiguration: any; icon?: string; iconUrl?: string; + docUrl?: string; uiResources?: string[]; uiResourceLoadError?: string; }; @@ -178,3 +184,54 @@ export interface RuleNodeComponentDescriptor extends ComponentDescriptor { type: RuleNodeType; configurationDescriptor?: RuleNodeConfigurationDescriptor; } + +const ruleNodeClazzHelpLinkMap = { + 'org.thingsboard.rule.engine.filter.TbCheckRelationNode': 'ruleNodeCheckRelation', + 'org.thingsboard.rule.engine.filter.TbJsFilterNode': 'ruleNodeJsFilter', + 'org.thingsboard.rule.engine.filter.TbJsSwitchNode': 'ruleNodeJsSwitch', + 'org.thingsboard.rule.engine.filter.TbMsgTypeFilterNode': 'ruleNodeMessageTypeFilter', + 'org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode': 'ruleNodeMessageTypeSwitch', + 'org.thingsboard.rule.engine.filter.TbOriginatorTypeFilterNode': 'ruleNodeOriginatorTypeFilter', + 'org.thingsboard.rule.engine.filter.TbOriginatorTypeSwitchNode': 'ruleNodeOriginatorTypeSwitch', + 'org.thingsboard.rule.engine.metadata.TbGetAttributesNode': 'ruleNodeOriginatorAttributes', + 'org.thingsboard.rule.engine.metadata.TbGetOriginatorFieldsNode': 'ruleNodeOriginatorFields', + 'org.thingsboard.rule.engine.metadata.TbGetCustomerAttributeNode': 'ruleNodeCustomerAttributes', + 'org.thingsboard.rule.engine.metadata.TbGetDeviceAttrNode': 'ruleNodeDeviceAttributes', + 'org.thingsboard.rule.engine.metadata.TbGetRelatedAttributeNode': 'ruleNodeRelatedAttributes', + 'org.thingsboard.rule.engine.metadata.TbGetTenantAttributeNode': 'ruleNodeTenantAttributes', + 'org.thingsboard.rule.engine.transform.TbChangeOriginatorNode': 'ruleNodeChangeOriginator', + 'org.thingsboard.rule.engine.transform.TbTransformMsgNode': 'ruleNodeTransformMsg', + 'org.thingsboard.rule.engine.mail.TbMsgToEmailNode': 'ruleNodeMsgToEmail', + 'org.thingsboard.rule.engine.action.TbClearAlarmNode': 'ruleNodeClearAlarm', + 'org.thingsboard.rule.engine.action.TbCreateAlarmNode': 'ruleNodeCreateAlarm', + 'org.thingsboard.rule.engine.delay.TbMsgDelayNode': 'ruleNodeMsgDelay', + 'org.thingsboard.rule.engine.debug.TbMsgGeneratorNode': 'ruleNodeMsgGenerator', + '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', + 'org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode': 'ruleNodeSaveTimeseries', + 'tb.internal.RuleChain': 'ruleNodeRuleChain', + 'org.thingsboard.rule.engine.aws.sns.TbSnsNode': 'ruleNodeAwsSns', + 'org.thingsboard.rule.engine.aws.sqs.TbSqsNode': 'ruleNodeAwsSqs', + 'org.thingsboard.rule.engine.kafka.TbKafkaNode': 'ruleNodeKafka', + 'org.thingsboard.rule.engine.mqtt.TbMqttNode': 'ruleNodeMqtt', + 'org.thingsboard.rule.engine.rabbitmq.TbRabbitMqNode': 'ruleNodeRabbitMq', + 'org.thingsboard.rule.engine.rest.TbRestApiCallNode': 'ruleNodeRestApiCall', + 'org.thingsboard.rule.engine.mail.TbSendEmailNode': 'ruleNodeSendEmail' +}; + +export function getRuleNodeHelpLink(component: RuleNodeComponentDescriptor): string { + if (component) { + if (component.configurationDescriptor && + component.configurationDescriptor.nodeDefinition && + component.configurationDescriptor.nodeDefinition.docUrl) { + return component.configurationDescriptor.nodeDefinition.docUrl; + } else if (component.clazz) { + if (ruleNodeClazzHelpLinkMap[component.clazz]) { + return ruleNodeClazzHelpLinkMap[component.clazz]; + } + } + } + return 'ruleEngine'; +} From a42bd5d7eeaf744aa58df15d5b943a3b7cc36d1a Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 23 Dec 2019 14:36:44 +0200 Subject: [PATCH 060/133] UI: RuleNode configuration --- .../rule/engine/debug/TbMsgGeneratorNode.java | 2 +- .../engine/filter/TbMsgTypeFilterNode.java | 2 +- .../filter/TbOriginatorTypeFilterNode.java | 2 +- .../metadata/TbGetCustomerAttributeNode.java | 2 +- .../metadata/TbGetRelatedAttributeNode.java | 2 +- .../metadata/TbGetTenantAttributeNode.java | 2 +- .../rule/engine/mqtt/TbMqttNode.java | 2 +- .../engine/telemetry/TbMsgAttributesNode.java | 2 +- .../engine/telemetry/TbMsgTimeseriesNode.java | 2 +- .../transform/TbChangeOriginatorNode.java | 2 +- .../engine/transform/TbTransformMsgNode.java | 2 +- .../static/rulenode/rulenode-core-config.css | 2 - .../static/rulenode/rulenode-core-config.js | 22 +- ui-ngx/angular.json | 5 +- ui-ngx/package-lock.json | 5 + ui-ngx/package.json | 1 + ui-ngx/proxy.conf.js | 35 +++ ui-ngx/src/app/core/api/public-api.ts | 22 ++ ui-ngx/src/app/core/http/dashboard.service.ts | 1 + ui-ngx/src/app/core/http/public-api.ts | 34 +++ .../src/app/core/http/rule-chain.service.ts | 63 ++++-- .../local-storage/local-storage.service.ts | 1 + ui-ngx/src/app/core/public-api.ts | 23 ++ .../dialog/confirm-dialog.component.ts | 1 + ui-ngx/src/app/core/services/public-api.ts | 32 +++ ui-ngx/src/app/core/services/raf.service.ts | 1 + .../app/core/services/resources.service.ts | 52 ++++- .../script/node-script-test.service.ts | 31 +++ ui-ngx/src/app/core/services/utils.service.ts | 4 + ui-ngx/src/app/core/utils.ts | 9 + .../core/ws/telemetry-websocket.service.ts | 1 + .../alias/aliases-entity-select.component.ts | 1 + .../alias/entity-alias-dialog.component.ts | 2 +- .../alias/entity-alias-select.component.ts | 2 +- .../entity-aliases-dialog.component.html | 2 +- .../alias/entity-aliases-dialog.component.ts | 5 + .../attribute/attribute-table.component.html | 4 +- .../import-export/import-export.service.ts | 1 + .../app/modules/home/components/public-api.ts | 17 ++ .../relation/relation-filters.component.html | 8 +- .../relation/relation-filters.component.ts | 4 + .../custom-action-pretty-editor.component.ts | 3 + .../widget/data-key-config.component.ts | 2 +- .../components/widget/data-keys.component.ts | 2 +- .../widget/legend-config.component.html | 2 +- .../widget/legend-config.component.ts | 1 + .../widget/lib/flot-widget.models.ts | 10 +- .../home/components/widget/lib/flot-widget.ts | 15 +- .../widget/widget-component.service.ts | 5 +- .../widget/widget-config.component.html | 8 +- .../widget/widget-config.component.ts | 6 +- .../dashboard/dashboard-page.component.ts | 1 + .../dashboard/dashboard-routing.module.ts | 1 + .../entity-state-controller.component.ts | 2 +- .../src/app/modules/home/pages/public-api.ts | 17 ++ .../add-rule-node-dialog.component.html | 56 +++++ .../rulechain/link-labels.component.html | 2 +- .../pages/rulechain/link-labels.conponent.ts | 8 +- .../rulechain/rule-node-config.component.html | 28 +++ .../rulechain/rule-node-config.component.scss | 27 +++ .../rulechain/rule-node-config.component.ts | 208 ++++++++++++++++++ .../rule-node-details.component.html | 6 +- .../rulechain/rule-node-details.component.ts | 1 + .../rulechain/rulechain-page.component.ts | 101 ++++++++- .../rulechain/rulechain-routing.module.ts | 26 ++- .../home/pages/rulechain/rulechain.module.ts | 10 +- .../pages/user/add-user-dialog.component.html | 4 +- .../pages/user/add-user-dialog.component.ts | 2 +- .../pages/widget/widget-editor.component.ts | 1 + .../widget/widget-library-routing.module.ts | 1 + ui-ngx/src/app/modules/home/public-api.ts | 17 ++ .../dashboard-autocomplete.component.ts | 2 +- .../components/dashboard-select.component.ts | 1 + .../entity/entity-autocomplete.component.ts | 2 +- .../entity/entity-keys-list.component.ts | 2 +- .../entity/entity-list.component.ts | 2 +- .../entity-subtype-autocomplete.component.ts | 2 +- .../entity/entity-subtype-list.component.ts | 2 +- .../entity/entity-type-list.component.ts | 2 +- .../components/fab-toolbar.component.ts | 1 + .../json-form/json-form.component.ts | 4 +- .../json-form/react/json-form-ace-editor.tsx | 1 - .../json-form/react/json-form-color.tsx | 4 +- .../json-form/react/json-form-schema-form.tsx | 3 +- .../json-form/react/json-form.models.ts | 4 +- .../mat-chip-draggable.directive.ts | 2 +- .../src/app/shared/components/public-api.ts | 17 ++ .../relation-type-autocomplete.component.ts | 2 +- .../components/time/timewindow.component.html | 8 +- .../components/time/timewindow.component.ts | 1 + ui-ngx/src/app/shared/models/constants.ts | 1 + ui-ngx/src/app/shared/models/id/public-api.ts | 33 +++ .../src/app/shared/models/material.models.ts | 3 +- .../src/app/shared/models/page/public-api.ts | 19 ++ ui-ngx/src/app/shared/models/public-api.ts | 47 ++++ .../src/app/shared/models/rule-node.models.ts | 69 ++++-- ui-ngx/src/app/shared/models/user.model.ts | 4 +- .../src/app/shared/pipe/enum-to-array.pipe.ts | 2 +- .../app/shared/pipe/keyboard-shortcut.pipe.ts | 1 + ui-ngx/src/app/shared/pipe/public-api.ts | 22 ++ ui-ngx/src/app/shared/public-api.ts | 20 ++ 101 files changed, 1151 insertions(+), 118 deletions(-) delete mode 100644 rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.css create mode 100644 ui-ngx/proxy.conf.js create mode 100644 ui-ngx/src/app/core/api/public-api.ts create mode 100644 ui-ngx/src/app/core/http/public-api.ts create mode 100644 ui-ngx/src/app/core/public-api.ts create mode 100644 ui-ngx/src/app/core/services/public-api.ts create mode 100644 ui-ngx/src/app/core/services/script/node-script-test.service.ts create mode 100644 ui-ngx/src/app/modules/home/components/public-api.ts create mode 100644 ui-ngx/src/app/modules/home/pages/public-api.ts create mode 100644 ui-ngx/src/app/modules/home/pages/rulechain/add-rule-node-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.ts create mode 100644 ui-ngx/src/app/modules/home/public-api.ts create mode 100644 ui-ngx/src/app/shared/components/public-api.ts create mode 100644 ui-ngx/src/app/shared/models/id/public-api.ts create mode 100644 ui-ngx/src/app/shared/models/page/public-api.ts create mode 100644 ui-ngx/src/app/shared/models/public-api.ts create mode 100644 ui-ngx/src/app/shared/pipe/public-api.ts create mode 100644 ui-ngx/src/app/shared/public-api.ts 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 478b0b4666..956241cb77 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 static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; nodeDescription = "Periodically generates messages", nodeDetails = "Generates messages with configurable period. Javascript function used for message generation.", inEnabled = false, - uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, + uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbActionNodeGeneratorConfig", icon = "repeat" ) 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 1873f0f29a..4717a482e3 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 @@ -32,7 +32,7 @@ import org.thingsboard.server.common.msg.TbMsg; relationTypes = {"True", "False"}, nodeDescription = "Filter incoming messages by Message Type", nodeDetails = "If incoming MessageType is expected - send Message via True chain, otherwise False chain is used.", - uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, + 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/TbOriginatorTypeFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNode.java index 974d130ce1..6b03210352 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 @@ -30,7 +30,7 @@ import org.thingsboard.server.common.msg.TbMsg; relationTypes = {"True", "False"}, nodeDescription = "Filter incoming messages by message Originator Type", nodeDetails = "If Originator Type of incoming message is expected - send Message via True chain, otherwise False chain is used.", - uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, + 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/metadata/TbGetCustomerAttributeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java index 993a55923d..6e24bd6ee3 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 @@ -32,7 +32,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType; "If Latest Telemetry enrichment configured, latest telemetry added into metadata. " + "To access those attributes in other nodes this template can be used " + "metadata.temperature.", - uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, + uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbEnrichmentNodeCustomerAttributesConfig") public class TbGetCustomerAttributeNode extends TbEntityGetAttrNode { 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 a0fb3894e6..23bbd779de 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 @@ -34,7 +34,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType; "If Latest Telemetry enrichment configured, latest telemetry added into metadata. " + "To access those attributes in other nodes this template can be used " + "metadata.temperature.", - uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, + uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbEnrichmentNodeRelatedAttributesConfig") public class TbGetRelatedAttributeNode extends TbEntityGetAttrNode { 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 4b3d5baff8..99f1967d3a 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 @@ -34,7 +34,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType; "If Latest Telemetry enrichment configured, latest telemetry added into metadata. " + "To access those attributes in other nodes this template can be used " + "metadata.temperature.", - uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, + uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbEnrichmentNodeTenantAttributesConfig") public class TbGetTenantAttributeNode extends TbEntityGetAttrNode { 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 dd17414b87..f02a660e8d 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 @@ -47,7 +47,7 @@ import java.util.concurrent.TimeoutException; configClazz = TbMqttNodeConfiguration.class, nodeDescription = "Publish messages to the MQTT broker", nodeDetails = "Will publish message payload to the MQTT broker with QoS AT_LEAST_ONCE.", - uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, + uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbActionNodeMqttConfig", icon = "call_split" ) 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 8d39b47958..35473d3880 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 @@ -42,7 +42,7 @@ import java.util.Set; configClazz = TbMsgAttributesNodeConfiguration.class, nodeDescription = "Saves attributes data", nodeDetails = "Saves entity attributes based on configurable scope parameter. Expects messages with 'POST_ATTRIBUTES_REQUEST' message type", - uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, + uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbActionNodeAttributesConfig", icon = "file_upload" ) 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 8607f93d22..f8468d028b 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 @@ -43,7 +43,7 @@ import java.util.Map; configClazz = TbMsgTimeseriesNodeConfiguration.class, nodeDescription = "Saves timeseries data", nodeDetails = "Saves timeseries telemetry data based on configurable TTL parameter. Expects messages with 'POST_TELEMETRY_REQUEST' message type", - uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, + uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbActionNodeTimeseriesConfig", icon = "file_upload" ) 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 a00267a663..14ce9cb669 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 @@ -44,7 +44,7 @@ import java.util.HashSet; nodeDetails = "Related Entity found using configured relation direction and Relation Type. " + "If multiple Related Entities are found, only first Entity is used as new Originator, other entities are discarded.
" + "Alarm Originator found only in case original Originator is Alarm entity.", - uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, + uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbTransformationNodeChangeOriginatorConfig", icon = "find_replace" ) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java index 81c4483166..686db51da0 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java @@ -33,7 +33,7 @@ import org.thingsboard.server.common.msg.TbMsg; "Should return the following structure:
" + "{ msg: new payload,
   metadata: new metadata,
   msgType: new msgType }

" + "All fields in resulting object are optional and will be taken from original message if not specified.", - uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, + uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbTransformationNodeScriptConfig") public class TbTransformMsgNode extends TbAbstractTransformNode { diff --git a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.css b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.css deleted file mode 100644 index b5109b5f90..0000000000 --- a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.css +++ /dev/null @@ -1,2 +0,0 @@ -.tb-message-type-autocomplete .tb-not-found{display:block;line-height:1.5;height:48px}.tb-message-type-autocomplete .tb-not-found .tb-no-entries{line-height:48px}.tb-message-type-autocomplete li{height:auto!important;white-space:normal!important}.tb-generator-config tb-json-content.tb-message-body,.tb-generator-config tb-json-object-edit.tb-metadata-json{height:200px;display:block}.tb-mqtt-config .tb-credentials-panel-group .tb-panel-title{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;min-width:90px}@media (min-width:960px){.tb-mqtt-config .tb-credentials-panel-group .tb-panel-title{min-width:180px}}.tb-mqtt-config .tb-credentials-panel-group .tb-panel-prompt{font-size:14px;color:rgba(0,0,0,.87);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tb-mqtt-config .tb-credentials-panel-group.disabled .tb-panel-prompt,.tb-mqtt-config .tb-credentials-panel-group.disabled .tb-panel-title{color:rgba(0,0,0,.38)}.tb-mqtt-config .tb-credentials-panel-group md-icon.md-expansion-panel-icon{margin-right:0}.tb-mqtt-config .tb-container{width:100%}.tb-mqtt-config .dropdown-messages .tb-error-message{padding:5px 0 0}.tb-kv-map-config .header{padding-left:5px;padding-right:5px;padding-bottom:5px}.tb-kv-map-config .header .cell{padding-left:5px;padding-right:5px;color:rgba(0,0,0,.54);font-size:12px;font-weight:700;white-space:nowrap}.tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:20px;max-height:300px;overflow:auto}.tb-kv-map-config .body .row{padding-top:5px;max-height:40px}.tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}.tb-kv-map-config .body md-input-container.cell{margin:0;max-height:40px}.tb-kv-map-config .body .md-button{margin:0} -/*# sourceMappingURL=rulenode-core-config.css.map*/ \ No newline at end of file diff --git a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js index 0b8ad1cd05..6764e0e1b5 100644 --- a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js +++ b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js @@ -1,6 +1,16 @@ -!function(e){function t(i){if(n[i])return n[i].exports;var a=n[i]={exports:{},id:i,loaded:!1};return e[i].call(a.exports,a,a.exports,t),a.loaded=!0,a.exports}var n={};return t.m=e,t.c=n,t.p="/static/",t(0)}(function(e){for(var t in e)if(Object.prototype.hasOwnProperty.call(e,t))switch(typeof e[t]){case"function":break;case"object":e[t]=function(t){var n=t.slice(1),i=e[t[0]];return function(e,t,a){i.apply(this,[e,t,a].concat(n))}}(e[t]);break;default:e[t]=e[e[t]]}return e}([function(e,t,n){e.exports=n(101)},function(e,t){},1,1,1,1,function(e,t){e.exports="
tb.rulenode.customer-name-pattern-required
tb.rulenode.customer-name-pattern-hint
{{ 'tb.rulenode.create-customer-if-not-exists' | translate }}
tb.rulenode.customer-cache-expiration-required
tb.rulenode.customer-cache-expiration-range
tb.rulenode.customer-cache-expiration-hint
"},function(e,t){e.exports='
{{scope.name | translate}}
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-details-function' | translate }}
tb.rulenode.alarm-type-required
tb.rulenode.entity-type-pattern-hint
"},function(e,t){e.exports="
{{ 'tb.rulenode.test-details-function' | translate }}
{{ 'tb.rulenode.use-message-alarm-data' | translate }}
tb.rulenode.alarm-type-required
tb.rulenode.entity-type-pattern-hint
{{ severity.name | translate}}
tb.rulenode.alarm-severity-required
{{ 'tb.rulenode.propagate' | translate }}
"},function(e,t){e.exports="
{{ ('relation.search-direction.' + direction) | translate}}
tb.rulenode.entity-name-pattern-required
tb.rulenode.entity-name-pattern-hint
tb.rulenode.entity-type-pattern-required
tb.rulenode.entity-type-pattern-hint
tb.rulenode.relation-type-pattern-required
tb.rulenode.relation-type-pattern-hint
{{ 'tb.rulenode.create-entity-if-not-exists' | translate }}
tb.rulenode.create-entity-if-not-exists-hint
{{ 'tb.rulenode.remove-current-relations' | translate }}
tb.rulenode.remove-current-relations-hint
{{ 'tb.rulenode.change-originator-to-related-entity' | translate }}
tb.rulenode.change-originator-to-related-entity-hint
tb.rulenode.entity-cache-expiration-required
tb.rulenode.entity-cache-expiration-range
tb.rulenode.entity-cache-expiration-hint
"},function(e,t){e.exports="
{{ 'tb.rulenode.delete-relation-to-specific-entity' | translate }}
tb.rulenode.delete-relation-hint
{{ ('relation.search-direction.' + direction) | translate}}
tb.rulenode.entity-name-pattern-required
tb.rulenode.entity-name-pattern-hint
tb.rulenode.relation-type-pattern-required
tb.rulenode.relation-type-pattern-hint
tb.rulenode.entity-cache-expiration-required
tb.rulenode.entity-cache-expiration-range
tb.rulenode.entity-cache-expiration-hint
"},function(e,t){e.exports="
tb.rulenode.message-count-required
tb.rulenode.min-message-count-message
tb.rulenode.period-seconds-required
tb.rulenode.min-period-seconds-message
{{ 'tb.rulenode.test-generator-function' | translate }}
"},function(e,t){e.exports='
tb.rulenode.latitude-key-name-required
tb.rulenode.longitude-key-name-required
{{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}
{{ type.name | translate}}
tb.rulenode.circle-center-latitude-required
tb.rulenode.circle-center-longitude-required
tb.rulenode.range-required
{{ type.name | translate}}
tb.rulenode.polygon-definition-required
tb.rulenode.polygon-definition-hint
tb.rulenode.min-inside-duration-value-required
tb.rulenode.time-value-range
tb.rulenode.time-value-range
{{timeUnit.name | translate}}
tb.rulenode.min-outside-duration-value-required
tb.rulenode.time-value-range
tb.rulenode.time-value-range
{{timeUnit.name | translate}}
'},function(e,t){e.exports='
tb.rulenode.topic-pattern-required
tb.rulenode.bootstrap-servers-required
tb.rulenode.min-retries-message
tb.rulenode.min-batch-size-bytes-message
tb.rulenode.min-linger-ms-message
tb.rulenode.min-buffer-memory-bytes-message
{{ ackValue }}
tb.rulenode.key-serializer-required
tb.rulenode.value-serializer-required
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-to-string-function' | translate }}
"},function(e,t){e.exports='
tb.rulenode.topic-pattern-required
tb.rulenode.mqtt-topic-pattern-hint
tb.rulenode.host-required
tb.rulenode.port-required
tb.rulenode.port-range
tb.rulenode.port-range
tb.rulenode.connect-timeout-required
tb.rulenode.connect-timeout-range
tb.rulenode.connect-timeout-range
{{ \'tb.rulenode.clean-session\' | translate }} {{ \'tb.rulenode.enable-ssl\' | translate }}
{{ \'tb.rulenode.credentials\' | translate }}
{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}
{{ \'tb.rulenode.credentials\' | translate }}
{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}
{{credentialsValue.name | translate}}
tb.rulenode.credentials-type-required
tb.rulenode.username-required
tb.rulenode.password-required
'},function(e,t){e.exports="
tb.rulenode.interval-seconds-required
tb.rulenode.min-interval-seconds-message
tb.rulenode.output-timeseries-key-prefix-required
"; -},function(e,t){e.exports='
{{ \'tb.rulenode.use-metadata-period-in-seconds-patterns\' | translate }}
tb.rulenode.use-metadata-period-in-seconds-patterns-hint
tb.rulenode.period-seconds-required
tb.rulenode.min-period-0-seconds-message
tb.rulenode.period-in-seconds-pattern-required
tb.rulenode.period-in-seconds-pattern-hint
tb.rulenode.max-pending-messages-required
tb.rulenode.max-pending-messages-range
tb.rulenode.max-pending-messages-range
'},function(e,t){e.exports="
tb.rulenode.gcp-project-id-required
tb.rulenode.pubsub-topic-name-required
{{ 'action.remove' | translate }} close
tb.rulenode.message-attributes-hint
"},function(e,t){e.exports='
{{ property }}
tb.rulenode.host-required
tb.rulenode.port-required
tb.rulenode.port-range
tb.rulenode.port-range
{{ \'tb.rulenode.automatic-recovery\' | translate }}
tb.rulenode.min-connection-timeout-ms-message
tb.rulenode.min-handshake-timeout-ms-message
'},function(e,t){e.exports='
tb.rulenode.endpoint-url-pattern-required
tb.rulenode.endpoint-url-pattern-hint
{{ type }} {{ \'tb.rulenode.use-simple-client-http-factory\' | translate }}
tb.rulenode.headers-hint
'},function(e,t){e.exports="
"},function(e,t){e.exports="
tb.rulenode.timeout-required
tb.rulenode.min-timeout-message
"},function(e,t){e.exports='
tb.rulenode.custom-table-name-required
tb.rulenode.custom-table-hint
'},function(e,t){e.exports='
{{ \'tb.rulenode.use-system-smtp-settings\' | translate }}
{{smtpProtocol.toUpperCase()}}
tb.rulenode.smtp-host-required
tb.rulenode.smtp-port-required
tb.rulenode.smtp-port-range
tb.rulenode.smtp-port-range
tb.rulenode.timeout-required
tb.rulenode.min-timeout-msec-message
{{ \'tb.rulenode.enable-tls\' | translate }}
'},function(e,t){e.exports="
tb.rulenode.topic-arn-pattern-required
tb.rulenode.topic-arn-pattern-hint
tb.rulenode.aws-access-key-id-required
tb.rulenode.aws-secret-access-key-required
tb.rulenode.aws-region-required
"},function(e,t){e.exports='
{{ type.name | translate }}
tb.rulenode.queue-url-pattern-required
tb.rulenode.queue-url-pattern-hint
tb.rulenode.min-delay-seconds-message
tb.rulenode.max-delay-seconds-message
tb.rulenode.message-attributes-hint
tb.rulenode.aws-access-key-id-required
tb.rulenode.aws-secret-access-key-required
tb.rulenode.aws-region-required
'},function(e,t){e.exports="
tb.rulenode.default-ttl-required
tb.rulenode.min-default-ttl-message
"},function(e,t){e.exports="
tb.rulenode.customer-name-pattern-required
tb.rulenode.customer-name-pattern-hint
tb.rulenode.customer-cache-expiration-required
tb.rulenode.customer-cache-expiration-range
tb.rulenode.customer-cache-expiration-hint
"},function(e,t){e.exports='
{{ (\'relation.search-direction.\' + direction) | translate}}
relation.relation-type
device.device-types
'},function(e,t){e.exports="
{{ 'tb.rulenode.latest-telemetry' | translate }}
"},function(e,t){e.exports='
{{ \'tb.rulenode.tell-failure-if-absent\' | translate }}
tb.rulenode.tell-failure-if-absent-hint
'},function(e,t){e.exports='
{{\'tb.rulenode.entity-details-\'+item.toLowerCase() | translate}} tb.rulenode.no-entity-details-matching {{\'tb.rulenode.entity-details-\'+$chip.toLowerCase() | translate}} {{ \'tb.rulenode.add-to-metadata\' | translate }}
tb.rulenode.add-to-metadata-hint
'},function(e,t){e.exports='
{{ type }}
tb.rulenode.fetch-mode-hint
{{ type }}
tb.rulenode.order-by-hint
{{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}
tb.rulenode.use-metadata-interval-patterns-hint
tb.rulenode.start-interval-value-required
tb.rulenode.time-value-range
tb.rulenode.time-value-range
{{timeUnit.name | translate}}
tb.rulenode.end-interval-value-required
tb.rulenode.time-value-range
tb.rulenode.time-value-range
{{timeUnit.name | translate}}
tb.rulenode.start-interval-pattern-required
tb.rulenode.start-interval-pattern-hint
tb.rulenode.end-interval-pattern-required
tb.rulenode.end-interval-pattern-hint
'},function(e,t){e.exports='
{{ \'tb.rulenode.tell-failure-if-absent\' | translate }}
tb.rulenode.tell-failure-if-absent-hint
'},function(e,t){e.exports='
'; -},function(e,t){e.exports="
{{ 'tb.rulenode.latest-telemetry' | translate }}
"},31,function(e,t){e.exports='
tb.rulenode.separator-hint
tb.rulenode.separator-hint
{{ \'tb.rulenode.check-all-keys\' | translate }}
tb.rulenode.check-all-keys-hint
'},function(e,t){e.exports="
{{ 'tb.rulenode.check-relation-to-specific-entity' | translate }}
tb.rulenode.check-relation-hint
{{ ('relation.search-direction.' + direction) | translate}}
"},function(e,t){e.exports='
tb.rulenode.latitude-key-name-required
tb.rulenode.longitude-key-name-required
{{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}
{{ type.name | translate}}
tb.rulenode.circle-center-latitude-required
tb.rulenode.circle-center-longitude-required
tb.rulenode.range-required
{{ type.name | translate}}
tb.rulenode.polygon-definition-required
tb.rulenode.polygon-definition-hint
'},function(e,t){e.exports='
{{item}}
tb.rulenode.no-message-types-found
tb.rulenode.no-message-type-matching tb.rulenode.create-new-message-type
{{$chip.name}}
'},function(e,t){e.exports='
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-filter-function' | translate }}
"},function(e,t){e.exports="
{{ 'tb.rulenode.test-switch-function' | translate }}
"},function(e,t){e.exports='
{{ keyText }} {{ valText }}  
{{keyRequiredText}}
{{valRequiredText}}
{{ \'tb.key-val.remove-entry\' | translate }} close
{{ \'tb.key-val.add-entry\' | translate }} add {{ \'action.add\' | translate }}
'},function(e,t){e.exports="
{{ ('relation.search-direction.' + direction) | translate}}
relation.relation-filters
"},function(e,t){e.exports='
{{ source.name | translate}}
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-transformer-function' | translate }}
"},function(e,t){e.exports="
tb.rulenode.from-template-required
tb.rulenode.from-template-hint
tb.rulenode.to-template-required
tb.rulenode.mail-address-list-template-hint
tb.rulenode.mail-address-list-template-hint
tb.rulenode.mail-address-list-template-hint
tb.rulenode.subject-template-required
tb.rulenode.subject-template-hint
tb.rulenode.body-template-required
tb.rulenode.body-template-hint
"},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(6),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(7),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,i){var a=function(a,r,l,s){var d=o.default;r.html(d),a.types=n,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue},a.testDetailsBuildJs=function(e){var n=angular.copy(a.configuration.alarmDetailsBuildJs);i.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(8),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,i){var a=function(a,r,l,s){var d=o.default;r.html(d),a.types=n,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue},a.testDetailsBuildJs=function(e){var n=angular.copy(a.configuration.alarmDetailsBuildJs);i.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(9),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(10),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(11),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,i){var a=function(a,r,l,s){var d=o.default;r.html(d),a.types=n,a.originator=null,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue,a.configuration.originatorId&&a.configuration.originatorType?a.originator={id:a.configuration.originatorId,entityType:a.configuration.originatorType}:a.originator=null,a.$watch("originator",function(e,t){angular.equals(e,t)||(a.originator?(s.$viewValue.originatorId=a.originator.id,s.$viewValue.originatorType=a.originator.entityType):(s.$viewValue.originatorId=null,s.$viewValue.originatorType=null))},!0)},a.testScript=function(e){var n=angular.copy(a.configuration.jsScript);i.testNodeScript(e,n,"generate",t.instant("tb.rulenode.generator")+"","Generate",["prevMsg","prevMetadata","prevMsgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,s.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a,n(1);var r=n(12),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(13),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(74),r=i(a),o=n(52),l=i(o),s=n(57),d=i(s),u=n(54),c=i(u),m=n(53),g=i(m),p=n(61),f=i(p),b=n(68),v=i(b),y=n(69),h=i(y),q=n(67),$=i(q),x=n(60),k=i(x),T=n(72),C=i(T),w=n(73),M=i(w),N=n(66),_=i(N),S=n(62),E=i(S),F=n(71),P=i(F),A=n(64),V=i(A),I=n(63),j=i(I),O=n(51),D=i(O),R=n(75),K=i(R),L=n(56),U=i(L),z=n(55),B=i(z),H=n(70),G=i(H),Y=n(58),Q=i(Y),J=n(65),W=i(J);t.default=angular.module("thingsboard.ruleChain.config.action",[]).directive("tbActionNodeTimeseriesConfig",r.default).directive("tbActionNodeAttributesConfig",l.default).directive("tbActionNodeGeneratorConfig",d.default).directive("tbActionNodeCreateAlarmConfig",c.default).directive("tbActionNodeClearAlarmConfig",g.default).directive("tbActionNodeLogConfig",f.default).directive("tbActionNodeRpcReplyConfig",v.default).directive("tbActionNodeRpcRequestConfig",h.default).directive("tbActionNodeRestApiCallConfig",$.default).directive("tbActionNodeKafkaConfig",k.default).directive("tbActionNodeSnsConfig",C.default).directive("tbActionNodeSqsConfig",M.default).directive("tbActionNodeRabbitMqConfig",_.default).directive("tbActionNodeMqttConfig",E.default).directive("tbActionNodeSendEmailConfig",P.default).directive("tbActionNodeMsgDelayConfig",V.default).directive("tbActionNodeMsgCountConfig",j.default).directive("tbActionNodeAssignToCustomerConfig",D.default).directive("tbActionNodeUnAssignToCustomerConfig",K.default).directive("tbActionNodeDeleteRelationConfig",U.default).directive("tbActionNodeCreateRelationConfig",B.default).directive("tbActionNodeCustomTableConfig",G.default).directive("tbActionNodeGpsGeofencingConfig",Q.default).directive("tbActionNodePubSubConfig",W.default).name},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.ackValues=["all","-1","0","1"],t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(14),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){var s=o.default;a.html(s),i.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(i.configuration)}),l.$render=function(){i.configuration=l.$viewValue},i.testScript=function(e){var a=angular.copy(i.configuration.jsScript);n.testNodeScript(e,a,"string",t.instant("tb.rulenode.to-string")+"","ToString",["msg","metadata","msgType"],i.ruleNodeId).then(function(e){i.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:i}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(15),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){var s=o.default;a.html(s),i.$mdExpansionPanel=t,i.ruleNodeTypes=n,i.credentialsTypeChanged=function(){var e=i.configuration.credentials.type;i.configuration.credentials={},i.configuration.credentials.type=e,i.updateValidity()},i.certFileAdded=function(e,t){var n=new FileReader;n.onload=function(n){i.$apply(function(){if(n.target.result){l.$setDirty();var a=n.target.result;a&&a.length>0&&("caCert"==t&&(i.configuration.credentials.caCertFileName=e.name,i.configuration.credentials.caCert=a),"privateKey"==t&&(i.configuration.credentials.privateKeyFileName=e.name,i.configuration.credentials.privateKey=a),"Cert"==t&&(i.configuration.credentials.certFileName=e.name,i.configuration.credentials.cert=a)),i.updateValidity()}})},n.readAsText(e.file)},i.clearCertFile=function(e){l.$setDirty(),"caCert"==e&&(i.configuration.credentials.caCertFileName=null,i.configuration.credentials.caCert=null),"privateKey"==e&&(i.configuration.credentials.privateKeyFileName=null,i.configuration.credentials.privateKey=null),"Cert"==e&&(i.configuration.credentials.certFileName=null,i.configuration.credentials.cert=null),i.updateValidity()},i.updateValidity=function(){var e=!0,t=i.configuration.credentials;t.type==n.mqttCredentialTypes["cert.PEM"].value&&(t.caCert&&t.cert&&t.privateKey||(e=!1)),l.$setValidity("Certs",e)},i.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(i.configuration)}),l.$render=function(){i.configuration=l.$viewValue},e(a.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:i}}a.$inject=["$compile","$mdExpansionPanel","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a,n(2);var r=n(16),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(17),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(18),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.serviceAccountFileAdded=function(e){var t=new FileReader;t.onload=function(t){n.$apply(function(){if(t.target.result){r.$setDirty();var i=t.target.result;i&&i.length>0&&(n.configuration.serviceAccountKeyFileName=e.name,n.configuration.serviceAccountKey=i),n.updateValidity()}})},t.readAsText(e.file)},n.clearServiceAccountFile=function(){r.$setDirty(),n.configuration.serviceAccountKeyFileName=null,n.configuration.serviceAccountKey=null,n.updateValidity()},n.updateValidity=function(){var e=!0,t=n.configuration;t.serviceAccountKeyFileName&&t.serviceAccountKey||(e=!1),r.$setValidity("SAKey",e)},n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(19),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(20),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(21),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(22),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(23),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(24),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.smtpProtocols=["smtp","smtps"],t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly" -},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(25),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(26),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(27),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(28),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(29),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||r.$setViewValue(n.query)}),r.$render=function(){n.query=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(30),o=i(r)},function(e,t){"use strict";function n(e){var t=function(t,n,i,a){n.html("
"),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}n.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(31),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(32),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),n.entityDetailsList=[];for(var s in t.entityDetails){var d=s;n.entityDetailsList.push(d)}r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(33),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){var s=o.default;a.html(s);var d=186;i.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,d],i.ruleNodeTypes=n,i.aggPeriodTimeUnits={},i.aggPeriodTimeUnits.MINUTES=n.timeUnit.MINUTES,i.aggPeriodTimeUnits.HOURS=n.timeUnit.HOURS,i.aggPeriodTimeUnits.DAYS=n.timeUnit.DAYS,i.aggPeriodTimeUnits.MILLISECONDS=n.timeUnit.MILLISECONDS,i.aggPeriodTimeUnits.SECONDS=n.timeUnit.SECONDS,i.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(i.configuration)}),l.$render=function(){i.configuration=l.$viewValue},e(a.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{},link:i}}a.$inject=["$compile","$mdConstant","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(34),o=i(r);n(3)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(83),r=i(a),o=n(84),l=i(o),s=n(79),d=i(s),u=n(85),c=i(u),m=n(78),g=i(m),p=n(86),f=i(p),b=n(81),v=i(b),y=n(80),h=i(y);t.default=angular.module("thingsboard.ruleChain.config.enrichment",[]).directive("tbEnrichmentNodeOriginatorAttributesConfig",r.default).directive("tbEnrichmentNodeOriginatorFieldsConfig",l.default).directive("tbEnrichmentNodeDeviceAttributesConfig",d.default).directive("tbEnrichmentNodeRelatedAttributesConfig",c.default).directive("tbEnrichmentNodeCustomerAttributesConfig",g.default).directive("tbEnrichmentNodeTenantAttributesConfig",f.default).directive("tbEnrichmentNodeGetTelemetryFromDatabase",v.default).directive("tbEnrichmentNodeEntityDetailsConfig",h.default).name},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(35),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(36),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(37),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(38),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(39),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(40),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(41),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(93),r=i(a),o=n(91),l=i(o),s=n(94),d=i(s),u=n(88),c=i(u),m=n(92),g=i(m),p=n(87),f=i(p),b=n(89),v=i(b);t.default=angular.module("thingsboard.ruleChain.config.filter",[]).directive("tbFilterNodeScriptConfig",r.default).directive("tbFilterNodeMessageTypeConfig",l.default).directive("tbFilterNodeSwitchConfig",d.default).directive("tbFilterNodeCheckRelationConfig",c.default).directive("tbFilterNodeOriginatorTypeConfig",g.default).directive("tbFilterNodeCheckMessageConfig",f.default).directive("tbFilterNodeGpsGeofencingConfig",v.default).name},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){function s(){if(l.$viewValue){for(var e=[],t=0;t-1&&t.kvList.splice(e,1)}function l(){t.kvList||(t.kvList=[]),t.kvList.push({key:"",value:""})}function s(){var e={};t.kvList.forEach(function(t){t.key&&(e[t.key]=t.value)}),a.$setViewValue(e),d()}function d(){var e=!0;t.required&&!t.kvList.length&&(e=!1),a.$setValidity("kvMap",e)}var u=o.default;n.html(u),t.ngModelCtrl=a,t.removeKeyVal=r,t.addKeyVal=l,t.kvList=[],t.$watch("query",function(e,n){angular.equals(e,n)||a.$setViewValue(t.query)}),a.$render=function(){if(a.$viewValue){var e=a.$viewValue;t.kvList.length=0;for(var n in e)t.kvList.push({key:n,value:e[n]})}t.$watch("kvList",function(e,t){angular.equals(e,t)||s()},!0),d()},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",disabled:"=ngDisabled",requiredText:"=",keyText:"=",keyRequiredText:"=",valText:"=",valRequiredText:"="},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(46),o=i(r);n(5)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||r.$setViewValue(n.query)}),r.$render=function(){n.query=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(47),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(48),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(97),r=i(a),o=n(99),l=i(o),s=n(100),d=i(s);t.default=angular.module("thingsboard.ruleChain.config.transform",[]).directive("tbTransformationNodeChangeOriginatorConfig",r.default).directive("tbTransformationNodeScriptConfig",l.default).directive("tbTransformationNodeToEmailConfig",d.default).name},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){var s=o.default;a.html(s),i.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(i.configuration)}),l.$render=function(){i.configuration=l.$viewValue},i.testScript=function(e){var a=angular.copy(i.configuration.jsScript);n.testNodeScript(e,a,"update",t.instant("tb.rulenode.transformer")+"","Transform",["msg","metadata","msgType"],i.ruleNodeId).then(function(e){i.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:i}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(49),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(50),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(104),r=i(a),o=n(90),l=i(o),s=n(82),d=i(s),u=n(98),c=i(u),m=n(59),g=i(m),p=n(77),f=i(p),b=n(96),v=i(b),y=n(76),h=i(y),q=n(95),$=i(q),x=n(103),k=i(x);t.default=angular.module("thingsboard.ruleChain.config",[r.default,l.default,d.default,c.default,g.default]).directive("tbNodeEmptyConfig",f.default).directive("tbRelationsQueryConfig",v.default).directive("tbDeviceRelationsQueryConfig",h.default).directive("tbKvMapConfig",$.default).config(k.default).name},function(e,t){"use strict";function n(e){var t={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-name-pattern-hint":"Name pattern, use ${metaKeyName} to substitute variables from metadata","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-type-pattern-hint":"Type pattern, use ${metaKeyName} to substitute variables from metadata","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-name-pattern-hint":"Customer name pattern, use ${metaKeyName} to substitute variables from metadata","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.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.","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","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","latest-timeseries":"Latest timeseries","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-hint":"Relation type pattern, use ${metaKeyName} to substitute variables from metadata","relation-type-pattern-required":"Relation type pattern is required","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","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","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 metadata period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds metadata pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","period-in-seconds-pattern-hint":"Period in seconds pattern, use ${metaKeyName} to substitute variables from metadata","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",propagate:"Propagate",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","from-template-hint":"From address template, use ${metaKeyName} to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use ${metaKeyName} to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use ${metaKeyName} to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use ${metaKeyName} to substitute variables from metadata","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","endpoint-url-pattern-hint":"HTTP URL address pattern, use ${metaKeyName} to substitute variables from metadata","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory",headers:"Headers","headers-hint":"Use ${metaKeyName} in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use ${metaKeyName} to substitute variables from metadata","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","topic-arn-pattern-hint":"Topic ARN pattern, use ${metaKeyName} to substitute variables from metadata","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","queue-url-pattern-hint":"Queue URL pattern, use ${metaKeyName} to substitute variables from metadata","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 ${metaKeyName} in name/value fields to substitute variables from metadata","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","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","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"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-interval-patterns":"Use metadata interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","check-all-keys":"Check that all selected keys 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-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","start-interval-pattern-hint":"Start interval pattern, use ${metaKeyName} to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use ${metaKeyName} to substitute variables from metadata","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","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":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-country":"Country","entity-details-state":"State","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","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","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":"You should 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-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".'},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}};e.translations("en_US",t)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){(0,o.default)(e)}a.$inject=["$translateProvider"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(102),o=i(r)},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=angular.module("thingsboard.ruleChain.config.types",[]).constant("ruleNodeTypes",{originatorSource:{CUSTOMER:{name:"tb.rulenode.originator-customer",value:"CUSTOMER"},TENANT:{name:"tb.rulenode.originator-tenant",value:"TENANT"},RELATED:{name:"tb.rulenode.originator-related",value:"RELATED"},ALARM_ORIGINATOR:{name:"tb.rulenode.originator-alarm-originator",value:"ALARM_ORIGINATOR"}},fetchModeType:["FIRST","LAST","ALL"],samplingOrder:["ASC","DESC"],httpRequestType:["GET","POST","PUT","DELETE"],entityDetails:{COUNTRY:{name:"tb.rulenode.entity-details-country",value:"COUNTRY"},STATE:{name:"tb.rulenode.entity-details-state",value:"STATE"},ZIP:{name:"tb.rulenode.entity-details-zip",value:"ZIP"},ADDRESS:{name:"tb.rulenode.entity-details-address",value:"ADDRESS"},ADDRESS2:{name:"tb.rulenode.entity-details-address2",value:"ADDRESS2"},PHONE:{name:"tb.rulenode.entity-details-phone",value:"PHONE"},EMAIL:{name:"tb.rulenode.entity-details-email",value:"EMAIL"},ADDITIONAL_INFO:{name:"tb.rulenode.entity-details-additional_info",value:"ADDITIONAL_INFO"}},sqsQueueType:{STANDARD:{name:"tb.rulenode.sqs-queue-standard",value:"STANDARD"},FIFO:{name:"tb.rulenode.sqs-queue-fifo",value:"FIFO"}},perimeterType:{CIRCLE:{name:"tb.rulenode.perimeter-circle",value:"CIRCLE"},POLYGON:{name:"tb.rulenode.perimeter-polygon",value:"POLYGON"}},timeUnit:{MILLISECONDS:{value:"MILLISECONDS",name:"tb.rulenode.time-unit-milliseconds"},SECONDS:{value:"SECONDS",name:"tb.rulenode.time-unit-seconds"},MINUTES:{value:"MINUTES",name:"tb.rulenode.time-unit-minutes"},HOURS:{value:"HOURS",name:"tb.rulenode.time-unit-hours"},DAYS:{value:"DAYS",name:"tb.rulenode.time-unit-days"}},rangeUnit:{METER:{value:"METER",name:"tb.rulenode.range-unit-meter"},KILOMETER:{value:"KILOMETER",name:"tb.rulenode.range-unit-kilometer"},FOOT:{value:"FOOT",name:"tb.rulenode.range-unit-foot"},MILE:{value:"MILE",name:"tb.rulenode.range-unit-mile"},NAUTICAL_MILE:{value:"NAUTICAL_MILE",name:"tb.rulenode.range-unit-nautical-mile"}},mqttCredentialTypes:{anonymous:{value:"anonymous",name:"tb.rulenode.credentials-anonymous"},basic:{value:"basic",name:"tb.rulenode.credentials-basic"},"cert.PEM":{value:"cert.PEM",name:"tb.rulenode.credentials-pem"}}}).name}])); -//# sourceMappingURL=rulenode-core-config.js.map \ No newline at end of file +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@angular/core"),require("@angular/common"),require("@ngx-translate/core"),require("@shared/public-api"),require("@ngrx/store"),require("@angular/forms")):"function"==typeof define&&define.amd?define("rulenode-core-config",["exports","@angular/core","@angular/common","@ngx-translate/core","@shared/public-api","@ngrx/store","@angular/forms"],t):t((e=e||self)["rulenode-core-config"]={},e.ng.core,e.ng.common,e["ngx-translate"],e.shared,e["ngrx-store"],e.ng.forms)}(this,(function(e,t,r,i,a,n,s){"use strict"; +/*! ***************************************************************************** + Copyright (c) Microsoft Corporation. All rights reserved. + 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 + + THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED + WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, + MERCHANTABLITY OR NON-INFRINGEMENT. + + See the Apache Version 2.0 License for specific language governing permissions + and limitations under the License. + ***************************************************************************** */var o=function(e,t){return(o=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)t.hasOwnProperty(r)&&(e[r]=t[r])})(e,t)};function u(e,t){function r(){this.constructor=e}o(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)}var d=function(e){function r(t){var r=e.call(this,t)||this;return r.store=t,r}return u(r,e),r.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},r.prototype.onConfigurationSet=function(e){},r.decorators=[{type:t.Component,args:[{selector:"tb-node-empty-config",template:"
"}]}],r.ctorParameters=function(){return[{type:n.Store}]},r}(a.RuleNodeConfigurationComponent);var l=function(e){function r(t,r){var i=e.call(this,t)||this;return i.store=t,i.fb=r,i.attributeScopes=Object.keys(a.AttributeScope),i.telemetryTypeTranslationsMap=a.telemetryTypeTranslations,i}return u(r,e),r.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},r.prototype.onConfigurationSet=function(e){var t=this;this.attributesConfigForm=this.fb.group({scope:[e?e.scope:null,[s.Validators.required]]}),this.attributesConfigForm.valueChanges.subscribe((function(e){t.attributesConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},r.decorators=[{type:t.Component,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'}]}],r.ctorParameters=function(){return[{type:n.Store},{type:s.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var m=function(e){function r(t,r){var i=e.call(this,t)||this;return i.store=t,i.fb=r,i}return u(r,e),r.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},r.prototype.onConfigurationSet=function(e){var t=this;this.timeseriesConfigForm=this.fb.group({defaultTTL:[e?e.defaultTTL:null,[s.Validators.required,s.Validators.min(0)]]}),this.timeseriesConfigForm.valueChanges.subscribe((function(e){t.timeseriesConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},r.decorators=[{type:t.Component,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'}]}],r.ctorParameters=function(){return[{type:n.Store},{type:s.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var c=function(){function e(){}return e.decorators=[{type:t.NgModule,args:[{declarations:[l,m],imports:[r.CommonModule,a.SharedModule],exports:[l,m]}]}],e}(),p=function(){function e(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-name-pattern-hint":"Name pattern, use ${metaKeyName} to substitute variables from metadata","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-type-pattern-hint":"Type pattern, use ${metaKeyName} to substitute variables from metadata","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-name-pattern-hint":"Customer name pattern, use ${metaKeyName} to substitute variables from metadata","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.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.","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","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","latest-timeseries":"Latest timeseries","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-hint":"Relation type pattern, use ${metaKeyName} to substitute variables from metadata","relation-type-pattern-required":"Relation type pattern is required","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","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","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 metadata period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds metadata pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","period-in-seconds-pattern-hint":"Period in seconds pattern, use ${metaKeyName} to substitute variables from metadata","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",propagate:"Propagate",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","from-template-hint":"From address template, use ${metaKeyName} to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use ${metaKeyName} to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use ${metaKeyName} to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use ${metaKeyName} to substitute variables from metadata","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","endpoint-url-pattern-hint":"HTTP URL address pattern, use ${metaKeyName} to substitute variables from metadata","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory",headers:"Headers","headers-hint":"Use ${metaKeyName} in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use ${metaKeyName} to substitute variables from metadata","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","topic-arn-pattern-hint":"Topic ARN pattern, use ${metaKeyName} to substitute variables from metadata","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","queue-url-pattern-hint":"Queue URL pattern, use ${metaKeyName} to substitute variables from metadata","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 ${metaKeyName} in name/value fields to substitute variables from metadata","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","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","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"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-interval-patterns":"Use metadata interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","check-all-keys":"Check that all selected keys 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-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","start-interval-pattern-hint":"Start interval pattern, use ${metaKeyName} to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use ${metaKeyName} to substitute variables from metadata","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","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":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","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","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","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":"You should 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-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".'},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}},!0)}(e)}return e.decorators=[{type:t.NgModule,args:[{declarations:[d],imports:[r.CommonModule,a.SharedModule],exports:[c,d]}]}],e.ctorParameters=function(){return[{type:i.TranslateService}]},e}();e.RuleNodeCoreConfigModule=p,e.default=p,e.ɵa=d,e.ɵb=c,e.ɵc=l,e.ɵd=m,Object.defineProperty(e,"__esModule",{value:!0})})); +//# sourceMappingURL=rulenode-core-config.umd.min.js.map \ No newline at end of file diff --git a/ui-ngx/angular.json b/ui-ngx/angular.json index c68e36a90b..3a6fb4fdb6 100644 --- a/ui-ngx/angular.json +++ b/ui-ngx/angular.json @@ -78,7 +78,8 @@ "node_modules/ace-builds/src-min/snippets/css.js", "node_modules/ace-builds/src-min/snippets/json.js", "node_modules/ace-builds/src-min/snippets/java.js", - "node_modules/ace-builds/src-min/snippets/javascript.js" + "node_modules/ace-builds/src-min/snippets/javascript.js", + "node_modules/systemjs/dist/system.js" ], "es5BrowserSupport": true, "customWebpackConfig": { @@ -116,7 +117,7 @@ "builder": "@angular-builders/custom-webpack:dev-server", "options": { "browserTarget": "thingsboard:build", - "proxyConfig": "proxy.conf.json" + "proxyConfig": "proxy.conf.js" }, "configurations": { "production": { diff --git a/ui-ngx/package-lock.json b/ui-ngx/package-lock.json index c435a8bec2..1922bf20ed 100644 --- a/ui-ngx/package-lock.json +++ b/ui-ngx/package-lock.json @@ -12624,6 +12624,11 @@ "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", "dev": true }, + "systemjs": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/systemjs/-/systemjs-0.21.5.tgz", + "integrity": "sha512-GWzZhN/x7Fsae2CYkz2GF7OgOS+YDgKulcgd5L1kTogZHMKDrPx5T8zI8I0y5RoU9Dx78Z7j1XMfuFa1thD84A==" + }, "tapable": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", diff --git a/ui-ngx/package.json b/ui-ngx/package.json index db02a1ca6a..c952c89c51 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -74,6 +74,7 @@ "schema-inspector": "^1.6.8", "screenfull": "^5.0.0", "split.js": "^1.5.11", + "systemjs": "0.21.5", "tinycolor2": "^1.4.1", "tooltipster": "^4.2.7", "tslib": "^1.10.0", diff --git a/ui-ngx/proxy.conf.js b/ui-ngx/proxy.conf.js new file mode 100644 index 0000000000..6b45570da1 --- /dev/null +++ b/ui-ngx/proxy.conf.js @@ -0,0 +1,35 @@ +/* + * Copyright © 2016-2019 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. + */ + +const ruleNodeUiforwardHost = 'localhost'; +const ruleNodeUiforwardPort = 8080; + +const PROXY_CONFIG = { + '/api': { + 'target': 'http://localhost:8080', + 'secure': false + }, + '/static/rulenode': { + 'target': `http://${ruleNodeUiforwardHost}:${ruleNodeUiforwardPort}`, + 'secure': false + }, + '/api/ws': { + 'target': 'ws://localhost:8080', + 'ws': true + } +} + +module.exports = PROXY_CONFIG; diff --git a/ui-ngx/src/app/core/api/public-api.ts b/ui-ngx/src/app/core/api/public-api.ts new file mode 100644 index 0000000000..4726c8f670 --- /dev/null +++ b/ui-ngx/src/app/core/api/public-api.ts @@ -0,0 +1,22 @@ +/// +/// Copyright © 2016-2019 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 './alias-controller'; +export * from './data-aggregator'; +export * from './datasource.service'; +export * from './datasource-subcription'; +export * from './widget-api.models'; +export * from './widget-subscription'; diff --git a/ui-ngx/src/app/core/http/dashboard.service.ts b/ui-ngx/src/app/core/http/dashboard.service.ts index 96b714a1c1..ba45fed342 100644 --- a/ui-ngx/src/app/core/http/dashboard.service.ts +++ b/ui-ngx/src/app/core/http/dashboard.service.ts @@ -25,6 +25,7 @@ import {WINDOW} from '@core/services/window.service'; import { ActivationEnd, NavigationEnd, Router } from '@angular/router'; import { filter, map, publishReplay, refCount } from 'rxjs/operators'; +// @dynamic @Injectable({ providedIn: 'root' }) diff --git a/ui-ngx/src/app/core/http/public-api.ts b/ui-ngx/src/app/core/http/public-api.ts new file mode 100644 index 0000000000..998ef6ac52 --- /dev/null +++ b/ui-ngx/src/app/core/http/public-api.ts @@ -0,0 +1,34 @@ +/// +/// Copyright © 2016-2019 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 './admin.service'; +export * from './alarm.service'; +export * from './asset.service'; +export * from './attribute.service'; +export * from './audit-log.service'; +export * from './component-descriptor.service'; +export * from './customer.service'; +export * from './dashboard.service'; +export * from './device.service'; +export * from './entity.service'; +export * from './entity-relation.service'; +export * from './entity-view.service'; +export * from './event.service'; +export * from './http-utils'; +export * from './rule-chain.service'; +export * from './tenant.service'; +export * from './user.service'; +export * from './widget.service'; diff --git a/ui-ngx/src/app/core/http/rule-chain.service.ts b/ui-ngx/src/app/core/http/rule-chain.service.ts index 8619c0e3f6..4fe6b37683 100644 --- a/ui-ngx/src/app/core/http/rule-chain.service.ts +++ b/ui-ngx/src/app/core/http/rule-chain.service.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Injectable } from '@angular/core'; +import { ComponentFactory, Injectable } from '@angular/core'; import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; import { forkJoin, Observable, of } from 'rxjs/index'; import { HttpClient } from '@angular/common/http'; @@ -28,12 +28,16 @@ import { ruleNodeTypeComponentTypes, unknownNodeComponent } from '@shared/models/rule-chain.models'; import { ComponentDescriptorService } from './component-descriptor.service'; -import { LinkLabel, RuleNodeComponentDescriptor } from '@app/shared/models/rule-node.models'; +import { + IRuleNodeConfigurationComponent, + LinkLabel, + RuleNodeComponentDescriptor +} from '@app/shared/models/rule-node.models'; import { ResourcesService } from '../services/resources.service'; import { catchError, map, mergeMap } from 'rxjs/operators'; import { TranslateService } from '@ngx-translate/core'; import { EntityType } from '@shared/models/entity-type.models'; -import { deepClone } from '@core/utils'; +import { deepClone, snakeCase } from '@core/utils'; @Injectable({ providedIn: 'root' @@ -41,6 +45,7 @@ import { deepClone } from '@core/utils'; export class RuleChainService { private ruleNodeComponents: Array; + private ruleNodeConfigFactories: {[directive: string]: ComponentFactory} = {}; constructor( private http: HttpClient, @@ -105,13 +110,14 @@ export class RuleChainService { ); } - public getRuleNodeComponents(config?: RequestConfig): Observable> { + public getRuleNodeComponents(ruleNodeConfigResourcesModulesMap: {[key: string]: any}, config?: RequestConfig): + Observable> { if (this.ruleNodeComponents) { return of(this.ruleNodeComponents); } else { return this.loadRuleNodeComponents(config).pipe( mergeMap((components) => { - return this.resolveRuleNodeComponentsUiResources(components).pipe( + return this.resolveRuleNodeComponentsUiResources(components, ruleNodeConfigResourcesModulesMap).pipe( map((ruleNodeComponents) => { this.ruleNodeComponents = ruleNodeComponents; this.ruleNodeComponents.push(ruleChainNodeComponent); @@ -132,6 +138,10 @@ export class RuleChainService { } } + public getRuleNodeConfigFactory(directive: string): ComponentFactory { + return this.ruleNodeConfigFactories[directive]; + } + public getRuleNodeComponentByClazz(clazz: string): RuleNodeComponentDescriptor { const found = this.ruleNodeComponents.filter((component) => component.clazz === clazz); if (found && found.length) { @@ -192,11 +202,12 @@ export class RuleChainService { ); } - private resolveRuleNodeComponentsUiResources(components: Array): + private resolveRuleNodeComponentsUiResources(components: Array, + ruleNodeConfigResourcesModulesMap: {[key: string]: any}): Observable> { const tasks: Observable[] = []; components.forEach((component) => { - tasks.push(this.resolveRuleNodeComponentUiResources(component)); + tasks.push(this.resolveRuleNodeComponentUiResources(component, ruleNodeConfigResourcesModulesMap)); }); return forkJoin(tasks).pipe( catchError((err) => { @@ -205,13 +216,39 @@ export class RuleChainService { ); } - private resolveRuleNodeComponentUiResources(component: RuleNodeComponentDescriptor): Observable { - const uiResources = component.configurationDescriptor.nodeDefinition.uiResources; + private resolveRuleNodeComponentUiResources(component: RuleNodeComponentDescriptor, + ruleNodeConfigResourcesModulesMap: {[key: string]: any}): + Observable { + const nodeDefinition = component.configurationDescriptor.nodeDefinition; + const uiResources = nodeDefinition.uiResources; if (uiResources && uiResources.length) { + const commonResources = uiResources.filter((resource) => !resource.endsWith('.js')); + const moduleResource = uiResources.find((resource) => resource.endsWith('.js')); const tasks: Observable[] = []; - uiResources.forEach((uiResource) => { - tasks.push(this.resourcesService.loadResource(uiResource)); - }); + if (commonResources && commonResources.length) { + commonResources.forEach((resource) => { + tasks.push(this.resourcesService.loadResource(resource)); + }); + } + if (moduleResource) { + tasks.push(this.resourcesService.loadModule(moduleResource, ruleNodeConfigResourcesModulesMap).pipe( + map((res) => { + if (nodeDefinition.configDirective && nodeDefinition.configDirective.length) { + const selector = snakeCase(nodeDefinition.configDirective, '-'); + const componentFactory = res.componentFactories.find((factory) => + factory.selector === selector); + if (componentFactory) { + this.ruleNodeConfigFactories[nodeDefinition.configDirective] = componentFactory; + } else { + component.configurationDescriptor.nodeDefinition.uiResourceLoadError = + this.translate.instant('rulenode.directive-is-not-loaded', + {directiveName: nodeDefinition.configDirective}); + } + } + return of(component); + }) + )); + } return forkJoin(tasks).pipe( map((res) => { return component; @@ -231,7 +268,7 @@ export class RuleChainService { map(ruleChain => ruleChain), catchError((err) => { const ruleChain = { - id: { + id: { entityType: EntityType.RULE_CHAIN, id: ruleChainId } diff --git a/ui-ngx/src/app/core/local-storage/local-storage.service.ts b/ui-ngx/src/app/core/local-storage/local-storage.service.ts index 0c0bff21fc..10214e28a4 100644 --- a/ui-ngx/src/app/core/local-storage/local-storage.service.ts +++ b/ui-ngx/src/app/core/local-storage/local-storage.service.ts @@ -18,6 +18,7 @@ import { Injectable } from '@angular/core'; const APP_PREFIX = 'TB-'; +// @dynamic @Injectable( { providedIn: 'root' diff --git a/ui-ngx/src/app/core/public-api.ts b/ui-ngx/src/app/core/public-api.ts new file mode 100644 index 0000000000..9490c61ec1 --- /dev/null +++ b/ui-ngx/src/app/core/public-api.ts @@ -0,0 +1,23 @@ +/// +/// Copyright © 2016-2019 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 './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 './core.state'; +export * from './core.module'; diff --git a/ui-ngx/src/app/core/services/dialog/confirm-dialog.component.ts b/ui-ngx/src/app/core/services/dialog/confirm-dialog.component.ts index 13c384624a..e5665ed2d1 100644 --- a/ui-ngx/src/app/core/services/dialog/confirm-dialog.component.ts +++ b/ui-ngx/src/app/core/services/dialog/confirm-dialog.component.ts @@ -24,6 +24,7 @@ export interface ConfirmDialogData { ok: string; } +// @dynamic @Component({ selector: 'tb-confirm-dialog', templateUrl: './confirm-dialog.component.html', diff --git a/ui-ngx/src/app/core/services/public-api.ts b/ui-ngx/src/app/core/services/public-api.ts new file mode 100644 index 0000000000..c68fc65813 --- /dev/null +++ b/ui-ngx/src/app/core/services/public-api.ts @@ -0,0 +1,32 @@ +/// +/// Copyright © 2016-2019 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 './script/node-script-test.service'; +export * from './broadcast.models'; +export * from './broadcast.service'; +export * from './dashboard-utils.service'; +export * from './dialog.service'; +export * from './dynamic-component-factory.service'; +export * from './item-buffer.service'; +export * from './menu.models'; +export * from './menu.service'; +export * from './notification.service'; +export * from './raf.service'; +export * from './resources.service'; +export * from './time.service'; +export * from './title.service'; +export * from './utils.service'; +export * from './window.service'; diff --git a/ui-ngx/src/app/core/services/raf.service.ts b/ui-ngx/src/app/core/services/raf.service.ts index b844dd810a..fbe6639935 100644 --- a/ui-ngx/src/app/core/services/raf.service.ts +++ b/ui-ngx/src/app/core/services/raf.service.ts @@ -20,6 +20,7 @@ import { WINDOW } from '@core/services/window.service'; export type CancelAnimationFrame = () => void; +// @dynamic @Injectable({ providedIn: 'root' }) diff --git a/ui-ngx/src/app/core/services/resources.service.ts b/ui-ngx/src/app/core/services/resources.service.ts index 8b1b94d227..757f9f3b96 100644 --- a/ui-ngx/src/app/core/services/resources.service.ts +++ b/ui-ngx/src/app/core/services/resources.service.ts @@ -14,20 +14,25 @@ /// limitations under the License. /// -import { Injectable, Inject } from '@angular/core'; +import { Injectable, Inject, ModuleWithComponentFactories, Compiler, Injector } from '@angular/core'; import { DOCUMENT } from '@angular/common'; import { ReplaySubject, Observable, throwError } from 'rxjs'; +declare const SystemJS; + @Injectable({ providedIn: 'root' }) export class ResourcesService { private loadedResources: { [url: string]: ReplaySubject } = {}; + private loadedModules: { [url: string]: ReplaySubject> } = {}; private anchor = this.document.getElementsByTagName('head')[0] || this.document.getElementsByTagName('body')[0]; - constructor(@Inject(DOCUMENT) private readonly document: any) {} + constructor(@Inject(DOCUMENT) private readonly document: any, + private compiler: Compiler, + private injector: Injector) {} public loadResource(url: string): Observable { if (this.loadedResources[url]) { @@ -47,6 +52,49 @@ export class ResourcesService { return this.loadResourceByType(fileType, url); } + public loadModule(url: string, modulesMap: {[key: string]: any}): Observable> { + if (this.loadedModules[url]) { + return this.loadedModules[url].asObservable(); + } + const subject = new ReplaySubject>(); + this.loadedModules[url] = subject; + if (modulesMap) { + for (const moduleId of Object.keys(modulesMap)) { + SystemJS.set(moduleId, modulesMap[moduleId]); + } + } + SystemJS.import(url).then( + (module) => { + if (module.default) { + this.compiler.compileModuleAndAllComponentsAsync(module.default).then( + (compiled) => { + try { + compiled.ngModuleFactory.create(this.injector); + this.loadedModules[url].next(compiled); + this.loadedModules[url].complete(); + } catch (e) { + this.loadedModules[url].error(new Error(`Unable to init module from url: ${url}`)); + delete this.loadedModules[url]; + } + }, + (e) => { + this.loadedModules[url].error(new Error(`Unable to compile module from url: ${url}`)); + delete this.loadedModules[url]; + } + ); + } else { + this.loadedModules[url].error(new Error(`Module '${url}' doesn't have default export!`)); + delete this.loadedModules[url]; + } + }, + (e) => { + this.loadedModules[url].error(new Error(`Unable to load module from url: ${url}`)); + delete this.loadedModules[url]; + } + ); + return subject.asObservable(); + } + private loadResourceByType(type: 'css' | 'js', url: string): Observable { const subject = new ReplaySubject(); this.loadedResources[url] = subject; 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 new file mode 100644 index 0000000000..5391c2d602 --- /dev/null +++ b/ui-ngx/src/app/core/services/script/node-script-test.service.ts @@ -0,0 +1,31 @@ +/// +/// Copyright © 2016-2019 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 { Injectable } from '@angular/core'; +import { Observable, of } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class NodeScriptTestService { + + testNodeScript(script: string, scriptType: any, functionTitle: string, + functionName: string, argNames: string[], ruleNodeId: string): Observable { + console.log(`testNodeScript TODO: ${script}`); + return of(script); + } + +} diff --git a/ui-ngx/src/app/core/services/utils.service.ts b/ui-ngx/src/app/core/services/utils.service.ts index bdb3999c8f..e5c6196412 100644 --- a/ui-ngx/src/app/core/services/utils.service.ts +++ b/ui-ngx/src/app/core/services/utils.service.ts @@ -14,6 +14,9 @@ /// limitations under the License. /// +// tslint:disable-next-line:no-reference +/// + import { Inject, Injectable, NgZone } from '@angular/core'; import { WINDOW } from '@core/services/window.service'; import { ExceptionData } from '@app/shared/models/error.models'; @@ -67,6 +70,7 @@ const commonMaterialIcons: Array = [ 'more_horiz', 'more_vert', 'open_in 'settings', 'notifications', 'notifications_active', 'info', 'info_outline', 'warning', 'list', 'file_download', 'import_export', 'share', 'add', 'edit', 'done' ]; +// @dynamic @Injectable({ providedIn: 'root' }) diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index a84ef56958..21618ba623 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -387,3 +387,12 @@ export function guid(): string { return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); } + +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(); + }); +} diff --git a/ui-ngx/src/app/core/ws/telemetry-websocket.service.ts b/ui-ngx/src/app/core/ws/telemetry-websocket.service.ts index 89b50cf0a0..331efb9150 100644 --- a/ui-ngx/src/app/core/ws/telemetry-websocket.service.ts +++ b/ui-ngx/src/app/core/ws/telemetry-websocket.service.ts @@ -40,6 +40,7 @@ const RECONNECT_INTERVAL = 2000; const WS_IDLE_TIMEOUT = 90000; const MAX_PUBLISH_COMMANDS = 10; +// @dynamic @Injectable({ providedIn: 'root' }) diff --git a/ui-ngx/src/app/modules/home/components/alias/aliases-entity-select.component.ts b/ui-ngx/src/app/modules/home/components/alias/aliases-entity-select.component.ts index cc9b6b91ea..1b4164be34 100644 --- a/ui-ngx/src/app/modules/home/components/alias/aliases-entity-select.component.ts +++ b/ui-ngx/src/app/modules/home/components/alias/aliases-entity-select.component.ts @@ -30,6 +30,7 @@ import { AliasesEntitySelectPanelData } from './aliases-entity-select-panel.component'; +// @dynamic @Component({ selector: 'tb-aliases-entity-select', templateUrl: './aliases-entity-select.component.html', diff --git a/ui-ngx/src/app/modules/home/components/alias/entity-alias-dialog.component.ts b/ui-ngx/src/app/modules/home/components/alias/entity-alias-dialog.component.ts index 75b24cdf7d..dd869f16b7 100644 --- a/ui-ngx/src/app/modules/home/components/alias/entity-alias-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/alias/entity-alias-dialog.component.ts @@ -73,7 +73,7 @@ export class EntityAliasDialogComponent extends DialogComponent, private fb: FormBuilder, private utils: UtilsService, - private translate: TranslateService, + public translate: TranslateService, private entityService: EntityService) { super(store, router, dialogRef); this.isAdd = data.isAdd; diff --git a/ui-ngx/src/app/modules/home/components/alias/entity-alias-select.component.ts b/ui-ngx/src/app/modules/home/components/alias/entity-alias-select.component.ts index d7759fac21..ebb3cb301d 100644 --- a/ui-ngx/src/app/modules/home/components/alias/entity-alias-select.component.ts +++ b/ui-ngx/src/app/modules/home/components/alias/entity-alias-select.component.ts @@ -92,7 +92,7 @@ export class EntityAliasSelectComponent implements ControlValueAccessor, OnInit, filteredEntityAliases: Observable>; - private searchText = ''; + searchText = ''; private dirty = false; diff --git a/ui-ngx/src/app/modules/home/components/alias/entity-aliases-dialog.component.html b/ui-ngx/src/app/modules/home/components/alias/entity-aliases-dialog.component.html index c5f8539e48..3d930f6e46 100644 --- a/ui-ngx/src/app/modules/home/components/alias/entity-aliases-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/alias/entity-aliases-dialog.component.html @@ -42,7 +42,7 @@
+ *ngFor="let entityAliasControl of entityAliasesFormArray().controls; let $index = index"> {{$index + 1}}.
diff --git a/ui-ngx/src/app/modules/home/components/alias/entity-aliases-dialog.component.ts b/ui-ngx/src/app/modules/home/components/alias/entity-aliases-dialog.component.ts index 095435bfa3..5041c24dee 100644 --- a/ui-ngx/src/app/modules/home/components/alias/entity-aliases-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/alias/entity-aliases-dialog.component.ts @@ -154,6 +154,11 @@ export class EntityAliasesDialogComponent extends DialogComponent + (ngModelChange)="onWidgetsBundleChanged()">
diff --git a/ui-ngx/src/app/modules/home/components/widget/legend-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/legend-config.component.ts index a61a1648f4..956bab327d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/legend-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/legend-config.component.ts @@ -59,6 +59,7 @@ import { LegendConfigPanelData } from '@home/components/widget/legend-config-panel.component'; +// @dynamic @Component({ selector: 'tb-legend-config', templateUrl: './legend-config.component.html', diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts index 767b7109b5..e5db3c2666 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts @@ -14,6 +14,9 @@ /// limitations under the License. /// +// tslint:disable-next-line:no-reference +/// + import { JsonSettingsSchema, DataKey, DatasourceData } from '@shared/models/widget.models'; export declare type ChartType = 'line' | 'pie' | 'bar' | 'state' | 'graph'; @@ -395,8 +398,8 @@ export function flotSettingsSchema(chartType: ChartType): JsonSettingsSchema { return schema; } -export function flotPieSettingsSchema(): JsonSettingsSchema { - return { +export const flotPieSettingsSchema: JsonSettingsSchema = + { schema: { type: 'object', title: 'Settings', @@ -477,8 +480,7 @@ export function flotPieSettingsSchema(): JsonSettingsSchema { }, 'fontSize' ] - }; -} +}; export function flotDatakeySettingsSchema(defaultShowLines: boolean): JsonSettingsSchema { return { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts index 8f8096f04e..b4b50e0b1c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts @@ -36,12 +36,17 @@ import { TbFlotTicksFormatterFunction, TooltipValueFormatFunction } from './flot-widget.models'; -import * as moment from 'moment'; -import * as tinycolor from 'tinycolor2'; +import * as moment_ from 'moment'; +import * as tinycolor_ from 'tinycolor2'; import { AggregationType } from '@shared/models/time/time.models'; import { CancelAnimationFrame } from '@core/services/raf.service'; import Timeout = NodeJS.Timeout; +const tinycolor = tinycolor_; +const moment = moment_; + +const flotPieSettingsSchemaValue = flotPieSettingsSchema; + export class TbFlot { private settings: TbFlotSettings; @@ -89,11 +94,11 @@ export class TbFlot { private pieAnimationLastTime: number; private pieAnimationCaf: CancelAnimationFrame; - static get pieSettingsSchema(): JsonSettingsSchema { - return flotPieSettingsSchema(); + static pieSettingsSchema(): JsonSettingsSchema { + return flotPieSettingsSchemaValue; } - static get pieDatakeySettingsSchema(): JsonSettingsSchema { + static pieDatakeySettingsSchema(): JsonSettingsSchema { return {}; } 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 10981de3a4..75317f1108 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 @@ -38,14 +38,17 @@ import { SharedModule } from '@shared/shared.module'; import { WidgetComponentsModule } from '@home/components/widget/widget-components.module'; import { WINDOW } from '@core/services/window.service'; -import * as tinycolor from 'tinycolor2'; +import * as tinycolor_ from 'tinycolor2'; import { TbFlot } from './lib/flot-widget'; import { NULL_UUID } from '@shared/models/id/has-uuid'; import { WidgetTypeId } from '@app/shared/models/id/widget-type-id'; import { TenantId } from '@app/shared/models/id/tenant-id'; +const tinycolor = tinycolor_; + // declare var jQuery: any; +// @dynamic @Injectable() export class WidgetComponentService { 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 2fcdb0a9af..df4fe6be07 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 @@ -71,7 +71,7 @@ class="tb-hint">{{ 'widget-config.maximum-datasources' | translate:{count: modelValue?.typeParameters.maxDatasources} }}
-
+
datasource.add-datasource-prompt
@@ -89,7 +89,7 @@
+ *ngFor="let datasourceControl of datasourcesFormArray().controls; let $index = index;"> {{$index + 1}}.
- diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts index b5981e5700..2f34e67c9f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts @@ -128,7 +128,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont selectedTab: number; - private modelValue: WidgetConfigComponentData; + modelValue: WidgetConfigComponentData; private propagateChange = null; @@ -307,6 +307,10 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont this.fb.control(null, [])); } + datasourcesFormArray(): FormArray { + return this.dataSettings.get('datasources') as FormArray; + } + registerOnChange(fn: any): void { this.propagateChange = fn; } diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.ts index fe0f2655c2..0b41ca71d0 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.ts @@ -86,6 +86,7 @@ import { import { ImportExportService } from '@home/components/import-export/import-export.service'; import { AuthState } from '@app/core/auth/auth.models'; +// @dynamic @Component({ selector: 'tb-dashboard-page', templateUrl: './dashboard-page.component.html', diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-routing.module.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-routing.module.ts index 0d1e1c6db1..cb5825f677 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-routing.module.ts @@ -90,6 +90,7 @@ const routes: Routes = [ } ]; +// @dynamic @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule], diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/entity-state-controller.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/states/entity-state-controller.component.ts index 316a1a139e..c9d50a3270 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/states/entity-state-controller.component.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/entity-state-controller.component.ts @@ -47,7 +47,7 @@ import { map } from 'rxjs/operators'; }) export class EntityStateControllerComponent extends StateControllerComponent implements OnInit, OnDestroy { - private selectedStateIndex = -1; + selectedStateIndex = -1; constructor(protected router: Router, protected route: ActivatedRoute, diff --git a/ui-ngx/src/app/modules/home/pages/public-api.ts b/ui-ngx/src/app/modules/home/pages/public-api.ts new file mode 100644 index 0000000000..8100286509 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/public-api.ts @@ -0,0 +1,17 @@ +/// +/// Copyright © 2016-2019 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 './home-pages.module'; diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/add-rule-node-dialog.component.html b/ui-ngx/src/app/modules/home/pages/rulechain/add-rule-node-dialog.component.html new file mode 100644 index 0000000000..cb6f73385a --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/rulechain/add-rule-node-dialog.component.html @@ -0,0 +1,56 @@ + +
+ +

rulenode.add

+ +
+ +
+ + +
+
+ + +
+
+
+ + + +
+
diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/link-labels.component.html b/ui-ngx/src/app/modules/home/pages/rulechain/link-labels.component.html index 5dd617479c..593613f143 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/link-labels.component.html +++ b/ui-ngx/src/app/modules/home/pages/rulechain/link-labels.component.html @@ -45,7 +45,7 @@ - +
rulenode.no-link-labels-found diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/link-labels.conponent.ts b/ui-ngx/src/app/modules/home/pages/rulechain/link-labels.conponent.ts index c7303f145f..ca165caa4d 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/link-labels.conponent.ts +++ b/ui-ngx/src/app/modules/home/pages/rulechain/link-labels.conponent.ts @@ -94,9 +94,9 @@ export class LinkLabelsComponent implements ControlValueAccessor, OnInit, OnChan filteredLabels: Observable>; - private labels: Array = []; + labels: Array = []; - private searchText = ''; + searchText = ''; private propagateChange = (v: any) => { }; @@ -190,7 +190,7 @@ export class LinkLabelsComponent implements ControlValueAccessor, OnInit, OnChan } add(event: MatChipInputEvent): void { - if (!this.matAutocomplete.isOpen) { + if (!this.matAutocomplete.isOpen || this.allowCustom) { this.transformLinkLabel(event.value); } } @@ -250,8 +250,8 @@ export class LinkLabelsComponent implements ControlValueAccessor, OnInit, OnChan if (this.required) { this.chipList.errorState = false; } + this.updateModel(); } - this.updateModel(); } clear(value: string = '') { diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.html b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.html new file mode 100644 index 0000000000..5ce6a69cc7 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.html @@ -0,0 +1,28 @@ + +
+ +
{{definedDirectiveError}}
+ + +
diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.scss b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.scss new file mode 100644 index 0000000000..774758fdbd --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.scss @@ -0,0 +1,27 @@ +/** + * Copyright © 2016-2019 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-json-object-edit.tb-rule-node-configuration-json { + display: block; + height: 300px; + } + + .tb-rulenode-directive-error { + font-size: 13px; + font-weight: 400; + color: rgb(221, 44, 0); + } +} 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 new file mode 100644 index 0000000000..11ebc02284 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.ts @@ -0,0 +1,208 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + AfterViewInit, + Component, ElementRef, + EventEmitter, forwardRef, + Input, + OnChanges, + OnInit, + Output, + SimpleChanges, + ViewChild, + Compiler, + Injector, ComponentRef, OnDestroy +} from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, NgForm, Validators } from '@angular/forms'; +import { FcRuleNode, FcRuleEdge } from './rulechain-page.models'; +import { RuleNodeType, LinkLabel, RuleNodeDefinition, RuleNodeConfiguration, IRuleNodeConfigurationComponent } from '@shared/models/rule-node.models'; +import { EntityType } from '@shared/models/entity-type.models'; +import { Observable, of, Subscription } from 'rxjs'; +import { RuleChainService } from '@core/http/rule-chain.service'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { deepClone } from '@core/utils'; +import { EntityAlias } from '@shared/models/alias.models'; +import { TruncatePipe } from '@shared/pipe/truncate.pipe'; +import { MatChipList, MatAutocomplete, MatChipInputEvent, MatAutocompleteSelectedEvent } from '@angular/material'; +import { TranslateService } from '@ngx-translate/core'; +import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes'; +import { catchError, map, mergeMap, share } from 'rxjs/operators'; +import { DynamicWidgetComponent } from '@home/components/widget/dynamic-widget.component'; +import { SharedModule } from '@shared/shared.module'; +import { WidgetComponentsModule } from '@home/components/widget/widget-components.module'; +import { DynamicComponentFactoryService } from '@core/services/dynamic-component-factory.service'; +import { ViewContainerRef } from '@angular/core'; +import { JsonObjectEditComponent } from '@shared/components/json-object-edit.component'; + +@Component({ + selector: 'tb-rule-node-config', + templateUrl: './rule-node-config.component.html', + styleUrls: ['./rule-node-config.component.scss'], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => RuleNodeConfigComponent), + multi: true + }] +}) +export class RuleNodeConfigComponent implements ControlValueAccessor, OnInit, OnDestroy, AfterViewInit { + + @ViewChild('definedConfigContent', {read: ViewContainerRef, static: true}) definedConfigContainer: ViewContainerRef; + + @ViewChild('jsonObjectEditComponent', {static: false}) jsonObjectEditComponent: JsonObjectEditComponent; + + private requiredValue: boolean; + get required(): boolean { + return this.requiredValue; + } + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + @Input() + disabled: boolean; + + @Input() + ruleNodeId: string; + + nodeDefinitionValue: RuleNodeDefinition; + + @Input() + set nodeDefinition(nodeDefinition: RuleNodeDefinition) { + if (this.nodeDefinitionValue !== nodeDefinition) { + this.nodeDefinitionValue = nodeDefinition; + if (this.nodeDefinitionValue) { + this.validateDefinedDirective(); + } + } + } + + get nodeDefinition(): RuleNodeDefinition { + return this.nodeDefinitionValue; + } + + definedDirectiveError: string; + + ruleNodeConfigFormGroup: FormGroup; + + changeSubscription: Subscription; + + private definedConfigComponentRef: ComponentRef; + private definedConfigComponent: IRuleNodeConfigurationComponent; + + private configuration: RuleNodeConfiguration; + + private propagateChange = (v: any) => { }; + + constructor(private translate: TranslateService, + private ruleChainService: RuleChainService, + private fb: FormBuilder) { + this.ruleNodeConfigFormGroup = this.fb.group({ + configuration: [null, Validators.required] + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + ngOnInit(): void { + } + + ngOnDestroy(): void { + if (this.definedConfigComponentRef) { + this.definedConfigComponentRef.destroy(); + } + } + + ngAfterViewInit(): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.ruleNodeConfigFormGroup.disable({emitEvent: false}); + } else { + this.ruleNodeConfigFormGroup.enable({emitEvent: false}); + } + } + + writeValue(value: RuleNodeConfiguration): void { + this.configuration = value; + if (this.changeSubscription) { + this.changeSubscription.unsubscribe(); + this.changeSubscription = null; + } + if (this.definedConfigComponent) { + this.definedConfigComponent.configuration = this.configuration; + this.changeSubscription = this.definedConfigComponent.configurationChanged.subscribe((configuration) => { + this.updateModel(configuration); + }); + } else { + this.ruleNodeConfigFormGroup.get('configuration').patchValue(value, {emitEvent: false}); + this.changeSubscription = this.ruleNodeConfigFormGroup.get('configuration').valueChanges.subscribe( + (configuration: RuleNodeConfiguration) => { + this.updateModel(configuration); + } + ); + } + } + + useDefinedDirective(): boolean { + return this.nodeDefinition && + (this.nodeDefinition.configDirective && + this.nodeDefinition.configDirective.length) && !this.definedDirectiveError; + } + + private updateModel(configuration: RuleNodeConfiguration) { + if (this.definedConfigComponent || this.ruleNodeConfigFormGroup.valid) { + this.propagateChange(configuration); + } else { + this.propagateChange(this.required ? null : configuration); + } + } + + private validateDefinedDirective() { + if (this.definedConfigComponentRef) { + this.definedConfigComponentRef.destroy(); + this.definedConfigComponentRef = null; + } + if (this.nodeDefinition.uiResourceLoadError && this.nodeDefinition.uiResourceLoadError.length) { + this.definedDirectiveError = this.nodeDefinition.uiResourceLoadError; + } else if (this.nodeDefinition.configDirective && this.nodeDefinition.configDirective.length) { + if (this.changeSubscription) { + this.changeSubscription.unsubscribe(); + this.changeSubscription = null; + } + this.definedConfigContainer.clear(); + const factory = this.ruleChainService.getRuleNodeConfigFactory(this.nodeDefinition.configDirective); + this.definedConfigComponentRef = this.definedConfigContainer.createComponent(factory); + this.definedConfigComponent = this.definedConfigComponentRef.instance; + this.definedConfigComponent.ruleNodeId = this.ruleNodeId; + this.definedConfigComponent.configuration = this.configuration; + this.changeSubscription = this.definedConfigComponent.configurationChanged.subscribe((configuration) => { + this.updateModel(configuration); + }); + } + } +} 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 1d27fcf7f5..6a5f070c8b 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 @@ -31,7 +31,11 @@ {{ 'rulenode.debug-mode' | translate }}
- TODO: tb-rule-node-config + +
rulenode.description 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 8a3f654824..74b203ccd5 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 @@ -70,6 +70,7 @@ export class RuleNodeDetailsComponent extends PageComponent implements OnInit, O this.ruleNodeFormGroup = this.fb.group({ name: [this.ruleNode.name, [Validators.required]], debugMode: [this.ruleNode.debugMode, []], + configuration: [this.ruleNode.configuration, [Validators.required]], additionalInfo: this.fb.group( { description: [this.ruleNode.additionalInfo ? this.ruleNode.additionalInfo.description : ''], 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 04e86b6090..a7f4a6e92c 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 @@ -151,7 +151,6 @@ export class RuleChainPageComponent extends PageComponent return source.type === FlowchartConstants.rightConnectorType && destination.type === FlowchartConstants.leftConnectorType; }, createEdge: (event, edge: FcRuleEdge) => { - console.log('TODO'); const sourceNode = this.ruleChainCanvas.modelService.nodes.getNodeByConnectorId(edge.source) as FcRuleNode; if (sourceNode.component.type === RuleNodeType.INPUT) { const destNode = this.ruleChainCanvas.modelService.nodes.getNodeByConnectorId(edge.destination) as FcRuleNode; @@ -185,9 +184,8 @@ export class RuleChainPageComponent extends PageComponent } } }, - dropNode: (event, node) => { - console.log('TODO dropNode'); - console.log(node); + dropNode: (event, node: FcRuleNode) => { + this.addRuleNode(node); } }; @@ -269,7 +267,7 @@ export class RuleChainPageComponent extends PageComponent this.createRuleChainModel(); } - private updateRuleChainLibrary() { + updateRuleChainLibrary() { const search = this.ruleNodeTypeSearch.toUpperCase(); const res = this.ruleNodeComponents.filter( (ruleNodeComponent) => ruleNodeComponent.name.toUpperCase().includes(search)); @@ -734,6 +732,46 @@ export class RuleChainPageComponent extends PageComponent this.createRuleChainModel(); } + addRuleNode(ruleNode: FcRuleNode) { + ruleNode.configuration = deepClone(ruleNode.component.configurationDescriptor.nodeDefinition.defaultConfiguration); + const ruleChainId = this.ruleChain.id ? this.ruleChain.id.id : null; + this.dialog.open(AddRuleNodeDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + ruleNode, + ruleChainId + } + }).afterClosed().subscribe( + (addedRuleNode) => { + if (addedRuleNode) { + addedRuleNode.id = 'rule-chain-node-' + this.nextNodeID++; + addedRuleNode.connectors = []; + if (addedRuleNode.component.configurationDescriptor.nodeDefinition.inEnabled) { + addedRuleNode.connectors.push( + { + id: (this.nextConnectorID++) + '', + type: FlowchartConstants.leftConnectorType + } + ); + } + if (addedRuleNode.component.configurationDescriptor.nodeDefinition.outEnabled) { + addedRuleNode.connectors.push( + { + id: (this.nextConnectorID++) + '', + type: FlowchartConstants.rightConnectorType + } + ); + } + this.ruleChainModel.nodes.push(addedRuleNode); + this.onModelChanged(); + this.updateRuleNodesHighlight(); + } + } + ); + } + addRuleNodeLink(link: FcRuleEdge, labels: {[label: string]: LinkLabel}, allowCustomLabels: boolean): Observable { return this.dialog.open(AddRuleNodeLinkDialogComponent, { @@ -885,3 +923,56 @@ export class AddRuleNodeLinkDialogComponent extends DialogComponent + implements OnInit, ErrorStateMatcher { + + ruleNode: FcRuleNode; + ruleChainId: string; + + submitted = false; + + constructor(protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) public data: AddRuleNodeDialogData, + @SkipSelf() private errorStateMatcher: ErrorStateMatcher, + public dialogRef: MatDialogRef) { + super(store, router, dialogRef); + + this.ruleNode = this.data.ruleNode; + this.ruleChainId = this.data.ruleChainId; + } + + ngOnInit(): void { + } + + isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { + const originalErrorState = this.errorStateMatcher.isErrorState(control, form); + const customErrorState = !!(control && control.invalid && this.submitted); + return originalErrorState || customErrorState; + } + + helpLinkIdForRuleNodeType(): string { + return getRuleNodeHelpLink(this.ruleNode.component); + } + + cancel(): void { + this.dialogRef.close(null); + } + + add(): void { + this.submitted = true; + this.dialogRef.close(this.ruleNode); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts index 93f25ecc7f..f080ad64c8 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts @@ -39,6 +39,29 @@ import { RuleChainPageComponent } from '@home/pages/rulechain/rulechain-page.com import { RuleNodeComponentDescriptor } from '@shared/models/rule-node.models'; import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard'; +import * as AngularCommon from '@angular/common'; +import * as AngularCore from '@angular/core'; +import * as AngularForms from '@angular/forms'; +import * as AngularCdkCoercion from '@angular/cdk/coercion'; +import * as NgrxStore from '@ngrx/store'; +import * as TranslateCore from '@ngx-translate/core'; +import * as TbCore from '@core/public-api'; +import * as TbShared from '@shared/public-api'; + +declare const SystemJS; + +const ruleNodeConfigResourcesModulesMap = { + '@angular/core': SystemJS.newModule(AngularCore), + '@angular/common': SystemJS.newModule(AngularCommon), + '@angular/forms': SystemJS.newModule(AngularForms), + '@ngrx/store': SystemJS.newModule(NgrxStore), + '@ngx-translate/core': SystemJS.newModule(TranslateCore), + '@core/public-api': SystemJS.newModule(TbCore), + '@shared/public-api': SystemJS.newModule(TbShared) +}; + +const t = SystemJS.newModule(AngularCore); + @Injectable() export class RuleChainResolver implements Resolve { @@ -71,7 +94,7 @@ export class RuleNodeComponentsResolver implements Resolve> { - return this.ruleChainService.getRuleNodeComponents(); + return this.ruleChainService.getRuleNodeComponents(ruleNodeConfigResourcesModulesMap); } } @@ -150,6 +173,7 @@ const routes: Routes = [ } ]; +// @dynamic @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule], diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.module.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.module.ts index 665d287320..8705cfaeff 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.module.ts +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.module.ts @@ -21,19 +21,21 @@ import {RuleChainComponent} from '@modules/home/pages/rulechain/rulechain.compon import {RuleChainRoutingModule} from '@modules/home/pages/rulechain/rulechain-routing.module'; import {HomeComponentsModule} from '@modules/home/components/home-components.module'; import { RuleChainTabsComponent } from '@home/pages/rulechain/rulechain-tabs.component'; -import { RuleChainPageComponent, AddRuleNodeLinkDialogComponent } from './rulechain-page.component'; +import { RuleChainPageComponent, AddRuleNodeLinkDialogComponent, AddRuleNodeDialogComponent } from './rulechain-page.component'; import { RuleNodeComponent } from '@home/pages/rulechain/rulenode.component'; import { FC_NODE_COMPONENT_CONFIG } from 'ngx-flowchart/dist/ngx-flowchart'; import { RuleNodeDetailsComponent } from './rule-node-details.component'; import { RuleNodeLinkComponent } from './rule-node-link.component'; import { LinkLabelsComponent } from '@home/pages/rulechain/link-labels.conponent'; +import { RuleNodeConfigComponent } from './rule-node-config.component'; @NgModule({ entryComponents: [ RuleChainComponent, RuleChainTabsComponent, RuleNodeComponent, - AddRuleNodeLinkDialogComponent + AddRuleNodeLinkDialogComponent, + AddRuleNodeDialogComponent ], declarations: [ RuleChainComponent, @@ -41,9 +43,11 @@ import { LinkLabelsComponent } from '@home/pages/rulechain/link-labels.conponent RuleChainPageComponent, RuleNodeComponent, RuleNodeDetailsComponent, + RuleNodeConfigComponent, LinkLabelsComponent, RuleNodeLinkComponent, - AddRuleNodeLinkDialogComponent + AddRuleNodeLinkDialogComponent, + AddRuleNodeDialogComponent ], providers: [ { diff --git a/ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.html b/ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.html index 092aa03842..a6444c325c 100644 --- a/ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.html @@ -34,8 +34,8 @@ user.activation-method - - {{ activationMethodTranslations.get(activationMethods[activationMethod]) | translate }} + + {{ activationMethodTranslations.get(activationMethod) | translate }} diff --git a/ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.ts index 95e9d678da..b6edc66ee6 100644 --- a/ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.ts @@ -49,7 +49,7 @@ export class AddUserDialogComponent extends DialogComponent>; - private searchText = ''; + searchText = ''; private propagateChange = (v: any) => { }; diff --git a/ui-ngx/src/app/shared/components/dashboard-select.component.ts b/ui-ngx/src/app/shared/components/dashboard-select.component.ts index c78ef98cc2..73652174e7 100644 --- a/ui-ngx/src/app/shared/components/dashboard-select.component.ts +++ b/ui-ngx/src/app/shared/components/dashboard-select.component.ts @@ -40,6 +40,7 @@ import { } from './dashboard-select-panel.component'; import { NULL_UUID } from '@shared/models/id/has-uuid'; +// @dynamic @Component({ selector: 'tb-dashboard-select', templateUrl: './dashboard-select.component.html', diff --git a/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts b/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts index 815c3ba8da..aedae5285d 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts @@ -94,7 +94,7 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit filteredEntities: Observable>>; - private searchText = ''; + searchText = ''; private dirty = false; diff --git a/ui-ngx/src/app/shared/components/entity/entity-keys-list.component.ts b/ui-ngx/src/app/shared/components/entity/entity-keys-list.component.ts index dc006ecf57..de5e0ffcda 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-keys-list.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-keys-list.component.ts @@ -92,7 +92,7 @@ export class EntityKeysListComponent implements ControlValueAccessor, OnInit, Af separatorKeysCodes: number[] = [ENTER, COMMA, SEMICOLON]; - private searchText = ''; + searchText = ''; private dirty = false; diff --git a/ui-ngx/src/app/shared/components/entity/entity-list.component.ts b/ui-ngx/src/app/shared/components/entity/entity-list.component.ts index 54cb2f37db..af40b416b8 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-list.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-list.component.ts @@ -90,7 +90,7 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV entities: Array> = []; filteredEntities: Observable>>; - private searchText = ''; + searchText = ''; private dirty = false; diff --git a/ui-ngx/src/app/shared/components/entity/entity-subtype-autocomplete.component.ts b/ui-ngx/src/app/shared/components/entity/entity-subtype-autocomplete.component.ts index 7e809471bf..111fb0082e 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-subtype-autocomplete.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-subtype-autocomplete.component.ts @@ -79,7 +79,7 @@ export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor, private broadcastSubscription: Subscription; - private searchText = ''; + searchText = ''; private dirty = false; diff --git a/ui-ngx/src/app/shared/components/entity/entity-subtype-list.component.ts b/ui-ngx/src/app/shared/components/entity/entity-subtype-list.component.ts index 58ed2600b5..20a9658c7f 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-subtype-list.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-subtype-list.component.ts @@ -106,7 +106,7 @@ export class EntitySubTypeListComponent implements ControlValueAccessor, OnInit, separatorKeysCodes: number[] = [ENTER, COMMA, SEMICOLON]; - private searchText = ''; + searchText = ''; private dirty = false; diff --git a/ui-ngx/src/app/shared/components/entity/entity-type-list.component.ts b/ui-ngx/src/app/shared/components/entity/entity-type-list.component.ts index 8360699f60..97cec76f72 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-type-list.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-type-list.component.ts @@ -102,7 +102,7 @@ export class EntityTypeListComponent implements ControlValueAccessor, OnInit, Af placeholder: string; secondaryPlaceholder: string; - private searchText = ''; + searchText = ''; private dirty = false; diff --git a/ui-ngx/src/app/shared/components/fab-toolbar.component.ts b/ui-ngx/src/app/shared/components/fab-toolbar.component.ts index 92447f36b4..144a3910aa 100644 --- a/ui-ngx/src/app/shared/components/fab-toolbar.component.ts +++ b/ui-ngx/src/app/shared/components/fab-toolbar.component.ts @@ -63,6 +63,7 @@ export class FabActionsDirective implements OnInit { } +// @dynamic @Component({ selector: 'mat-fab-toolbar', templateUrl: './fab-toolbar.component.html', diff --git a/ui-ngx/src/app/shared/components/json-form/json-form.component.ts b/ui-ngx/src/app/shared/components/json-form/json-form.component.ts index 8100ea3a3c..e3b0dcd7e1 100644 --- a/ui-ngx/src/app/shared/components/json-form/json-form.component.ts +++ b/ui-ngx/src/app/shared/components/json-form/json-form.component.ts @@ -35,7 +35,7 @@ import { deepClone, isString } from '@app/core/utils'; import { TranslateService } from '@ngx-translate/core'; import { JsonFormProps } from './react/json-form.models'; import inspector from 'schema-inspector'; -import * as tinycolor from 'tinycolor2'; +import * as tinycolor_ from 'tinycolor2'; import { DialogService } from '@app/core/services/dialog.service'; import * as React from 'react'; import * as ReactDOM from 'react-dom'; @@ -43,6 +43,8 @@ import ReactSchemaForm from './react/json-form-react'; import JsonFormUtils from './react/json-form-utils'; import { JsonFormComponentData } from './json-form-component.models'; +const tinycolor = tinycolor_; + @Component({ selector: 'tb-json-form', templateUrl: './json-form.component.html', diff --git a/ui-ngx/src/app/shared/components/json-form/react/json-form-ace-editor.tsx b/ui-ngx/src/app/shared/components/json-form/react/json-form-ace-editor.tsx index 9bb42df9db..1cc48877b8 100644 --- a/ui-ngx/src/app/shared/components/json-form/react/json-form-ace-editor.tsx +++ b/ui-ngx/src/app/shared/components/json-form/react/json-form-ace-editor.tsx @@ -20,7 +20,6 @@ import reactCSS from 'reactcss'; import ReactAce from 'react-ace'; import Button from '@material-ui/core/Button'; import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models'; -import * as tinycolor from 'tinycolor2'; import { IEditorProps } from 'react-ace/src/types'; interface ThingsboardAceEditorProps extends JsonFormFieldProps { diff --git a/ui-ngx/src/app/shared/components/json-form/react/json-form-color.tsx b/ui-ngx/src/app/shared/components/json-form/react/json-form-color.tsx index 74025e2b32..aa4336dad8 100644 --- a/ui-ngx/src/app/shared/components/json-form/react/json-form-color.tsx +++ b/ui-ngx/src/app/shared/components/json-form/react/json-form-color.tsx @@ -17,13 +17,15 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom'; import ThingsboardBaseComponent from './json-form-base-component'; import reactCSS from 'reactcss'; -import * as tinycolor from 'tinycolor2'; +import * as tinycolor_ from 'tinycolor2'; import TextField from '@material-ui/core/TextField'; import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models'; import IconButton from '@material-ui/core/IconButton'; import ClearIcon from '@material-ui/icons/Clear'; import Tooltip from '@material-ui/core/Tooltip'; +const tinycolor = tinycolor_; + interface ThingsboardColorState extends JsonFormFieldState { color: tinycolor.ColorFormats.RGBA | null; focused: boolean; diff --git a/ui-ngx/src/app/shared/components/json-form/react/json-form-schema-form.tsx b/ui-ngx/src/app/shared/components/json-form/react/json-form-schema-form.tsx index 145c2aedb9..45e9513ef9 100644 --- a/ui-ngx/src/app/shared/components/json-form/react/json-form-schema-form.tsx +++ b/ui-ngx/src/app/shared/components/json-form/react/json-form-schema-form.tsx @@ -35,7 +35,8 @@ import ThingsboardFieldSet from './json-form-fieldset'; import { JsonFormProps, GroupInfo, JsonFormData, onChangeFn, OnColorClickFn } from './json-form.models'; import _ from 'lodash'; -import * as tinycolor from 'tinycolor2'; +import * as tinycolor_ from 'tinycolor2'; +const tinycolor = tinycolor_; class ThingsboardSchemaForm extends React.Component { diff --git a/ui-ngx/src/app/shared/components/json-form/react/json-form.models.ts b/ui-ngx/src/app/shared/components/json-form/react/json-form.models.ts index f53d52177e..7e3c466e24 100644 --- a/ui-ngx/src/app/shared/components/json-form/react/json-form.models.ts +++ b/ui-ngx/src/app/shared/components/json-form/react/json-form.models.ts @@ -18,7 +18,9 @@ import { isUndefined, isDefined, isString } from '@app/core/utils'; import * as equal from 'deep-equal'; import ObjectPath from 'objectpath'; import * as React from 'react'; -import * as tinycolor from 'tinycolor2'; +import * as tinycolor_ from 'tinycolor2'; + +const tinycolor = tinycolor_; export interface SchemaValidationResult { valid: boolean; diff --git a/ui-ngx/src/app/shared/components/mat-chip-draggable.directive.ts b/ui-ngx/src/app/shared/components/mat-chip-draggable.directive.ts index 8fa1cd86d5..3d20f45a39 100644 --- a/ui-ngx/src/app/shared/components/mat-chip-draggable.directive.ts +++ b/ui-ngx/src/app/shared/components/mat-chip-draggable.directive.ts @@ -49,7 +49,7 @@ export class MatChipDraggableDirective implements AfterViewInit { private elementRef: ElementRef) { } - @HostListener('document:mouseup', ['$event']) + @HostListener('document:mouseup') onDocumentMouseUp() { this.draggableChips.forEach((draggableChip) => { draggableChip.preventDrag = false; diff --git a/ui-ngx/src/app/shared/components/public-api.ts b/ui-ngx/src/app/shared/components/public-api.ts new file mode 100644 index 0000000000..01943cf9f2 --- /dev/null +++ b/ui-ngx/src/app/shared/components/public-api.ts @@ -0,0 +1,17 @@ +/// +/// Copyright © 2016-2019 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 './page.component'; diff --git a/ui-ngx/src/app/shared/components/relation/relation-type-autocomplete.component.ts b/ui-ngx/src/app/shared/components/relation/relation-type-autocomplete.component.ts index 3c1536b361..489c87d7df 100644 --- a/ui-ngx/src/app/shared/components/relation/relation-type-autocomplete.component.ts +++ b/ui-ngx/src/app/shared/components/relation/relation-type-autocomplete.component.ts @@ -69,7 +69,7 @@ export class RelationTypeAutocompleteComponent implements ControlValueAccessor, filteredRelationTypes: Observable>; - private searchText = ''; + searchText = ''; private dirty = false; diff --git a/ui-ngx/src/app/shared/components/time/timewindow.component.html b/ui-ngx/src/app/shared/components/time/timewindow.component.html index d8e94e6227..e503382dc5 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow.component.html @@ -17,7 +17,7 @@ --> @@ -25,20 +25,20 @@ class="tb-timewindow" fxLayout="row" fxLayoutAlign="start center"> {{innerValue?.displayValue}} + + + +
+
+
+
+
+
+
+ +
+ TODO: payloadForm +
+
+
+
+
+ + TODO: metadataForm +
+
+
+
+
+
+
+
+ +
+ TODO: funcBodyForm +
+
+
+
+
+ +
+ TODO: output +
+
+
+
+
+
+ + + + +
+ diff --git a/ui-ngx/src/app/core/services/script/node-script-test-dialog.component.scss b/ui-ngx/src/app/core/services/script/node-script-test-dialog.component.scss new file mode 100644 index 0000000000..e848572f2f --- /dev/null +++ b/ui-ngx/src/app/core/services/script/node-script-test-dialog.component.scss @@ -0,0 +1,93 @@ +:host { + .tb-split { + box-sizing: border-box; + overflow-x: hidden; + overflow-y: auto; + } + + .tb-content { + padding-top: 5px; + padding-left: 5px; + border: 1px solid #c0c0c0; + } + + .gutter { + background-color: #eee; + + background-repeat: no-repeat; + background-position: 50%; + } + + .gutter { + background-color: #eee; + background-repeat: no-repeat; + background-position: 50%; + } + + .gutter.gutter-horizontal { + cursor: col-resize; + background-image: url("../../../../assets/split.js/grips/vertical.png"); + } + + .gutter.gutter-vertical { + cursor: row-resize; + background-image: url("../../../../assets/split.js/grips/horizontal.png"); + } + + .tb-split.tb-split-horizontal, + .gutter.gutter-horizontal { + float: left; + height: 100%; + } + + .tb-split.tb-split-vertical { + display: flex; + + .tb-split.tb-content { + height: 100%; + } + } + + div.tb-editor-area-title-panel { + position: absolute; + top: 13px; + right: 40px; + z-index: 5; + font-size: .8rem; + font-weight: 500; + + &.tb-js-function { + right: 80px; + } + + label { + padding: 4px; + color: #00acc1; + text-transform: uppercase; + background: rgba(220, 220, 220, .35); + border-radius: 5px; + } + + .mat-button { + min-width: 32px; + min-height: 15px; + padding: 4px; + margin: 0; + font-size: .8rem; + line-height: 15px; + color: #7b7b7b; + background: rgba(220, 220, 220, .35); + } + } + + .tb-resize-container { + position: relative; + width: 100%; + height: 100%; + overflow-y: auto; + + .ace_editor { + height: 100%; + } + } +} diff --git a/ui-ngx/src/app/core/services/script/node-script-test-dialog.component.ts b/ui-ngx/src/app/core/services/script/node-script-test-dialog.component.ts new file mode 100644 index 0000000000..d5e4cb28f8 --- /dev/null +++ b/ui-ngx/src/app/core/services/script/node-script-test-dialog.component.ts @@ -0,0 +1,176 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + AfterViewInit, + Component, + ElementRef, + Inject, + OnInit, + QueryList, + SkipSelf, + ViewChild, + ViewChildren +} from '@angular/core'; +import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { + FormBuilder, + FormControl, + FormGroup, + FormGroupDirective, + NgForm, + ValidatorFn, + Validators +} from '@angular/forms'; +import { combineLatest, Observable, of } from 'rxjs'; +import { Router } from '@angular/router'; +import { DialogComponent } from '@app/shared/components/dialog.component'; +import { + toCustomAction, + WidgetActionCallbacks, + WidgetActionDescriptorInfo, + WidgetActionsData +} from '@home/components/widget/action/manage-widget-actions.component.models'; +import { UtilsService } from '@core/services/utils.service'; +import { WidgetActionSource, WidgetActionType, widgetActionTypeTranslationMap } from '@shared/models/widget.models'; +import { map, mergeMap, startWith, tap } from 'rxjs/operators'; +import { DashboardService } from '@core/http/dashboard.service'; +import { Dashboard } from '@shared/models/dashboard.models'; +import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; + +export interface NodeScriptTestDialogData { + script: string; + scriptType: string; + functionTitle: string; + functionName: string; + argNames: string[]; + msg?: any; + metadata?: any; + msgType?: string; +} + +@Component({ + selector: 'tb-node-script-test-dialog', + templateUrl: './node-script-test-dialog.component.html', + providers: [{provide: ErrorStateMatcher, useExisting: NodeScriptTestDialogComponent}], + styleUrls: ['./node-script-test-dialog.component.scss'] +}) +export class NodeScriptTestDialogComponent extends DialogComponent implements OnInit, AfterViewInit, ErrorStateMatcher { + + @ViewChildren('topPanel') + topPanelElmRef: QueryList>; + + @ViewChildren('topLeftPanel') + topLeftPanelElmRef: QueryList>; + + @ViewChildren('topRightPanel') + topRightPanelElmRef: QueryList>; + + @ViewChildren('bottomPanel') + bottomPanelElmRef: QueryList>; + + @ViewChildren('bottomLeftPanel') + bottomLeftPanelElmRef: QueryList>; + + @ViewChildren('bottomLeftPanel') + bottomRightPanelElmRef: QueryList>; + + nodeScriptTestFormGroup: FormGroup; + + functionTitle: string; + + submitted = false; + + constructor(protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) public data: NodeScriptTestDialogData, + @SkipSelf() private errorStateMatcher: ErrorStateMatcher, + public dialogRef: MatDialogRef, + public fb: FormBuilder) { + super(store, router, dialogRef); + this.functionTitle = this.data.functionTitle; + } + + ngOnInit(): void { + this.nodeScriptTestFormGroup = this.fb.group({}); + } + + ngAfterViewInit(): void { + combineLatest(this.topPanelElmRef.changes, + this.topLeftPanelElmRef.changes, + this.topRightPanelElmRef.changes, + this.bottomPanelElmRef.changes, + this.bottomLeftPanelElmRef.changes, + this.bottomRightPanelElmRef.changes).subscribe(() => { + if (this.topPanelElmRef.length && this.topLeftPanelElmRef.length && + this.topRightPanelElmRef.length && this.bottomPanelElmRef.length && + this.bottomLeftPanelElmRef.length && this.bottomRightPanelElmRef.length) { + this.initSplitLayout(this.topPanelElmRef.first.nativeElement, + this.topLeftPanelElmRef.first.nativeElement, + this.topRightPanelElmRef.first.nativeElement, + this.bottomPanelElmRef.first.nativeElement, + this.bottomLeftPanelElmRef.first.nativeElement, + this.bottomRightPanelElmRef.first.nativeElement); + } + }); + } + + private initSplitLayout(topPanel: any, + topLeftPanel: any, + topRightPanel: any, + bottomPanel: any, + bottomLeftPanel: any, + bottomRightPanel: any) { + + Split([topPanel, bottomPanel], { + sizes: [35, 65], + gutterSize: 8, + cursor: 'row-resize', + direction: 'vertical' + }); + + Split([topLeftPanel, topRightPanel], { + sizes: [50, 50], + gutterSize: 8, + cursor: 'col-resize' + }); + + Split([bottomLeftPanel, bottomRightPanel], { + sizes: [50, 50], + gutterSize: 8, + cursor: 'col-resize' + }); + } + + isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { + const originalErrorState = this.errorStateMatcher.isErrorState(control, form); + const customErrorState = !!(control && control.invalid && this.submitted); + return originalErrorState || customErrorState; + } + + cancel(): void { + this.dialogRef.close(null); + } + + save(): void { + this.submitted = true; + const script: string = this.nodeScriptTestFormGroup.get('funcBody').value; + this.dialogRef.close(script); + } +} 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 5391c2d602..3efa935c2e 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 @@ -16,14 +16,63 @@ import { Injectable } from '@angular/core'; import { Observable, of } from 'rxjs'; +import { RuleChainService } from '@core/http/rule-chain.service'; +import { map, switchMap } from 'rxjs/operators'; @Injectable({ providedIn: 'root' }) export class NodeScriptTestService { - testNodeScript(script: string, scriptType: any, functionTitle: string, + constructor(private ruleChainService: RuleChainService) { + } + + testNodeScript(script: string, scriptType: string, functionTitle: string, functionName: string, argNames: string[], ruleNodeId: string): Observable { + if (ruleNodeId) { + return this.ruleChainService.getLatestRuleNodeDebugInput(ruleNodeId).pipe( + switchMap((debugIn) => { + let msg: any; + let metadata: any; + let msgType: string; + if (debugIn) { + if (debugIn.data) { + msg = JSON.parse(debugIn.data); + } + if (debugIn.metadata) { + metadata = JSON.parse(debugIn.metadata); + } + msgType = debugIn.msgType; + } + return this.openTestScriptDialog(script, scriptType, functionTitle, + functionName, argNames, msg, metadata, msgType); + }) + ); + } else { + return this.openTestScriptDialog(script, scriptType, functionTitle, + functionName, argNames); + } + } + + private openTestScriptDialog(script: string, scriptType: string, + functionTitle: string, functionName: string, argNames: string[], + msg?: any, metadata?: any, msgType?: string): Observable { + if (!msg) { + msg = { + temperature: 22.4, + humidity: 78 + }; + } + if (!metadata) { + metadata = { + deviceType: 'default', + deviceName: 'Test Device', + ts: new Date().getTime() + '' + }; + } + if (!msgType) { + msgType = 'POST_TELEMETRY_REQUEST'; + } console.log(`testNodeScript TODO: ${script}`); return of(script); } 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 11ebc02284..180c97708f 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 @@ -205,4 +205,10 @@ export class RuleNodeConfigComponent implements ControlValueAccessor, OnInit, On }); } } + + validate() { + if (this.useDefinedDirective()) { + this.definedConfigComponent.validate(); + } + } } 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 6a5f070c8b..df44f40b19 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 @@ -31,7 +31,7 @@ {{ 'rulenode.debug-mode' | translate }}
- 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 74b203ccd5..10f80c6dd1 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, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild } from '@angular/core'; +import { Component, Input, OnChanges, OnInit, SimpleChanges, ViewChild } from '@angular/core'; import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; @@ -24,6 +24,8 @@ import { RuleNodeType } from '@shared/models/rule-node.models'; import { EntityType } from '@shared/models/entity-type.models'; import { Subscription } from 'rxjs'; import { RuleChainService } from '@core/http/rule-chain.service'; +import { JsonObjectEditComponent } from '@shared/components/json-object-edit.component'; +import { RuleNodeConfigComponent } from './rule-node-config.component'; @Component({ selector: 'tb-rule-node', @@ -34,6 +36,8 @@ export class RuleNodeDetailsComponent extends PageComponent implements OnInit, O @ViewChild('ruleNodeForm', {static: true}) ruleNodeForm: NgForm; + @ViewChild('ruleNodeConfigComponent', {static: false}) ruleNodeConfigComponent: RuleNodeConfigComponent; + @Input() ruleNode: FcRuleNode; @@ -127,4 +131,10 @@ export class RuleNodeDetailsComponent extends PageComponent implements OnInit, O } } } + + validate() { + if (this.ruleNode.component.type !== RuleNodeType.RULE_CHAIN) { + this.ruleNodeConfigComponent.validate(); + } + } } 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 a7f4a6e92c..a4ef9a7d9d 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 @@ -67,6 +67,7 @@ import { DialogComponent } from '@shared/components/dialog.component'; import { UtilsService } from '@core/services/utils.service'; import { EntityService } from '@core/http/entity.service'; import { AddWidgetDialogComponent, AddWidgetDialogData } from '@home/pages/dashboard/add-widget-dialog.component'; +import { RuleNodeConfigComponent } from '@home/pages/rulechain/rule-node-config.component'; @Component({ selector: 'tb-rulechain-page', @@ -585,14 +586,17 @@ export class RuleChainPageComponent extends PageComponent } saveRuleNode() { - this.ruleNodeComponent.ruleNodeFormGroup.markAsPristine(); - if (this.editingRuleNode.error) { - delete this.editingRuleNode.error; + this.ruleNodeComponent.validate(); + if (this.ruleNodeComponent.ruleNodeFormGroup.valid) { + this.ruleNodeComponent.ruleNodeFormGroup.markAsPristine(); + if (this.editingRuleNode.error) { + delete this.editingRuleNode.error; + } + this.ruleChainModel.nodes[this.editingRuleNodeIndex] = this.editingRuleNode; + this.editingRuleNode = deepClone(this.editingRuleNode); + this.onModelChanged(); + this.updateRuleNodesHighlight(); } - this.ruleChainModel.nodes[this.editingRuleNodeIndex] = this.editingRuleNode; - this.editingRuleNode = deepClone(this.editingRuleNode); - this.onModelChanged(); - this.updateRuleNodesHighlight(); } saveRuleNodeLink() { @@ -938,6 +942,8 @@ export interface AddRuleNodeDialogData { export class AddRuleNodeDialogComponent extends DialogComponent implements OnInit, ErrorStateMatcher { + @ViewChild('tbRuleNode', {static: true}) ruleNodeDetailsComponent: RuleNodeDetailsComponent; + ruleNode: FcRuleNode; ruleChainId: string; @@ -973,6 +979,9 @@ export class AddRuleNodeDialogComponent extends DialogComponent; + validate(); [key: string]: any; } @@ -97,11 +98,17 @@ export abstract class RuleNodeConfigurationComponent extends PageComponent imple this.onConfigurationSet(this.configuration); } + validate() { + this.onValidate(); + } + protected abstract onConfigurationSet(configuration: RuleNodeConfiguration); protected notifyConfigurationUpdated(configuration: RuleNodeConfiguration) { this.configurationChangedEmiter.emit(configuration); } + + protected onValidate() {} } From 72b02eb7d3bba8505e72b6b588cb426df5d92070 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 24 Dec 2019 16:27:09 +0200 Subject: [PATCH 063/133] Test script function dialog. --- .../script/node-script-test.service.ts | 22 ++++++-- .../node-script-test-dialog.component.html | 7 +-- .../node-script-test-dialog.component.scss | 28 +++++++---- .../node-script-test-dialog.component.ts | 50 +++++++------------ ui-ngx/src/app/shared/shared.module.ts | 6 ++- 5 files changed, 65 insertions(+), 48 deletions(-) rename ui-ngx/src/app/{core/services/script => shared/components/dialog}/node-script-test-dialog.component.html (95%) rename ui-ngx/src/app/{core/services/script => shared/components/dialog}/node-script-test-dialog.component.scss (69%) rename ui-ngx/src/app/{core/services/script => shared/components/dialog}/node-script-test-dialog.component.ts (81%) 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 3efa935c2e..d946425698 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 @@ -18,13 +18,16 @@ import { Injectable } from '@angular/core'; import { Observable, of } from 'rxjs'; import { RuleChainService } from '@core/http/rule-chain.service'; import { map, switchMap } from 'rxjs/operators'; +import { MatDialog } from '@angular/material'; +import { NodeScriptTestDialogComponent, NodeScriptTestDialogData } from '@shared/components/dialog/node-script-test-dialog.component'; @Injectable({ providedIn: 'root' }) export class NodeScriptTestService { - constructor(private ruleChainService: RuleChainService) { + constructor(private ruleChainService: RuleChainService, + public dialog: MatDialog) { } testNodeScript(script: string, scriptType: string, functionTitle: string, @@ -73,8 +76,21 @@ export class NodeScriptTestService { if (!msgType) { msgType = 'POST_TELEMETRY_REQUEST'; } - console.log(`testNodeScript TODO: ${script}`); - return of(script); + return this.dialog.open(NodeScriptTestDialogComponent, + { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog', 'tb-fullscreen-dialog-gt-sm'], + data: { + msg, + metadata, + msgType, + functionTitle, + functionName, + script, + scriptType, + argNames + } + }).afterClosed(); } } diff --git a/ui-ngx/src/app/core/services/script/node-script-test-dialog.component.html b/ui-ngx/src/app/shared/components/dialog/node-script-test-dialog.component.html similarity index 95% rename from ui-ngx/src/app/core/services/script/node-script-test-dialog.component.html rename to ui-ngx/src/app/shared/components/dialog/node-script-test-dialog.component.html index 8a3e17247b..c1b20aef19 100644 --- a/ui-ngx/src/app/core/services/script/node-script-test-dialog.component.html +++ b/ui-ngx/src/app/shared/components/dialog/node-script-test-dialog.component.html @@ -15,7 +15,8 @@ limitations under the License. --> -
+

{{ 'rulenode.test-script-function' | translate }}

@@ -43,8 +44,8 @@
- TODO: metadataForm
+ TODO: metadataForm
@@ -72,7 +73,6 @@ @@ -85,6 +85,7 @@ \n
\n
\n'}]}],r.ctorParameters=function(){return[{type:n.Store},{type:o.FormBuilder},{type:s.NodeScriptTestService},{type:i.TranslateService}]},r.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},r}(a.RuleNodeConfigurationComponent);var g=function(){function e(){}return e.decorators=[{type:t.NgModule,args:[{declarations:[m,c,p,f],imports:[r.CommonModule,a.SharedModule],exports:[m,c,p,f]}]}],e}(),y=function(){function e(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-name-pattern-hint":"Name pattern, use ${metaKeyName} to substitute variables from metadata","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-type-pattern-hint":"Type pattern, use ${metaKeyName} to substitute variables from metadata","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-name-pattern-hint":"Customer name pattern, use ${metaKeyName} to substitute variables from metadata","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.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.","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","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","latest-timeseries":"Latest timeseries","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-hint":"Relation type pattern, use ${metaKeyName} to substitute variables from metadata","relation-type-pattern-required":"Relation type pattern is required","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","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","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 metadata period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds metadata pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","period-in-seconds-pattern-hint":"Period in seconds pattern, use ${metaKeyName} to substitute variables from metadata","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",propagate:"Propagate",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","from-template-hint":"From address template, use ${metaKeyName} to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use ${metaKeyName} to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use ${metaKeyName} to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use ${metaKeyName} to substitute variables from metadata","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","endpoint-url-pattern-hint":"HTTP URL address pattern, use ${metaKeyName} to substitute variables from metadata","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory",headers:"Headers","headers-hint":"Use ${metaKeyName} in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use ${metaKeyName} to substitute variables from metadata","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","topic-arn-pattern-hint":"Topic ARN pattern, use ${metaKeyName} to substitute variables from metadata","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","queue-url-pattern-hint":"Queue URL pattern, use ${metaKeyName} to substitute variables from metadata","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 ${metaKeyName} in name/value fields to substitute variables from metadata","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","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","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"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-interval-patterns":"Use metadata interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","check-all-keys":"Check that all selected keys 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-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","start-interval-pattern-hint":"Start interval pattern, use ${metaKeyName} to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use ${metaKeyName} to substitute variables from metadata","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","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":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","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","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","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":"You should 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-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".'},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}},!0)}(e)}return e.decorators=[{type:t.NgModule,args:[{declarations:[d],imports:[r.CommonModule,a.SharedModule],exports:[g,d]}]}],e.ctorParameters=function(){return[{type:i.TranslateService}]},e}();e.RuleNodeCoreConfigModule=y,e.default=y,e.ɵa=d,e.ɵb=g,e.ɵc=m,e.ɵd=c,e.ɵe=p,e.ɵf=f,Object.defineProperty(e,"__esModule",{value:!0})})); //# sourceMappingURL=rulenode-core-config.umd.min.js.map \ No newline at end of file From db02d42e3762e39f793f265ba64e081cc072a439 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 27 Dec 2019 16:35:11 +0200 Subject: [PATCH 065/133] Rule Node Test Script --- .../src/app/core/http/rule-chain.service.ts | 6 +- .../interceptors/global-http-interceptor.ts | 10 +- .../script/node-script-test.service.ts | 4 +- .../node-script-test-dialog.component.html | 48 ++- .../node-script-test-dialog.component.scss | 4 + .../node-script-test-dialog.component.ts | 100 +++++-- .../components/json-content.component.html | 39 +++ .../components/json-content.component.scss | 57 ++++ .../components/json-content.component.ts | 275 ++++++++++++++++++ .../components/json-object-edit.component.ts | 32 +- .../shared/components/kv-map.component.html | 60 ++++ .../shared/components/kv-map.component.scss | 41 +++ .../app/shared/components/kv-map.component.ts | 160 ++++++++++ .../message-type-autocomplete.component.html | 43 +++ .../message-type-autocomplete.component.ts | 178 ++++++++++++ ui-ngx/src/app/shared/models/constants.ts | 37 +++ .../src/app/shared/models/rule-node.models.ts | 52 ++++ ui-ngx/src/app/shared/shared.module.ts | 9 + 18 files changed, 1112 insertions(+), 43 deletions(-) create mode 100644 ui-ngx/src/app/shared/components/json-content.component.html create mode 100644 ui-ngx/src/app/shared/components/json-content.component.scss create mode 100644 ui-ngx/src/app/shared/components/json-content.component.ts create mode 100644 ui-ngx/src/app/shared/components/kv-map.component.html create mode 100644 ui-ngx/src/app/shared/components/kv-map.component.scss create mode 100644 ui-ngx/src/app/shared/components/kv-map.component.ts create mode 100644 ui-ngx/src/app/shared/components/message-type-autocomplete.component.html create mode 100644 ui-ngx/src/app/shared/components/message-type-autocomplete.component.ts diff --git a/ui-ngx/src/app/core/http/rule-chain.service.ts b/ui-ngx/src/app/core/http/rule-chain.service.ts index 1f9ade5601..ce7b23c47c 100644 --- a/ui-ngx/src/app/core/http/rule-chain.service.ts +++ b/ui-ngx/src/app/core/http/rule-chain.service.ts @@ -31,7 +31,7 @@ import { ComponentDescriptorService } from './component-descriptor.service'; import { IRuleNodeConfigurationComponent, LinkLabel, - RuleNodeComponentDescriptor + RuleNodeComponentDescriptor, TestScriptInputParams, TestScriptResult } from '@app/shared/models/rule-node.models'; import { ResourcesService } from '../services/resources.service'; import { catchError, map, mergeMap } from 'rxjs/operators'; @@ -175,6 +175,10 @@ export class RuleChainService { return this.http.get(`/api/ruleNode/${ruleNodeId}/debugIn`, defaultHttpOptionsFromConfig(config)); } + public testScript(inputParams: TestScriptInputParams, config?: RequestConfig): Observable { + return this.http.post('/api/ruleChain/testScript', inputParams, defaultHttpOptionsFromConfig(config)); + } + private resolveTargetRuleChains(ruleChainConnections: Array): Observable<{[ruleChainId: string]: RuleChain}> { if (ruleChainConnections && ruleChainConnections.length) { const tasks: Observable[] = []; diff --git a/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts b/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts index 7955604908..fe11d8461d 100644 --- a/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts +++ b/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts @@ -23,7 +23,7 @@ import { HttpResponseBase } from '@angular/common/http'; import { Observable } from 'rxjs/internal/Observable'; -import { Injectable } from '@angular/core'; +import { Inject, Injectable } from '@angular/core'; import { AuthService } from '../auth/auth.service'; import { Constants } from '../../shared/models/constants'; import { InterceptorHttpParams } from './interceptor-http-params'; @@ -52,10 +52,10 @@ export class GlobalHttpInterceptor implements HttpInterceptor { private activeRequests = 0; - constructor(private store: Store, - private dialogService: DialogService, - private translate: TranslateService, - private authService: AuthService) { + constructor(@Inject(Store) private store: Store, + @Inject(DialogService) private dialogService: DialogService, + @Inject(TranslateService) private translate: TranslateService, + @Inject(AuthService) private authService: AuthService) { } intercept(req: HttpRequest, next: HttpHandler): Observable> { 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 d946425698..86213c717f 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 @@ -36,7 +36,7 @@ export class NodeScriptTestService { return this.ruleChainService.getLatestRuleNodeDebugInput(ruleNodeId).pipe( switchMap((debugIn) => { let msg: any; - let metadata: any; + let metadata: {[key: string]: string}; let msgType: string; if (debugIn) { if (debugIn.data) { @@ -59,7 +59,7 @@ export class NodeScriptTestService { private openTestScriptDialog(script: string, scriptType: string, functionTitle: string, functionName: string, argNames: string[], - msg?: any, metadata?: any, msgType?: string): Observable { + msg?: any, metadata?: {[key: string]: string}, msgType?: string): Observable { if (!msg) { msg = { temperature: 22.4, diff --git a/ui-ngx/src/app/shared/components/dialog/node-script-test-dialog.component.html b/ui-ngx/src/app/shared/components/dialog/node-script-test-dialog.component.html index c1b20aef19..ee1146368f 100644 --- a/ui-ngx/src/app/shared/components/dialog/node-script-test-dialog.component.html +++ b/ui-ngx/src/app/shared/components/dialog/node-script-test-dialog.component.html @@ -26,9 +26,6 @@ close - - -
@@ -37,7 +34,24 @@
- TODO: payloadForm +
+
+ + +
+ + +
@@ -45,7 +59,10 @@
- TODO: metadataForm + +
@@ -55,7 +72,14 @@
- TODO: funcBodyForm + +
@@ -63,7 +87,15 @@
- TODO: output + +
@@ -79,7 +111,7 @@ + +
+
+
+
+
diff --git a/ui-ngx/src/app/shared/components/json-content.component.scss b/ui-ngx/src/app/shared/components/json-content.component.scss new file mode 100644 index 0000000000..7d8c222e12 --- /dev/null +++ b/ui-ngx/src/app/shared/components/json-content.component.scss @@ -0,0 +1,57 @@ +/** + * Copyright © 2016-2019 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 { + position: relative; + + .fill-height { + height: 100%; + } +} + +.tb-json-content-toolbar { + button.mat-button, button.mat-icon-button, button.mat-icon-button.tb-mat-32 { + align-items: center; + vertical-align: middle; + min-width: 32px; + min-height: 15px; + padding: 4px; + margin: 0; + font-size: .8rem; + line-height: 15px; + color: #7b7b7b; + background: rgba(220, 220, 220, .35); + &:not(:last-child) { + margin-right: 4px; + } + } +} + +.tb-json-content-panel { + height: 100%; + margin-left: 15px; + border: 1px solid #c0c0c0; + + #tb-json-input { + width: 100%; + min-width: 200px; + height: 100%; + + &:not(.fill-height) { + min-height: 200px; + } + } +} diff --git a/ui-ngx/src/app/shared/components/json-content.component.ts b/ui-ngx/src/app/shared/components/json-content.component.ts new file mode 100644 index 0000000000..e8c61c8bbc --- /dev/null +++ b/ui-ngx/src/app/shared/components/json-content.component.ts @@ -0,0 +1,275 @@ +/// +/// Copyright © 2016-2019 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, + forwardRef, + Input, + OnChanges, + OnInit, + ViewChild, + SimpleChanges, + OnDestroy +} from '@angular/core'; +import { ControlValueAccessor, FormControl, NG_VALIDATORS, NG_VALUE_ACCESSOR, Validator } from '@angular/forms'; +import * as ace from 'ace-builds'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { ActionNotificationHide, ActionNotificationShow } from '@core/notification/notification.actions'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { ContentType, contentTypesMap } from '@shared/models/constants'; +import { CancelAnimationFrame, RafService } from '@core/services/raf.service'; + +@Component({ + selector: 'tb-json-content', + templateUrl: './json-content.component.html', + styleUrls: ['./json-content.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => JsonContentComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => JsonContentComponent), + multi: true, + } + ] +}) +export class JsonContentComponent implements OnInit, ControlValueAccessor, Validator, OnChanges, OnDestroy { + + @ViewChild('jsonEditor', {static: true}) + jsonEditorElmRef: ElementRef; + + private jsonEditor: ace.Ace.Editor; + private editorsResizeCaf: CancelAnimationFrame; + private editorResizeListener: any; + + @Input() label: string; + + @Input() contentType: ContentType; + + @Input() disabled: boolean; + + @Input() fillHeight: boolean; + + @Input() editorStyle: {[klass: string]: any}; + + private readonlyValue: boolean; + get readonly(): boolean { + return this.readonlyValue; + } + @Input() + set readonly(value: boolean) { + this.readonlyValue = coerceBooleanProperty(value); + } + + private validateContentValue: boolean; + get validateContent(): boolean { + return this.validateContentValue; + } + @Input() + set validateContent(value: boolean) { + this.validateContentValue = coerceBooleanProperty(value); + } + + fullscreen = false; + + contentBody: string; + + contentValid: boolean; + + validationError: string; + + errorShowed = false; + + private propagateChange = null; + + constructor(public elementRef: ElementRef, + protected store: Store, + private raf: RafService) { + } + + ngOnInit(): void { + const editorElement = this.jsonEditorElmRef.nativeElement; + let mode = 'text'; + if (this.contentType) { + mode = contentTypesMap.get(this.contentType).code; + } + let editorOptions: Partial = { + mode: `ace/mode/${mode}`, + theme: 'ace/theme/github', + showGutter: true, + showPrintMargin: false, + readOnly: this.readonly + }; + + const advancedOptions = { + enableSnippets: true, + enableBasicAutocompletion: true, + enableLiveAutocompletion: true + }; + + editorOptions = {...editorOptions, ...advancedOptions}; + this.jsonEditor = ace.edit(editorElement, editorOptions); + this.jsonEditor.session.setUseWrapMode(true); + this.jsonEditor.setValue(this.contentBody ? this.contentBody : '', -1); + this.jsonEditor.on('change', () => { + this.cleanupJsonErrors(); + this.updateView(); + }); + this.editorResizeListener = this.onAceEditorResize.bind(this); + // @ts-ignore + addResizeListener(editorElement, this.editorResizeListener); + } + + ngOnDestroy(): void { + if (this.editorResizeListener) { + const editorElement = this.jsonEditorElmRef.nativeElement; + // @ts-ignore + removeResizeListener(editorElement, this.editorResizeListener); + } + } + + private onAceEditorResize() { + if (this.editorsResizeCaf) { + this.editorsResizeCaf(); + this.editorsResizeCaf = null; + } + this.editorsResizeCaf = this.raf.raf(() => { + this.jsonEditor.resize(); + this.jsonEditor.renderer.updateFull(); + }); + } + + ngOnChanges(changes: SimpleChanges): void { + for (const propName of Object.keys(changes)) { + const change = changes[propName]; + if (!change.firstChange && change.currentValue !== change.previousValue) { + if (propName === 'contentType') { + if (this.jsonEditor) { + let mode = 'text'; + if (this.contentType) { + mode = contentTypesMap.get(this.contentType).code; + } + this.jsonEditor.session.setMode(`ace/mode/${mode}`); + } + } + } + } + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + public validate(c: FormControl) { + return (this.contentValid) ? null : { + contentBody: { + valid: false, + }, + }; + } + + validateOnSubmit(): void { + if (!this.readonly) { + this.cleanupJsonErrors(); + this.contentValid = true; + this.propagateChange(this.contentBody); + this.contentValid = this.doValidate(); + this.propagateChange(this.contentBody); + } + } + + private doValidate(): boolean { + try { + if (this.validateContent && this.contentType === ContentType.JSON) { + JSON.parse(this.contentBody); + } + return true; + } catch (ex) { + let errorInfo = 'Error:'; + if (ex.name) { + errorInfo += ' ' + ex.name + ':'; + } + if (ex.message) { + errorInfo += ' ' + ex.message; + } + this.store.dispatch(new ActionNotificationShow( + { + message: errorInfo, + type: 'error', + target: 'jsonContentEditor', + verticalPosition: 'bottom', + horizontalPosition: 'left' + })); + this.errorShowed = true; + return false; + } + } + + cleanupJsonErrors(): void { + if (this.errorShowed) { + this.store.dispatch(new ActionNotificationHide( + { + target: 'jsonContentEditor' + })); + this.errorShowed = false; + } + } + + writeValue(value: string): void { + this.contentBody = value; + this.contentValid = true; + if (this.jsonEditor) { + this.jsonEditor.setValue(this.contentBody ? this.contentBody : '', -1); + // this.jsonEditor. + } + } + + updateView() { + const editorValue = this.jsonEditor.getValue(); + if (this.contentBody !== editorValue) { + this.contentBody = editorValue; + this.contentValid = true; + this.propagateChange(this.contentBody); + } + } + + beautifyJson() { + const res = js_beautify(this.contentBody, {indent_size: 4, wrap_line_length: 60}); + this.jsonEditor.setValue(res ? res : '', -1); + this.updateView(); + } + + onFullscreen() { + if (this.jsonEditor) { + setTimeout(() => { + this.jsonEditor.resize(); + }, 0); + } + } + +} diff --git a/ui-ngx/src/app/shared/components/json-object-edit.component.ts b/ui-ngx/src/app/shared/components/json-object-edit.component.ts index 0ee763ecc0..1e1a4ec6a9 100644 --- a/ui-ngx/src/app/shared/components/json-object-edit.component.ts +++ b/ui-ngx/src/app/shared/components/json-object-edit.component.ts @@ -19,7 +19,7 @@ import { Component, ElementRef, forwardRef, - Input, + Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; @@ -29,6 +29,7 @@ import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { ActionNotificationHide, ActionNotificationShow } from '@core/notification/notification.actions'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; +import { CancelAnimationFrame, RafService } from '@core/services/raf.service'; @Component({ selector: 'tb-json-object-edit', @@ -47,12 +48,14 @@ import { AppState } from '@core/core.state'; } ] }) -export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Validator { +export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Validator, OnDestroy { @ViewChild('jsonEditor', {static: true}) jsonEditorElmRef: ElementRef; private jsonEditor: ace.Ace.Editor; + private editorsResizeCaf: CancelAnimationFrame; + private editorResizeListener: any; @Input() label: string; @@ -95,7 +98,8 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va private propagateChange = null; constructor(public elementRef: ElementRef, - protected store: Store) { + protected store: Store, + private raf: RafService) { } ngOnInit(): void { @@ -122,6 +126,28 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va this.cleanupJsonErrors(); this.updateView(); }); + this.editorResizeListener = this.onAceEditorResize.bind(this); + // @ts-ignore + addResizeListener(editorElement, this.editorResizeListener); + } + + ngOnDestroy(): void { + if (this.editorResizeListener) { + const editorElement = this.jsonEditorElmRef.nativeElement; + // @ts-ignore + removeResizeListener(editorElement, this.editorResizeListener); + } + } + + private onAceEditorResize() { + if (this.editorsResizeCaf) { + this.editorsResizeCaf(); + this.editorsResizeCaf = null; + } + this.editorsResizeCaf = this.raf.raf(() => { + this.jsonEditor.resize(); + this.jsonEditor.renderer.updateFull(); + }); } registerOnChange(fn: any): void { diff --git a/ui-ngx/src/app/shared/components/kv-map.component.html b/ui-ngx/src/app/shared/components/kv-map.component.html new file mode 100644 index 0000000000..da03296492 --- /dev/null +++ b/ui-ngx/src/app/shared/components/kv-map.component.html @@ -0,0 +1,60 @@ + + +
+ +
+ + + + + + + + + +
+ {{noDataText ? noDataText : 'key-val.no-data'}} +
+ +
+
diff --git a/ui-ngx/src/app/shared/components/kv-map.component.scss b/ui-ngx/src/app/shared/components/kv-map.component.scss new file mode 100644 index 0000000000..0507cd4d09 --- /dev/null +++ b/ui-ngx/src/app/shared/components/kv-map.component.scss @@ -0,0 +1,41 @@ +/** + * Copyright © 2016-2019 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-kv-map { + span.no-data-found { + position: relative; + display: flex; + height: 40px; + text-transform: uppercase; + + &.disabled { + color: rgba(0, 0, 0, .38); + } + } + } +} + +:host ::ng-deep { + .mat-form-field-wrapper { + padding-bottom: 0; + } + .mat-form-field-infix { + border-top: 0; + } + .mat-form-field-underline { + bottom: 0; + } +} diff --git a/ui-ngx/src/app/shared/components/kv-map.component.ts b/ui-ngx/src/app/shared/components/kv-map.component.ts new file mode 100644 index 0000000000..41df0999c0 --- /dev/null +++ b/ui-ngx/src/app/shared/components/kv-map.component.ts @@ -0,0 +1,160 @@ +/// +/// Copyright © 2016-2019 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, forwardRef, Input, OnInit, Output } from '@angular/core'; +import { + AbstractControl, + ControlValueAccessor, FormArray, + FormBuilder, FormControl, + FormGroup, NG_VALIDATORS, + NG_VALUE_ACCESSOR, Validator, + ValidatorFn, + Validators +} from '@angular/forms'; +import { AliasFilterType, aliasFilterTypeTranslationMap, EntityAliasFilter } from '@shared/models/alias.models'; +import { AliasEntityType, EntityType } from '@shared/models/entity-type.models'; +import { TranslateService } from '@ngx-translate/core'; +import { EntityService } from '@core/http/entity.service'; +import { EntitySearchDirection, entitySearchDirectionTranslations, EntityTypeFilter } from '@shared/models/relation.models'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { Subscription } from 'rxjs'; + +@Component({ + selector: 'tb-key-val-map', + templateUrl: './kv-map.component.html', + styleUrls: ['./kv-map.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => KeyValMapComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => KeyValMapComponent), + multi: true, + } + ] +}) +export class KeyValMapComponent extends PageComponent implements ControlValueAccessor, OnInit, Validator { + + @Input() disabled: boolean; + + @Input() titleText: string; + + @Input() keyPlaceholderText: string; + + @Input() valuePlaceholderText: string; + + @Input() noDataText: string; + + kvListFormGroup: FormGroup; + + private propagateChange = null; + + private valueChangeSubscription: Subscription = null; + + constructor(protected store: Store, + private fb: FormBuilder) { + super(store); + } + + ngOnInit(): void { + this.kvListFormGroup = this.fb.group({}); + this.kvListFormGroup.addControl('keyVals', + this.fb.array([])); + } + + keyValsFormArray(): FormArray { + return this.kvListFormGroup.get('keyVals') as FormArray; + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState?(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.kvListFormGroup.disable({emitEvent: false}); + } else { + this.kvListFormGroup.enable({emitEvent: false}); + } + } + + writeValue(keyValMap: {[key: string]: string}): void { + if (this.valueChangeSubscription) { + this.valueChangeSubscription.unsubscribe(); + } + const keyValsControls: Array = []; + if (keyValMap) { + for (const property of Object.keys(keyValMap)) { + if (Object.prototype.hasOwnProperty.call(keyValMap, property)) { + keyValsControls.push(this.fb.group({ + key: [property, [Validators.required]], + value: [keyValMap[property], [Validators.required]] + })); + } + } + } + this.kvListFormGroup.setControl('keyVals', this.fb.array(keyValsControls)); + this.valueChangeSubscription = this.kvListFormGroup.valueChanges.subscribe(() => { + this.updateModel(); + }); + } + + public removeKeyVal(index: number) { + (this.kvListFormGroup.get('keyVals') as FormArray).removeAt(index); + } + + public addKeyVal() { + const keyValsFormArray = this.kvListFormGroup.get('keyVals') as FormArray; + keyValsFormArray.push(this.fb.group({ + key: ['', [Validators.required]], + value: ['', [Validators.required]] + })); + } + + public validate(c: FormControl) { + const kvList: {key: string; value: string}[] = this.kvListFormGroup.get('keyVals').value; + let valid = true; + for (const entry of kvList) { + if (!entry.key || !entry.value) { + valid = false; + break; + } + } + return (valid) ? null : { + keyVals: { + valid: false, + }, + }; + } + + private updateModel() { + const kvList: {key: string; value: string}[] = this.kvListFormGroup.get('keyVals').value; + const keyValMap: {[key: string]: string} = {}; + kvList.forEach((entry) => { + keyValMap[entry.key] = entry.value; + }); + this.propagateChange(keyValMap); + } +} diff --git a/ui-ngx/src/app/shared/components/message-type-autocomplete.component.html b/ui-ngx/src/app/shared/components/message-type-autocomplete.component.html new file mode 100644 index 0000000000..3c63fb0168 --- /dev/null +++ b/ui-ngx/src/app/shared/components/message-type-autocomplete.component.html @@ -0,0 +1,43 @@ + + + {{ 'rulenode.message-type' | translate }} + + + + + + + + + {{ 'rulenode.message-type-required' | translate }} + + diff --git a/ui-ngx/src/app/shared/components/message-type-autocomplete.component.ts b/ui-ngx/src/app/shared/components/message-type-autocomplete.component.ts new file mode 100644 index 0000000000..1f9b936292 --- /dev/null +++ b/ui-ngx/src/app/shared/components/message-type-autocomplete.component.ts @@ -0,0 +1,178 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { AfterViewInit, Component, ElementRef, forwardRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { Observable, of } from 'rxjs'; +import { map, mergeMap, startWith, tap } from 'rxjs/operators'; +import { Store } from '@ngrx/store'; +import { AppState } from '@app/core/core.state'; +import { TranslateService } from '@ngx-translate/core'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { MessageType, messageTypeNames } from '@shared/models/rule-node.models'; + +@Component({ + selector: 'tb-message-type-autocomplete', + templateUrl: './message-type-autocomplete.component.html', + styleUrls: [], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => MessageTypeAutocompleteComponent), + multi: true + }] +}) +export class MessageTypeAutocompleteComponent implements ControlValueAccessor, OnInit, AfterViewInit, OnDestroy { + + messageTypeFormGroup: FormGroup; + + modelValue: string | null; + + private requiredValue: boolean; + get required(): boolean { + return this.requiredValue; + } + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + @Input() + disabled: boolean; + + @ViewChild('messageTypeInput', {static: true}) messageTypeInput: ElementRef; + + filteredMessageTypes: Observable>; + + searchText = ''; + + private dirty = false; + + private propagateChange = (v: any) => { }; + + constructor(private store: Store, + public translate: TranslateService, + private fb: FormBuilder) { + this.messageTypeFormGroup = this.fb.group({ + messageType: [null] + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + ngOnInit() { + this.filteredMessageTypes = this.messageTypeFormGroup.get('messageType').valueChanges + .pipe( + tap(value => { + this.updateView(value); + }), + startWith(''), + map(value => value ? value : ''), + mergeMap(messageType => this.fetchMessageTypes(messageType) ) + ); + } + + ngAfterViewInit(): void { + } + + ngOnDestroy(): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.messageTypeFormGroup.disable({emitEvent: false}); + } else { + this.messageTypeFormGroup.enable({emitEvent: false}); + } + } + + writeValue(value: string | null): void { + this.searchText = ''; + this.modelValue = value; + let res: MessageType | string = null; + if (value) { + if (Object.values(MessageType).includes(value)) { + res = MessageType[value]; + } else { + res = value; + } + } + this.messageTypeFormGroup.get('messageType').patchValue(res, {emitEvent: false}); + this.dirty = true; + } + + onFocus() { + if (this.dirty) { + this.messageTypeFormGroup.get('messageType').updateValueAndValidity({onlySelf: true, emitEvent: true}); + this.dirty = false; + } + } + + updateView(value: MessageType | string | null) { + let res: string = null; + if (value) { + if (Object.values(MessageType).includes(value)) { + res = MessageType[value]; + } else { + res = value; + } + } + if (this.modelValue !== res) { + this.modelValue = res; + this.propagateChange(this.modelValue); + } + } + + displayMessageTypeFn(messageType?: MessageType | string): string | undefined { + if (messageType) { + if (Object.values(MessageType).includes(messageType)) { + return messageTypeNames.get(MessageType[messageType]); + } else { + return messageType; + } + } + return undefined; + } + + fetchMessageTypes(searchText?: string): Observable> { + this.searchText = searchText; + const result: Array = []; + messageTypeNames.forEach((value, key) => { + if (value.toUpperCase().includes(searchText.toUpperCase())) { + result.push(key); + } + }); + if (result.length) { + return of(result); + } else { + return of([searchText]); + } + } + + clear() { + this.messageTypeFormGroup.get('messageType').patchValue(null, {emitEvent: true}); + setTimeout(() => { + this.messageTypeInput.nativeElement.blur(); + this.messageTypeInput.nativeElement.focus(); + }, 0); + } + +} diff --git a/ui-ngx/src/app/shared/models/constants.ts b/ui-ngx/src/app/shared/models/constants.ts index 9dda11b09f..0bbb06ce2b 100644 --- a/ui-ngx/src/app/shared/models/constants.ts +++ b/ui-ngx/src/app/shared/models/constants.ts @@ -156,4 +156,41 @@ export const valueTypesMap = new Map( ] ); +export interface ContentTypeData { + name: string; + code: string; +} + +export enum ContentType { + JSON = 'JSON', + TEXT = 'TEXT', + BINARY = 'BINARY' +} + +export const contentTypesMap = new Map( + [ + [ + ContentType.JSON, + { + name: 'content-type.json', + code: 'json' + } + ], + [ + ContentType.TEXT, + { + name: 'content-type.text', + code: 'text' + } + ], + [ + ContentType.BINARY, + { + name: 'content-type.binary', + code: 'text' + } + ] + ] +); + export const customTranslationsPrefix = 'custom.'; 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 a1225ef8da..b611eca66c 100644 --- a/ui-ngx/src/app/shared/models/rule-node.models.ts +++ b/ui-ngx/src/app/shared/models/rule-node.models.ts @@ -232,6 +232,58 @@ export interface RuleNodeComponentDescriptor extends ComponentDescriptor { configurationDescriptor?: RuleNodeConfigurationDescriptor; } +export interface TestScriptInputParams { + script: string; + scriptType: string; + argNames: string[]; + msg: string; + metadata: {[key: string]: string}; + msgType: string; +} + +export interface TestScriptResult { + output: string; + error: string; +} + +export enum MessageType { + POST_ATTRIBUTES_REQUEST = 'POST_ATTRIBUTES_REQUEST', + POST_TELEMETRY_REQUEST = 'POST_TELEMETRY_REQUEST', + TO_SERVER_RPC_REQUEST = 'TO_SERVER_RPC_REQUEST', + RPC_CALL_FROM_SERVER_TO_DEVICE = 'RPC_CALL_FROM_SERVER_TO_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' +} + +export const messageTypeNames = new Map( + [ + [MessageType.POST_ATTRIBUTES_REQUEST, 'Post attributes'], + [MessageType.POST_TELEMETRY_REQUEST, 'Post telemetry'], + [MessageType.TO_SERVER_RPC_REQUEST, 'RPC Request from Device'], + [MessageType.RPC_CALL_FROM_SERVER_TO_DEVICE, 'RPC Request to Device'], + [MessageType.ACTIVITY_EVENT, 'Activity Event'], + [MessageType.INACTIVITY_EVENT, 'Inactivity Event'], + [MessageType.CONNECT_EVENT, 'Connect Event'], + [MessageType.DISCONNECT_EVENT, 'Disconnect Event'], + [MessageType.ENTITY_CREATED, 'Entity Created'], + [MessageType.ENTITY_UPDATED, 'Entity Updated'], + [MessageType.ENTITY_DELETED, 'Entity Deleted'], + [MessageType.ENTITY_ASSIGNED, 'Entity Assigned'], + [MessageType.ENTITY_UNASSIGNED, 'Entity Unassigned'], + [MessageType.ATTRIBUTES_UPDATED, 'Attributes Updated'], + [MessageType.ATTRIBUTES_DELETED, 'Attributes Deleted'] + ] +); + const ruleNodeClazzHelpLinkMap = { 'org.thingsboard.rule.engine.filter.TbCheckRelationNode': 'ruleNodeCheckRelation', 'org.thingsboard.rule.engine.filter.TbCheckMessageNode': 'ruleNodeCheckExistenceFields', diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index d7773efac0..73657a320e 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -115,6 +115,9 @@ import { MaterialIconSelectComponent } from '@shared/components/material-icon-se import { ImageInputComponent } from './components/image-input.component'; import { FileInputComponent } from './components/file-input.component'; import { NodeScriptTestDialogComponent } from '@shared/components/dialog/node-script-test-dialog.component'; +import { MessageTypeAutocompleteComponent } from './components/message-type-autocomplete.component'; +import { JsonContentComponent } from './components/json-content.component'; +import { KeyValMapComponent } from './components/kv-map.component'; @NgModule({ providers: [ @@ -175,6 +178,7 @@ import { NodeScriptTestDialogComponent } from '@shared/components/dialog/node-sc RelationTypeAutocompleteComponent, SocialSharePanelComponent, JsonObjectEditComponent, + JsonContentComponent, JsFuncComponent, FabTriggerDirective, FabActionsDirective, @@ -188,6 +192,8 @@ import { NodeScriptTestDialogComponent } from '@shared/components/dialog/node-sc JsonFormComponent, ImageInputComponent, FileInputComponent, + MessageTypeAutocompleteComponent, + KeyValMapComponent, NospacePipe, MillisecondsToTimeStringPipe, EnumToArrayPipe, @@ -276,6 +282,7 @@ import { NodeScriptTestDialogComponent } from '@shared/components/dialog/node-sc RelationTypeAutocompleteComponent, SocialSharePanelComponent, JsonObjectEditComponent, + JsonContentComponent, JsFuncComponent, FabTriggerDirective, FabActionsDirective, @@ -331,6 +338,8 @@ import { NodeScriptTestDialogComponent } from '@shared/components/dialog/node-sc JsonFormComponent, ImageInputComponent, FileInputComponent, + MessageTypeAutocompleteComponent, + KeyValMapComponent, NospacePipe, MillisecondsToTimeStringPipe, EnumToArrayPipe, From d0b4089fa93d6e5e73f892f530d2d3e5402416d8 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 14 Jan 2020 16:57:42 +0200 Subject: [PATCH 066/133] UI: Rule nodes configuration --- .../static/rulenode/rulenode-core-config.js | 4 +-- ....conponent.ts => link-labels.component.ts} | 0 .../rule-node-details.component.scss | 21 ++++++++++++ .../rulechain/rule-node-details.component.ts | 2 +- .../rulechain/rulechain-page.component.html | 5 ++- .../rulechain/rulechain-routing.module.ts | 34 +++++++++---------- .../home/pages/rulechain/rulechain.module.ts | 2 +- .../components/json-content.component.ts | 1 - .../components/json-object-edit.component.ts | 1 - .../app/shared/models/entity-type.models.ts | 9 +++++ 10 files changed, 55 insertions(+), 24 deletions(-) rename ui-ngx/src/app/modules/home/pages/rulechain/{link-labels.conponent.ts => link-labels.component.ts} (100%) create mode 100644 ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.scss diff --git a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js index d6d4a1711e..69600384cb 100644 --- a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js +++ b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js @@ -1,4 +1,4 @@ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@angular/core"),require("@angular/common"),require("@ngx-translate/core"),require("@shared/public-api"),require("@ngrx/store"),require("@angular/forms"),require("@core/public-api")):"function"==typeof define&&define.amd?define("rulenode-core-config",["exports","@angular/core","@angular/common","@ngx-translate/core","@shared/public-api","@ngrx/store","@angular/forms","@core/public-api"],t):t((e=e||self)["rulenode-core-config"]={},e.ng.core,e.ng.common,e["ngx-translate"],e.shared,e["ngrx-store"],e.ng.forms,e.core)}(this,(function(e,t,r,i,a,n,o,s){"use strict"; +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@angular/core"),require("@angular/common"),require("@ngx-translate/core"),require("@shared/public-api"),require("@ngrx/store"),require("@angular/forms"),require("@core/public-api"),require("@angular/cdk/keycodes"),require("@angular/material"),require("rxjs"),require("rxjs/operators"),require("@home/components/public-api"),require("@angular/cdk/coercion")):"function"==typeof define&&define.amd?define("rulenode-core-config",["exports","@angular/core","@angular/common","@ngx-translate/core","@shared/public-api","@ngrx/store","@angular/forms","@core/public-api","@angular/cdk/keycodes","@angular/material","rxjs","rxjs/operators","@home/components/public-api","@angular/cdk/coercion"],t):t((e=e||self)["rulenode-core-config"]={},e.ng.core,e.ng.common,e["ngx-translate"],e.shared,e["ngrx-store"],e.ng.forms,e.core,e.ng.cdk.keycodes,e.ng.material,e.rxjs,e.rxjs.operators,e.publicApi$2,e.ng.cdk.coercion)}(this,(function(e,t,n,r,a,i,o,s,l,u,m,d,p,c){"use strict"; /*! ***************************************************************************** Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use @@ -12,5 +12,5 @@ See the Apache Version 2.0 License for specific language governing permissions and limitations under the License. - ***************************************************************************** */var u=function(e,t){return(u=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)t.hasOwnProperty(r)&&(e[r]=t[r])})(e,t)};function l(e,t){function r(){this.constructor=e}u(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)}var d=function(e){function r(t){var r=e.call(this,t)||this;return r.store=t,r}return l(r,e),r.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},r.prototype.onConfigurationSet=function(e){},r.decorators=[{type:t.Component,args:[{selector:"tb-node-empty-config",template:"
"}]}],r.ctorParameters=function(){return[{type:n.Store}]},r}(a.RuleNodeConfigurationComponent);var m=function(e){function r(t,r){var i=e.call(this,t)||this;return i.store=t,i.fb=r,i.attributeScopes=Object.keys(a.AttributeScope),i.telemetryTypeTranslationsMap=a.telemetryTypeTranslations,i}return l(r,e),r.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},r.prototype.onConfigurationSet=function(e){var t=this;this.attributesConfigForm=this.fb.group({scope:[e?e.scope:null,[o.Validators.required]]}),this.attributesConfigForm.valueChanges.subscribe((function(e){t.attributesConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},r.decorators=[{type:t.Component,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'}]}],r.ctorParameters=function(){return[{type:n.Store},{type:o.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var c=function(e){function r(t,r){var i=e.call(this,t)||this;return i.store=t,i.fb=r,i}return l(r,e),r.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},r.prototype.onConfigurationSet=function(e){var t=this;this.timeseriesConfigForm=this.fb.group({defaultTTL:[e?e.defaultTTL:null,[o.Validators.required,o.Validators.min(0)]]}),this.timeseriesConfigForm.valueChanges.subscribe((function(e){t.timeseriesConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},r.decorators=[{type:t.Component,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'}]}],r.ctorParameters=function(){return[{type:n.Store},{type:o.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var p=function(e){function r(t,r){var i=e.call(this,t)||this;return i.store=t,i.fb=r,i}return l(r,e),r.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},r.prototype.onConfigurationSet=function(e){var t=this;this.rpcRequestConfigForm=this.fb.group({timeoutInSeconds:[e?e.timeoutInSeconds:null,[o.Validators.required,o.Validators.min(0)]]}),this.rpcRequestConfigForm.valueChanges.subscribe((function(e){t.rpcRequestConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},r.decorators=[{type:t.Component,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'}]}],r.ctorParameters=function(){return[{type:n.Store},{type:o.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var f=function(e){function r(t,r,i,a){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.nodeScriptTestService=i,n.translate=a,n}return l(r,e),r.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},r.prototype.onConfigurationSet=function(e){var t=this;this.logConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[o.Validators.required]]}),this.logConfigForm.valueChanges.subscribe((function(e){t.logConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},r.prototype.testScript=function(){var e=this,t=this.logConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"string",this.translate.instant("tb.rulenode.to-string"),"ToString",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.logConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.decorators=[{type:t.Component,args:[{selector:"tb-action-node-log-config",template:'
\n \n \n \n
\n \n
\n
\n'}]}],r.ctorParameters=function(){return[{type:n.Store},{type:o.FormBuilder},{type:s.NodeScriptTestService},{type:i.TranslateService}]},r.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},r}(a.RuleNodeConfigurationComponent);var g=function(){function e(){}return e.decorators=[{type:t.NgModule,args:[{declarations:[m,c,p,f],imports:[r.CommonModule,a.SharedModule],exports:[m,c,p,f]}]}],e}(),y=function(){function e(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-name-pattern-hint":"Name pattern, use ${metaKeyName} to substitute variables from metadata","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-type-pattern-hint":"Type pattern, use ${metaKeyName} to substitute variables from metadata","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-name-pattern-hint":"Customer name pattern, use ${metaKeyName} to substitute variables from metadata","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.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.","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","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","latest-timeseries":"Latest timeseries","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-hint":"Relation type pattern, use ${metaKeyName} to substitute variables from metadata","relation-type-pattern-required":"Relation type pattern is required","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","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","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 metadata period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds metadata pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","period-in-seconds-pattern-hint":"Period in seconds pattern, use ${metaKeyName} to substitute variables from metadata","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",propagate:"Propagate",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","from-template-hint":"From address template, use ${metaKeyName} to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use ${metaKeyName} to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use ${metaKeyName} to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use ${metaKeyName} to substitute variables from metadata","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","endpoint-url-pattern-hint":"HTTP URL address pattern, use ${metaKeyName} to substitute variables from metadata","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory",headers:"Headers","headers-hint":"Use ${metaKeyName} in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use ${metaKeyName} to substitute variables from metadata","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","topic-arn-pattern-hint":"Topic ARN pattern, use ${metaKeyName} to substitute variables from metadata","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","queue-url-pattern-hint":"Queue URL pattern, use ${metaKeyName} to substitute variables from metadata","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 ${metaKeyName} in name/value fields to substitute variables from metadata","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","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","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"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-interval-patterns":"Use metadata interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","check-all-keys":"Check that all selected keys 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-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","start-interval-pattern-hint":"Start interval pattern, use ${metaKeyName} to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use ${metaKeyName} to substitute variables from metadata","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","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":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","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","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","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":"You should 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-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".'},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}},!0)}(e)}return e.decorators=[{type:t.NgModule,args:[{declarations:[d],imports:[r.CommonModule,a.SharedModule],exports:[g,d]}]}],e.ctorParameters=function(){return[{type:i.TranslateService}]},e}();e.RuleNodeCoreConfigModule=y,e.default=y,e.ɵa=d,e.ɵb=g,e.ɵc=m,e.ɵd=c,e.ɵe=p,e.ɵf=f,Object.defineProperty(e,"__esModule",{value:!0})})); + ***************************************************************************** */var f=function(e,t){return(f=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])})(e,t)};function g(e,t){function n(){this.constructor=e}f(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}function y(e){var t="function"==typeof Symbol&&e[Symbol.iterator],n=0;return t?t.call(e):{next:function(){return e&&n>=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}}}var h=function(e){function n(t){var n=e.call(this,t)||this;return n.store=t,n}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.onConfigurationSet=function(e){},n.decorators=[{type:t.Component,args:[{selector:"tb-node-empty-config",template:"
"}]}],n.ctorParameters=function(){return[{type:i.Store}]},n}(a.RuleNodeConfigurationComponent);var b=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.attributeScopes=Object.keys(a.AttributeScope),r.telemetryTypeTranslationsMap=a.telemetryTypeTranslations,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.onConfigurationSet=function(e){var t=this;this.attributesConfigForm=this.fb.group({scope:[e?e.scope:null,[o.Validators.required]]}),this.attributesConfigForm.valueChanges.subscribe((function(e){t.attributesConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.decorators=[{type:t.Component,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.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var v=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.onConfigurationSet=function(e){var t=this;this.timeseriesConfigForm=this.fb.group({defaultTTL:[e?e.defaultTTL:null,[o.Validators.required,o.Validators.min(0)]]}),this.timeseriesConfigForm.valueChanges.subscribe((function(e){t.timeseriesConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.decorators=[{type:t.Component,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'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var C=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.onConfigurationSet=function(e){var t=this;this.rpcRequestConfigForm=this.fb.group({timeoutInSeconds:[e?e.timeoutInSeconds:null,[o.Validators.required,o.Validators.min(0)]]}),this.rpcRequestConfigForm.valueChanges.subscribe((function(e){t.rpcRequestConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.decorators=[{type:t.Component,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'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var F=function(e){function n(t,n,r,a){var i=e.call(this,t)||this;return i.store=t,i.fb=n,i.nodeScriptTestService=r,i.translate=a,i}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.onConfigurationSet=function(e){var t=this;this.logConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[o.Validators.required]]}),this.logConfigForm.valueChanges.subscribe((function(e){t.logConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.testScript=function(){var e=this,t=this.logConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"string",this.translate.instant("tb.rulenode.to-string"),"ToString",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.logConfigForm.get("jsScript").setValue(t)}))},n.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},n.decorators=[{type:t.Component,args:[{selector:"tb-action-node-log-config",template:'
\n \n \n \n
\n \n
\n
\n'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder},{type:s.NodeScriptTestService},{type:r.TranslateService}]},n.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},n}(a.RuleNodeConfigurationComponent);var T=function(){function e(){}return e.decorators=[{type:t.NgModule,args:[{declarations:[b,v,C,F],imports:[n.CommonModule,a.SharedModule],exports:[b,v,C,F]}]}],e}(),x=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.separatorKeysCodes=[l.ENTER,l.COMMA,l.SEMICOLON],r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.checkMessageConfigForm=this.fb.group({messageNames:[e?e.messageNames:null,[]],metadataNames:[e?e.metadataNames:null,[]],checkAllKeys:[!!e&&e.checkAllKeys,[]]}),this.checkMessageConfigForm.valueChanges.subscribe((function(e){t.validateConfig()?t.notifyConfigurationUpdated(t.checkMessageConfigForm.value):t.notifyConfigurationUpdated(null)}))},n.prototype.validateConfig=function(){var e=this.checkMessageConfigForm.get("messageNames").value,t=this.checkMessageConfigForm.get("metadataNames").value;return e.length>0||t.length>0},n.prototype.removeMessageName=function(e){var t=this.checkMessageConfigForm.get("messageNames").value,n=t.indexOf(e);n>=0&&(t.splice(n,1),this.checkMessageConfigForm.get("messageNames").setValue(t,{emitEvent:!0}))},n.prototype.removeMetadataName=function(e){var t=this.checkMessageConfigForm.get("metadataNames").value,n=t.indexOf(e);n>=0&&(t.splice(n,1),this.checkMessageConfigForm.get("metadataNames").setValue(t,{emitEvent:!0}))},n.prototype.addMessageName=function(e){var t=e.input,n=e.value;if((n||"").trim()){n=n.trim();var r=this.checkMessageConfigForm.get("messageNames").value;r&&-1!==r.indexOf(n)||(r||(r=[]),r.push(n),this.checkMessageConfigForm.get("messageNames").setValue(r,{emitEvent:!0}))}t&&(t.value="")},n.prototype.addMetadataName=function(e){var t=e.input,n=e.value;if((n||"").trim()){n=n.trim();var r=this.checkMessageConfigForm.get("metadataNames").value;r&&-1!==r.indexOf(n)||(r||(r=[]),r.push(n),this.checkMessageConfigForm.get("metadataNames").setValue(r,{emitEvent:!0}))}t&&(t.value="")},n.decorators=[{type:t.Component,args:[{selector:"tb-filter-node-check-message-config",template:'
\n \n \n \n \n \n {{messageName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n \n \n \n \n {{metadataName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\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:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var I=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.entitySearchDirection=Object.keys(a.EntitySearchDirection),r.entitySearchDirectionTranslationsMap=a.entitySearchDirectionTranslations,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.checkRelationConfigForm=this.fb.group({checkForSingleEntity:[!!e&&e.checkForSingleEntity,[]],direction:[e?e.direction:null,[]],entityType:[e?e.entityType:null,e&&e.checkForSingleEntity?[o.Validators.required]:[]],entityId:[e?e.entityId:null,e&&e.checkForSingleEntity?[o.Validators.required]:[]],relationType:[e?e.relationType:null,[o.Validators.required]]}),this.checkRelationConfigForm.get("checkForSingleEntity").valueChanges.subscribe((function(e){t.checkRelationConfigForm.get("entityType").setValidators(e?[o.Validators.required]:[]),t.checkRelationConfigForm.get("entityType").updateValueAndValidity(),t.checkRelationConfigForm.get("entityId").setValidators(e?[o.Validators.required]:[]),t.checkRelationConfigForm.get("entityId").updateValueAndValidity()})),this.checkRelationConfigForm.valueChanges.subscribe((function(e){t.validateConfig()?t.notifyConfigurationUpdated(t.checkRelationConfigForm.value):t.notifyConfigurationUpdated(null)}))},n.prototype.validateConfig=function(){return this.checkRelationConfigForm.valid},n.decorators=[{type:t.Component,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'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var q={CIRCLE:"CIRCLE",POLYGON:"POLYGON"},k=new Map([[q.CIRCLE,"tb.rulenode.perimeter-circle"],[q.POLYGON,"tb.rulenode.perimeter-polygon"]]),N={MILLISECONDS:"MILLISECONDS",SECONDS:"SECONDS",MINUTES:"MINUTES",HOURS:"HOURS",DAYS:"DAYS"},S=new Map([[N.MILLISECONDS,"tb.rulenode.time-unit-milliseconds"],[N.SECONDS,"tb.rulenode.time-unit-seconds"],[N.MINUTES,"tb.rulenode.time-unit-minutes"],[N.HOURS,"tb.rulenode.time-unit-hours"],[N.DAYS,"tb.rulenode.time-unit-days"]]),A={METER:"METER",KILOMETER:"KILOMETER",FOOT:"FOOT",MILE:"MILE",NAUTICAL_MILE:"NAUTICAL_MILE"},L=new Map([[A.METER,"tb.rulenode.range-unit-meter"],[A.KILOMETER,"tb.rulenode.range-unit-kilometer"],[A.FOOT,"tb.rulenode.range-unit-foot"],[A.MILE,"tb.rulenode.range-unit-mile"],[A.NAUTICAL_MILE,"tb.rulenode.range-unit-nautical-mile"]]),M={TITLE:"TITLE",COUNTRY:"COUNTRY",STATE:"STATE",ZIP:"ZIP",ADDRESS:"ADDRESS",ADDRESS2:"ADDRESS2",PHONE:"PHONE",EMAIL:"EMAIL",ADDITIONAL_INFO:"ADDITIONAL_INFO"},E=new Map([[M.TITLE,"tb.rulenode.entity-details-title"],[M.COUNTRY,"tb.rulenode.entity-details-country"],[M.STATE,"tb.rulenode.entity-details-state"],[M.ZIP,"tb.rulenode.entity-details-zip"],[M.ADDRESS,"tb.rulenode.entity-details-address"],[M.ADDRESS2,"tb.rulenode.entity-details-address2"],[M.PHONE,"tb.rulenode.entity-details-phone"],[M.EMAIL,"tb.rulenode.entity-details-email"],[M.ADDITIONAL_INFO,"tb.rulenode.entity-details-additional_info"]]),V={FIRST:"FIRST",LAST:"LAST",ALL:"ALL"},O={ASC:"ASC",DESC:"DESC"},w=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.perimeterType=q,r.perimeterTypes=Object.keys(q),r.perimeterTypeTranslationMap=k,r.rangeUnits=Object.keys(A),r.rangeUnitTranslationMap=L,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.geoFilterConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[o.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[o.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType: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,[]]}),this.updateValidators(!1),this.geoFilterConfigForm.get("fetchPerimeterInfoFromMessageMetadata").valueChanges.subscribe((function(){t.updateValidators(!0)})),this.geoFilterConfigForm.get("perimeterType").valueChanges.subscribe((function(){t.updateValidators(!0)})),this.geoFilterConfigForm.valueChanges.subscribe((function(e){t.validateConfig()?t.notifyConfigurationUpdated(t.geoFilterConfigForm.value):t.notifyConfigurationUpdated(null)}))},n.prototype.updateValidators=function(e){var t=this.geoFilterConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,n=this.geoFilterConfigForm.get("perimeterType").value;t?this.geoFilterConfigForm.get("perimeterType").setValidators([]):this.geoFilterConfigForm.get("perimeterType").setValidators([o.Validators.required]),t||n!==q.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([o.Validators.required]),this.geoFilterConfigForm.get("centerLongitude").setValidators([o.Validators.required]),this.geoFilterConfigForm.get("range").setValidators([o.Validators.required]),this.geoFilterConfigForm.get("rangeUnit").setValidators([o.Validators.required])),t||n!==q.POLYGON?this.geoFilterConfigForm.get("polygonsDefinition").setValidators([]):this.geoFilterConfigForm.get("polygonsDefinition").setValidators([o.Validators.required]),this.geoFilterConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),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})},n.prototype.validateConfig=function(){return this.geoFilterConfigForm.valid},n.decorators=[{type:t.Component,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.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \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'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var D=function(e){function n(t,n,r,i){var o,s,u=e.call(this,t)||this;u.store=t,u.translate=n,u.truncate=r,u.fb=i,u.separatorKeysCodes=[l.ENTER,l.COMMA,l.SEMICOLON],u.messageTypes=[],u.messageTypesList=[],u.searchText="",u.messageTypeConfigForm=u.fb.group({messageType:[null]});try{for(var m=y(Object.keys(a.MessageType)),d=m.next();!d.done;d=m.next()){var p=d.value;u.messageTypesList.push({name:a.messageTypeNames.get(a.MessageType[p]),value:p})}}catch(e){o={error:e}}finally{try{d&&!d.done&&(s=m.return)&&s.call(m)}finally{if(o)throw o.error}}return u}return g(n,e),n.prototype.ngOnInit=function(){var t=this;e.prototype.ngOnInit.call(this),this.filteredMessageTypes=this.messageTypeConfigForm.get("messageType").valueChanges.pipe(d.startWith(""),d.map((function(e){return e||""})),d.mergeMap((function(e){return t.fetchMessageTypes(e)})),d.share())},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.messageTypes.length||e.updateModel()}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.searchText="",this.messageTypes.length=0,e&&e.messageTypes&&e.messageTypes.forEach((function(e){var n=t.messageTypesList.find((function(t){return t.value===e}));n?t.messageTypes.push({name:n.name,value:n.value}):t.messageTypes.push({name:e,value:e})})),this.messageTypeConfigForm.get("messageType").patchValue("",{emitEvent:!0})},n.prototype.displayMessageTypeFn=function(e){return e?e.name:void 0},n.prototype.textIsNotEmpty=function(e){return!!(e&&null!=e&&e.length>0)},n.prototype.createMessageType=function(e,t){e.preventDefault(),this.transformMessageType(t)},n.prototype.add=function(e){this.transformMessageType(e.value)},n.prototype.fetchMessageTypes=function(e){if(this.searchText=e,this.searchText&&this.searchText.length){var t=this.searchText.toUpperCase();return m.of(this.messageTypesList.filter((function(e){return e.name.toUpperCase().includes(t)})))}return m.of(this.messageTypesList)},n.prototype.transformMessageType=function(e){if((e||"").trim()){var t=null,n=e.trim(),r=this.messageTypesList.find((function(e){return e.name===n}));(t=r?{name:r.name,value:r.value}:{name:n,value:n})&&this.addMessageType(t)}this.clear("")},n.prototype.remove=function(e){var t=this.messageTypes.indexOf(e);t>=0&&(this.messageTypes.splice(t,1),this.updateModel())},n.prototype.selected=function(e){this.addMessageType(e.option.value),this.clear("")},n.prototype.addMessageType=function(e){-1===this.messageTypes.findIndex((function(t){return t.value===e.value}))&&(this.messageTypes.push(e),this.updateModel())},n.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.messageTypeInput.nativeElement.value=e,this.messageTypeConfigForm.get("messageType").patchValue(null,{emitEvent:!0}),setTimeout((function(){t.messageTypeInput.nativeElement.blur(),t.messageTypeInput.nativeElement.focus()}),0)},n.prototype.updateModel=function(){if(this.messageTypes.length){this.chipList.errorState=!1;var e={messageTypes:this.messageTypes.map((function(e){return e.value}))};this.notifyConfigurationUpdated(e)}else this.chipList.errorState=!0,this.notifyConfigurationUpdated(null)},n.decorators=[{type:t.Component,args:[{selector:"tb-filter-node-message-type-config",template:'\n tb.rulenode.message-types-filter\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 {{ translate.get(\'tb.rulenode.no-message-type-matching\',\n {messageType: truncate.transform(searchText, true, 6, '...')}) | async }}\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'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:r.TranslateService},{type:a.TruncatePipe},{type:o.FormBuilder}]},n.propDecorators={chipList:[{type:t.ViewChild,args:["chipList",{static:!1}]}],matAutocomplete:[{type:t.ViewChild,args:["messageTypeAutocomplete",{static:!1}]}],messageTypeInput:[{type:t.ViewChild,args:["messageTypeInput",{static:!1}]}]},n}(a.RuleNodeConfigurationComponent);var R=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.allowedEntityTypes=[a.EntityType.DEVICE,a.EntityType.ASSET,a.EntityType.ENTITY_VIEW,a.EntityType.TENANT,a.EntityType.CUSTOMER,a.EntityType.USER,a.EntityType.DASHBOARD,a.EntityType.RULE_CHAIN,a.EntityType.RULE_NODE],r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.originatorTypeConfigForm=this.fb.group({originatorTypes:[e?e.originatorTypes:null,[o.Validators.required]]}),this.originatorTypeConfigForm.valueChanges.subscribe((function(e){t.originatorTypeConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.validateConfig=function(){return this.originatorTypeConfigForm.valid},n.decorators=[{type:t.Component,args:[{selector:"tb-filter-node-originator-type-config",template:'
\n \n \n \n
\n',styles:[":host ::ng-deep tb-entity-type-list .mat-form-field-flex{padding-top:0}:host ::ng-deep tb-entity-type-list .mat-form-field-infix{border-top:0}"]}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var U=function(e){function n(t,n,r,a){var i=e.call(this,t)||this;return i.store=t,i.fb=n,i.nodeScriptTestService=r,i.translate=a,i}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.onConfigurationSet=function(e){var t=this;this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[o.Validators.required]]}),this.scriptConfigForm.valueChanges.subscribe((function(e){t.scriptConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"filter",this.translate.instant("tb.rulenode.filter"),"Filter",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},n.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},n.decorators=[{type:t.Component,args:[{selector:"tb-filter-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder},{type:s.NodeScriptTestService},{type:r.TranslateService}]},n.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},n}(a.RuleNodeConfigurationComponent);var P=function(e){function n(t,n,r,a){var i=e.call(this,t)||this;return i.store=t,i.fb=n,i.nodeScriptTestService=r,i.translate=a,i}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.onConfigurationSet=function(e){var t=this;this.switchConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[o.Validators.required]]}),this.switchConfigForm.valueChanges.subscribe((function(e){t.switchConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.testScript=function(){var e=this,t=this.switchConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"switch",this.translate.instant("tb.rulenode.switch"),"Switch",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.switchConfigForm.get("jsScript").setValue(t)}))},n.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},n.decorators=[{type:t.Component,args:[{selector:"tb-filter-node-switch-config",template:'
\n \n \n \n
\n \n
\n
\n'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder},{type:s.NodeScriptTestService},{type:r.TranslateService}]},n.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},n}(a.RuleNodeConfigurationComponent);var K=function(){function e(){}return e.decorators=[{type:t.NgModule,args:[{declarations:[x,I,w,D,R,U,P],imports:[n.CommonModule,a.SharedModule],exports:[x,I,w,D,R,U,P]}]}],e}(),j=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.customerAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[o.Validators.required]]}),this.customerAttributesConfigForm.valueChanges.subscribe((function(e){t.customerAttributesConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.validateConfig=function(){return this.customerAttributesConfigForm.valid},n.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-customer-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var G=function(e){function n(t,n,r,a){var i=e.call(this,t)||this;return i.store=t,i.translate=n,i.injector=r,i.fb=a,i.propagateChange=null,i.valueChangeSubscription=null,i}return g(n,e),Object.defineProperty(n.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=c.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),n.prototype.ngOnInit=function(){this.ngControl=this.injector.get(o.NgControl),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.kvListFormGroup=this.fb.group({}),this.kvListFormGroup.addControl("keyVals",this.fb.array([]))},n.prototype.keyValsFormArray=function(){return this.kvListFormGroup.get("keyVals")},n.prototype.registerOnChange=function(e){this.propagateChange=e},n.prototype.registerOnTouched=function(e){},n.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.kvListFormGroup.disable({emitEvent:!1}):this.kvListFormGroup.enable({emitEvent:!1})},n.prototype.writeValue=function(e){var t,n,r=this;this.valueChangeSubscription&&this.valueChangeSubscription.unsubscribe();var a=[];if(e)try{for(var i=y(Object.keys(e)),s=i.next();!s.done;s=i.next()){var l=s.value;Object.prototype.hasOwnProperty.call(e,l)&&a.push(this.fb.group({key:[l,[o.Validators.required]],value:[e[l],[o.Validators.required]]}))}}catch(e){t={error:e}}finally{try{s&&!s.done&&(n=i.return)&&n.call(i)}finally{if(t)throw t.error}}this.kvListFormGroup.setControl("keyVals",this.fb.array(a)),this.valueChangeSubscription=this.kvListFormGroup.valueChanges.subscribe((function(){r.updateModel()}))},n.prototype.removeKeyVal=function(e){this.kvListFormGroup.get("keyVals").removeAt(e)},n.prototype.addKeyVal=function(){this.kvListFormGroup.get("keyVals").push(this.fb.group({key:["",[o.Validators.required]],value:["",[o.Validators.required]]}))},n.prototype.validate=function(e){var t=!0;return this.kvListFormGroup.get("keyVals").value.length||(t=!this.required),t?null:{kvMap:{valid:!1}}},n.prototype.updateModel=function(){var e=this.kvListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.kvListFormGroup.valid)this.propagateChange(null);else{var t={};e.forEach((function(e){t[e.key]=e.value})),this.propagateChange(t)}},n.decorators=[{type:t.Component,args:[{selector:"tb-kv-map-config",template:'
\n
\n {{ keyText }}\n {{ valText }}\n \n
\n
\n
\n \n \n \n \n {{ keyRequiredText | translate }}\n \n \n \n \n \n \n {{ valRequiredText | translate }}\n \n \n \n
\n
\n \n
\n \n
\n
\n',providers:[{provide:o.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0},{provide:o.NG_VALIDATORS,useExisting:t.forwardRef((function(){return n})),multi:!0}],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:rgba(0,0,0,.54);font-size:12px;font-weight:700;white-space:nowrap}:host .tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:20px;max-height:300px;overflow:auto}:host .tb-kv-map-config .body .row{padding-top:5px;max-height:40px}:host .tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell{margin:0;max-height:40px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell .mat-form-field-infix{border-top:0}:host ::ng-deep .tb-kv-map-config .body button.mat-button{margin:0}"]}]}],n.ctorParameters=function(){return[{type:i.Store},{type:r.TranslateService},{type:t.Injector},{type:o.FormBuilder}]},n.propDecorators={disabled:[{type:t.Input}],requiredText:[{type:t.Input}],keyText:[{type:t.Input}],keyRequiredText:[{type:t.Input}],valText:[{type:t.Input}],valRequiredText:[{type:t.Input}],required:[{type:t.Input}]},n}(a.PageComponent);var B=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.directionTypes=Object.keys(a.EntitySearchDirection),r.directionTypeTranslations=a.entitySearchDirectionTranslations,r.entityType=a.EntityType,r.propagateChange=null,r}return g(n,e),Object.defineProperty(n.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=c.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),n.prototype.ngOnInit=function(){var e=this;this.deviceRelationsQueryFormGroup=this.fb.group({direction:[null,[o.Validators.required]],maxLevel:[null,[]],relationType:[null],deviceTypes:[null,[o.Validators.required]]}),this.deviceRelationsQueryFormGroup.valueChanges.subscribe((function(t){e.deviceRelationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},n.prototype.registerOnChange=function(e){this.propagateChange=e},n.prototype.registerOnTouched=function(e){},n.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.deviceRelationsQueryFormGroup.disable({emitEvent:!1}):this.deviceRelationsQueryFormGroup.enable({emitEvent:!1})},n.prototype.writeValue=function(e){this.deviceRelationsQueryFormGroup.reset(e,{emitEvent:!1})},n.decorators=[{type:t.Component,args:[{selector:"tb-device-relations-query-config",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
\n
relation.relation-type
\n \n \n
device.device-types
\n \n \n
\n',providers:[{provide:o.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0}]}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n.propDecorators={disabled:[{type:t.Input}],required:[{type:t.Input}]},n}(a.PageComponent);var $=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.directionTypes=Object.keys(a.EntitySearchDirection),r.directionTypeTranslations=a.entitySearchDirectionTranslations,r.propagateChange=null,r}return g(n,e),Object.defineProperty(n.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=c.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),n.prototype.ngOnInit=function(){var e=this;this.relationsQueryFormGroup=this.fb.group({direction:[null,[o.Validators.required]],maxLevel:[null,[]],filters:[null]}),this.relationsQueryFormGroup.valueChanges.subscribe((function(t){e.relationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},n.prototype.registerOnChange=function(e){this.propagateChange=e},n.prototype.registerOnTouched=function(e){},n.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.relationsQueryFormGroup.disable({emitEvent:!1}):this.relationsQueryFormGroup.enable({emitEvent:!1})},n.prototype.writeValue=function(e){this.relationsQueryFormGroup.reset(e,{emitEvent:!1})},n.decorators=[{type:t.Component,args:[{selector:"tb-relations-query-config",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
\n
relation.relation-filters
\n \n
\n',providers:[{provide:o.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0}]}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n.propDecorators={disabled:[{type:t.Input}],required:[{type:t.Input}]},n}(a.PageComponent);var _=function(){function e(){}return e.decorators=[{type:t.NgModule,args:[{declarations:[G,B,$],imports:[n.CommonModule,a.SharedModule,p.HomeComponentsModule],exports:[G,B,$]}]}],e}(),H=function(e){function n(t,n,r){var a,i,o=e.call(this,t)||this;o.store=t,o.translate=n,o.fb=r,o.entityDetailsTranslationsMap=E,o.entityDetailsList=[],o.searchText="",o.displayDetailsFn=o.displayDetails.bind(o);try{for(var s=y(Object.keys(M)),l=s.next();!l.done;l=s.next()){var u=l.value;o.entityDetailsList.push(M[u])}}catch(e){a={error:e}}finally{try{l&&!l.done&&(i=s.return)&&i.call(s)}finally{if(a)throw a.error}}return o}return g(n,e),n.prototype.ngOnInit=function(){var t=this;this.detailsFormControl=new o.FormControl(""),e.prototype.ngOnInit.call(this),this.filteredEntityDetails=this.detailsFormControl.valueChanges.pipe(d.startWith(""),d.map((function(e){return e||""})),d.mergeMap((function(e){return t.fetchEntityDetails(e)})),d.share())},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.searchText="",this.entityDetailsConfigForm=this.fb.group({detailsList:[e?e.detailsList:null,[o.Validators.required]],addToMetadata:[!!e&&e.addToMetadata,[]]}),this.entityDetailsConfigForm.valueChanges.subscribe((function(e){t.entityDetailsConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)})),this.detailsFormControl.patchValue("",{emitEvent:!0})},n.prototype.displayDetails=function(e){return e?this.translate.instant(E.get(e)):void 0},n.prototype.fetchEntityDetails=function(e){var t=this;if(this.searchText=e,this.searchText&&this.searchText.length){var n=this.searchText.toUpperCase();return m.of(this.entityDetailsList.filter((function(e){return t.translate.instant(E.get(M[e])).toUpperCase().includes(n)})))}return m.of(this.entityDetailsList)},n.prototype.detailsFieldSelected=function(e){this.addDetailsField(e.option.value),this.clear("")},n.prototype.removeDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;if(t){var n=t.indexOf(e);n>=0&&(t.splice(n,1),this.entityDetailsConfigForm.get("detailsList").setValue(t))}},n.prototype.addDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;t||(t=[]),-1===t.indexOf(e)&&(t.push(e),this.entityDetailsConfigForm.get("detailsList").setValue(t))},n.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.detailsInput.nativeElement.value=e,this.detailsFormControl.patchValue(null,{emitEvent:!0}),setTimeout((function(){t.detailsInput.nativeElement.blur(),t.detailsInput.nativeElement.focus()}),0)},n.prototype.validateConfig=function(){return this.entityDetailsConfigForm.valid},n.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-entity-details-config",template:'
\n \n \n \n \n \n {{entityDetailsTranslationsMap.get(details) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-entity-details-matching\n
\n
\n
\n
\n
\n \n \n {{ \'tb.rulenode.add-to-metadata\' | translate }}\n \n
tb.rulenode.add-to-metadata-hint
\n
\n',styles:[":host ::ng-deep mat-form-field.entity-fields-list .mat-form-field-wrapper{margin-bottom:-1.25em}"]}]}],n.ctorParameters=function(){return[{type:i.Store},{type:r.TranslateService},{type:o.FormBuilder}]},n.propDecorators={detailsInput:[{type:t.ViewChild,args:["detailsInput",{static:!1}]}]},n}(a.RuleNodeConfigurationComponent);var Q=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.separatorKeysCodes=[l.ENTER,l.COMMA,l.SEMICOLON],r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.deviceAttributesConfigForm=this.fb.group({deviceRelationsQuery:[e?e.deviceRelationsQuery:null,[o.Validators.required]],tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]}),this.deviceAttributesConfigForm.valueChanges.subscribe((function(e){t.validateConfig()?t.notifyConfigurationUpdated(t.deviceAttributesConfigForm.value):t.notifyConfigurationUpdated(null)}))},n.prototype.validateConfig=function(){return this.deviceAttributesConfigForm.valid},n.prototype.removeKey=function(e,t){var n=this.deviceAttributesConfigForm.get(t).value,r=n.indexOf(e);r>=0&&(n.splice(r,1),this.deviceAttributesConfigForm.get(t).setValue(n,{emitEvent:!0}))},n.prototype.addKey=function(e,t){var n=e.input,r=e.value;if((r||"").trim()){r=r.trim();var a=this.deviceAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(r)||(a||(a=[]),a.push(r),this.deviceAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}n&&(n.value="")},n.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-device-attributes-config",template:'
\n \n \n \n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var z=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.separatorKeysCodes=[l.ENTER,l.COMMA,l.SEMICOLON],r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.originatorAttributesConfigForm=this.fb.group({tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]}),this.originatorAttributesConfigForm.valueChanges.subscribe((function(e){t.validateConfig()?t.notifyConfigurationUpdated(t.originatorAttributesConfigForm.value):t.notifyConfigurationUpdated(null)}))},n.prototype.validateConfig=function(){return this.originatorAttributesConfigForm.valid},n.prototype.removeKey=function(e,t){var n=this.originatorAttributesConfigForm.get(t).value,r=n.indexOf(e);r>=0&&(n.splice(r,1),this.originatorAttributesConfigForm.get(t).setValue(n,{emitEvent:!0}))},n.prototype.addKey=function(e,t){var n=e.input,r=e.value;if((r||"").trim()){r=r.trim();var a=this.originatorAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(r)||(a||(a=[]),a.push(r),this.originatorAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}n&&(n.value="")},n.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-originator-attributes-config",template:'
\n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var W=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.originatorFieldsConfigForm=this.fb.group({fieldsMapping:[e?e.fieldsMapping:null,[o.Validators.required]]}),this.originatorFieldsConfigForm.valueChanges.subscribe((function(e){t.originatorFieldsConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.validateConfig=function(){return this.originatorFieldsConfigForm.valid},n.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-originator-fields-config",template:'
\n \n \n \n
\n'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var Y=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.separatorKeysCodes=[l.ENTER,l.COMMA,l.SEMICOLON],r.fetchMode=V,r.fetchModes=Object.keys(V),r.samplingOrders=Object.keys(O),r.timeUnits=Object.keys(N),r.timeUnitsTranslationMap=S,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.getTelemetryFromDatabaseConfigForm=this.fb.group({latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],fetchMode:[e?e.fetchMode:null,[o.Validators.required]],orderBy:[e?e.orderBy:null,[]],limit:[e?e.limit:null,[]],useMetadataIntervalPatterns:[!!e&&e.useMetadataIntervalPatterns,[]],startInterval:[e?e.startInterval:null,[]],startIntervalTimeUnit:[e?e.startIntervalTimeUnit:null,[]],endInterval:[e?e.endInterval:null,[]],endIntervalTimeUnit:[e?e.endIntervalTimeUnit:null,[]],startIntervalPattern:[e?e.startIntervalPattern:null,[]],endIntervalPattern:[e?e.endIntervalPattern:null,[]]}),this.updateValidators(!1),this.getTelemetryFromDatabaseConfigForm.get("fetchMode").valueChanges.subscribe((function(){t.updateValidators(!0)})),this.getTelemetryFromDatabaseConfigForm.get("useMetadataIntervalPatterns").valueChanges.subscribe((function(){t.updateValidators(!0)})),this.getTelemetryFromDatabaseConfigForm.valueChanges.subscribe((function(e){t.validateConfig()?t.notifyConfigurationUpdated(t.getTelemetryFromDatabaseConfigForm.value):t.notifyConfigurationUpdated(null)}))},n.prototype.updateValidators=function(e){var t=this.getTelemetryFromDatabaseConfigForm.get("fetchMode").value,n=this.getTelemetryFromDatabaseConfigForm.get("useMetadataIntervalPatterns").value;t&&t===V.ALL?(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([o.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([o.Validators.required,o.Validators.min(2),o.Validators.max(1e3)])):(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([])),n?(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([o.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([o.Validators.required])):(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([o.Validators.required,o.Validators.min(1),o.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([o.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([o.Validators.required,o.Validators.min(1),o.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([o.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([])),this.getTelemetryFromDatabaseConfigForm.get("orderBy").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("limit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").updateValueAndValidity({emitEvent:e})},n.prototype.validateConfig=function(){return this.getTelemetryFromDatabaseConfigForm.valid},n.prototype.removeKey=function(e,t){var n=this.getTelemetryFromDatabaseConfigForm.get(t).value,r=n.indexOf(e);r>=0&&(n.splice(r,1),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(n,{emitEvent:!0}))},n.prototype.addKey=function(e,t){var n=e.input,r=e.value;if((r||"").trim()){r=r.trim();var a=this.getTelemetryFromDatabaseConfigForm.get(t).value;a&&-1!==a.indexOf(r)||(a||(a=[]),a.push(r),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(a,{emitEvent:!0}))}n&&(n.value="")},n.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-get-telemetry-from-database",template:'
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n tb.rulenode.fetch-mode\n \n \n {{ mode }}\n \n \n tb.rulenode.fetch-mode-hint\n \n
\n \n tb.rulenode.order-by\n \n \n {{ order }}\n \n \n tb.rulenode.order-by-hint\n \n \n tb.rulenode.limit\n \n tb.rulenode.limit-hint\n \n
\n \n {{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-interval-patterns-hint
\n
\n
\n \n tb.rulenode.start-interval\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.start-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.end-interval\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.end-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n \n tb.rulenode.start-interval-pattern\n \n \n {{ \'tb.rulenode.start-interval-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.end-interval-pattern\n \n \n {{ \'tb.rulenode.end-interval-pattern-required\' | translate }}\n \n \n \n \n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var Z=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.relatedAttributesConfigForm=this.fb.group({relationsQuery:[!!e&&e.relationsQuery,[o.Validators.required]],telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[o.Validators.required]]}),this.relatedAttributesConfigForm.valueChanges.subscribe((function(e){t.relatedAttributesConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.validateConfig=function(){return this.relatedAttributesConfigForm.valid},n.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-related-attributes-config",template:'
\n \n \n \n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var J=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.tenantAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[o.Validators.required]]}),this.tenantAttributesConfigForm.valueChanges.subscribe((function(e){t.tenantAttributesConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.validateConfig=function(){return this.tenantAttributesConfigForm.valid},n.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-tenant-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var X=function(){function e(){}return e.decorators=[{type:t.NgModule,args:[{declarations:[j,H,Q,z,W,Y,Z,J],imports:[n.CommonModule,a.SharedModule,_],exports:[j,H,Q,z,W,Y,Z,J]}]}],e}(),ee=function(){function e(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-name-pattern-hint":"Name pattern, use ${metaKeyName} to substitute variables from metadata","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-type-pattern-hint":"Type pattern, use ${metaKeyName} to substitute variables from metadata","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-name-pattern-hint":"Customer name pattern, use ${metaKeyName} to substitute variables from metadata","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.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.",limit:"Limit","limit-hint":"Min limit value is 2, max - 1000. In case you want to fetch a single entry, select fetch mode 'FIRST' or 'LAST'.","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","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","latest-timeseries":"Latest timeseries","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-hint":"Relation type pattern, use ${metaKeyName} to substitute variables from metadata","relation-type-pattern-required":"Relation type pattern is required","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","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","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 metadata period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds metadata pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","period-in-seconds-pattern-hint":"Period in seconds pattern, use ${metaKeyName} to substitute variables from metadata","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",propagate:"Propagate",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","from-template-hint":"From address template, use ${metaKeyName} to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use ${metaKeyName} to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use ${metaKeyName} to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use ${metaKeyName} to substitute variables from metadata","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","endpoint-url-pattern-hint":"HTTP URL address pattern, use ${metaKeyName} to substitute variables from metadata","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory",headers:"Headers","headers-hint":"Use ${metaKeyName} in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use ${metaKeyName} to substitute variables from metadata","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","topic-arn-pattern-hint":"Topic ARN pattern, use ${metaKeyName} to substitute variables from metadata","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","queue-url-pattern-hint":"Queue URL pattern, use ${metaKeyName} to substitute variables from metadata","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 ${metaKeyName} in name/value fields to substitute variables from metadata","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","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","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"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-interval-patterns":"Use metadata interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","check-all-keys":"Check that all selected keys 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-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","start-interval-pattern-hint":"Start interval pattern, use ${metaKeyName} to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use ${metaKeyName} to substitute variables from metadata","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","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":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","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","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","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":"You should 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-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 Latest telemetry with Timestamp","get-latest-value-with-ts-hint":'If selected, latest telemetry values will be added to the outbound message metadata with timestamp, e.g: "temp": "{\\"ts\\":1574329385897,\\"value\\":42}"'},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}},!0)}(e)}return e.decorators=[{type:t.NgModule,args:[{declarations:[h],imports:[n.CommonModule,a.SharedModule],exports:[T,K,X,h]}]}],e.ctorParameters=function(){return[{type:r.TranslateService}]},e}();e.RuleNodeCoreConfigModule=ee,e.default=ee,e.ɵa=h,e.ɵb=T,e.ɵba=$,e.ɵc=b,e.ɵd=v,e.ɵe=C,e.ɵf=F,e.ɵg=K,e.ɵh=x,e.ɵi=I,e.ɵj=w,e.ɵk=D,e.ɵl=R,e.ɵm=U,e.ɵn=P,e.ɵo=X,e.ɵp=j,e.ɵq=H,e.ɵr=Q,e.ɵs=z,e.ɵt=W,e.ɵu=Y,e.ɵv=Z,e.ɵw=J,e.ɵx=_,e.ɵy=G,e.ɵz=B,Object.defineProperty(e,"__esModule",{value:!0})})); //# sourceMappingURL=rulenode-core-config.umd.min.js.map \ No newline at end of file diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/link-labels.conponent.ts b/ui-ngx/src/app/modules/home/pages/rulechain/link-labels.component.ts similarity index 100% rename from ui-ngx/src/app/modules/home/pages/rulechain/link-labels.conponent.ts rename to ui-ngx/src/app/modules/home/pages/rulechain/link-labels.component.ts diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.scss b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.scss new file mode 100644 index 0000000000..6945764733 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.scss @@ -0,0 +1,21 @@ +/** + * Copyright © 2016-2019 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 { + form { + overflow-x: hidden !important; + } +} 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 10f80c6dd1..e87fde4b45 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 @@ -30,7 +30,7 @@ import { RuleNodeConfigComponent } from './rule-node-config.component'; @Component({ selector: 'tb-rule-node', templateUrl: './rule-node-details.component.html', - styleUrls: [] + styleUrls: ['./rule-node-details.component.scss'] }) export class RuleNodeDetailsComponent extends PageComponent implements OnInit, OnChanges { 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 8e86f155ea..b62a9d1afc 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 @@ -95,7 +95,10 @@ position="end"> = { mode: `ace/mode/${mode}`, - theme: 'ace/theme/github', showGutter: true, showPrintMargin: false, readOnly: this.readonly diff --git a/ui-ngx/src/app/shared/components/json-object-edit.component.ts b/ui-ngx/src/app/shared/components/json-object-edit.component.ts index 1e1a4ec6a9..7ff6d02beb 100644 --- a/ui-ngx/src/app/shared/components/json-object-edit.component.ts +++ b/ui-ngx/src/app/shared/components/json-object-edit.component.ts @@ -106,7 +106,6 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va const editorElement = this.jsonEditorElmRef.nativeElement; let editorOptions: Partial = { mode: 'ace/mode/json', - theme: 'ace/theme/github', showGutter: true, showPrintMargin: false, readOnly: this.readonly diff --git a/ui-ngx/src/app/shared/models/entity-type.models.ts b/ui-ngx/src/app/shared/models/entity-type.models.ts index 598fa983a1..1baacb7d39 100644 --- a/ui-ngx/src/app/shared/models/entity-type.models.ts +++ b/ui-ngx/src/app/shared/models/entity-type.models.ts @@ -151,6 +151,15 @@ export const entityTypeTranslations = new Map Date: Tue, 14 Jan 2020 19:51:31 +0200 Subject: [PATCH 067/133] UI: Rule nodes configuration --- .../resources/public/static/rulenode/rulenode-core-config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js index 69600384cb..1d40921e0d 100644 --- a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js +++ b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js @@ -1,4 +1,4 @@ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@angular/core"),require("@angular/common"),require("@ngx-translate/core"),require("@shared/public-api"),require("@ngrx/store"),require("@angular/forms"),require("@core/public-api"),require("@angular/cdk/keycodes"),require("@angular/material"),require("rxjs"),require("rxjs/operators"),require("@home/components/public-api"),require("@angular/cdk/coercion")):"function"==typeof define&&define.amd?define("rulenode-core-config",["exports","@angular/core","@angular/common","@ngx-translate/core","@shared/public-api","@ngrx/store","@angular/forms","@core/public-api","@angular/cdk/keycodes","@angular/material","rxjs","rxjs/operators","@home/components/public-api","@angular/cdk/coercion"],t):t((e=e||self)["rulenode-core-config"]={},e.ng.core,e.ng.common,e["ngx-translate"],e.shared,e["ngrx-store"],e.ng.forms,e.core,e.ng.cdk.keycodes,e.ng.material,e.rxjs,e.rxjs.operators,e.publicApi$2,e.ng.cdk.coercion)}(this,(function(e,t,n,r,a,i,o,s,l,u,m,d,p,c){"use strict"; +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@angular/core"),require("@angular/common"),require("@ngx-translate/core"),require("@shared/public-api"),require("@ngrx/store"),require("@angular/forms"),require("@core/public-api"),require("@angular/cdk/keycodes"),require("@angular/material"),require("rxjs"),require("rxjs/operators"),require("@home/components/public-api"),require("@angular/cdk/coercion")):"function"==typeof define&&define.amd?define("rulenode-core-config",["exports","@angular/core","@angular/common","@ngx-translate/core","@shared/public-api","@ngrx/store","@angular/forms","@core/public-api","@angular/cdk/keycodes","@angular/material","rxjs","rxjs/operators","@home/components/public-api","@angular/cdk/coercion"],t):t((e=e||self)["rulenode-core-config"]={},e.ng.core,e.ng.common,e["ngx-translate"],e.shared,e["ngrx-store"],e.ng.forms,e.core,e.ng.cdk.keycodes,e.ng.material,e.rxjs,e.rxjs.operators,e.publicApi$2,e.ng.cdk.coercion)}(this,(function(e,t,n,r,a,i,o,s,l,m,u,d,p,c){"use strict"; /*! ***************************************************************************** Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use @@ -12,5 +12,5 @@ See the Apache Version 2.0 License for specific language governing permissions and limitations under the License. - ***************************************************************************** */var f=function(e,t){return(f=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])})(e,t)};function g(e,t){function n(){this.constructor=e}f(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}function y(e){var t="function"==typeof Symbol&&e[Symbol.iterator],n=0;return t?t.call(e):{next:function(){return e&&n>=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}}}var h=function(e){function n(t){var n=e.call(this,t)||this;return n.store=t,n}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.onConfigurationSet=function(e){},n.decorators=[{type:t.Component,args:[{selector:"tb-node-empty-config",template:"
"}]}],n.ctorParameters=function(){return[{type:i.Store}]},n}(a.RuleNodeConfigurationComponent);var b=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.attributeScopes=Object.keys(a.AttributeScope),r.telemetryTypeTranslationsMap=a.telemetryTypeTranslations,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.onConfigurationSet=function(e){var t=this;this.attributesConfigForm=this.fb.group({scope:[e?e.scope:null,[o.Validators.required]]}),this.attributesConfigForm.valueChanges.subscribe((function(e){t.attributesConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.decorators=[{type:t.Component,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.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var v=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.onConfigurationSet=function(e){var t=this;this.timeseriesConfigForm=this.fb.group({defaultTTL:[e?e.defaultTTL:null,[o.Validators.required,o.Validators.min(0)]]}),this.timeseriesConfigForm.valueChanges.subscribe((function(e){t.timeseriesConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.decorators=[{type:t.Component,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'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var C=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.onConfigurationSet=function(e){var t=this;this.rpcRequestConfigForm=this.fb.group({timeoutInSeconds:[e?e.timeoutInSeconds:null,[o.Validators.required,o.Validators.min(0)]]}),this.rpcRequestConfigForm.valueChanges.subscribe((function(e){t.rpcRequestConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.decorators=[{type:t.Component,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'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var F=function(e){function n(t,n,r,a){var i=e.call(this,t)||this;return i.store=t,i.fb=n,i.nodeScriptTestService=r,i.translate=a,i}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.onConfigurationSet=function(e){var t=this;this.logConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[o.Validators.required]]}),this.logConfigForm.valueChanges.subscribe((function(e){t.logConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.testScript=function(){var e=this,t=this.logConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"string",this.translate.instant("tb.rulenode.to-string"),"ToString",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.logConfigForm.get("jsScript").setValue(t)}))},n.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},n.decorators=[{type:t.Component,args:[{selector:"tb-action-node-log-config",template:'
\n \n \n \n
\n \n
\n
\n'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder},{type:s.NodeScriptTestService},{type:r.TranslateService}]},n.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},n}(a.RuleNodeConfigurationComponent);var T=function(){function e(){}return e.decorators=[{type:t.NgModule,args:[{declarations:[b,v,C,F],imports:[n.CommonModule,a.SharedModule],exports:[b,v,C,F]}]}],e}(),x=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.separatorKeysCodes=[l.ENTER,l.COMMA,l.SEMICOLON],r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.checkMessageConfigForm=this.fb.group({messageNames:[e?e.messageNames:null,[]],metadataNames:[e?e.metadataNames:null,[]],checkAllKeys:[!!e&&e.checkAllKeys,[]]}),this.checkMessageConfigForm.valueChanges.subscribe((function(e){t.validateConfig()?t.notifyConfigurationUpdated(t.checkMessageConfigForm.value):t.notifyConfigurationUpdated(null)}))},n.prototype.validateConfig=function(){var e=this.checkMessageConfigForm.get("messageNames").value,t=this.checkMessageConfigForm.get("metadataNames").value;return e.length>0||t.length>0},n.prototype.removeMessageName=function(e){var t=this.checkMessageConfigForm.get("messageNames").value,n=t.indexOf(e);n>=0&&(t.splice(n,1),this.checkMessageConfigForm.get("messageNames").setValue(t,{emitEvent:!0}))},n.prototype.removeMetadataName=function(e){var t=this.checkMessageConfigForm.get("metadataNames").value,n=t.indexOf(e);n>=0&&(t.splice(n,1),this.checkMessageConfigForm.get("metadataNames").setValue(t,{emitEvent:!0}))},n.prototype.addMessageName=function(e){var t=e.input,n=e.value;if((n||"").trim()){n=n.trim();var r=this.checkMessageConfigForm.get("messageNames").value;r&&-1!==r.indexOf(n)||(r||(r=[]),r.push(n),this.checkMessageConfigForm.get("messageNames").setValue(r,{emitEvent:!0}))}t&&(t.value="")},n.prototype.addMetadataName=function(e){var t=e.input,n=e.value;if((n||"").trim()){n=n.trim();var r=this.checkMessageConfigForm.get("metadataNames").value;r&&-1!==r.indexOf(n)||(r||(r=[]),r.push(n),this.checkMessageConfigForm.get("metadataNames").setValue(r,{emitEvent:!0}))}t&&(t.value="")},n.decorators=[{type:t.Component,args:[{selector:"tb-filter-node-check-message-config",template:'
\n \n \n \n \n \n {{messageName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n \n \n \n \n {{metadataName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\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:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var I=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.entitySearchDirection=Object.keys(a.EntitySearchDirection),r.entitySearchDirectionTranslationsMap=a.entitySearchDirectionTranslations,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.checkRelationConfigForm=this.fb.group({checkForSingleEntity:[!!e&&e.checkForSingleEntity,[]],direction:[e?e.direction:null,[]],entityType:[e?e.entityType:null,e&&e.checkForSingleEntity?[o.Validators.required]:[]],entityId:[e?e.entityId:null,e&&e.checkForSingleEntity?[o.Validators.required]:[]],relationType:[e?e.relationType:null,[o.Validators.required]]}),this.checkRelationConfigForm.get("checkForSingleEntity").valueChanges.subscribe((function(e){t.checkRelationConfigForm.get("entityType").setValidators(e?[o.Validators.required]:[]),t.checkRelationConfigForm.get("entityType").updateValueAndValidity(),t.checkRelationConfigForm.get("entityId").setValidators(e?[o.Validators.required]:[]),t.checkRelationConfigForm.get("entityId").updateValueAndValidity()})),this.checkRelationConfigForm.valueChanges.subscribe((function(e){t.validateConfig()?t.notifyConfigurationUpdated(t.checkRelationConfigForm.value):t.notifyConfigurationUpdated(null)}))},n.prototype.validateConfig=function(){return this.checkRelationConfigForm.valid},n.decorators=[{type:t.Component,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'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var q={CIRCLE:"CIRCLE",POLYGON:"POLYGON"},k=new Map([[q.CIRCLE,"tb.rulenode.perimeter-circle"],[q.POLYGON,"tb.rulenode.perimeter-polygon"]]),N={MILLISECONDS:"MILLISECONDS",SECONDS:"SECONDS",MINUTES:"MINUTES",HOURS:"HOURS",DAYS:"DAYS"},S=new Map([[N.MILLISECONDS,"tb.rulenode.time-unit-milliseconds"],[N.SECONDS,"tb.rulenode.time-unit-seconds"],[N.MINUTES,"tb.rulenode.time-unit-minutes"],[N.HOURS,"tb.rulenode.time-unit-hours"],[N.DAYS,"tb.rulenode.time-unit-days"]]),A={METER:"METER",KILOMETER:"KILOMETER",FOOT:"FOOT",MILE:"MILE",NAUTICAL_MILE:"NAUTICAL_MILE"},L=new Map([[A.METER,"tb.rulenode.range-unit-meter"],[A.KILOMETER,"tb.rulenode.range-unit-kilometer"],[A.FOOT,"tb.rulenode.range-unit-foot"],[A.MILE,"tb.rulenode.range-unit-mile"],[A.NAUTICAL_MILE,"tb.rulenode.range-unit-nautical-mile"]]),M={TITLE:"TITLE",COUNTRY:"COUNTRY",STATE:"STATE",ZIP:"ZIP",ADDRESS:"ADDRESS",ADDRESS2:"ADDRESS2",PHONE:"PHONE",EMAIL:"EMAIL",ADDITIONAL_INFO:"ADDITIONAL_INFO"},E=new Map([[M.TITLE,"tb.rulenode.entity-details-title"],[M.COUNTRY,"tb.rulenode.entity-details-country"],[M.STATE,"tb.rulenode.entity-details-state"],[M.ZIP,"tb.rulenode.entity-details-zip"],[M.ADDRESS,"tb.rulenode.entity-details-address"],[M.ADDRESS2,"tb.rulenode.entity-details-address2"],[M.PHONE,"tb.rulenode.entity-details-phone"],[M.EMAIL,"tb.rulenode.entity-details-email"],[M.ADDITIONAL_INFO,"tb.rulenode.entity-details-additional_info"]]),V={FIRST:"FIRST",LAST:"LAST",ALL:"ALL"},O={ASC:"ASC",DESC:"DESC"},w=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.perimeterType=q,r.perimeterTypes=Object.keys(q),r.perimeterTypeTranslationMap=k,r.rangeUnits=Object.keys(A),r.rangeUnitTranslationMap=L,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.geoFilterConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[o.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[o.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType: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,[]]}),this.updateValidators(!1),this.geoFilterConfigForm.get("fetchPerimeterInfoFromMessageMetadata").valueChanges.subscribe((function(){t.updateValidators(!0)})),this.geoFilterConfigForm.get("perimeterType").valueChanges.subscribe((function(){t.updateValidators(!0)})),this.geoFilterConfigForm.valueChanges.subscribe((function(e){t.validateConfig()?t.notifyConfigurationUpdated(t.geoFilterConfigForm.value):t.notifyConfigurationUpdated(null)}))},n.prototype.updateValidators=function(e){var t=this.geoFilterConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,n=this.geoFilterConfigForm.get("perimeterType").value;t?this.geoFilterConfigForm.get("perimeterType").setValidators([]):this.geoFilterConfigForm.get("perimeterType").setValidators([o.Validators.required]),t||n!==q.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([o.Validators.required]),this.geoFilterConfigForm.get("centerLongitude").setValidators([o.Validators.required]),this.geoFilterConfigForm.get("range").setValidators([o.Validators.required]),this.geoFilterConfigForm.get("rangeUnit").setValidators([o.Validators.required])),t||n!==q.POLYGON?this.geoFilterConfigForm.get("polygonsDefinition").setValidators([]):this.geoFilterConfigForm.get("polygonsDefinition").setValidators([o.Validators.required]),this.geoFilterConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),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})},n.prototype.validateConfig=function(){return this.geoFilterConfigForm.valid},n.decorators=[{type:t.Component,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.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \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'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var D=function(e){function n(t,n,r,i){var o,s,u=e.call(this,t)||this;u.store=t,u.translate=n,u.truncate=r,u.fb=i,u.separatorKeysCodes=[l.ENTER,l.COMMA,l.SEMICOLON],u.messageTypes=[],u.messageTypesList=[],u.searchText="",u.messageTypeConfigForm=u.fb.group({messageType:[null]});try{for(var m=y(Object.keys(a.MessageType)),d=m.next();!d.done;d=m.next()){var p=d.value;u.messageTypesList.push({name:a.messageTypeNames.get(a.MessageType[p]),value:p})}}catch(e){o={error:e}}finally{try{d&&!d.done&&(s=m.return)&&s.call(m)}finally{if(o)throw o.error}}return u}return g(n,e),n.prototype.ngOnInit=function(){var t=this;e.prototype.ngOnInit.call(this),this.filteredMessageTypes=this.messageTypeConfigForm.get("messageType").valueChanges.pipe(d.startWith(""),d.map((function(e){return e||""})),d.mergeMap((function(e){return t.fetchMessageTypes(e)})),d.share())},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.messageTypes.length||e.updateModel()}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.searchText="",this.messageTypes.length=0,e&&e.messageTypes&&e.messageTypes.forEach((function(e){var n=t.messageTypesList.find((function(t){return t.value===e}));n?t.messageTypes.push({name:n.name,value:n.value}):t.messageTypes.push({name:e,value:e})})),this.messageTypeConfigForm.get("messageType").patchValue("",{emitEvent:!0})},n.prototype.displayMessageTypeFn=function(e){return e?e.name:void 0},n.prototype.textIsNotEmpty=function(e){return!!(e&&null!=e&&e.length>0)},n.prototype.createMessageType=function(e,t){e.preventDefault(),this.transformMessageType(t)},n.prototype.add=function(e){this.transformMessageType(e.value)},n.prototype.fetchMessageTypes=function(e){if(this.searchText=e,this.searchText&&this.searchText.length){var t=this.searchText.toUpperCase();return m.of(this.messageTypesList.filter((function(e){return e.name.toUpperCase().includes(t)})))}return m.of(this.messageTypesList)},n.prototype.transformMessageType=function(e){if((e||"").trim()){var t=null,n=e.trim(),r=this.messageTypesList.find((function(e){return e.name===n}));(t=r?{name:r.name,value:r.value}:{name:n,value:n})&&this.addMessageType(t)}this.clear("")},n.prototype.remove=function(e){var t=this.messageTypes.indexOf(e);t>=0&&(this.messageTypes.splice(t,1),this.updateModel())},n.prototype.selected=function(e){this.addMessageType(e.option.value),this.clear("")},n.prototype.addMessageType=function(e){-1===this.messageTypes.findIndex((function(t){return t.value===e.value}))&&(this.messageTypes.push(e),this.updateModel())},n.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.messageTypeInput.nativeElement.value=e,this.messageTypeConfigForm.get("messageType").patchValue(null,{emitEvent:!0}),setTimeout((function(){t.messageTypeInput.nativeElement.blur(),t.messageTypeInput.nativeElement.focus()}),0)},n.prototype.updateModel=function(){if(this.messageTypes.length){this.chipList.errorState=!1;var e={messageTypes:this.messageTypes.map((function(e){return e.value}))};this.notifyConfigurationUpdated(e)}else this.chipList.errorState=!0,this.notifyConfigurationUpdated(null)},n.decorators=[{type:t.Component,args:[{selector:"tb-filter-node-message-type-config",template:'\n tb.rulenode.message-types-filter\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 {{ translate.get(\'tb.rulenode.no-message-type-matching\',\n {messageType: truncate.transform(searchText, true, 6, '...')}) | async }}\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'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:r.TranslateService},{type:a.TruncatePipe},{type:o.FormBuilder}]},n.propDecorators={chipList:[{type:t.ViewChild,args:["chipList",{static:!1}]}],matAutocomplete:[{type:t.ViewChild,args:["messageTypeAutocomplete",{static:!1}]}],messageTypeInput:[{type:t.ViewChild,args:["messageTypeInput",{static:!1}]}]},n}(a.RuleNodeConfigurationComponent);var R=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.allowedEntityTypes=[a.EntityType.DEVICE,a.EntityType.ASSET,a.EntityType.ENTITY_VIEW,a.EntityType.TENANT,a.EntityType.CUSTOMER,a.EntityType.USER,a.EntityType.DASHBOARD,a.EntityType.RULE_CHAIN,a.EntityType.RULE_NODE],r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.originatorTypeConfigForm=this.fb.group({originatorTypes:[e?e.originatorTypes:null,[o.Validators.required]]}),this.originatorTypeConfigForm.valueChanges.subscribe((function(e){t.originatorTypeConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.validateConfig=function(){return this.originatorTypeConfigForm.valid},n.decorators=[{type:t.Component,args:[{selector:"tb-filter-node-originator-type-config",template:'
\n \n \n \n
\n',styles:[":host ::ng-deep tb-entity-type-list .mat-form-field-flex{padding-top:0}:host ::ng-deep tb-entity-type-list .mat-form-field-infix{border-top:0}"]}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var U=function(e){function n(t,n,r,a){var i=e.call(this,t)||this;return i.store=t,i.fb=n,i.nodeScriptTestService=r,i.translate=a,i}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.onConfigurationSet=function(e){var t=this;this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[o.Validators.required]]}),this.scriptConfigForm.valueChanges.subscribe((function(e){t.scriptConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"filter",this.translate.instant("tb.rulenode.filter"),"Filter",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},n.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},n.decorators=[{type:t.Component,args:[{selector:"tb-filter-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder},{type:s.NodeScriptTestService},{type:r.TranslateService}]},n.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},n}(a.RuleNodeConfigurationComponent);var P=function(e){function n(t,n,r,a){var i=e.call(this,t)||this;return i.store=t,i.fb=n,i.nodeScriptTestService=r,i.translate=a,i}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.onConfigurationSet=function(e){var t=this;this.switchConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[o.Validators.required]]}),this.switchConfigForm.valueChanges.subscribe((function(e){t.switchConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.testScript=function(){var e=this,t=this.switchConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"switch",this.translate.instant("tb.rulenode.switch"),"Switch",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.switchConfigForm.get("jsScript").setValue(t)}))},n.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},n.decorators=[{type:t.Component,args:[{selector:"tb-filter-node-switch-config",template:'
\n \n \n \n
\n \n
\n
\n'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder},{type:s.NodeScriptTestService},{type:r.TranslateService}]},n.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},n}(a.RuleNodeConfigurationComponent);var K=function(){function e(){}return e.decorators=[{type:t.NgModule,args:[{declarations:[x,I,w,D,R,U,P],imports:[n.CommonModule,a.SharedModule],exports:[x,I,w,D,R,U,P]}]}],e}(),j=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.customerAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[o.Validators.required]]}),this.customerAttributesConfigForm.valueChanges.subscribe((function(e){t.customerAttributesConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.validateConfig=function(){return this.customerAttributesConfigForm.valid},n.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-customer-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var G=function(e){function n(t,n,r,a){var i=e.call(this,t)||this;return i.store=t,i.translate=n,i.injector=r,i.fb=a,i.propagateChange=null,i.valueChangeSubscription=null,i}return g(n,e),Object.defineProperty(n.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=c.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),n.prototype.ngOnInit=function(){this.ngControl=this.injector.get(o.NgControl),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.kvListFormGroup=this.fb.group({}),this.kvListFormGroup.addControl("keyVals",this.fb.array([]))},n.prototype.keyValsFormArray=function(){return this.kvListFormGroup.get("keyVals")},n.prototype.registerOnChange=function(e){this.propagateChange=e},n.prototype.registerOnTouched=function(e){},n.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.kvListFormGroup.disable({emitEvent:!1}):this.kvListFormGroup.enable({emitEvent:!1})},n.prototype.writeValue=function(e){var t,n,r=this;this.valueChangeSubscription&&this.valueChangeSubscription.unsubscribe();var a=[];if(e)try{for(var i=y(Object.keys(e)),s=i.next();!s.done;s=i.next()){var l=s.value;Object.prototype.hasOwnProperty.call(e,l)&&a.push(this.fb.group({key:[l,[o.Validators.required]],value:[e[l],[o.Validators.required]]}))}}catch(e){t={error:e}}finally{try{s&&!s.done&&(n=i.return)&&n.call(i)}finally{if(t)throw t.error}}this.kvListFormGroup.setControl("keyVals",this.fb.array(a)),this.valueChangeSubscription=this.kvListFormGroup.valueChanges.subscribe((function(){r.updateModel()}))},n.prototype.removeKeyVal=function(e){this.kvListFormGroup.get("keyVals").removeAt(e)},n.prototype.addKeyVal=function(){this.kvListFormGroup.get("keyVals").push(this.fb.group({key:["",[o.Validators.required]],value:["",[o.Validators.required]]}))},n.prototype.validate=function(e){var t=!0;return this.kvListFormGroup.get("keyVals").value.length||(t=!this.required),t?null:{kvMap:{valid:!1}}},n.prototype.updateModel=function(){var e=this.kvListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.kvListFormGroup.valid)this.propagateChange(null);else{var t={};e.forEach((function(e){t[e.key]=e.value})),this.propagateChange(t)}},n.decorators=[{type:t.Component,args:[{selector:"tb-kv-map-config",template:'
\n
\n {{ keyText }}\n {{ valText }}\n \n
\n
\n
\n \n \n \n \n {{ keyRequiredText | translate }}\n \n \n \n \n \n \n {{ valRequiredText | translate }}\n \n \n \n
\n
\n \n
\n \n
\n
\n',providers:[{provide:o.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0},{provide:o.NG_VALIDATORS,useExisting:t.forwardRef((function(){return n})),multi:!0}],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:rgba(0,0,0,.54);font-size:12px;font-weight:700;white-space:nowrap}:host .tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:20px;max-height:300px;overflow:auto}:host .tb-kv-map-config .body .row{padding-top:5px;max-height:40px}:host .tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell{margin:0;max-height:40px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell .mat-form-field-infix{border-top:0}:host ::ng-deep .tb-kv-map-config .body button.mat-button{margin:0}"]}]}],n.ctorParameters=function(){return[{type:i.Store},{type:r.TranslateService},{type:t.Injector},{type:o.FormBuilder}]},n.propDecorators={disabled:[{type:t.Input}],requiredText:[{type:t.Input}],keyText:[{type:t.Input}],keyRequiredText:[{type:t.Input}],valText:[{type:t.Input}],valRequiredText:[{type:t.Input}],required:[{type:t.Input}]},n}(a.PageComponent);var B=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.directionTypes=Object.keys(a.EntitySearchDirection),r.directionTypeTranslations=a.entitySearchDirectionTranslations,r.entityType=a.EntityType,r.propagateChange=null,r}return g(n,e),Object.defineProperty(n.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=c.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),n.prototype.ngOnInit=function(){var e=this;this.deviceRelationsQueryFormGroup=this.fb.group({direction:[null,[o.Validators.required]],maxLevel:[null,[]],relationType:[null],deviceTypes:[null,[o.Validators.required]]}),this.deviceRelationsQueryFormGroup.valueChanges.subscribe((function(t){e.deviceRelationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},n.prototype.registerOnChange=function(e){this.propagateChange=e},n.prototype.registerOnTouched=function(e){},n.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.deviceRelationsQueryFormGroup.disable({emitEvent:!1}):this.deviceRelationsQueryFormGroup.enable({emitEvent:!1})},n.prototype.writeValue=function(e){this.deviceRelationsQueryFormGroup.reset(e,{emitEvent:!1})},n.decorators=[{type:t.Component,args:[{selector:"tb-device-relations-query-config",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
\n
relation.relation-type
\n \n \n
device.device-types
\n \n \n
\n',providers:[{provide:o.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0}]}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n.propDecorators={disabled:[{type:t.Input}],required:[{type:t.Input}]},n}(a.PageComponent);var $=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.directionTypes=Object.keys(a.EntitySearchDirection),r.directionTypeTranslations=a.entitySearchDirectionTranslations,r.propagateChange=null,r}return g(n,e),Object.defineProperty(n.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=c.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),n.prototype.ngOnInit=function(){var e=this;this.relationsQueryFormGroup=this.fb.group({direction:[null,[o.Validators.required]],maxLevel:[null,[]],filters:[null]}),this.relationsQueryFormGroup.valueChanges.subscribe((function(t){e.relationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},n.prototype.registerOnChange=function(e){this.propagateChange=e},n.prototype.registerOnTouched=function(e){},n.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.relationsQueryFormGroup.disable({emitEvent:!1}):this.relationsQueryFormGroup.enable({emitEvent:!1})},n.prototype.writeValue=function(e){this.relationsQueryFormGroup.reset(e,{emitEvent:!1})},n.decorators=[{type:t.Component,args:[{selector:"tb-relations-query-config",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
\n
relation.relation-filters
\n \n
\n',providers:[{provide:o.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0}]}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n.propDecorators={disabled:[{type:t.Input}],required:[{type:t.Input}]},n}(a.PageComponent);var _=function(){function e(){}return e.decorators=[{type:t.NgModule,args:[{declarations:[G,B,$],imports:[n.CommonModule,a.SharedModule,p.HomeComponentsModule],exports:[G,B,$]}]}],e}(),H=function(e){function n(t,n,r){var a,i,o=e.call(this,t)||this;o.store=t,o.translate=n,o.fb=r,o.entityDetailsTranslationsMap=E,o.entityDetailsList=[],o.searchText="",o.displayDetailsFn=o.displayDetails.bind(o);try{for(var s=y(Object.keys(M)),l=s.next();!l.done;l=s.next()){var u=l.value;o.entityDetailsList.push(M[u])}}catch(e){a={error:e}}finally{try{l&&!l.done&&(i=s.return)&&i.call(s)}finally{if(a)throw a.error}}return o}return g(n,e),n.prototype.ngOnInit=function(){var t=this;this.detailsFormControl=new o.FormControl(""),e.prototype.ngOnInit.call(this),this.filteredEntityDetails=this.detailsFormControl.valueChanges.pipe(d.startWith(""),d.map((function(e){return e||""})),d.mergeMap((function(e){return t.fetchEntityDetails(e)})),d.share())},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.searchText="",this.entityDetailsConfigForm=this.fb.group({detailsList:[e?e.detailsList:null,[o.Validators.required]],addToMetadata:[!!e&&e.addToMetadata,[]]}),this.entityDetailsConfigForm.valueChanges.subscribe((function(e){t.entityDetailsConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)})),this.detailsFormControl.patchValue("",{emitEvent:!0})},n.prototype.displayDetails=function(e){return e?this.translate.instant(E.get(e)):void 0},n.prototype.fetchEntityDetails=function(e){var t=this;if(this.searchText=e,this.searchText&&this.searchText.length){var n=this.searchText.toUpperCase();return m.of(this.entityDetailsList.filter((function(e){return t.translate.instant(E.get(M[e])).toUpperCase().includes(n)})))}return m.of(this.entityDetailsList)},n.prototype.detailsFieldSelected=function(e){this.addDetailsField(e.option.value),this.clear("")},n.prototype.removeDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;if(t){var n=t.indexOf(e);n>=0&&(t.splice(n,1),this.entityDetailsConfigForm.get("detailsList").setValue(t))}},n.prototype.addDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;t||(t=[]),-1===t.indexOf(e)&&(t.push(e),this.entityDetailsConfigForm.get("detailsList").setValue(t))},n.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.detailsInput.nativeElement.value=e,this.detailsFormControl.patchValue(null,{emitEvent:!0}),setTimeout((function(){t.detailsInput.nativeElement.blur(),t.detailsInput.nativeElement.focus()}),0)},n.prototype.validateConfig=function(){return this.entityDetailsConfigForm.valid},n.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-entity-details-config",template:'
\n \n \n \n \n \n {{entityDetailsTranslationsMap.get(details) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-entity-details-matching\n
\n
\n
\n
\n
\n \n \n {{ \'tb.rulenode.add-to-metadata\' | translate }}\n \n
tb.rulenode.add-to-metadata-hint
\n
\n',styles:[":host ::ng-deep mat-form-field.entity-fields-list .mat-form-field-wrapper{margin-bottom:-1.25em}"]}]}],n.ctorParameters=function(){return[{type:i.Store},{type:r.TranslateService},{type:o.FormBuilder}]},n.propDecorators={detailsInput:[{type:t.ViewChild,args:["detailsInput",{static:!1}]}]},n}(a.RuleNodeConfigurationComponent);var Q=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.separatorKeysCodes=[l.ENTER,l.COMMA,l.SEMICOLON],r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.deviceAttributesConfigForm=this.fb.group({deviceRelationsQuery:[e?e.deviceRelationsQuery:null,[o.Validators.required]],tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]}),this.deviceAttributesConfigForm.valueChanges.subscribe((function(e){t.validateConfig()?t.notifyConfigurationUpdated(t.deviceAttributesConfigForm.value):t.notifyConfigurationUpdated(null)}))},n.prototype.validateConfig=function(){return this.deviceAttributesConfigForm.valid},n.prototype.removeKey=function(e,t){var n=this.deviceAttributesConfigForm.get(t).value,r=n.indexOf(e);r>=0&&(n.splice(r,1),this.deviceAttributesConfigForm.get(t).setValue(n,{emitEvent:!0}))},n.prototype.addKey=function(e,t){var n=e.input,r=e.value;if((r||"").trim()){r=r.trim();var a=this.deviceAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(r)||(a||(a=[]),a.push(r),this.deviceAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}n&&(n.value="")},n.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-device-attributes-config",template:'
\n \n \n \n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var z=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.separatorKeysCodes=[l.ENTER,l.COMMA,l.SEMICOLON],r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.originatorAttributesConfigForm=this.fb.group({tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]}),this.originatorAttributesConfigForm.valueChanges.subscribe((function(e){t.validateConfig()?t.notifyConfigurationUpdated(t.originatorAttributesConfigForm.value):t.notifyConfigurationUpdated(null)}))},n.prototype.validateConfig=function(){return this.originatorAttributesConfigForm.valid},n.prototype.removeKey=function(e,t){var n=this.originatorAttributesConfigForm.get(t).value,r=n.indexOf(e);r>=0&&(n.splice(r,1),this.originatorAttributesConfigForm.get(t).setValue(n,{emitEvent:!0}))},n.prototype.addKey=function(e,t){var n=e.input,r=e.value;if((r||"").trim()){r=r.trim();var a=this.originatorAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(r)||(a||(a=[]),a.push(r),this.originatorAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}n&&(n.value="")},n.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-originator-attributes-config",template:'
\n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var W=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.originatorFieldsConfigForm=this.fb.group({fieldsMapping:[e?e.fieldsMapping:null,[o.Validators.required]]}),this.originatorFieldsConfigForm.valueChanges.subscribe((function(e){t.originatorFieldsConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.validateConfig=function(){return this.originatorFieldsConfigForm.valid},n.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-originator-fields-config",template:'
\n \n \n \n
\n'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var Y=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.separatorKeysCodes=[l.ENTER,l.COMMA,l.SEMICOLON],r.fetchMode=V,r.fetchModes=Object.keys(V),r.samplingOrders=Object.keys(O),r.timeUnits=Object.keys(N),r.timeUnitsTranslationMap=S,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.getTelemetryFromDatabaseConfigForm=this.fb.group({latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],fetchMode:[e?e.fetchMode:null,[o.Validators.required]],orderBy:[e?e.orderBy:null,[]],limit:[e?e.limit:null,[]],useMetadataIntervalPatterns:[!!e&&e.useMetadataIntervalPatterns,[]],startInterval:[e?e.startInterval:null,[]],startIntervalTimeUnit:[e?e.startIntervalTimeUnit:null,[]],endInterval:[e?e.endInterval:null,[]],endIntervalTimeUnit:[e?e.endIntervalTimeUnit:null,[]],startIntervalPattern:[e?e.startIntervalPattern:null,[]],endIntervalPattern:[e?e.endIntervalPattern:null,[]]}),this.updateValidators(!1),this.getTelemetryFromDatabaseConfigForm.get("fetchMode").valueChanges.subscribe((function(){t.updateValidators(!0)})),this.getTelemetryFromDatabaseConfigForm.get("useMetadataIntervalPatterns").valueChanges.subscribe((function(){t.updateValidators(!0)})),this.getTelemetryFromDatabaseConfigForm.valueChanges.subscribe((function(e){t.validateConfig()?t.notifyConfigurationUpdated(t.getTelemetryFromDatabaseConfigForm.value):t.notifyConfigurationUpdated(null)}))},n.prototype.updateValidators=function(e){var t=this.getTelemetryFromDatabaseConfigForm.get("fetchMode").value,n=this.getTelemetryFromDatabaseConfigForm.get("useMetadataIntervalPatterns").value;t&&t===V.ALL?(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([o.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([o.Validators.required,o.Validators.min(2),o.Validators.max(1e3)])):(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([])),n?(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([o.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([o.Validators.required])):(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([o.Validators.required,o.Validators.min(1),o.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([o.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([o.Validators.required,o.Validators.min(1),o.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([o.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([])),this.getTelemetryFromDatabaseConfigForm.get("orderBy").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("limit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").updateValueAndValidity({emitEvent:e})},n.prototype.validateConfig=function(){return this.getTelemetryFromDatabaseConfigForm.valid},n.prototype.removeKey=function(e,t){var n=this.getTelemetryFromDatabaseConfigForm.get(t).value,r=n.indexOf(e);r>=0&&(n.splice(r,1),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(n,{emitEvent:!0}))},n.prototype.addKey=function(e,t){var n=e.input,r=e.value;if((r||"").trim()){r=r.trim();var a=this.getTelemetryFromDatabaseConfigForm.get(t).value;a&&-1!==a.indexOf(r)||(a||(a=[]),a.push(r),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(a,{emitEvent:!0}))}n&&(n.value="")},n.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-get-telemetry-from-database",template:'
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n tb.rulenode.fetch-mode\n \n \n {{ mode }}\n \n \n tb.rulenode.fetch-mode-hint\n \n
\n \n tb.rulenode.order-by\n \n \n {{ order }}\n \n \n tb.rulenode.order-by-hint\n \n \n tb.rulenode.limit\n \n tb.rulenode.limit-hint\n \n
\n \n {{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-interval-patterns-hint
\n
\n
\n \n tb.rulenode.start-interval\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.start-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.end-interval\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.end-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n \n tb.rulenode.start-interval-pattern\n \n \n {{ \'tb.rulenode.start-interval-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.end-interval-pattern\n \n \n {{ \'tb.rulenode.end-interval-pattern-required\' | translate }}\n \n \n \n \n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var Z=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.relatedAttributesConfigForm=this.fb.group({relationsQuery:[!!e&&e.relationsQuery,[o.Validators.required]],telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[o.Validators.required]]}),this.relatedAttributesConfigForm.valueChanges.subscribe((function(e){t.relatedAttributesConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.validateConfig=function(){return this.relatedAttributesConfigForm.valid},n.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-related-attributes-config",template:'
\n \n \n \n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var J=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.tenantAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[o.Validators.required]]}),this.tenantAttributesConfigForm.valueChanges.subscribe((function(e){t.tenantAttributesConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.validateConfig=function(){return this.tenantAttributesConfigForm.valid},n.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-tenant-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var X=function(){function e(){}return e.decorators=[{type:t.NgModule,args:[{declarations:[j,H,Q,z,W,Y,Z,J],imports:[n.CommonModule,a.SharedModule,_],exports:[j,H,Q,z,W,Y,Z,J]}]}],e}(),ee=function(){function e(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-name-pattern-hint":"Name pattern, use ${metaKeyName} to substitute variables from metadata","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-type-pattern-hint":"Type pattern, use ${metaKeyName} to substitute variables from metadata","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-name-pattern-hint":"Customer name pattern, use ${metaKeyName} to substitute variables from metadata","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.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.",limit:"Limit","limit-hint":"Min limit value is 2, max - 1000. In case you want to fetch a single entry, select fetch mode 'FIRST' or 'LAST'.","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","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","latest-timeseries":"Latest timeseries","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-hint":"Relation type pattern, use ${metaKeyName} to substitute variables from metadata","relation-type-pattern-required":"Relation type pattern is required","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","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","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 metadata period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds metadata pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","period-in-seconds-pattern-hint":"Period in seconds pattern, use ${metaKeyName} to substitute variables from metadata","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",propagate:"Propagate",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","from-template-hint":"From address template, use ${metaKeyName} to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use ${metaKeyName} to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use ${metaKeyName} to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use ${metaKeyName} to substitute variables from metadata","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","endpoint-url-pattern-hint":"HTTP URL address pattern, use ${metaKeyName} to substitute variables from metadata","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory",headers:"Headers","headers-hint":"Use ${metaKeyName} in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use ${metaKeyName} to substitute variables from metadata","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","topic-arn-pattern-hint":"Topic ARN pattern, use ${metaKeyName} to substitute variables from metadata","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","queue-url-pattern-hint":"Queue URL pattern, use ${metaKeyName} to substitute variables from metadata","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 ${metaKeyName} in name/value fields to substitute variables from metadata","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","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","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"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-interval-patterns":"Use metadata interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","check-all-keys":"Check that all selected keys 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-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","start-interval-pattern-hint":"Start interval pattern, use ${metaKeyName} to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use ${metaKeyName} to substitute variables from metadata","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","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":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","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","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","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":"You should 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-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 Latest telemetry with Timestamp","get-latest-value-with-ts-hint":'If selected, latest telemetry values will be added to the outbound message metadata with timestamp, e.g: "temp": "{\\"ts\\":1574329385897,\\"value\\":42}"'},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}},!0)}(e)}return e.decorators=[{type:t.NgModule,args:[{declarations:[h],imports:[n.CommonModule,a.SharedModule],exports:[T,K,X,h]}]}],e.ctorParameters=function(){return[{type:r.TranslateService}]},e}();e.RuleNodeCoreConfigModule=ee,e.default=ee,e.ɵa=h,e.ɵb=T,e.ɵba=$,e.ɵc=b,e.ɵd=v,e.ɵe=C,e.ɵf=F,e.ɵg=K,e.ɵh=x,e.ɵi=I,e.ɵj=w,e.ɵk=D,e.ɵl=R,e.ɵm=U,e.ɵn=P,e.ɵo=X,e.ɵp=j,e.ɵq=H,e.ɵr=Q,e.ɵs=z,e.ɵt=W,e.ɵu=Y,e.ɵv=Z,e.ɵw=J,e.ɵx=_,e.ɵy=G,e.ɵz=B,Object.defineProperty(e,"__esModule",{value:!0})})); + ***************************************************************************** */var f=function(e,t){return(f=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])})(e,t)};function g(e,t){function n(){this.constructor=e}f(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}function y(e){var t="function"==typeof Symbol&&e[Symbol.iterator],n=0;return t?t.call(e):{next:function(){return e&&n>=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}}}var b=function(e){function n(t){var n=e.call(this,t)||this;return n.store=t,n}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.onConfigurationSet=function(e){},n.decorators=[{type:t.Component,args:[{selector:"tb-node-empty-config",template:"
"}]}],n.ctorParameters=function(){return[{type:i.Store}]},n}(a.RuleNodeConfigurationComponent);var h=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.attributeScopes=Object.keys(a.AttributeScope),r.telemetryTypeTranslationsMap=a.telemetryTypeTranslations,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.onConfigurationSet=function(e){var t=this;this.attributesConfigForm=this.fb.group({scope:[e?e.scope:null,[o.Validators.required]]}),this.attributesConfigForm.valueChanges.subscribe((function(e){t.attributesConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.decorators=[{type:t.Component,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.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var C=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.onConfigurationSet=function(e){var t=this;this.timeseriesConfigForm=this.fb.group({defaultTTL:[e?e.defaultTTL:null,[o.Validators.required,o.Validators.min(0)]]}),this.timeseriesConfigForm.valueChanges.subscribe((function(e){t.timeseriesConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.decorators=[{type:t.Component,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'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var v=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.onConfigurationSet=function(e){var t=this;this.rpcRequestConfigForm=this.fb.group({timeoutInSeconds:[e?e.timeoutInSeconds:null,[o.Validators.required,o.Validators.min(0)]]}),this.rpcRequestConfigForm.valueChanges.subscribe((function(e){t.rpcRequestConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.decorators=[{type:t.Component,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'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var F=function(e){function n(t,n,r,a){var i=e.call(this,t)||this;return i.store=t,i.fb=n,i.nodeScriptTestService=r,i.translate=a,i}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.onConfigurationSet=function(e){var t=this;this.logConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[o.Validators.required]]}),this.logConfigForm.valueChanges.subscribe((function(e){t.logConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.testScript=function(){var e=this,t=this.logConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"string",this.translate.instant("tb.rulenode.to-string"),"ToString",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.logConfigForm.get("jsScript").setValue(t)}))},n.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},n.decorators=[{type:t.Component,args:[{selector:"tb-action-node-log-config",template:'
\n \n \n \n
\n \n
\n
\n'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder},{type:s.NodeScriptTestService},{type:r.TranslateService}]},n.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},n}(a.RuleNodeConfigurationComponent);var T=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.assignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[o.Validators.required]],createCustomerIfNotExists:[!!e&&e.createCustomerIfNotExists,[]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[o.Validators.required,o.Validators.min(0)]]}),this.assignCustomerConfigForm.valueChanges.subscribe((function(e){t.assignCustomerConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.validateConfig=function(){return this.assignCustomerConfigForm.valid},n.decorators=[{type:t.Component,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 \n \n
\n'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var x=function(e){function n(t,n,r,a){var i=e.call(this,t)||this;return i.store=t,i.fb=n,i.nodeScriptTestService=r,i.translate=a,i}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.onConfigurationSet=function(e){var t=this;this.clearAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[o.Validators.required]],alarmType:[e?e.alarmType:null,[o.Validators.required]]}),this.clearAlarmConfigForm.valueChanges.subscribe((function(e){t.clearAlarmConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.testScript=function(){var e=this,t=this.clearAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.clearAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},n.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},n.decorators=[{type:t.Component,args:[{selector:"tb-action-node-clear-alarm-config",template:'
\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'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder},{type:s.NodeScriptTestService},{type:r.TranslateService}]},n.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},n}(a.RuleNodeConfigurationComponent);var I=function(e){function n(t,n,r,i){var o=e.call(this,t)||this;return o.store=t,o.fb=n,o.nodeScriptTestService=r,o.translate=i,o.alarmSeverities=Object.keys(a.AlarmSeverity),o.alarmSeverityTranslationMap=a.alarmSeverityTranslations,o.separatorKeysCodes=[l.ENTER,l.COMMA,l.SEMICOLON],o}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.createAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[o.Validators.required]],useMessageAlarmData:[!!e&&e.useMessageAlarmData,[]],alarmType:[e?e.alarmType:null,[]],severity:[e?e.severity:null,[]],propagate:[!!e&&e.propagate,[]],relationTypes:[e?e.relationTypes:null,[]]}),this.updateValidators(!1),this.createAlarmConfigForm.get("useMessageAlarmData").valueChanges.subscribe((function(){t.updateValidators(!0)})),this.createAlarmConfigForm.valueChanges.subscribe((function(e){t.validateConfig()?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.updateValidators=function(e){this.createAlarmConfigForm.get("useMessageAlarmData").value?(this.createAlarmConfigForm.get("alarmType").setValidators([]),this.createAlarmConfigForm.get("severity").setValidators([])):(this.createAlarmConfigForm.get("alarmType").setValidators([o.Validators.required]),this.createAlarmConfigForm.get("severity").setValidators([o.Validators.required])),this.createAlarmConfigForm.get("alarmType").updateValueAndValidity({emitEvent:e}),this.createAlarmConfigForm.get("severity").updateValueAndValidity({emitEvent:e})},n.prototype.validateConfig=function(){return this.createAlarmConfigForm.valid},n.prototype.testScript=function(){var e=this,t=this.createAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.createAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},n.prototype.removeKey=function(e,t){var n=this.createAlarmConfigForm.get(t).value,r=n.indexOf(e);r>=0&&(n.splice(r,1),this.createAlarmConfigForm.get(t).setValue(n,{emitEvent:!0}))},n.prototype.addKey=function(e,t){var n=e.input,r=e.value;if((r||"").trim()){r=r.trim();var a=this.createAlarmConfigForm.get(t).value;a&&-1!==a.indexOf(r)||(a||(a=[]),a.push(r),this.createAlarmConfigForm.get(t).setValue(a,{emitEvent:!0}))}n&&(n.value="")},n.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},n.decorators=[{type:t.Component,args:[{selector:"tb-action-node-create-alarm-config",template:'
\n \n \n \n
\n \n
\n \n {{ \'tb.rulenode.use-message-alarm-data\' | translate }}\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.alarm-severity\n \n \n {{ alarmSeverityTranslationMap.get(severity) | translate }}\n \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 \n \n
\n
\n
\n'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder},{type:s.NodeScriptTestService},{type:r.TranslateService}]},n.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},n}(a.RuleNodeConfigurationComponent);var q=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.directionTypes=Object.keys(a.EntitySearchDirection),r.directionTypeTranslations=a.entitySearchDirectionTranslations,r.entityType=a.EntityType,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.createRelationConfigForm=this.fb.group({direction:[e?e.direction:null,[o.Validators.required]],entityType:[e?e.entityType:null,[o.Validators.required]],entityNamePattern:[e?e.entityNamePattern:null,[]],entityTypePattern:[e?e.entityTypePattern:null,[]],relationType:[e?e.relationType:null,[o.Validators.required]],createEntityIfNotExists:[!!e&&e.createEntityIfNotExists,[]],removeCurrentRelations:[!!e&&e.removeCurrentRelations,[]],changeOriginatorToRelatedEntity:[!!e&&e.changeOriginatorToRelatedEntity,[]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[o.Validators.required,o.Validators.min(0)]]}),this.updateValidators(!1),this.createRelationConfigForm.get("entityType").valueChanges.subscribe((function(){t.updateValidators(!0)})),this.createRelationConfigForm.valueChanges.subscribe((function(e){t.validateConfig()?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.updateValidators=function(e){var t=this.createRelationConfigForm.get("entityType").value;t?this.createRelationConfigForm.get("entityNamePattern").setValidators([o.Validators.required]):this.createRelationConfigForm.get("entityNamePattern").setValidators([]),!t||t!==a.EntityType.DEVICE&&t!==a.EntityType.ASSET?this.createRelationConfigForm.get("entityTypePattern").setValidators([]):this.createRelationConfigForm.get("entityTypePattern").setValidators([o.Validators.required]),this.createRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e}),this.createRelationConfigForm.get("entityTypePattern").updateValueAndValidity({emitEvent:e})},n.prototype.validateConfig=function(){return this.createRelationConfigForm.valid},n.decorators=[{type:t.Component,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 \n \n
\n'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var S=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.msgDelayConfigForm=this.fb.group({useMetadataPeriodInSecondsPatterns:[!!e&&e.useMetadataPeriodInSecondsPatterns,[]],periodInSeconds:[e?e.periodInSeconds:null,[]],periodInSecondsPattern:[e?e.periodInSecondsPattern:null,[]],maxPendingMsgs:[e?e.maxPendingMsgs:null,[o.Validators.required,o.Validators.min(1),o.Validators.max(1e5)]]}),this.updateValidators(!1),this.msgDelayConfigForm.get("useMetadataPeriodInSecondsPatterns").valueChanges.subscribe((function(){t.updateValidators(!0)})),this.msgDelayConfigForm.valueChanges.subscribe((function(e){t.msgDelayConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.updateValidators=function(e){this.msgDelayConfigForm.get("useMetadataPeriodInSecondsPatterns").value?(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([o.Validators.required]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([])):(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([o.Validators.required,o.Validators.min(0)])),this.msgDelayConfigForm.get("periodInSecondsPattern").updateValueAndValidity({emitEvent:e}),this.msgDelayConfigForm.get("periodInSeconds").updateValueAndValidity({emitEvent:e})},n.prototype.validateConfig=function(){return this.msgDelayConfigForm.valid},n.decorators=[{type:t.Component,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'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var k=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.directionTypes=Object.keys(a.EntitySearchDirection),r.directionTypeTranslations=a.entitySearchDirectionTranslations,r.entityType=a.EntityType,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.deleteRelationConfigForm=this.fb.group({deleteForSingleEntity:[!!e&&e.deleteForSingleEntity,[]],direction:[e?e.direction:null,[o.Validators.required]],entityType:[e?e.entityType:null,[]],entityNamePattern:[e?e.entityNamePattern:null,[]],relationType:[e?e.relationType:null,[o.Validators.required]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[o.Validators.required,o.Validators.min(0)]]}),this.updateValidators(!1,!1),this.deleteRelationConfigForm.get("deleteForSingleEntity").valueChanges.subscribe((function(){t.updateValidators(!0,!0)})),this.deleteRelationConfigForm.get("entityType").valueChanges.subscribe((function(){t.updateValidators(!0,!1)})),this.deleteRelationConfigForm.valueChanges.subscribe((function(e){t.validateConfig()?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.updateValidators=function(e,t){var n=this.deleteRelationConfigForm.get("deleteForSingleEntity").value,r=this.deleteRelationConfigForm.get("entityType").value;n?this.deleteRelationConfigForm.get("entityType").setValidators([o.Validators.required]):this.deleteRelationConfigForm.get("entityType").setValidators([]),n&&r?this.deleteRelationConfigForm.get("entityNamePattern").setValidators([o.Validators.required]):this.deleteRelationConfigForm.get("entityNamePattern").setValidators([]),this.deleteRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:t}),this.deleteRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e})},n.prototype.validateConfig=function(){return this.deleteRelationConfigForm.valid},n.decorators=[{type:t.Component,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 \n \n
\n'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var N=function(){function e(){}return e.decorators=[{type:t.NgModule,args:[{declarations:[h,C,v,F,T,x,I,q,S,k],imports:[n.CommonModule,a.SharedModule],exports:[h,C,v,F,T,x,I,q,S,k]}]}],e}(),A=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.separatorKeysCodes=[l.ENTER,l.COMMA,l.SEMICOLON],r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.checkMessageConfigForm=this.fb.group({messageNames:[e?e.messageNames:null,[]],metadataNames:[e?e.metadataNames:null,[]],checkAllKeys:[!!e&&e.checkAllKeys,[]]}),this.checkMessageConfigForm.valueChanges.subscribe((function(e){t.validateConfig()?t.notifyConfigurationUpdated(t.checkMessageConfigForm.value):t.notifyConfigurationUpdated(null)}))},n.prototype.validateConfig=function(){var e=this.checkMessageConfigForm.get("messageNames").value,t=this.checkMessageConfigForm.get("metadataNames").value;return e.length>0||t.length>0},n.prototype.removeMessageName=function(e){var t=this.checkMessageConfigForm.get("messageNames").value,n=t.indexOf(e);n>=0&&(t.splice(n,1),this.checkMessageConfigForm.get("messageNames").setValue(t,{emitEvent:!0}))},n.prototype.removeMetadataName=function(e){var t=this.checkMessageConfigForm.get("metadataNames").value,n=t.indexOf(e);n>=0&&(t.splice(n,1),this.checkMessageConfigForm.get("metadataNames").setValue(t,{emitEvent:!0}))},n.prototype.addMessageName=function(e){var t=e.input,n=e.value;if((n||"").trim()){n=n.trim();var r=this.checkMessageConfigForm.get("messageNames").value;r&&-1!==r.indexOf(n)||(r||(r=[]),r.push(n),this.checkMessageConfigForm.get("messageNames").setValue(r,{emitEvent:!0}))}t&&(t.value="")},n.prototype.addMetadataName=function(e){var t=e.input,n=e.value;if((n||"").trim()){n=n.trim();var r=this.checkMessageConfigForm.get("metadataNames").value;r&&-1!==r.indexOf(n)||(r||(r=[]),r.push(n),this.checkMessageConfigForm.get("metadataNames").setValue(r,{emitEvent:!0}))}t&&(t.value="")},n.decorators=[{type:t.Component,args:[{selector:"tb-filter-node-check-message-config",template:'
\n \n \n \n \n \n {{messageName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n \n \n \n \n {{metadataName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\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:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var E=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.entitySearchDirection=Object.keys(a.EntitySearchDirection),r.entitySearchDirectionTranslationsMap=a.entitySearchDirectionTranslations,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.checkRelationConfigForm=this.fb.group({checkForSingleEntity:[!!e&&e.checkForSingleEntity,[]],direction:[e?e.direction:null,[]],entityType:[e?e.entityType:null,e&&e.checkForSingleEntity?[o.Validators.required]:[]],entityId:[e?e.entityId:null,e&&e.checkForSingleEntity?[o.Validators.required]:[]],relationType:[e?e.relationType:null,[o.Validators.required]]}),this.checkRelationConfigForm.get("checkForSingleEntity").valueChanges.subscribe((function(e){t.checkRelationConfigForm.get("entityType").setValidators(e?[o.Validators.required]:[]),t.checkRelationConfigForm.get("entityType").updateValueAndValidity(),t.checkRelationConfigForm.get("entityId").setValidators(e?[o.Validators.required]:[]),t.checkRelationConfigForm.get("entityId").updateValueAndValidity()})),this.checkRelationConfigForm.valueChanges.subscribe((function(e){t.validateConfig()?t.notifyConfigurationUpdated(t.checkRelationConfigForm.value):t.notifyConfigurationUpdated(null)}))},n.prototype.validateConfig=function(){return this.checkRelationConfigForm.valid},n.decorators=[{type:t.Component,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'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var V={CUSTOMER:"CUSTOMER",TENANT:"TENANT",RELATED:"RELATED",ALARM_ORIGINATOR:"ALARM_ORIGINATOR"},L=new Map([[V.CUSTOMER,"tb.rulenode.originator-customer"],[V.TENANT,"tb.rulenode.originator-tenant"],[V.RELATED,"tb.rulenode.originator-related"],[V.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator"]]),M={CIRCLE:"CIRCLE",POLYGON:"POLYGON"},O=new Map([[M.CIRCLE,"tb.rulenode.perimeter-circle"],[M.POLYGON,"tb.rulenode.perimeter-polygon"]]),R={MILLISECONDS:"MILLISECONDS",SECONDS:"SECONDS",MINUTES:"MINUTES",HOURS:"HOURS",DAYS:"DAYS"},D=new Map([[R.MILLISECONDS,"tb.rulenode.time-unit-milliseconds"],[R.SECONDS,"tb.rulenode.time-unit-seconds"],[R.MINUTES,"tb.rulenode.time-unit-minutes"],[R.HOURS,"tb.rulenode.time-unit-hours"],[R.DAYS,"tb.rulenode.time-unit-days"]]),w={METER:"METER",KILOMETER:"KILOMETER",FOOT:"FOOT",MILE:"MILE",NAUTICAL_MILE:"NAUTICAL_MILE"},P=new Map([[w.METER,"tb.rulenode.range-unit-meter"],[w.KILOMETER,"tb.rulenode.range-unit-kilometer"],[w.FOOT,"tb.rulenode.range-unit-foot"],[w.MILE,"tb.rulenode.range-unit-mile"],[w.NAUTICAL_MILE,"tb.rulenode.range-unit-nautical-mile"]]),U={TITLE:"TITLE",COUNTRY:"COUNTRY",STATE:"STATE",ZIP:"ZIP",ADDRESS:"ADDRESS",ADDRESS2:"ADDRESS2",PHONE:"PHONE",EMAIL:"EMAIL",ADDITIONAL_INFO:"ADDITIONAL_INFO"},K=new Map([[U.TITLE,"tb.rulenode.entity-details-title"],[U.COUNTRY,"tb.rulenode.entity-details-country"],[U.STATE,"tb.rulenode.entity-details-state"],[U.ZIP,"tb.rulenode.entity-details-zip"],[U.ADDRESS,"tb.rulenode.entity-details-address"],[U.ADDRESS2,"tb.rulenode.entity-details-address2"],[U.PHONE,"tb.rulenode.entity-details-phone"],[U.EMAIL,"tb.rulenode.entity-details-email"],[U.ADDITIONAL_INFO,"tb.rulenode.entity-details-additional_info"]]),j={FIRST:"FIRST",LAST:"LAST",ALL:"ALL"},G={ASC:"ASC",DESC:"DESC"},B=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.perimeterType=M,r.perimeterTypes=Object.keys(M),r.perimeterTypeTranslationMap=O,r.rangeUnits=Object.keys(w),r.rangeUnitTranslationMap=P,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.geoFilterConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[o.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[o.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType: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,[]]}),this.updateValidators(!1),this.geoFilterConfigForm.get("fetchPerimeterInfoFromMessageMetadata").valueChanges.subscribe((function(){t.updateValidators(!0)})),this.geoFilterConfigForm.get("perimeterType").valueChanges.subscribe((function(){t.updateValidators(!0)})),this.geoFilterConfigForm.valueChanges.subscribe((function(e){t.validateConfig()?t.notifyConfigurationUpdated(t.geoFilterConfigForm.value):t.notifyConfigurationUpdated(null)}))},n.prototype.updateValidators=function(e){var t=this.geoFilterConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,n=this.geoFilterConfigForm.get("perimeterType").value;t?this.geoFilterConfigForm.get("perimeterType").setValidators([]):this.geoFilterConfigForm.get("perimeterType").setValidators([o.Validators.required]),t||n!==M.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([o.Validators.required]),this.geoFilterConfigForm.get("centerLongitude").setValidators([o.Validators.required]),this.geoFilterConfigForm.get("range").setValidators([o.Validators.required]),this.geoFilterConfigForm.get("rangeUnit").setValidators([o.Validators.required])),t||n!==M.POLYGON?this.geoFilterConfigForm.get("polygonsDefinition").setValidators([]):this.geoFilterConfigForm.get("polygonsDefinition").setValidators([o.Validators.required]),this.geoFilterConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),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})},n.prototype.validateConfig=function(){return this.geoFilterConfigForm.valid},n.decorators=[{type:t.Component,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.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \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'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var H=function(e){function n(t,n,r,i){var o,s,m=e.call(this,t)||this;m.store=t,m.translate=n,m.truncate=r,m.fb=i,m.separatorKeysCodes=[l.ENTER,l.COMMA,l.SEMICOLON],m.messageTypes=[],m.messageTypesList=[],m.searchText="",m.messageTypeConfigForm=m.fb.group({messageType:[null]});try{for(var u=y(Object.keys(a.MessageType)),d=u.next();!d.done;d=u.next()){var p=d.value;m.messageTypesList.push({name:a.messageTypeNames.get(a.MessageType[p]),value:p})}}catch(e){o={error:e}}finally{try{d&&!d.done&&(s=u.return)&&s.call(u)}finally{if(o)throw o.error}}return m}return g(n,e),n.prototype.ngOnInit=function(){var t=this;e.prototype.ngOnInit.call(this),this.filteredMessageTypes=this.messageTypeConfigForm.get("messageType").valueChanges.pipe(d.startWith(""),d.map((function(e){return e||""})),d.mergeMap((function(e){return t.fetchMessageTypes(e)})),d.share())},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.messageTypes.length||e.updateModel()}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.searchText="",this.messageTypes.length=0,e&&e.messageTypes&&e.messageTypes.forEach((function(e){var n=t.messageTypesList.find((function(t){return t.value===e}));n?t.messageTypes.push({name:n.name,value:n.value}):t.messageTypes.push({name:e,value:e})})),this.messageTypeConfigForm.get("messageType").patchValue("",{emitEvent:!0})},n.prototype.displayMessageTypeFn=function(e){return e?e.name:void 0},n.prototype.textIsNotEmpty=function(e){return!!(e&&null!=e&&e.length>0)},n.prototype.createMessageType=function(e,t){e.preventDefault(),this.transformMessageType(t)},n.prototype.add=function(e){this.transformMessageType(e.value)},n.prototype.fetchMessageTypes=function(e){if(this.searchText=e,this.searchText&&this.searchText.length){var t=this.searchText.toUpperCase();return u.of(this.messageTypesList.filter((function(e){return e.name.toUpperCase().includes(t)})))}return u.of(this.messageTypesList)},n.prototype.transformMessageType=function(e){if((e||"").trim()){var t=null,n=e.trim(),r=this.messageTypesList.find((function(e){return e.name===n}));(t=r?{name:r.name,value:r.value}:{name:n,value:n})&&this.addMessageType(t)}this.clear("")},n.prototype.remove=function(e){var t=this.messageTypes.indexOf(e);t>=0&&(this.messageTypes.splice(t,1),this.updateModel())},n.prototype.selected=function(e){this.addMessageType(e.option.value),this.clear("")},n.prototype.addMessageType=function(e){-1===this.messageTypes.findIndex((function(t){return t.value===e.value}))&&(this.messageTypes.push(e),this.updateModel())},n.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.messageTypeInput.nativeElement.value=e,this.messageTypeConfigForm.get("messageType").patchValue(null,{emitEvent:!0}),setTimeout((function(){t.messageTypeInput.nativeElement.blur(),t.messageTypeInput.nativeElement.focus()}),0)},n.prototype.updateModel=function(){if(this.messageTypes.length){this.chipList.errorState=!1;var e={messageTypes:this.messageTypes.map((function(e){return e.value}))};this.notifyConfigurationUpdated(e)}else this.chipList.errorState=!0,this.notifyConfigurationUpdated(null)},n.decorators=[{type:t.Component,args:[{selector:"tb-filter-node-message-type-config",template:'\n tb.rulenode.message-types-filter\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 {{ translate.get(\'tb.rulenode.no-message-type-matching\',\n {messageType: truncate.transform(searchText, true, 6, '...')}) | async }}\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'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:r.TranslateService},{type:a.TruncatePipe},{type:o.FormBuilder}]},n.propDecorators={chipList:[{type:t.ViewChild,args:["chipList",{static:!1}]}],matAutocomplete:[{type:t.ViewChild,args:["messageTypeAutocomplete",{static:!1}]}],messageTypeInput:[{type:t.ViewChild,args:["messageTypeInput",{static:!1}]}]},n}(a.RuleNodeConfigurationComponent);var $=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.allowedEntityTypes=[a.EntityType.DEVICE,a.EntityType.ASSET,a.EntityType.ENTITY_VIEW,a.EntityType.TENANT,a.EntityType.CUSTOMER,a.EntityType.USER,a.EntityType.DASHBOARD,a.EntityType.RULE_CHAIN,a.EntityType.RULE_NODE],r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.originatorTypeConfigForm=this.fb.group({originatorTypes:[e?e.originatorTypes:null,[o.Validators.required]]}),this.originatorTypeConfigForm.valueChanges.subscribe((function(e){t.originatorTypeConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.validateConfig=function(){return this.originatorTypeConfigForm.valid},n.decorators=[{type:t.Component,args:[{selector:"tb-filter-node-originator-type-config",template:'
\n \n \n \n
\n',styles:[":host ::ng-deep tb-entity-type-list .mat-form-field-flex{padding-top:0}:host ::ng-deep tb-entity-type-list .mat-form-field-infix{border-top:0}"]}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var _=function(e){function n(t,n,r,a){var i=e.call(this,t)||this;return i.store=t,i.fb=n,i.nodeScriptTestService=r,i.translate=a,i}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.onConfigurationSet=function(e){var t=this;this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[o.Validators.required]]}),this.scriptConfigForm.valueChanges.subscribe((function(e){t.scriptConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"filter",this.translate.instant("tb.rulenode.filter"),"Filter",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},n.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},n.decorators=[{type:t.Component,args:[{selector:"tb-filter-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder},{type:s.NodeScriptTestService},{type:r.TranslateService}]},n.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},n}(a.RuleNodeConfigurationComponent);var Q=function(e){function n(t,n,r,a){var i=e.call(this,t)||this;return i.store=t,i.fb=n,i.nodeScriptTestService=r,i.translate=a,i}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.onConfigurationSet=function(e){var t=this;this.switchConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[o.Validators.required]]}),this.switchConfigForm.valueChanges.subscribe((function(e){t.switchConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.testScript=function(){var e=this,t=this.switchConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"switch",this.translate.instant("tb.rulenode.switch"),"Switch",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.switchConfigForm.get("jsScript").setValue(t)}))},n.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},n.decorators=[{type:t.Component,args:[{selector:"tb-filter-node-switch-config",template:'
\n \n \n \n
\n \n
\n
\n'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder},{type:s.NodeScriptTestService},{type:r.TranslateService}]},n.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},n}(a.RuleNodeConfigurationComponent);var z=function(){function e(){}return e.decorators=[{type:t.NgModule,args:[{declarations:[A,E,B,H,$,_,Q],imports:[n.CommonModule,a.SharedModule],exports:[A,E,B,H,$,_,Q]}]}],e}(),W=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.customerAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[o.Validators.required]]}),this.customerAttributesConfigForm.valueChanges.subscribe((function(e){t.customerAttributesConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.validateConfig=function(){return this.customerAttributesConfigForm.valid},n.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-customer-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var Y=function(e){function n(t,n,r,a){var i=e.call(this,t)||this;return i.store=t,i.translate=n,i.injector=r,i.fb=a,i.propagateChange=null,i.valueChangeSubscription=null,i}return g(n,e),Object.defineProperty(n.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=c.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),n.prototype.ngOnInit=function(){this.ngControl=this.injector.get(o.NgControl),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.kvListFormGroup=this.fb.group({}),this.kvListFormGroup.addControl("keyVals",this.fb.array([]))},n.prototype.keyValsFormArray=function(){return this.kvListFormGroup.get("keyVals")},n.prototype.registerOnChange=function(e){this.propagateChange=e},n.prototype.registerOnTouched=function(e){},n.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.kvListFormGroup.disable({emitEvent:!1}):this.kvListFormGroup.enable({emitEvent:!1})},n.prototype.writeValue=function(e){var t,n,r=this;this.valueChangeSubscription&&this.valueChangeSubscription.unsubscribe();var a=[];if(e)try{for(var i=y(Object.keys(e)),s=i.next();!s.done;s=i.next()){var l=s.value;Object.prototype.hasOwnProperty.call(e,l)&&a.push(this.fb.group({key:[l,[o.Validators.required]],value:[e[l],[o.Validators.required]]}))}}catch(e){t={error:e}}finally{try{s&&!s.done&&(n=i.return)&&n.call(i)}finally{if(t)throw t.error}}this.kvListFormGroup.setControl("keyVals",this.fb.array(a)),this.valueChangeSubscription=this.kvListFormGroup.valueChanges.subscribe((function(){r.updateModel()}))},n.prototype.removeKeyVal=function(e){this.kvListFormGroup.get("keyVals").removeAt(e)},n.prototype.addKeyVal=function(){this.kvListFormGroup.get("keyVals").push(this.fb.group({key:["",[o.Validators.required]],value:["",[o.Validators.required]]}))},n.prototype.validate=function(e){var t=!0;return this.kvListFormGroup.get("keyVals").value.length||(t=!this.required),t?null:{kvMap:{valid:!1}}},n.prototype.updateModel=function(){var e=this.kvListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.kvListFormGroup.valid)this.propagateChange(null);else{var t={};e.forEach((function(e){t[e.key]=e.value})),this.propagateChange(t)}},n.decorators=[{type:t.Component,args:[{selector:"tb-kv-map-config",template:'
\n
\n {{ keyText }}\n {{ valText }}\n \n
\n
\n
\n \n \n \n \n {{ keyRequiredText | translate }}\n \n \n \n \n \n \n {{ valRequiredText | translate }}\n \n \n \n
\n
\n \n
\n \n
\n
\n',providers:[{provide:o.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0},{provide:o.NG_VALIDATORS,useExisting:t.forwardRef((function(){return n})),multi:!0}],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:rgba(0,0,0,.54);font-size:12px;font-weight:700;white-space:nowrap}:host .tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:20px;max-height:300px;overflow:auto}:host .tb-kv-map-config .body .row{padding-top:5px;max-height:40px}:host .tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell{margin:0;max-height:40px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell .mat-form-field-infix{border-top:0}:host ::ng-deep .tb-kv-map-config .body button.mat-button{margin:0}"]}]}],n.ctorParameters=function(){return[{type:i.Store},{type:r.TranslateService},{type:t.Injector},{type:o.FormBuilder}]},n.propDecorators={disabled:[{type:t.Input}],requiredText:[{type:t.Input}],keyText:[{type:t.Input}],keyRequiredText:[{type:t.Input}],valText:[{type:t.Input}],valRequiredText:[{type:t.Input}],required:[{type:t.Input}]},n}(a.PageComponent);var J=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.directionTypes=Object.keys(a.EntitySearchDirection),r.directionTypeTranslations=a.entitySearchDirectionTranslations,r.entityType=a.EntityType,r.propagateChange=null,r}return g(n,e),Object.defineProperty(n.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=c.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),n.prototype.ngOnInit=function(){var e=this;this.deviceRelationsQueryFormGroup=this.fb.group({direction:[null,[o.Validators.required]],maxLevel:[null,[]],relationType:[null],deviceTypes:[null,[o.Validators.required]]}),this.deviceRelationsQueryFormGroup.valueChanges.subscribe((function(t){e.deviceRelationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},n.prototype.registerOnChange=function(e){this.propagateChange=e},n.prototype.registerOnTouched=function(e){},n.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.deviceRelationsQueryFormGroup.disable({emitEvent:!1}):this.deviceRelationsQueryFormGroup.enable({emitEvent:!1})},n.prototype.writeValue=function(e){this.deviceRelationsQueryFormGroup.reset(e,{emitEvent:!1})},n.decorators=[{type:t.Component,args:[{selector:"tb-device-relations-query-config",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
\n
relation.relation-type
\n \n \n
device.device-types
\n \n \n
\n',providers:[{provide:o.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0}]}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n.propDecorators={disabled:[{type:t.Input}],required:[{type:t.Input}]},n}(a.PageComponent);var Z=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.directionTypes=Object.keys(a.EntitySearchDirection),r.directionTypeTranslations=a.entitySearchDirectionTranslations,r.propagateChange=null,r}return g(n,e),Object.defineProperty(n.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=c.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),n.prototype.ngOnInit=function(){var e=this;this.relationsQueryFormGroup=this.fb.group({direction:[null,[o.Validators.required]],maxLevel:[null,[]],filters:[null]}),this.relationsQueryFormGroup.valueChanges.subscribe((function(t){e.relationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},n.prototype.registerOnChange=function(e){this.propagateChange=e},n.prototype.registerOnTouched=function(e){},n.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.relationsQueryFormGroup.disable({emitEvent:!1}):this.relationsQueryFormGroup.enable({emitEvent:!1})},n.prototype.writeValue=function(e){this.relationsQueryFormGroup.reset(e,{emitEvent:!1})},n.decorators=[{type:t.Component,args:[{selector:"tb-relations-query-config",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
\n
relation.relation-filters
\n \n
\n',providers:[{provide:o.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0}]}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n.propDecorators={disabled:[{type:t.Input}],required:[{type:t.Input}]},n}(a.PageComponent);var X=function(){function e(){}return e.decorators=[{type:t.NgModule,args:[{declarations:[Y,J,Z],imports:[n.CommonModule,a.SharedModule,p.HomeComponentsModule],exports:[Y,J,Z]}]}],e}(),ee=function(e){function n(t,n,r){var a,i,o=e.call(this,t)||this;o.store=t,o.translate=n,o.fb=r,o.entityDetailsTranslationsMap=K,o.entityDetailsList=[],o.searchText="",o.displayDetailsFn=o.displayDetails.bind(o);try{for(var s=y(Object.keys(U)),l=s.next();!l.done;l=s.next()){var m=l.value;o.entityDetailsList.push(U[m])}}catch(e){a={error:e}}finally{try{l&&!l.done&&(i=s.return)&&i.call(s)}finally{if(a)throw a.error}}return o}return g(n,e),n.prototype.ngOnInit=function(){var t=this;this.detailsFormControl=new o.FormControl(""),e.prototype.ngOnInit.call(this),this.filteredEntityDetails=this.detailsFormControl.valueChanges.pipe(d.startWith(""),d.map((function(e){return e||""})),d.mergeMap((function(e){return t.fetchEntityDetails(e)})),d.share())},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.searchText="",this.entityDetailsConfigForm=this.fb.group({detailsList:[e?e.detailsList:null,[o.Validators.required]],addToMetadata:[!!e&&e.addToMetadata,[]]}),this.entityDetailsConfigForm.valueChanges.subscribe((function(e){t.entityDetailsConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)})),this.detailsFormControl.patchValue("",{emitEvent:!0})},n.prototype.displayDetails=function(e){return e?this.translate.instant(K.get(e)):void 0},n.prototype.fetchEntityDetails=function(e){var t=this;if(this.searchText=e,this.searchText&&this.searchText.length){var n=this.searchText.toUpperCase();return u.of(this.entityDetailsList.filter((function(e){return t.translate.instant(K.get(U[e])).toUpperCase().includes(n)})))}return u.of(this.entityDetailsList)},n.prototype.detailsFieldSelected=function(e){this.addDetailsField(e.option.value),this.clear("")},n.prototype.removeDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;if(t){var n=t.indexOf(e);n>=0&&(t.splice(n,1),this.entityDetailsConfigForm.get("detailsList").setValue(t))}},n.prototype.addDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;t||(t=[]),-1===t.indexOf(e)&&(t.push(e),this.entityDetailsConfigForm.get("detailsList").setValue(t))},n.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.detailsInput.nativeElement.value=e,this.detailsFormControl.patchValue(null,{emitEvent:!0}),setTimeout((function(){t.detailsInput.nativeElement.blur(),t.detailsInput.nativeElement.focus()}),0)},n.prototype.validateConfig=function(){return this.entityDetailsConfigForm.valid},n.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-entity-details-config",template:'
\n \n \n \n \n \n {{entityDetailsTranslationsMap.get(details) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-entity-details-matching\n
\n
\n
\n
\n
\n \n \n {{ \'tb.rulenode.add-to-metadata\' | translate }}\n \n
tb.rulenode.add-to-metadata-hint
\n
\n',styles:[":host ::ng-deep mat-form-field.entity-fields-list .mat-form-field-wrapper{margin-bottom:-1.25em}"]}]}],n.ctorParameters=function(){return[{type:i.Store},{type:r.TranslateService},{type:o.FormBuilder}]},n.propDecorators={detailsInput:[{type:t.ViewChild,args:["detailsInput",{static:!1}]}]},n}(a.RuleNodeConfigurationComponent);var te=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.separatorKeysCodes=[l.ENTER,l.COMMA,l.SEMICOLON],r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.deviceAttributesConfigForm=this.fb.group({deviceRelationsQuery:[e?e.deviceRelationsQuery:null,[o.Validators.required]],tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]}),this.deviceAttributesConfigForm.valueChanges.subscribe((function(e){t.validateConfig()?t.notifyConfigurationUpdated(t.deviceAttributesConfigForm.value):t.notifyConfigurationUpdated(null)}))},n.prototype.validateConfig=function(){return this.deviceAttributesConfigForm.valid},n.prototype.removeKey=function(e,t){var n=this.deviceAttributesConfigForm.get(t).value,r=n.indexOf(e);r>=0&&(n.splice(r,1),this.deviceAttributesConfigForm.get(t).setValue(n,{emitEvent:!0}))},n.prototype.addKey=function(e,t){var n=e.input,r=e.value;if((r||"").trim()){r=r.trim();var a=this.deviceAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(r)||(a||(a=[]),a.push(r),this.deviceAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}n&&(n.value="")},n.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-device-attributes-config",template:'
\n \n \n \n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var ne=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.separatorKeysCodes=[l.ENTER,l.COMMA,l.SEMICOLON],r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.originatorAttributesConfigForm=this.fb.group({tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]}),this.originatorAttributesConfigForm.valueChanges.subscribe((function(e){t.validateConfig()?t.notifyConfigurationUpdated(t.originatorAttributesConfigForm.value):t.notifyConfigurationUpdated(null)}))},n.prototype.validateConfig=function(){return this.originatorAttributesConfigForm.valid},n.prototype.removeKey=function(e,t){var n=this.originatorAttributesConfigForm.get(t).value,r=n.indexOf(e);r>=0&&(n.splice(r,1),this.originatorAttributesConfigForm.get(t).setValue(n,{emitEvent:!0}))},n.prototype.addKey=function(e,t){var n=e.input,r=e.value;if((r||"").trim()){r=r.trim();var a=this.originatorAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(r)||(a||(a=[]),a.push(r),this.originatorAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}n&&(n.value="")},n.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-originator-attributes-config",template:'
\n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var re=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.originatorFieldsConfigForm=this.fb.group({fieldsMapping:[e?e.fieldsMapping:null,[o.Validators.required]]}),this.originatorFieldsConfigForm.valueChanges.subscribe((function(e){t.originatorFieldsConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.validateConfig=function(){return this.originatorFieldsConfigForm.valid},n.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-originator-fields-config",template:'
\n \n \n \n
\n'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var ae=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.separatorKeysCodes=[l.ENTER,l.COMMA,l.SEMICOLON],r.fetchMode=j,r.fetchModes=Object.keys(j),r.samplingOrders=Object.keys(G),r.timeUnits=Object.keys(R),r.timeUnitsTranslationMap=D,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.getTelemetryFromDatabaseConfigForm=this.fb.group({latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],fetchMode:[e?e.fetchMode:null,[o.Validators.required]],orderBy:[e?e.orderBy:null,[]],limit:[e?e.limit:null,[]],useMetadataIntervalPatterns:[!!e&&e.useMetadataIntervalPatterns,[]],startInterval:[e?e.startInterval:null,[]],startIntervalTimeUnit:[e?e.startIntervalTimeUnit:null,[]],endInterval:[e?e.endInterval:null,[]],endIntervalTimeUnit:[e?e.endIntervalTimeUnit:null,[]],startIntervalPattern:[e?e.startIntervalPattern:null,[]],endIntervalPattern:[e?e.endIntervalPattern:null,[]]}),this.updateValidators(!1),this.getTelemetryFromDatabaseConfigForm.get("fetchMode").valueChanges.subscribe((function(){t.updateValidators(!0)})),this.getTelemetryFromDatabaseConfigForm.get("useMetadataIntervalPatterns").valueChanges.subscribe((function(){t.updateValidators(!0)})),this.getTelemetryFromDatabaseConfigForm.valueChanges.subscribe((function(e){t.validateConfig()?t.notifyConfigurationUpdated(t.getTelemetryFromDatabaseConfigForm.value):t.notifyConfigurationUpdated(null)}))},n.prototype.updateValidators=function(e){var t=this.getTelemetryFromDatabaseConfigForm.get("fetchMode").value,n=this.getTelemetryFromDatabaseConfigForm.get("useMetadataIntervalPatterns").value;t&&t===j.ALL?(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([o.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([o.Validators.required,o.Validators.min(2),o.Validators.max(1e3)])):(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([])),n?(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([o.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([o.Validators.required])):(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([o.Validators.required,o.Validators.min(1),o.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([o.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([o.Validators.required,o.Validators.min(1),o.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([o.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([])),this.getTelemetryFromDatabaseConfigForm.get("orderBy").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("limit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").updateValueAndValidity({emitEvent:e})},n.prototype.validateConfig=function(){return this.getTelemetryFromDatabaseConfigForm.valid},n.prototype.removeKey=function(e,t){var n=this.getTelemetryFromDatabaseConfigForm.get(t).value,r=n.indexOf(e);r>=0&&(n.splice(r,1),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(n,{emitEvent:!0}))},n.prototype.addKey=function(e,t){var n=e.input,r=e.value;if((r||"").trim()){r=r.trim();var a=this.getTelemetryFromDatabaseConfigForm.get(t).value;a&&-1!==a.indexOf(r)||(a||(a=[]),a.push(r),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(a,{emitEvent:!0}))}n&&(n.value="")},n.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-get-telemetry-from-database",template:'
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n tb.rulenode.fetch-mode\n \n \n {{ mode }}\n \n \n tb.rulenode.fetch-mode-hint\n \n
\n \n tb.rulenode.order-by\n \n \n {{ order }}\n \n \n tb.rulenode.order-by-hint\n \n \n tb.rulenode.limit\n \n tb.rulenode.limit-hint\n \n
\n \n {{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-interval-patterns-hint
\n
\n
\n \n tb.rulenode.start-interval\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.start-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.end-interval\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.end-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n \n tb.rulenode.start-interval-pattern\n \n \n {{ \'tb.rulenode.start-interval-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.end-interval-pattern\n \n \n {{ \'tb.rulenode.end-interval-pattern-required\' | translate }}\n \n \n \n \n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var ie=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.relatedAttributesConfigForm=this.fb.group({relationsQuery:[e?e.relationsQuery:null,[o.Validators.required]],telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[o.Validators.required]]}),this.relatedAttributesConfigForm.valueChanges.subscribe((function(e){t.relatedAttributesConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.validateConfig=function(){return this.relatedAttributesConfigForm.valid},n.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-related-attributes-config",template:'
\n \n \n \n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var oe=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.tenantAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[o.Validators.required]]}),this.tenantAttributesConfigForm.valueChanges.subscribe((function(e){t.tenantAttributesConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.validateConfig=function(){return this.tenantAttributesConfigForm.valid},n.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-tenant-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var se=function(){function e(){}return e.decorators=[{type:t.NgModule,args:[{declarations:[W,ee,te,ne,re,ae,ie,oe],imports:[n.CommonModule,a.SharedModule,X],exports:[W,ee,te,ne,re,ae,ie,oe]}]}],e}(),le=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.originatorSource=V,r.originatorSources=Object.keys(V),r.originatorSourceTranslationMap=L,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.changeOriginatorConfigForm=this.fb.group({originatorSource:[e?e.originatorSource:null,[o.Validators.required]],relationsQuery:[e?e.relationsQuery:null,[]]}),this.updateValidators(!1),this.changeOriginatorConfigForm.get("originatorSource").valueChanges.subscribe((function(){t.updateValidators(!0)})),this.changeOriginatorConfigForm.valueChanges.subscribe((function(e){t.changeOriginatorConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.updateValidators=function(e){var t=this.changeOriginatorConfigForm.get("originatorSource").value;t&&t===V.RELATED?this.changeOriginatorConfigForm.get("relationsQuery").setValidators([o.Validators.required]):this.changeOriginatorConfigForm.get("relationsQuery").setValidators([]),this.changeOriginatorConfigForm.get("relationsQuery").updateValueAndValidity({emitEvent:e})},n.prototype.validateConfig=function(){return this.changeOriginatorConfigForm.valid},n.decorators=[{type:t.Component,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
\n
\n'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var me=function(e){function n(t,n,r,a){var i=e.call(this,t)||this;return i.store=t,i.fb=n,i.nodeScriptTestService=r,i.translate=a,i}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.onConfigurationSet=function(e){var t=this;this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[o.Validators.required]]}),this.scriptConfigForm.valueChanges.subscribe((function(e){t.scriptConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"update",this.translate.instant("tb.rulenode.transformer"),"Transform",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},n.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},n.decorators=[{type:t.Component,args:[{selector:"tb-transformation-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder},{type:s.NodeScriptTestService},{type:r.TranslateService}]},n.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},n}(a.RuleNodeConfigurationComponent);var ue=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.toEmailConfigForm=this.fb.group({fromTemplate:[e?e.fromTemplate:null,[o.Validators.required]],toTemplate:[e?e.toTemplate:null,[o.Validators.required]],ccTemplate:[e?e.ccTemplate:null,[]],bccTemplate:[e?e.bccTemplate:null,[]],subjectTemplate:[e?e.subjectTemplate:null,[o.Validators.required]],bodyTemplate:[e?e.bodyTemplate:null,[o.Validators.required]]}),this.toEmailConfigForm.valueChanges.subscribe((function(e){t.toEmailConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.validateConfig=function(){return this.toEmailConfigForm.valid},n.decorators=[{type:t.Component,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.body-template\n \n \n {{ \'tb.rulenode.body-template-required\' | translate }}\n \n \n \n
\n'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var de=function(){function e(){}return e.decorators=[{type:t.NgModule,args:[{declarations:[le,me,ue],imports:[n.CommonModule,a.SharedModule,X],exports:[le,me,ue]}]}],e}(),pe=function(){function e(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-name-pattern-hint":"Name pattern, use ${metaKeyName} to substitute variables from metadata","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-type-pattern-hint":"Type pattern, use ${metaKeyName} to substitute variables from metadata","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-name-pattern-hint":"Customer name pattern, use ${metaKeyName} to substitute variables from metadata","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.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.",limit:"Limit","limit-hint":"Min limit value is 2, max - 1000. In case you want to fetch a single entry, select fetch mode 'FIRST' or 'LAST'.","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","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","latest-timeseries":"Latest timeseries","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-hint":"Relation type pattern, use ${metaKeyName} to substitute variables from metadata","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","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","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","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 metadata period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds metadata pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","period-in-seconds-pattern-hint":"Period in seconds pattern, use ${metaKeyName} to substitute variables from metadata","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",propagate:"Propagate",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","from-template-hint":"From address template, use ${metaKeyName} to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use ${metaKeyName} to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use ${metaKeyName} to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use ${metaKeyName} to substitute variables from metadata","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","endpoint-url-pattern-hint":"HTTP URL address pattern, use ${metaKeyName} to substitute variables from metadata","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory",headers:"Headers","headers-hint":"Use ${metaKeyName} in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use ${metaKeyName} to substitute variables from metadata","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","topic-arn-pattern-hint":"Topic ARN pattern, use ${metaKeyName} to substitute variables from metadata","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","queue-url-pattern-hint":"Queue URL pattern, use ${metaKeyName} to substitute variables from metadata","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 ${metaKeyName} in name/value fields to substitute variables from metadata","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","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","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"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-interval-patterns":"Use metadata interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","check-all-keys":"Check that all selected keys 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-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","start-interval-pattern-hint":"Start interval pattern, use ${metaKeyName} to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use ${metaKeyName} to substitute variables from metadata","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","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":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","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","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","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":"You should 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-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 Latest telemetry with Timestamp","get-latest-value-with-ts-hint":'If selected, latest telemetry values will be added to the outbound message metadata with timestamp, e.g: "temp": "{\\"ts\\":1574329385897,\\"value\\":42}"'},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}},!0)}(e)}return e.decorators=[{type:t.NgModule,args:[{declarations:[b],imports:[n.CommonModule,a.SharedModule],exports:[N,z,se,de,b]}]}],e.ctorParameters=function(){return[{type:r.TranslateService}]},e}();e.RuleNodeCoreConfigModule=pe,e.default=pe,e.ɵa=b,e.ɵb=N,e.ɵba=ae,e.ɵbb=ie,e.ɵbc=oe,e.ɵbd=X,e.ɵbe=Y,e.ɵbf=J,e.ɵbg=Z,e.ɵbh=de,e.ɵbi=le,e.ɵbj=me,e.ɵbk=ue,e.ɵc=h,e.ɵd=C,e.ɵe=v,e.ɵf=F,e.ɵg=T,e.ɵh=x,e.ɵi=I,e.ɵj=q,e.ɵk=S,e.ɵl=k,e.ɵm=z,e.ɵn=A,e.ɵo=E,e.ɵp=B,e.ɵq=H,e.ɵr=$,e.ɵs=_,e.ɵt=Q,e.ɵu=se,e.ɵv=W,e.ɵw=ee,e.ɵx=te,e.ɵy=ne,e.ɵz=re,Object.defineProperty(e,"__esModule",{value:!0})})); //# sourceMappingURL=rulenode-core-config.umd.min.js.map \ No newline at end of file From 833de6465374f28dcadafcedd9d948e077dc01ee Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 16 Jan 2020 12:50:40 +0200 Subject: [PATCH 068/133] UI: RuleChain improvements. --- ui-ngx/src/app/core/utils.ts | 6 +- .../rulechain/rule-node-config.component.ts | 38 +++----- .../rulechain/rulechain-page.component.ts | 12 +-- .../components/file-input.component.html | 7 +- .../shared/components/file-input.component.ts | 56 +++++++++++- .../src/app/shared/models/rule-node.models.ts | 91 +++++++++++++++++-- 6 files changed, 161 insertions(+), 49 deletions(-) diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index 21618ba623..e58e3dd400 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -356,7 +356,7 @@ function utf8ToBytes(input: string, units?: number): number[] { return bytes; } -export function deepClone(target: T): T { +export function deepClone(target: T, ignoreFields?: string[]): T { if (target === null) { return target; } @@ -371,7 +371,9 @@ export function deepClone(target: T): T { if (typeof target === 'object' && target !== {}) { const cp = { ...(target as { [key: string]: any }) } as { [key: string]: any }; Object.keys(cp).forEach(k => { - cp[k] = deepClone(cp[k]); + if (!ignoreFields || ignoreFields.indexOf(k) === -1) { + cp[k] = deepClone(cp[k]); + } }); return cp as T; } 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 180c97708f..26c41289ae 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 @@ -16,39 +16,25 @@ import { AfterViewInit, - Component, ElementRef, - EventEmitter, forwardRef, + Component, + ComponentRef, + forwardRef, Input, - OnChanges, + OnDestroy, OnInit, - Output, - SimpleChanges, ViewChild, - Compiler, - Injector, ComponentRef, OnDestroy + ViewContainerRef } from '@angular/core'; -import { PageComponent } from '@shared/components/page.component'; -import { Store } from '@ngrx/store'; -import { AppState } from '@core/core.state'; -import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, NgForm, Validators } from '@angular/forms'; -import { FcRuleNode, FcRuleEdge } from './rulechain-page.models'; -import { RuleNodeType, LinkLabel, RuleNodeDefinition, RuleNodeConfiguration, IRuleNodeConfigurationComponent } from '@shared/models/rule-node.models'; -import { EntityType } from '@shared/models/entity-type.models'; -import { Observable, of, Subscription } from 'rxjs'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { + IRuleNodeConfigurationComponent, + RuleNodeConfiguration, + RuleNodeDefinition +} from '@shared/models/rule-node.models'; +import { Subscription } from 'rxjs'; import { RuleChainService } from '@core/http/rule-chain.service'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; -import { deepClone } from '@core/utils'; -import { EntityAlias } from '@shared/models/alias.models'; -import { TruncatePipe } from '@shared/pipe/truncate.pipe'; -import { MatChipList, MatAutocomplete, MatChipInputEvent, MatAutocompleteSelectedEvent } from '@angular/material'; import { TranslateService } from '@ngx-translate/core'; -import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes'; -import { catchError, map, mergeMap, share } from 'rxjs/operators'; -import { DynamicWidgetComponent } from '@home/components/widget/dynamic-widget.component'; -import { SharedModule } from '@shared/shared.module'; -import { WidgetComponentsModule } from '@home/components/widget/widget-components.module'; -import { DynamicComponentFactoryService } from '@core/services/dynamic-component-factory.service'; -import { ViewContainerRef } from '@angular/core'; import { JsonObjectEditComponent } from '@shared/components/json-object-edit.component'; @Component({ 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 a4ef9a7d9d..cd9950af6b 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 @@ -341,9 +341,9 @@ export class RuleChainPageComponent extends PageComponent this.nextNodeID = 1; this.nextConnectorID = 1; - this.selectedObjects.length = 0; - this.ruleChainModel.nodes.length = 0; - this.ruleChainModel.edges.length = 0; + this.selectedObjects = []; + this.ruleChainModel.nodes = []; + this.ruleChainModel.edges = []; this.inputConnectorId = this.nextConnectorID++; this.ruleChainModel.nodes.push( @@ -535,7 +535,7 @@ export class RuleChainPageComponent extends PageComponent this.editingRuleNodeLink = null; this.isEditingRuleNode = true; this.editingRuleNodeIndex = this.ruleChainModel.nodes.indexOf(node); - this.editingRuleNode = deepClone(node); + this.editingRuleNode = deepClone(node, ['component']); setTimeout(() => { this.ruleNodeComponent.ruleNodeFormGroup.markAsPristine(); }, 0); @@ -576,7 +576,7 @@ export class RuleChainPageComponent extends PageComponent onRevertRuleNodeEdit() { this.ruleNodeComponent.ruleNodeFormGroup.markAsPristine(); const node = this.ruleChainModel.nodes[this.editingRuleNodeIndex]; - this.editingRuleNode = deepClone(node); + this.editingRuleNode = deepClone(node, ['component']); } onRevertRuleNodeLinkEdit() { @@ -593,7 +593,7 @@ export class RuleChainPageComponent extends PageComponent delete this.editingRuleNode.error; } this.ruleChainModel.nodes[this.editingRuleNodeIndex] = this.editingRuleNode; - this.editingRuleNode = deepClone(this.editingRuleNode); + this.editingRuleNode = deepClone(this.editingRuleNode, ['component']); this.onModelChanged(); this.updateRuleNodesHighlight(); } diff --git a/ui-ngx/src/app/shared/components/file-input.component.html b/ui-ngx/src/app/shared/components/file-input.component.html index 0446834e92..32c4bd1de2 100644 --- a/ui-ngx/src/app/shared/components/file-input.component.html +++ b/ui-ngx/src/app/shared/components/file-input.component.html @@ -33,13 +33,14 @@
- - + +
-

{{ (lastBreadcrumb$ | async).label | translate }}

+

+ {{ breadcrumb.ignoreTranslate ? breadcrumb.label : (breadcrumb.label | translate) }} +

@@ -24,7 +26,7 @@ {{ breadcrumb.icon }} - {{ breadcrumb.label | translate }} + {{ breadcrumb.ignoreTranslate ? breadcrumb.label : (breadcrumb.label | translate) }} @@ -32,7 +34,7 @@ {{ breadcrumb.icon }} - {{ breadcrumb.label | translate }} + {{ breadcrumb.ignoreTranslate ? 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 3e7fb18adc..1ea092d637 100644 --- a/ui-ngx/src/app/shared/components/breadcrumb.component.ts +++ b/ui-ngx/src/app/shared/components/breadcrumb.component.ts @@ -16,9 +16,10 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { BehaviorSubject, Subject } from 'rxjs'; -import { BreadCrumb } from './breadcrumb'; +import { BreadCrumb, BreadCrumbConfig } from './breadcrumb'; import { ActivatedRoute, ActivatedRouteSnapshot, NavigationEnd, Router } from '@angular/router'; import { distinctUntilChanged, filter, map } from 'rxjs/operators'; +import { TranslateService } from '@ngx-translate/core'; @Component({ selector: '[tb-breadcrumb]', @@ -40,7 +41,8 @@ export class BreadcrumbComponent implements OnInit, OnDestroy { ); constructor(private router: Router, - private activatedRoute: ActivatedRoute) { + private activatedRoute: ActivatedRoute, + private translate: TranslateService) { } ngOnInit(): void { @@ -56,15 +58,24 @@ export class BreadcrumbComponent implements OnInit, OnDestroy { buildBreadCrumbs(route: ActivatedRouteSnapshot, breadcrumbs: Array = []): Array { let newBreadcrumbs = breadcrumbs; if (route.routeConfig && route.routeConfig.data) { - const breadcrumbData = route.routeConfig.data.breadcrumb; - if (breadcrumbData && !breadcrumbData.skip) { - const label = breadcrumbData.label || 'home.home'; - const icon = breadcrumbData.icon || 'home'; + const breadcrumbConfig = route.routeConfig.data.breadcrumb as BreadCrumbConfig; + if (breadcrumbConfig && !breadcrumbConfig.skip) { + let label; + let ignoreTranslate; + if (breadcrumbConfig.labelFunction) { + label = breadcrumbConfig.labelFunction(route, this.translate); + ignoreTranslate = true; + } else { + label = breadcrumbConfig.label || 'home.home'; + ignoreTranslate = false; + } + const icon = breadcrumbConfig.icon || 'home'; const isMdiIcon = icon.startsWith('mdi:'); const link = [ '/' + route.url.join('') ]; const queryParams = route.queryParams; const breadcrumb = { label, + ignoreTranslate, icon, isMdiIcon, link, diff --git a/ui-ngx/src/app/shared/components/breadcrumb.ts b/ui-ngx/src/app/shared/components/breadcrumb.ts index 0986bb4715..5db7b7b4c4 100644 --- a/ui-ngx/src/app/shared/components/breadcrumb.ts +++ b/ui-ngx/src/app/shared/components/breadcrumb.ts @@ -14,13 +14,23 @@ /// limitations under the License. /// -import { Params } from '@angular/router'; +import { ActivatedRouteSnapshot, Params } from '@angular/router'; +import { TranslateService } from '@ngx-translate/core'; export interface BreadCrumb { label: string; + ignoreTranslate: boolean; icon: string; isMdiIcon: boolean; link: any[]; queryParams: Params; } +export type BreadCrumbLabelFunction = (route: ActivatedRouteSnapshot, translate: TranslateService) => string; + +export interface BreadCrumbConfig { + labelFunction: BreadCrumbLabelFunction; + label: string; + icon: string; + skip: boolean; +} diff --git a/ui-ngx/src/app/shared/components/footer-fab-buttons.component.html b/ui-ngx/src/app/shared/components/footer-fab-buttons.component.html new file mode 100644 index 0000000000..5d98611cc6 --- /dev/null +++ b/ui-ngx/src/app/shared/components/footer-fab-buttons.component.html @@ -0,0 +1,39 @@ + + diff --git a/ui-ngx/src/app/shared/components/footer-fab-buttons.component.scss b/ui-ngx/src/app/shared/components/footer-fab-buttons.component.scss new file mode 100644 index 0000000000..711dccd246 --- /dev/null +++ b/ui-ngx/src/app/shared/components/footer-fab-buttons.component.scss @@ -0,0 +1,51 @@ +/** + * Copyright © 2016-2019 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 { + section.tb-footer-buttons { + position: fixed; + right: 20px; + bottom: 20px; + z-index: 30; + pointer-events: none; + + .fab-container { + display: flex; + flex-direction: column-reverse; + align-items: center; + > div { + display: flex; + flex-direction: column-reverse; + align-items: center; + margin-bottom: 5px; + + button { + margin-bottom: 17px; + } + } + } + + .tb-btn-footer { + position: relative !important; + display: inline-block !important; + animation: tbMoveFromBottomFade .3s ease both; + + &.tb-hide { + animation: tbMoveToBottomFade .3s ease both; + } + } + } +} diff --git a/ui-ngx/src/app/shared/components/footer-fab-buttons.component.ts b/ui-ngx/src/app/shared/components/footer-fab-buttons.component.ts new file mode 100644 index 0000000000..65b20b4f11 --- /dev/null +++ b/ui-ngx/src/app/shared/components/footer-fab-buttons.component.ts @@ -0,0 +1,85 @@ +/// +/// Copyright © 2016-2019 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, Input, HostListener } from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { speedDialFabAnimations } from '@shared/animations/speed-dial-fab.animations'; + +export interface FooterFabButton { + name: string; + icon: string; + onAction: ($event: Event) => void; +} + +export interface FooterFabButtons { + fabTogglerName: string; + fabTogglerIcon: string; + buttons: Array; +} + +@Component({ + selector: 'tb-footer-fab-buttons', + templateUrl: './footer-fab-buttons.component.html', + styleUrls: ['./footer-fab-buttons.component.scss'], + animations: speedDialFabAnimations +}) +export class FooterFabButtonsComponent extends PageComponent { + + @Input() + footerFabButtons: FooterFabButtons; + + buttons: Array = []; + fabTogglerState = 'inactive'; + + closeTimeout = null; + + @HostListener('focusout', ['$event']) + onFocusOut($event) { + if (!this.closeTimeout) { + this.closeTimeout = setTimeout(() => { + this.hideItems(); + }, 100); + } + } + + @HostListener('focusin', ['$event']) + onFocusIn($event) { + if (this.closeTimeout) { + clearTimeout(this.closeTimeout); + this.closeTimeout = null; + } + } + + constructor(protected store: Store) { + super(store); + } + + showItems() { + this.fabTogglerState = 'active'; + this.buttons = this.footerFabButtons.buttons; + } + + hideItems() { + this.fabTogglerState = 'inactive'; + this.buttons = []; + } + + onToggleFab() { + this.buttons.length ? this.hideItems() : this.showItems(); + } +} diff --git a/ui-ngx/src/app/shared/components/time/timeinterval.component.ts b/ui-ngx/src/app/shared/components/time/timeinterval.component.ts index 16d72cea85..f6fa4ac54d 100644 --- a/ui-ngx/src/app/shared/components/time/timeinterval.component.ts +++ b/ui-ngx/src/app/shared/components/time/timeinterval.component.ts @@ -46,6 +46,7 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor { set min(min: number) { if (typeof min !== 'undefined' && min !== this.minValue) { this.minValue = min; + this.maxValue = Math.max(this.maxValue, this.minValue); this.updateView(); } } @@ -54,6 +55,7 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor { set max(max: number) { if (typeof max !== 'undefined' && max !== this.maxValue) { this.maxValue = max; + this.minValue = Math.min(this.minValue, this.maxValue); this.updateView(); } } 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 0e267d0657..5c8b3eb56d 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow.component.scss +++ b/ui-ngx/src/app/shared/components/time/timewindow.component.scss @@ -14,7 +14,11 @@ * limitations under the License. */ :host { + min-width: 52px; section.tb-timewindow { + min-height: 32px; + padding: 0 6px; + span { overflow: hidden; text-overflow: ellipsis; diff --git a/ui-ngx/src/app/shared/models/dashboard.models.ts b/ui-ngx/src/app/shared/models/dashboard.models.ts index 6204818ca4..5de95e2a1f 100644 --- a/ui-ngx/src/app/shared/models/dashboard.models.ts +++ b/ui-ngx/src/app/shared/models/dashboard.models.ts @@ -25,6 +25,19 @@ export interface DashboardInfo extends BaseData { assignedCustomers: Array; } +export interface WidgetLayout { + sizeX: number; + sizeY: number; + mobileHeight: number; + mobileOrder: number; + col: number; + row: number; +} + +export interface WidgetLayouts { + [id: string]: WidgetLayout; +} + export interface DashboardConfiguration { [key: string]: any; // TODO: diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts new file mode 100644 index 0000000000..85516edf00 --- /dev/null +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -0,0 +1,171 @@ +/// +/// Copyright © 2016-2019 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 {BaseData} from '@shared/models/base-data'; +import {TenantId} from '@shared/models/id/tenant-id'; +import {WidgetsBundleId} from '@shared/models/id/widgets-bundle-id'; +import {WidgetTypeId} from '@shared/models/id/widget-type-id'; +import { AliasEntityType, EntityType, EntityTypeTranslation } from '@shared/models/entity-type.models'; +import { Timewindow } from '@shared/models/time/time.models'; + +export enum widgetType { + timeseries = 'timeseries', + latest = 'latest', + rpc = 'rpc', + alarm = 'alarm' +} + +export interface WidgetTypeTemplate { + bundleAlias: string; + alias: string; +} + +export interface WidgetTypeData { + name: string; + template: WidgetTypeTemplate; +} + +export const widgetTypesData = new Map( + [ + [ + widgetType.timeseries, + { + name: 'widget.timeseries', + template: { + bundleAlias: 'charts', + alias: 'basic_timeseries' + } + } + ], + [ + widgetType.latest, + { + name: 'widget.latest-values', + template: { + bundleAlias: 'cards', + alias: 'attributes_card' + } + } + ], + [ + widgetType.rpc, + { + name: 'widget.rpc', + template: { + bundleAlias: 'gpio_widgets', + alias: 'basic_gpio_control' + } + } + ], + [ + widgetType.alarm, + { + name: 'widget.alarm', + template: { + bundleAlias: 'alarm_widgets', + alias: 'alarms_table' + } + } + ] + ] +); + +export interface WidgetResource { + url: string; +} + +export interface WidgetTypeDescriptor { + type: widgetType; + resources: Array; + templateHtml: string; + templateCss: string; + controllerScript: string; + settingsSchema: string; + dataKeySettingsSchema: string; + defaultConfig: string; + sizeX: number; + sizeY: number; +} + +export interface WidgetType extends BaseData { + tenantId: TenantId; + bundleAlias: string; + alias: string; + name: string; + descriptor: WidgetTypeDescriptor; +} + +export interface WidgetInfo extends WidgetTypeDescriptor { + widgetName: string; + alias: string; +} + +export function toWidgetInfo(widgetTypeEntity: WidgetType): WidgetInfo { + return { + widgetName: widgetTypeEntity.name, + alias: widgetTypeEntity.alias, + type: widgetTypeEntity.descriptor.type, + sizeX: widgetTypeEntity.descriptor.sizeX, + sizeY: widgetTypeEntity.descriptor.sizeY, + resources: widgetTypeEntity.descriptor.resources, + templateHtml: widgetTypeEntity.descriptor.templateHtml, + templateCss: widgetTypeEntity.descriptor.templateCss, + controllerScript: widgetTypeEntity.descriptor.controllerScript, + settingsSchema: widgetTypeEntity.descriptor.settingsSchema, + dataKeySettingsSchema: widgetTypeEntity.descriptor.dataKeySettingsSchema, + defaultConfig: widgetTypeEntity.descriptor.defaultConfig + }; +} + +export interface WidgetConfig { + title?: string; + titleIcon?: string; + showTitle?: boolean; + showTitleIcon?: boolean; + iconColor?: string; + iconSize?: number; + dropShadow?: boolean; + enableFullscreen?: boolean; + useDashboardTimewindow?: boolean; + displayTimewindow?: boolean; + timewindow?: Timewindow; + mobileHeight?: number; + mobileOrder?: number; + color?: string; + backgroundColor?: string; + padding?: string; + margin?: string; + widgetStyle?: {[klass: string]: any}; + titleStyle?: {[klass: string]: any}; + [key: string]: any; + + // TODO: +} + +export interface Widget { + id?: string; + typeId: WidgetTypeId; + isSystemType: boolean; + bundleAlias: string; + typeAlias: string; + type: widgetType; + title: string; + sizeX: number; + sizeY: number; + row: number; + col: number; + config: WidgetConfig; +} diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index 44c5b27ac8..051d17c936 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -52,6 +52,7 @@ import { MatTooltipModule } from '@angular/material'; import {MatDatetimepickerModule, MatNativeDatetimeModule} from '@mat-datetimepicker/core'; +import {GridsterModule} from 'angular-gridster2'; import {FlexLayoutModule} from '@angular/flex-layout'; import {FormsModule, ReactiveFormsModule} from '@angular/forms'; import {RouterModule} from '@angular/router'; @@ -86,6 +87,7 @@ import {SocialSharePanelComponent} from './components/socialshare-panel.componen import { RelationTypeAutocompleteComponent } from '@shared/components/relation/relation-type-autocomplete.component'; import { EntityListSelectComponent } from './components/entity/entity-list-select.component'; import { JsonObjectEditComponent } from './components/json-object-edit.component'; +import { FooterFabButtonsComponent } from '@shared/components/footer-fab-buttons.component'; @NgModule({ providers: [ @@ -102,6 +104,7 @@ import { JsonObjectEditComponent } from './components/json-object-edit.component declarations: [ FooterComponent, LogoComponent, + FooterFabButtonsComponent, ToastDirective, FullscreenDirective, TbAnchorComponent, @@ -167,6 +170,7 @@ import { JsonObjectEditComponent } from './components/json-object-edit.component MatStepperModule, MatAutocompleteModule, MatChipsModule, + GridsterModule, ClipboardModule, FlexLayoutModule.withConfig({addFlexToParent: false}), FormsModule, @@ -177,6 +181,7 @@ import { JsonObjectEditComponent } from './components/json-object-edit.component exports: [ FooterComponent, LogoComponent, + FooterFabButtonsComponent, ToastDirective, FullscreenDirective, TbAnchorComponent, @@ -232,6 +237,7 @@ import { JsonObjectEditComponent } from './components/json-object-edit.component MatStepperModule, MatAutocompleteModule, MatChipsModule, + GridsterModule, ClipboardModule, FlexLayoutModule, FormsModule, diff --git a/ui-ngx/src/theme.scss b/ui-ngx/src/theme.scss index 861b998f35..ed82d0f56b 100644 --- a/ui-ngx/src/theme.scss +++ b/ui-ngx/src/theme.scss @@ -351,6 +351,16 @@ $tb-dark-theme: get-tb-dark-theme( .mat-icon { vertical-align: middle; + &.tb-mat-20 { + width: 20px; + height: 20px; + font-size: 20px; + svg { + width: 24px; + height: 24px; + transform: scale(0.83); + } + } &.tb-mat-32 { width: 32px; height: 32px; diff --git a/ui/package-lock.json b/ui/package-lock.json index e2fc919489..f2f85079de 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -6075,7 +6075,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -6096,12 +6097,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -6116,17 +6119,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -6243,7 +6249,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -6255,6 +6262,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -6269,6 +6277,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -6276,12 +6285,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -6300,6 +6311,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -6380,7 +6392,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -6392,6 +6405,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -6477,7 +6491,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -6513,6 +6528,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -6532,6 +6548,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -6575,12 +6592,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, From 9ec843cbfacc8fcfef106eaff278caa78ba1e2b4 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 5 Sep 2019 21:15:40 +0300 Subject: [PATCH 029/133] Widget component initial implementation --- ui-ngx/angular.json | 6 +- ui-ngx/package-lock.json | 13 +- ui-ngx/package.json | 3 + ui-ngx/src/app/core/api/widget-api.models.ts | 123 +++ ui-ngx/src/app/core/css/css.js | 688 ++++++++++++++++ ui-ngx/src/app/core/http/widget.service.ts | 295 ++++++- .../app/core/services/resources.service.ts | 83 ++ ui-ngx/src/app/core/services/utils.service.ts | 116 +++ ui-ngx/src/app/core/utils.ts | 156 ++++ .../dashboard/dashboard.component.html | 25 +- .../dashboard/dashboard.component.ts | 204 +++-- .../home/components/home-components.module.ts | 27 +- ...ynamic-widget-component-factory.service.ts | 100 +++ .../widget/dynamic-widget.component.ts | 63 ++ .../components/widget/legend.component.html | 42 + .../components/widget/legend.component.scss | 68 ++ .../components/widget/legend.component.ts | 55 ++ .../widget/widget-components.module.ts | 38 + .../components/widget/widget.component.html | 18 + .../components/widget/widget.component.scss | 47 ++ .../components/widget/widget.component.ts | 758 ++++++++++++++++++ .../home/models/dashboard-component.models.ts | 83 +- .../home/models/widget-component.models.ts | 56 +- .../widget/widget-library-routing.module.ts | 8 +- .../widget/widget-library.component.html | 8 +- .../pages/widget/widget-library.component.ts | 25 +- ui-ngx/src/app/shared/models/constants.ts | 2 + ui-ngx/src/app/shared/models/error.models.ts | 23 + ui-ngx/src/app/shared/models/widget.models.ts | 202 ++++- .../app/shared/models/window-message.model.ts | 22 + 30 files changed, 3188 insertions(+), 169 deletions(-) create mode 100644 ui-ngx/src/app/core/api/widget-api.models.ts create mode 100644 ui-ngx/src/app/core/css/css.js create mode 100644 ui-ngx/src/app/core/services/resources.service.ts create mode 100644 ui-ngx/src/app/core/services/utils.service.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/dynamic-widget-component-factory.service.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/legend.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/legend.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/legend.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/widget.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/widget.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/widget.component.ts create mode 100644 ui-ngx/src/app/shared/models/error.models.ts create mode 100644 ui-ngx/src/app/shared/models/window-message.model.ts diff --git a/ui-ngx/angular.json b/ui-ngx/angular.json index 0eab7d353c..113233b012 100644 --- a/ui-ngx/angular.json +++ b/ui-ngx/angular.json @@ -30,6 +30,8 @@ "src/styles.scss" ], "scripts": [ + "node_modules/javascript-detect-element-resize/detect-element-resize.js", + "node_modules/jquery/dist/jquery.min.js", "node_modules/ace-builds/src-min/ace.js", "node_modules/ace-builds/src-min/ext-language_tools.js", "node_modules/ace-builds/src-min/ext-searchbox.js", @@ -71,10 +73,10 @@ "sourceMap": false, "extractCss": true, "namedChunks": false, - "aot": true, + "aot": false, "extractLicenses": true, "vendorChunk": false, - "buildOptimizer": true, + "buildOptimizer": false, "budgets": [ { "type": "initial", diff --git a/ui-ngx/package-lock.json b/ui-ngx/package-lock.json index 26c4516940..71c03da869 100644 --- a/ui-ngx/package-lock.json +++ b/ui-ngx/package-lock.json @@ -1993,8 +1993,7 @@ "base64-js": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", - "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", - "dev": true + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" }, "base64id": { "version": "1.0.0", @@ -6336,6 +6335,16 @@ "integrity": "sha1-43zwsX8ZnM4jvqcbIDk5Uka07E4=", "dev": true }, + "javascript-detect-element-resize": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/javascript-detect-element-resize/-/javascript-detect-element-resize-0.5.3.tgz", + "integrity": "sha1-GnHNUd/lZZB/KZAS/nOilBBAJd4=" + }, + "jquery": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.4.1.tgz", + "integrity": "sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw==" + }, "js-tokens": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", diff --git a/ui-ngx/package.json b/ui-ngx/package.json index 2d8a1a5d2b..5548100061 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -33,11 +33,14 @@ "@ngx-translate/http-loader": "^4.0.0", "ace-builds": "^1.4.5", "angular-gridster2": "^8.1.0", + "base64-js": "^1.3.1", "compass-sass-mixins": "^0.12.7", "core-js": "^3.1.4", "deep-equal": "^1.0.1", "font-awesome": "^4.7.0", "hammerjs": "^2.0.8", + "javascript-detect-element-resize": "^0.5.3", + "jquery": "^3.4.1", "material-design-icons": "^3.0.1", "messageformat": "^2.3.0", "ngx-clipboard": "^12.2.0", diff --git a/ui-ngx/src/app/core/api/widget-api.models.ts b/ui-ngx/src/app/core/api/widget-api.models.ts new file mode 100644 index 0000000000..0f035cd195 --- /dev/null +++ b/ui-ngx/src/app/core/api/widget-api.models.ts @@ -0,0 +1,123 @@ +/// +/// Copyright © 2016-2019 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 { Observable } from 'rxjs'; +import { EntityId } from '@app/shared/models/id/entity-id'; +import { WidgetActionDescriptor, widgetType } from '@shared/models/widget.models'; +import { TimeService } from '../services/time.service'; +import { DeviceService } from '../http/device.service'; +import { AlarmService } from '../http/alarm.service'; +import { UtilsService } from '@core/services/utils.service'; + +export interface TimewindowFunctions { + onUpdateTimewindow: (startTimeMs: number, endTimeMs: number, interval: number) => void; + onResetTimewindow: () => void; +} + +export interface WidgetSubscriptionApi { + createSubscription: (options: WidgetSubscriptionOptions, subscribe: boolean) => Observable; + createSubscriptionFromInfo: (type: widgetType, subscriptionsInfo: Array, + options: WidgetSubscriptionOptions, useDefaultComponents: boolean, subscribe: boolean) + => Observable; + removeSubscription: (id: string) => void; +} + +export interface RpcApi { + sendOneWayCommand: (method: string, params?: any, timeout?: number) => Observable; + sendTwoWayCommand: (method: string, params?: any, timeout?: number) => Observable; +} + +export interface IWidgetUtils { + formatValue: (value: any, dec?: number, units?: string, showZeroDecimals?: boolean) => string | undefined; +} + +export interface WidgetActionsApi { + actionDescriptorsBySourceId: {[sourceId: string]: Array}; + getActionDescriptors: (actionSourceId: string) => Array; + handleWidgetAction: ($event: Event, descriptor: WidgetActionDescriptor, + entityId?: EntityId, entityName?: string, additionalParams?: any) => void; + elementClick: ($event: Event) => void; +} + +export interface IAliasController { + [key: string]: any | null; + // TODO: +} + +export interface StateObject { + id?: string; + params?: StateParams; +} + +export interface StateParams { + entityName?: string; + targetEntityParamName?: string; + entityId?: EntityId; + [key: string]: any | null; +} + +export interface IStateController { + getStateParams: () => StateParams; + openState: (id: string, params?: StateParams, openRightLayout?: boolean) => void; + updateState: (id?: string, params?: StateParams, openRightLayout?: boolean) => void; + // TODO: +} + +export interface EntityInfo { + entityId: EntityId; + entityName: string; +} + +export interface SubscriptionInfo { + [key: string]: any; + // TODO: +} + +export interface WidgetSubscriptionContext { + timeService: TimeService; + deviceService: DeviceService; + alarmService: AlarmService; + utils: UtilsService; + widgetUtils: IWidgetUtils; + dashboardTimewindowApi: TimewindowFunctions; + getServerTimeDiff: Observable; + aliasController: IAliasController; + [key: string]: any; + // TODO: +} + +export interface WidgetSubscriptionOptions { + [key: string]: any; + // TODO: +} + +export interface IWidgetSubscription { + + onUpdateTimewindow: (startTimeMs: number, endTimeMs: number, interval: number) => void; + onResetTimewindow: () => void; + + sendOneWayCommand: (method: string, params?: any, timeout?: number) => Observable; + sendTwoWayCommand: (method: string, params?: any, timeout?: number) => Observable; + + clearRpcError: () => void; + + getFirstEntityInfo: () => EntityInfo; + + destroy(): void; + + [key: string]: any; + // TODO: +} diff --git a/ui-ngx/src/app/core/css/css.js b/ui-ngx/src/app/core/css/css.js new file mode 100644 index 0000000000..d568f5e3b9 --- /dev/null +++ b/ui-ngx/src/app/core/css/css.js @@ -0,0 +1,688 @@ +/* + * Copyright © 2016-2019 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. + */ +/* eslint-disable */ + +/* jshint unused:false */ +/* global base64_decode, CSSWizardView, window, console, jQuery */ +var fi = function () { + + this.cssImportStatements = []; + this.cssKeyframeStatements = []; + + this.cssRegex = new RegExp('([\\s\\S]*?){([\\s\\S]*?)}', 'gi'); + this.cssMediaQueryRegex = '((@media [\\s\\S]*?){([\\s\\S]*?}\\s*?)})'; + this.cssKeyframeRegex = '((@.*?keyframes [\\s\\S]*?){([\\s\\S]*?}\\s*?)})'; + this.combinedCSSRegex = '((\\s*?@media[\\s\\S]*?){([\\s\\S]*?)}\\s*?})|(([\\s\\S]*?){([\\s\\S]*?)})'; //to match css & media queries together + this.cssCommentsRegex = '(\\/\\*[\\s\\S]*?\\*\\/)'; + this.cssImportStatementRegex = new RegExp('@import .*?;', 'gi'); +}; + +/* + Strip outs css comments and returns cleaned css string + + @param css, the original css string to be stipped out of comments + + @return cleanedCSS contains no css comments + */ +fi.prototype.stripComments = function (cssString) { + var regex = new RegExp(this.cssCommentsRegex, 'gi'); + + return cssString.replace(regex, ''); +}; + +/* + Parses given css string, and returns css object + keys as selectors and values are css rules + eliminates all css comments before parsing + + @param source css string to be parsed + + @return object css + */ +fi.prototype.parseCSS = function (source) { + + if (source === undefined) { + return []; + } + + var css = []; + //strip out comments + //source = this.stripComments(source); + + //get import statements + + while (true) { + var imports = this.cssImportStatementRegex.exec(source); + if (imports !== null) { + this.cssImportStatements.push(imports[0]); + css.push({ + selector: '@imports', + type: 'imports', + styles: imports[0] + }); + } else { + break; + } + } + source = source.replace(this.cssImportStatementRegex, ''); + //get keyframe statements + var keyframesRegex = new RegExp(this.cssKeyframeRegex, 'gi'); + var arr; + while (true) { + arr = keyframesRegex.exec(source); + if (arr === null) { + break; + } + css.push({ + selector: '@keyframes', + type: 'keyframes', + styles: arr[0] + }); + } + source = source.replace(keyframesRegex, ''); + + //unified regex + var unified = new RegExp(this.combinedCSSRegex, 'gi'); + + while (true) { + arr = unified.exec(source); + if (arr === null) { + break; + } + var selector = ''; + if (arr[2] === undefined) { + selector = arr[5].split('\r\n').join('\n').trim(); + } else { + selector = arr[2].split('\r\n').join('\n').trim(); + } + + /* + fetch comments and associate it with current selector + */ + var commentsRegex = new RegExp(this.cssCommentsRegex, 'gi'); + var comments = commentsRegex.exec(selector); + if (comments !== null) { + selector = selector.replace(commentsRegex, '').trim(); + } + + //determine the type + if (selector.indexOf('@media') !== -1) { + //we have a media query + var cssObject = { + selector: selector, + type: 'media', + subStyles: this.parseCSS(arr[3] + '\n}') //recursively parse media query inner css + }; + if (comments !== null) { + cssObject.comments = comments[0]; + } + css.push(cssObject); + } else { + //we have standart css + var rules = this.parseRules(arr[6]); + var style = { + selector: selector, + rules: rules + }; + if (selector === '@font-face') { + style.type = 'font-face'; + } + if (comments !== null) { + style.comments = comments[0]; + } + css.push(style); + } + } + + return css; +}; + +/* + parses given string containing css directives + and returns an array of objects containing ruleName:ruleValue pairs + + @param rules, css directive string example + \n\ncolor:white;\n font-size:18px;\n + */ +fi.prototype.parseRules = function (rules) { + //convert all windows style line endings to unix style line endings + rules = rules.split('\r\n').join('\n'); + var ret = []; + + // Split all rules but keep semicolon for base64 url data + rules = rules.split(/;(?![^\(]*\))/); + + //proccess rules line by line + for (var i = 0; i < rules.length; i++) { + var line = rules[i]; + + //determine if line is a valid css directive, ie color:white; + line = line.trim(); + if (line.indexOf(':') !== -1) { + //line contains : + line = line.split(':'); + var cssDirective = line[0].trim(); + var cssValue = line.slice(1).join(':').trim(); + + //more checks + if (cssDirective.length < 1 || cssValue.length < 1) { + continue; //there is no css directive or value that is of length 1 or 0 + // PLAIN WRONG WHAT ABOUT margin:0; ? + } + + //push rule + ret.push({ + directive: cssDirective, + value: cssValue + }); + } else { + //if there is no ':', but what if it was mis splitted value which starts with base64 + if (line.trim().substr(0, 7) == 'base64,') { //hack :) + ret[ret.length - 1].value += line.trim(); + } else { + //add rule, even if it is defective + if (line.length > 0) { + ret.push({ + directive: '', + value: line, + defective: true + }); + } + } + } + } + + return ret; //we are done! +}; +/* + just returns the rule having given directive + if not found returns false; + */ +fi.prototype.findCorrespondingRule = function (rules, directive, value) { + if (value === undefined) { + value = false; + } + var ret = false; + for (var i = 0; i < rules.length; i++) { + if (rules[i].directive == directive) { + ret = rules[i]; + if (value === rules[i].value) { + break; + } + } + } + return ret; +}; + +/* + Finds styles that have given selector, compress them, + and returns them + */ +fi.prototype.findBySelector = function (cssObjectArray, selector, contains) { + if (contains === undefined) { + contains = false; + } + + var found = []; + for (var i = 0; i < cssObjectArray.length; i++) { + if (contains === false) { + if (cssObjectArray[i].selector === selector) { + found.push(cssObjectArray[i]); + } + } else { + if (cssObjectArray[i].selector.indexOf(selector) !== -1) { + found.push(cssObjectArray[i]); + } + } + + } + if (found.length < 2) { + return found; + } else { + var base = found[0]; + for (i = 1; i < found.length; i++) { + this.intelligentCSSPush([base], found[i]); + } + return [base]; //we are done!! all properties merged into base! + } +}; + +/* + deletes cssObjects having given selector, and returns new array + */ +fi.prototype.deleteBySelector = function (cssObjectArray, selector) { + var ret = []; + for (var i = 0; i < cssObjectArray.length; i++) { + if (cssObjectArray[i].selector !== selector) { + ret.push(cssObjectArray[i]); + } + } + return ret; +}; + +/* + Compresses given cssObjectArray and tries to minimize + selector redundence. + */ +fi.prototype.compressCSS = function (cssObjectArray) { + var compressed = []; + var done = {}; + for (var i = 0; i < cssObjectArray.length; i++) { + var obj = cssObjectArray[i]; + if (done[obj.selector] === true) { + continue; + } + + var found = this.findBySelector(cssObjectArray, obj.selector); //found compressed + if (found.length !== 0) { + compressed.push(found[0]); + done[obj.selector] = true; + } + } + return compressed; +}; + +/* + Received 2 css objects with following structure + { + rules : [{directive:"", value:""}, {directive:"", value:""}, ...] + selector : "SOMESELECTOR" + } + + returns the changed(new,removed,updated) values on css1 parameter, on same structure + + if two css objects are the same, then returns false + + if a css directive exists in css1 and css2, and its value is different, it is included in diff + if a css directive exists in css1 and not css2, it is then included in diff + if a css directive exists in css2 but not css1, then it is deleted in css1, it would be included in diff but will be marked as type='DELETED' + + @object css1 css object + @object css2 css object + + @return diff css object contains changed values in css1 in regards to css2 see test input output in /test/data/css.js + */ +fi.prototype.cssDiff = function (css1, css2) { + if (css1.selector !== css2.selector) { + return false; + } + + //if one of them is media query return false, because diff function can not operate on media queries + if ((css1.type === 'media' || css2.type === 'media')) { + return false; + } + + var diff = { + selector: css1.selector, + rules: [] + }; + var rule1, rule2; + for (var i = 0; i < css1.rules.length; i++) { + rule1 = css1.rules[i]; + //find rule2 which has the same directive as rule1 + rule2 = this.findCorrespondingRule(css2.rules, rule1.directive, rule1.value); + if (rule2 === false) { + //rule1 is a new rule in css1 + diff.rules.push(rule1); + } else { + //rule2 was found only push if its value is different too + if (rule1.value !== rule2.value) { + diff.rules.push(rule1); + } + } + } + + //now for rules exists in css2 but not in css1, which means deleted rules + for (var ii = 0; ii < css2.rules.length; ii++) { + rule2 = css2.rules[ii]; + //find rule2 which has the same directive as rule1 + rule1 = this.findCorrespondingRule(css1.rules, rule2.directive); + if (rule1 === false) { + //rule1 is a new rule + rule2.type = 'DELETED'; //mark it as a deleted rule, so that other merge operations could be true + diff.rules.push(rule2); + } + } + + + if (diff.rules.length === 0) { + return false; + } + return diff; +}; + +/* + Merges 2 different css objects together + using intelligentCSSPush, + + @param cssObjectArray, target css object array + @param newArray, source array that will be pushed into cssObjectArray parameter + @param reverse, [optional], if given true, first parameter will be traversed on reversed order + effectively giving priority to the styles in newArray + */ +fi.prototype.intelligentMerge = function (cssObjectArray, newArray, reverse) { + if (reverse === undefined) { + reverse = false; + } + + + for (var i = 0; i < newArray.length; i++) { + this.intelligentCSSPush(cssObjectArray, newArray[i], reverse); + } + for (i = 0; i < cssObjectArray.length; i++) { + var cobj = cssObjectArray[i]; + if (cobj.type === 'media' || (cobj.type === 'keyframes')) { + continue; + } + cobj.rules = this.compactRules(cobj.rules); + } +}; + +/* + inserts new css objects into a bigger css object + with same selectors groupped together + + @param cssObjectArray, array of bigger css object to be pushed into + @param minimalObject, single css object + @param reverse [optional] default is false, if given, cssObjectArray will be reversly traversed + resulting more priority in minimalObject's styles + */ +fi.prototype.intelligentCSSPush = function (cssObjectArray, minimalObject, reverse) { + var pushSelector = minimalObject.selector; + //find correct selector if not found just push minimalObject into cssObject + var cssObject = false; + + if (reverse === undefined) { + reverse = false; + } + + if (reverse === false) { + for (var i = 0; i < cssObjectArray.length; i++) { + if (cssObjectArray[i].selector === minimalObject.selector) { + cssObject = cssObjectArray[i]; + break; + } + } + } else { + for (var j = cssObjectArray.length - 1; j > -1; j--) { + if (cssObjectArray[j].selector === minimalObject.selector) { + cssObject = cssObjectArray[j]; + break; + } + } + } + + if (cssObject === false) { + cssObjectArray.push(minimalObject); //just push, because cssSelector is new + } else { + if (minimalObject.type !== 'media') { + for (var ii = 0; ii < minimalObject.rules.length; ii++) { + var rule = minimalObject.rules[ii]; + //find rule inside cssObject + var oldRule = this.findCorrespondingRule(cssObject.rules, rule.directive); + if (oldRule === false) { + cssObject.rules.push(rule); + } else if (rule.type == 'DELETED') { + oldRule.type = 'DELETED'; + } else { + //rule found just update value + + oldRule.value = rule.value; + } + } + } else { + cssObject.subStyles = minimalObject.subStyles; //TODO, make this intelligent too + } + + } +}; + +/* + filter outs rule objects whose type param equal to DELETED + + @param rules, array of rules + + @returns rules array, compacted by deleting all unneccessary rules + */ +fi.prototype.compactRules = function (rules) { + var newRules = []; + for (var i = 0; i < rules.length; i++) { + if (rules[i].type !== 'DELETED') { + newRules.push(rules[i]); + } + } + return newRules; +}; +/* + computes string for ace editor using this.css or given cssBase optional parameter + + @param [optional] cssBase, if given computes cssString from cssObject array + */ +fi.prototype.getCSSForEditor = function (cssBase, depth) { + if (depth === undefined) { + depth = 0; + } + var ret = ''; + if (cssBase === undefined) { + cssBase = this.css; + } + //append imports + for (var i = 0; i < cssBase.length; i++) { + if (cssBase[i].type == 'imports') { + ret += cssBase[i].styles + '\n\n'; + } + } + for (i = 0; i < cssBase.length; i++) { + var tmp = cssBase[i]; + if (tmp.selector === undefined) { //temporarily omit media queries + continue; + } + var comments = ""; + if (tmp.comments !== undefined) { + comments = tmp.comments + '\n'; + } + + if (tmp.type == 'media') { //also put media queries to output + ret += comments + tmp.selector + '{\n'; + ret += this.getCSSForEditor(tmp.subStyles, depth + 1); + ret += '}\n\n'; + } else if (tmp.type !== 'keyframes' && tmp.type !== 'imports') { + ret += this.getSpaces(depth) + comments + tmp.selector + ' {\n'; + ret += this.getCSSOfRules(tmp.rules, depth + 1); + ret += this.getSpaces(depth) + '}\n\n'; + } + } + + //append keyFrames + for (i = 0; i < cssBase.length; i++) { + if (cssBase[i].type == 'keyframes') { + ret += cssBase[i].styles + '\n\n'; + } + } + + return ret; +}; + +fi.prototype.getImports = function (cssObjectArray) { + var imps = []; + for (var i = 0; i < cssObjectArray.length; i++) { + if (cssObjectArray[i].type == 'imports') { + imps.push(cssObjectArray[i].styles); + } + } + return imps; +}; +/* + given rules array, returns visually formatted css string + to be used inside editor + */ +fi.prototype.getCSSOfRules = function (rules, depth) { + var ret = ''; + for (var i = 0; i < rules.length; i++) { + if (rules[i] === undefined) { + continue; + } + if (rules[i].defective === undefined) { + ret += this.getSpaces(depth) + rules[i].directive + ' : ' + rules[i].value + ';\n'; + } else { + ret += this.getSpaces(depth) + rules[i].value + ';\n'; + } + + } + return ret || '\n'; +}; + +/* + A very simple helper function returns number of spaces appended in a single string, + the number depends input parameter, namely input*2 + */ +fi.prototype.getSpaces = function (num) { + var ret = ''; + for (var i = 0; i < num * 4; i++) { + ret += ' '; + } + return ret; +}; + +/* + Given css string or objectArray, parses it and then for every selector, + prepends this.cssPreviewNamespace to prevent css collision issues + + @returns css string in which this.cssPreviewNamespace prepended + */ +fi.prototype.applyNamespacing = function (css, forcedNamespace) { + var cssObjectArray = css; + var namespaceClass = '.' + this.cssPreviewNamespace; + if (forcedNamespace !== undefined) { + namespaceClass = forcedNamespace; + } + + if (typeof css === 'string') { + cssObjectArray = this.parseCSS(css); + } + + for (var i = 0; i < cssObjectArray.length; i++) { + var obj = cssObjectArray[i]; + + //bypass namespacing for @font-face @keyframes @import + if (obj.selector.indexOf('@font-face') > -1 || obj.selector.indexOf('keyframes') > -1 || obj.selector.indexOf('@import') > -1 || obj.selector.indexOf('.form-all') > -1 || obj.selector.indexOf('#stage') > -1) { + continue; + } + + if (obj.type !== 'media') { + var selector = obj.selector.split(','); + var newSelector = []; + for (var j = 0; j < selector.length; j++) { + if (selector[j].indexOf('.supernova') === -1) { //do not apply namespacing to selectors including supernova + newSelector.push(namespaceClass + ' ' + selector[j]); + } else { + newSelector.push(selector[j]); + } + } + obj.selector = newSelector.join(','); + } else { + obj.subStyles = this.applyNamespacing(obj.subStyles, forcedNamespace); //handle media queries as well + } + } + + return cssObjectArray; +}; + +/* + given css string or object array, clears possible namespacing from + all of the selectors inside the css + */ +fi.prototype.clearNamespacing = function (css, returnObj) { + if (returnObj === undefined) { + returnObj = false; + } + var cssObjectArray = css; + var namespaceClass = '.' + this.cssPreviewNamespace; + if (typeof css === 'string') { + cssObjectArray = this.parseCSS(css); + } + + for (var i = 0; i < cssObjectArray.length; i++) { + var obj = cssObjectArray[i]; + + if (obj.type !== 'media') { + var selector = obj.selector.split(','); + var newSelector = []; + for (var j = 0; j < selector.length; j++) { + newSelector.push(selector[j].split(namespaceClass + ' ').join('')); + } + obj.selector = newSelector.join(','); + } else { + obj.subStyles = this.clearNamespacing(obj.subStyles, true); //handle media queries as well + } + } + if (returnObj === false) { + return this.getCSSForEditor(cssObjectArray); + } else { + return cssObjectArray; + } + +}; + +/* + creates a new style tag (also destroys the previous one) + and injects given css string into that css tag + */ +fi.prototype.createStyleElement = function (id, css, format) { + if (format === undefined) { + format = false; + } + + if (this.testMode === false && format !== 'nonamespace') { + //apply namespacing classes + css = this.applyNamespacing(css); + } + + if (typeof css != 'string') { + css = this.getCSSForEditor(css); + } + //apply formatting for css + if (format === true) { + css = this.getCSSForEditor(this.parseCSS(css)); + } + + if (this.testMode !== false) { + return this.testMode('create style #' + id, css); //if test mode, just pass result to callback + } + + var __el = document.getElementById(id); + if (__el) { + __el.parentNode.removeChild(__el); + } + + var head = document.head || document.getElementsByTagName('head')[0], + style = document.createElement('style'); + + style.id = id; + style.type = 'text/css'; + + head.appendChild(style); + + if (style.styleSheet && !style.sheet) { + style.styleSheet.cssText = css; + } else { + style.appendChild(document.createTextNode(css)); + } +}; + +export default fi; + +/* eslint-enable */ diff --git a/ui-ngx/src/app/core/http/widget.service.ts b/ui-ngx/src/app/core/http/widget.service.ts index b6b1ec008c..151a2865eb 100644 --- a/ui-ngx/src/app/core/http/widget.service.ts +++ b/ui-ngx/src/app/core/http/widget.service.ts @@ -16,21 +16,46 @@ import {Injectable} from '@angular/core'; import {defaultHttpOptions} from './http-utils'; -import {Observable} from 'rxjs/index'; +import { Observable, ReplaySubject, Subject, of, forkJoin, throwError } from 'rxjs/index'; import {HttpClient} from '@angular/common/http'; import {PageLink} from '@shared/models/page/page-link'; import {PageData} from '@shared/models/page/page-data'; import {WidgetsBundle} from '@shared/models/widgets-bundle.model'; -import { WidgetType } from '@shared/models/widget.models'; +import { + WidgetControllerDescriptor, + WidgetInfo, + WidgetType, + WidgetTypeInstance, + widgetActionSources, + MissingWidgetType, toWidgetInfo, ErrorWidgetType +} from '@shared/models/widget.models'; +import { UtilsService } from '@core/services/utils.service'; +import { isFunction, isUndefined } from '@core/utils'; +import { TranslateService } from '@ngx-translate/core'; +import { AuthPayload } from '@core/auth/auth.models'; +import cssjs from '@core/css/css'; +import { ResourcesService } from '../services/resources.service'; +import { catchError, map, switchMap } from 'rxjs/operators'; @Injectable({ providedIn: 'root' }) export class WidgetService { + private cssParser = new cssjs(); + + private widgetsInfoInMemoryCache = new Map(); + + private widgetsInfoFetchQueue = new Map>>(); + constructor( - private http: HttpClient - ) { } + private http: HttpClient, + private utils: UtilsService, + private resources: ResourcesService, + private translate: TranslateService + ) { + this.cssParser.testMode = false; + } public getWidgetBundles(pageLink: PageLink, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { @@ -58,4 +83,266 @@ export class WidgetService { defaultHttpOptions(ignoreLoading, ignoreErrors)); } + public getWidgetType(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.get(`/api/widgetType?isSystem=${isSystem}&bundleAlias=${bundleAlias}&alias=${widgetTypeAlias}`, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public getWidgetInfo(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean): Observable { + const widgetInfoSubject = new ReplaySubject(); + const widgetInfo = this.getWidgetInfoFromCache(bundleAlias, widgetTypeAlias, isSystem); + if (widgetInfo) { + widgetInfoSubject.next(widgetInfo); + widgetInfoSubject.complete(); + } else { + if (this.utils.widgetEditMode) { + // TODO: + } else { + const key = this.createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem); + let fetchQueue = this.widgetsInfoFetchQueue.get(key); + if (fetchQueue) { + fetchQueue.push(widgetInfoSubject); + } else { + fetchQueue = new Array>(); + this.widgetsInfoFetchQueue.set(key, fetchQueue); + this.getWidgetType(bundleAlias, widgetTypeAlias, isSystem).subscribe( + (widgetType) => { + this.loadWidget(widgetType, bundleAlias, isSystem, widgetInfoSubject); + }, + () => { + widgetInfoSubject.next(MissingWidgetType); + widgetInfoSubject.complete(); + this.resolveWidgetsInfoFetchQueue(key, MissingWidgetType); + } + ); + } + } + } + return widgetInfoSubject.asObservable(); + } + + private loadWidget(widgetType: WidgetType, bundleAlias: string, isSystem: boolean, widgetInfoSubject: Subject) { + const widgetInfo = toWidgetInfo(widgetType); + const key = this.createWidgetInfoCacheKey(bundleAlias, widgetInfo.alias, isSystem); + this.loadWidgetResources(widgetInfo, bundleAlias, isSystem).subscribe( + () => { + let widgetControllerDescriptor: WidgetControllerDescriptor = null; + try { + widgetControllerDescriptor = this.createWidgetControllerDescriptor(widgetInfo, key); + } catch (e) { + const details = this.utils.parseException(e); + const errorMessage = `Failed to compile widget script. \n Error: ${details.message}`; + this.processWidgetLoadError([errorMessage], key, widgetInfoSubject); + } + if (widgetControllerDescriptor) { + if (widgetControllerDescriptor.settingsSchema) { + widgetInfo.typeSettingsSchema = widgetControllerDescriptor.settingsSchema; + } + if (widgetControllerDescriptor.dataKeySettingsSchema) { + widgetInfo.typeDataKeySettingsSchema = widgetControllerDescriptor.dataKeySettingsSchema; + } + widgetInfo.typeParameters = widgetControllerDescriptor.typeParameters; + widgetInfo.actionSources = widgetControllerDescriptor.actionSources; + widgetInfo.widgetTypeFunction = widgetControllerDescriptor.widgetTypeFunction; + this.putWidgetInfoToCache(widgetInfo, bundleAlias, widgetInfo.alias, isSystem); + if (widgetInfoSubject) { + widgetInfoSubject.next(widgetInfo); + widgetInfoSubject.complete(); + } + this.resolveWidgetsInfoFetchQueue(key, widgetInfo); + } + }, + (errorMessages: string[]) => { + this.processWidgetLoadError(errorMessages, key, widgetInfoSubject); + } + ); + } + + private loadWidgetResources(widgetInfo: WidgetInfo, bundleAlias: string, isSystem: boolean): Observable { + const widgetNamespace = `widget-type-${(isSystem ? 'sys-' : '')}${bundleAlias}-${widgetInfo.alias}`; + this.cssParser.cssPreviewNamespace = widgetNamespace; + this.cssParser.createStyleElement(widgetNamespace, widgetInfo.templateCss); + const resourceTasks: Observable[] = []; + if (widgetInfo.resources.length > 0) { + widgetInfo.resources.forEach((resource) => { + resourceTasks.push( + this.resources.loadResource(resource.url).pipe( + catchError(e => of(`Failed to load widget resource: '${resource.url}'`)) + ) + ); + }); + return forkJoin(resourceTasks).pipe( + switchMap(msgs => { + let errors: string[]; + if (msgs && msgs.length) { + errors = msgs.filter(msg => msg && msg.length > 0); + } + if (errors && errors.length) { + return throwError(errors); + } else { + return of(null); + } + } + )); + } else { + return of(null); + } + } + + private createWidgetControllerDescriptor(widgetInfo: WidgetInfo, name: string): WidgetControllerDescriptor { + let widgetTypeFunctionBody = `return function ${name} (ctx) {\n` + + ' var self = this;\n' + + ' self.ctx = ctx;\n\n'; /*+ + + ' self.onInit = function() {\n\n' + + + ' }\n\n' + + + ' self.onDataUpdated = function() {\n\n' + + + ' }\n\n' + + + ' self.useCustomDatasources = function() {\n\n' + + + ' }\n\n' + + + ' self.typeParameters = function() {\n\n' + + return { + useCustomDatasources: false, + maxDatasources: -1, //unlimited + maxDataKeys: -1, //unlimited + dataKeysOptional: false, + stateData: false + }; + ' }\n\n' + + + ' self.actionSources = function() {\n\n' + + return { + 'headerButton': { + name: 'Header button', + multiple: true + } + }; + }\n\n' + + ' self.onResize = function() {\n\n' + + + ' }\n\n' + + + ' self.onEditModeChanged = function() {\n\n' + + + ' }\n\n' + + + ' self.onMobileModeChanged = function() {\n\n' + + + ' }\n\n' + + + ' self.getSettingsSchema = function() {\n\n' + + + ' }\n\n' + + + ' self.getDataKeySettingsSchema = function() {\n\n' + + + ' }\n\n' + + + ' self.onDestroy = function() {\n\n' + + + ' }\n\n' + + '}';*/ + + widgetTypeFunctionBody += widgetInfo.controllerScript; + widgetTypeFunctionBody += '\n};\n'; + + try { + + const widgetTypeFunction = new Function(widgetTypeFunctionBody); + const widgetType = widgetTypeFunction.apply(this); + const widgetTypeInstance: WidgetTypeInstance = new widgetType(); + const result: WidgetControllerDescriptor = { + widgetTypeFunction: widgetType + }; + if (isFunction(widgetTypeInstance.getSettingsSchema)) { + result.settingsSchema = widgetTypeInstance.getSettingsSchema(); + } + if (isFunction(widgetTypeInstance.getDataKeySettingsSchema)) { + result.dataKeySettingsSchema = widgetTypeInstance.getDataKeySettingsSchema(); + } + if (isFunction(widgetTypeInstance.typeParameters)) { + result.typeParameters = widgetTypeInstance.typeParameters(); + } else { + result.typeParameters = {}; + } + if (isFunction(widgetTypeInstance.useCustomDatasources)) { + result.typeParameters.useCustomDatasources = widgetTypeInstance.useCustomDatasources(); + } else { + result.typeParameters.useCustomDatasources = false; + } + if (isUndefined(result.typeParameters.maxDatasources)) { + result.typeParameters.maxDatasources = -1; + } + if (isUndefined(result.typeParameters.maxDataKeys)) { + result.typeParameters.maxDataKeys = -1; + } + if (isUndefined(result.typeParameters.dataKeysOptional)) { + result.typeParameters.dataKeysOptional = false; + } + if (isUndefined(result.typeParameters.stateData)) { + result.typeParameters.stateData = false; + } + if (isFunction(widgetTypeInstance.actionSources)) { + result.actionSources = widgetTypeInstance.actionSources(); + } else { + result.actionSources = {}; + } + for (const actionSourceId of Object.keys(widgetActionSources)) { + result.actionSources[actionSourceId] = {...widgetActionSources[actionSourceId]}; + result.actionSources[actionSourceId].name = this.translate.instant(result.actionSources[actionSourceId].name); + } + return result; + } catch (e) { + this.utils.processWidgetException(e); + throw e; + } + } + + private processWidgetLoadError(errorMessages: string[], cacheKey: string, widgetInfoSubject: Subject) { + const widgetInfo = {...ErrorWidgetType}; + errorMessages.forEach(error => { + widgetInfo.templateHtml += `
${error}
`; + }); + widgetInfo.templateHtml += '
-
import.no-file
+ +
{{ noFileText }}
{{ fileName }}
diff --git a/ui-ngx/src/app/shared/components/file-input.component.ts b/ui-ngx/src/app/shared/components/file-input.component.ts index c7275d6e46..82db88f12f 100644 --- a/ui-ngx/src/app/shared/components/file-input.component.ts +++ b/ui-ngx/src/app/shared/components/file-input.component.ts @@ -14,7 +14,17 @@ /// limitations under the License. /// -import { AfterViewInit, Component, forwardRef, Input, OnDestroy, ViewChild } from '@angular/core'; +import { + AfterViewInit, + Component, + EventEmitter, + forwardRef, + Input, + OnChanges, + OnDestroy, + Output, SimpleChanges, + ViewChild +} from '@angular/core'; import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; @@ -22,6 +32,7 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { Subscription } from 'rxjs'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { FlowDirective } from '@flowjs/ngx-flow'; +import { TranslateService } from '@ngx-translate/core'; @Component({ selector: 'tb-file-input', @@ -35,7 +46,7 @@ import { FlowDirective } from '@flowjs/ngx-flow'; } ] }) -export class FileInputComponent extends PageComponent implements AfterViewInit, OnDestroy, ControlValueAccessor { +export class FileInputComponent extends PageComponent implements AfterViewInit, OnDestroy, ControlValueAccessor, OnChanges { @Input() label: string; @@ -43,6 +54,12 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, @Input() accept = '*/*'; + @Input() + noFileText = 'import.no-file'; + + @Input() + inputId = 'select'; + @Input() allowedExtensions: string; @@ -64,9 +81,27 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, } } + private requiredAsErrorValue: boolean; + get requiredAsError(): boolean { + return this.requiredAsErrorValue; + } + @Input() + set requiredAsError(value: boolean) { + const newVal = coerceBooleanProperty(value); + if (this.requiredAsErrorValue !== newVal) { + this.requiredAsErrorValue = newVal; + } + } + @Input() disabled: boolean; + @Input() + existingFileName: string; + + @Output() + fileNameChanged = new EventEmitter(); + fileName: string; fileContent: any; @@ -77,7 +112,8 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, private propagateChange = null; - constructor(protected store: Store) { + constructor(protected store: Store, + public translate: TranslateService) { super(store); } @@ -135,11 +171,23 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, } writeValue(value: any): void { - this.fileName = null; + this.fileName = this.existingFileName || null; + } + + ngOnChanges(changes: SimpleChanges): void { + for (const propName of Object.keys(changes)) { + const change = changes[propName]; + if (change.currentValue !== change.previousValue) { + if (propName === 'existingFileName') { + this.fileName = this.existingFileName || null; + } + } + } } private updateModel() { this.propagateChange(this.fileContent); + this.fileNameChanged.emit(this.fileName); } clearFile() { 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 b611eca66c..dd68fea554 100644 --- a/ui-ngx/src/app/shared/models/rule-node.models.ts +++ b/ui-ngx/src/app/shared/models/rule-node.models.ts @@ -24,10 +24,11 @@ import { ComponentDescriptor, ComponentType } from '@shared/models/component-des import { EntityType, EntityTypeResource } from '@shared/models/entity-type.models'; import { Observable } from 'rxjs'; import { PageComponent } from '@shared/components/page.component'; -import { ComponentFactory, EventEmitter, Inject, OnDestroy, OnInit } from '@angular/core'; +import { AfterViewInit, ComponentFactory, EventEmitter, Inject, OnDestroy, OnInit } from '@angular/core'; import { RafService } from '@core/services/raf.service'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; +import { AbstractControl, FormGroup } from '@angular/forms'; export enum MsgDataType { JSON = 'JSON', @@ -83,10 +84,28 @@ export interface IRuleNodeConfigurationComponent { } export abstract class RuleNodeConfigurationComponent extends PageComponent implements - IRuleNodeConfigurationComponent, OnInit { + IRuleNodeConfigurationComponent, OnInit, AfterViewInit { ruleNodeId: string; - configuration: RuleNodeConfiguration; + + configurationValue: RuleNodeConfiguration; + + private configurationSet = false; + + set configuration(value: RuleNodeConfiguration) { + this.configurationValue = value; + if (!this.configurationSet) { + this.configurationSet = true; + this.setupConfiguration(value); + } else { + this.updateConfiguration(value); + } + } + + get configuration(): RuleNodeConfiguration { + return this.configurationValue; + } + configurationChangedEmiter = new EventEmitter(); configurationChanged = this.configurationChangedEmiter.asObservable(); @@ -94,21 +113,77 @@ export abstract class RuleNodeConfigurationComponent extends PageComponent imple super(store); } - ngOnInit() { - this.onConfigurationSet(this.configuration); + ngOnInit() {} + + ngAfterViewInit(): void { + setTimeout(() => { + if (!this.validateConfig()) { + this.configurationChangedEmiter.emit(null); + } + }, 0); } validate() { this.onValidate(); } - protected abstract onConfigurationSet(configuration: RuleNodeConfiguration); + protected setupConfiguration(configuration: RuleNodeConfiguration) { + this.onConfigurationSet(this.prepareInputConfig(configuration)); + this.updateValidators(false); + for (const trigger of this.validatorTriggers()) { + const path = trigger.split('.'); + let control: AbstractControl = this.configForm(); + for (const part of path) { + control = control.get(part); + } + control.valueChanges.subscribe(() => { + this.updateValidators(true); + }); + } + this.configForm().valueChanges.subscribe((updated: RuleNodeConfiguration) => { + this.onConfigurationChanged(updated); + }); + } + + protected updateConfiguration(configuration: RuleNodeConfiguration) { + this.configForm().reset(this.prepareInputConfig(configuration), {emitEvent: false}); + this.updateValidators(false); + } + + protected updateValidators(emitEvent: boolean) { + } + + protected validatorTriggers(): string[] { + return []; + } + + protected onConfigurationChanged(updated: RuleNodeConfiguration) { + this.configurationValue = updated; + if (this.validateConfig()) { + this.configurationChangedEmiter.emit(this.prepareOutputConfig(updated)); + } else { + this.configurationChangedEmiter.emit(null); + } + } - protected notifyConfigurationUpdated(configuration: RuleNodeConfiguration) { - this.configurationChangedEmiter.emit(configuration); + protected prepareInputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + return configuration; + } + + protected prepareOutputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration { + return configuration; + } + + protected validateConfig(): boolean { + return this.configForm().valid; } protected onValidate() {} + + protected abstract configForm(): FormGroup; + + protected abstract onConfigurationSet(configuration: RuleNodeConfiguration); + } From 6e323fd9214939d6d785219694f44b090497dc51 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 16 Jan 2020 12:55:18 +0200 Subject: [PATCH 069/133] Update rule nodes config --- .../resources/public/static/rulenode/rulenode-core-config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js index 1d40921e0d..796b064749 100644 --- a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js +++ b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js @@ -1,4 +1,4 @@ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@angular/core"),require("@angular/common"),require("@ngx-translate/core"),require("@shared/public-api"),require("@ngrx/store"),require("@angular/forms"),require("@core/public-api"),require("@angular/cdk/keycodes"),require("@angular/material"),require("rxjs"),require("rxjs/operators"),require("@home/components/public-api"),require("@angular/cdk/coercion")):"function"==typeof define&&define.amd?define("rulenode-core-config",["exports","@angular/core","@angular/common","@ngx-translate/core","@shared/public-api","@ngrx/store","@angular/forms","@core/public-api","@angular/cdk/keycodes","@angular/material","rxjs","rxjs/operators","@home/components/public-api","@angular/cdk/coercion"],t):t((e=e||self)["rulenode-core-config"]={},e.ng.core,e.ng.common,e["ngx-translate"],e.shared,e["ngrx-store"],e.ng.forms,e.core,e.ng.cdk.keycodes,e.ng.material,e.rxjs,e.rxjs.operators,e.publicApi$2,e.ng.cdk.coercion)}(this,(function(e,t,n,r,a,i,o,s,l,m,u,d,p,c){"use strict"; +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@angular/core"),require("@angular/common"),require("@ngx-translate/core"),require("@shared/public-api"),require("@ngrx/store"),require("@angular/forms"),require("@core/public-api"),require("@angular/cdk/keycodes"),require("@home/components/public-api"),require("@angular/cdk/coercion"),require("@angular/material"),require("rxjs"),require("rxjs/operators")):"function"==typeof define&&define.amd?define("rulenode-core-config",["exports","@angular/core","@angular/common","@ngx-translate/core","@shared/public-api","@ngrx/store","@angular/forms","@core/public-api","@angular/cdk/keycodes","@home/components/public-api","@angular/cdk/coercion","@angular/material","rxjs","rxjs/operators"],t):t((e=e||self)["rulenode-core-config"]={},e.ng.core,e.ng.common,e["ngx-translate"],e.shared,e["ngrx-store"],e.ng.forms,e.core,e.ng.cdk.keycodes,e.publicApi$2,e.ng.cdk.coercion,e.ng.material,e.rxjs,e.rxjs.operators)}(this,(function(e,t,r,n,a,o,i,l,s,m,u,d,p,c){"use strict"; /*! ***************************************************************************** Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use @@ -12,5 +12,5 @@ See the Apache Version 2.0 License for specific language governing permissions and limitations under the License. - ***************************************************************************** */var f=function(e,t){return(f=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])})(e,t)};function g(e,t){function n(){this.constructor=e}f(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}function y(e){var t="function"==typeof Symbol&&e[Symbol.iterator],n=0;return t?t.call(e):{next:function(){return e&&n>=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}}}var b=function(e){function n(t){var n=e.call(this,t)||this;return n.store=t,n}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.onConfigurationSet=function(e){},n.decorators=[{type:t.Component,args:[{selector:"tb-node-empty-config",template:"
"}]}],n.ctorParameters=function(){return[{type:i.Store}]},n}(a.RuleNodeConfigurationComponent);var h=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.attributeScopes=Object.keys(a.AttributeScope),r.telemetryTypeTranslationsMap=a.telemetryTypeTranslations,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.onConfigurationSet=function(e){var t=this;this.attributesConfigForm=this.fb.group({scope:[e?e.scope:null,[o.Validators.required]]}),this.attributesConfigForm.valueChanges.subscribe((function(e){t.attributesConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.decorators=[{type:t.Component,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.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var C=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.onConfigurationSet=function(e){var t=this;this.timeseriesConfigForm=this.fb.group({defaultTTL:[e?e.defaultTTL:null,[o.Validators.required,o.Validators.min(0)]]}),this.timeseriesConfigForm.valueChanges.subscribe((function(e){t.timeseriesConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.decorators=[{type:t.Component,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'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var v=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.onConfigurationSet=function(e){var t=this;this.rpcRequestConfigForm=this.fb.group({timeoutInSeconds:[e?e.timeoutInSeconds:null,[o.Validators.required,o.Validators.min(0)]]}),this.rpcRequestConfigForm.valueChanges.subscribe((function(e){t.rpcRequestConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.decorators=[{type:t.Component,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'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var F=function(e){function n(t,n,r,a){var i=e.call(this,t)||this;return i.store=t,i.fb=n,i.nodeScriptTestService=r,i.translate=a,i}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.onConfigurationSet=function(e){var t=this;this.logConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[o.Validators.required]]}),this.logConfigForm.valueChanges.subscribe((function(e){t.logConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.testScript=function(){var e=this,t=this.logConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"string",this.translate.instant("tb.rulenode.to-string"),"ToString",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.logConfigForm.get("jsScript").setValue(t)}))},n.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},n.decorators=[{type:t.Component,args:[{selector:"tb-action-node-log-config",template:'
\n \n \n \n
\n \n
\n
\n'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder},{type:s.NodeScriptTestService},{type:r.TranslateService}]},n.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},n}(a.RuleNodeConfigurationComponent);var T=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.assignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[o.Validators.required]],createCustomerIfNotExists:[!!e&&e.createCustomerIfNotExists,[]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[o.Validators.required,o.Validators.min(0)]]}),this.assignCustomerConfigForm.valueChanges.subscribe((function(e){t.assignCustomerConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.validateConfig=function(){return this.assignCustomerConfigForm.valid},n.decorators=[{type:t.Component,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 \n \n
\n'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var x=function(e){function n(t,n,r,a){var i=e.call(this,t)||this;return i.store=t,i.fb=n,i.nodeScriptTestService=r,i.translate=a,i}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.onConfigurationSet=function(e){var t=this;this.clearAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[o.Validators.required]],alarmType:[e?e.alarmType:null,[o.Validators.required]]}),this.clearAlarmConfigForm.valueChanges.subscribe((function(e){t.clearAlarmConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.testScript=function(){var e=this,t=this.clearAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.clearAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},n.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},n.decorators=[{type:t.Component,args:[{selector:"tb-action-node-clear-alarm-config",template:'
\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'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder},{type:s.NodeScriptTestService},{type:r.TranslateService}]},n.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},n}(a.RuleNodeConfigurationComponent);var I=function(e){function n(t,n,r,i){var o=e.call(this,t)||this;return o.store=t,o.fb=n,o.nodeScriptTestService=r,o.translate=i,o.alarmSeverities=Object.keys(a.AlarmSeverity),o.alarmSeverityTranslationMap=a.alarmSeverityTranslations,o.separatorKeysCodes=[l.ENTER,l.COMMA,l.SEMICOLON],o}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.createAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[o.Validators.required]],useMessageAlarmData:[!!e&&e.useMessageAlarmData,[]],alarmType:[e?e.alarmType:null,[]],severity:[e?e.severity:null,[]],propagate:[!!e&&e.propagate,[]],relationTypes:[e?e.relationTypes:null,[]]}),this.updateValidators(!1),this.createAlarmConfigForm.get("useMessageAlarmData").valueChanges.subscribe((function(){t.updateValidators(!0)})),this.createAlarmConfigForm.valueChanges.subscribe((function(e){t.validateConfig()?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.updateValidators=function(e){this.createAlarmConfigForm.get("useMessageAlarmData").value?(this.createAlarmConfigForm.get("alarmType").setValidators([]),this.createAlarmConfigForm.get("severity").setValidators([])):(this.createAlarmConfigForm.get("alarmType").setValidators([o.Validators.required]),this.createAlarmConfigForm.get("severity").setValidators([o.Validators.required])),this.createAlarmConfigForm.get("alarmType").updateValueAndValidity({emitEvent:e}),this.createAlarmConfigForm.get("severity").updateValueAndValidity({emitEvent:e})},n.prototype.validateConfig=function(){return this.createAlarmConfigForm.valid},n.prototype.testScript=function(){var e=this,t=this.createAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.createAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},n.prototype.removeKey=function(e,t){var n=this.createAlarmConfigForm.get(t).value,r=n.indexOf(e);r>=0&&(n.splice(r,1),this.createAlarmConfigForm.get(t).setValue(n,{emitEvent:!0}))},n.prototype.addKey=function(e,t){var n=e.input,r=e.value;if((r||"").trim()){r=r.trim();var a=this.createAlarmConfigForm.get(t).value;a&&-1!==a.indexOf(r)||(a||(a=[]),a.push(r),this.createAlarmConfigForm.get(t).setValue(a,{emitEvent:!0}))}n&&(n.value="")},n.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},n.decorators=[{type:t.Component,args:[{selector:"tb-action-node-create-alarm-config",template:'
\n \n \n \n
\n \n
\n \n {{ \'tb.rulenode.use-message-alarm-data\' | translate }}\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.alarm-severity\n \n \n {{ alarmSeverityTranslationMap.get(severity) | translate }}\n \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 \n \n
\n
\n
\n'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder},{type:s.NodeScriptTestService},{type:r.TranslateService}]},n.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},n}(a.RuleNodeConfigurationComponent);var q=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.directionTypes=Object.keys(a.EntitySearchDirection),r.directionTypeTranslations=a.entitySearchDirectionTranslations,r.entityType=a.EntityType,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.createRelationConfigForm=this.fb.group({direction:[e?e.direction:null,[o.Validators.required]],entityType:[e?e.entityType:null,[o.Validators.required]],entityNamePattern:[e?e.entityNamePattern:null,[]],entityTypePattern:[e?e.entityTypePattern:null,[]],relationType:[e?e.relationType:null,[o.Validators.required]],createEntityIfNotExists:[!!e&&e.createEntityIfNotExists,[]],removeCurrentRelations:[!!e&&e.removeCurrentRelations,[]],changeOriginatorToRelatedEntity:[!!e&&e.changeOriginatorToRelatedEntity,[]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[o.Validators.required,o.Validators.min(0)]]}),this.updateValidators(!1),this.createRelationConfigForm.get("entityType").valueChanges.subscribe((function(){t.updateValidators(!0)})),this.createRelationConfigForm.valueChanges.subscribe((function(e){t.validateConfig()?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.updateValidators=function(e){var t=this.createRelationConfigForm.get("entityType").value;t?this.createRelationConfigForm.get("entityNamePattern").setValidators([o.Validators.required]):this.createRelationConfigForm.get("entityNamePattern").setValidators([]),!t||t!==a.EntityType.DEVICE&&t!==a.EntityType.ASSET?this.createRelationConfigForm.get("entityTypePattern").setValidators([]):this.createRelationConfigForm.get("entityTypePattern").setValidators([o.Validators.required]),this.createRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e}),this.createRelationConfigForm.get("entityTypePattern").updateValueAndValidity({emitEvent:e})},n.prototype.validateConfig=function(){return this.createRelationConfigForm.valid},n.decorators=[{type:t.Component,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 \n \n
\n'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var S=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.msgDelayConfigForm=this.fb.group({useMetadataPeriodInSecondsPatterns:[!!e&&e.useMetadataPeriodInSecondsPatterns,[]],periodInSeconds:[e?e.periodInSeconds:null,[]],periodInSecondsPattern:[e?e.periodInSecondsPattern:null,[]],maxPendingMsgs:[e?e.maxPendingMsgs:null,[o.Validators.required,o.Validators.min(1),o.Validators.max(1e5)]]}),this.updateValidators(!1),this.msgDelayConfigForm.get("useMetadataPeriodInSecondsPatterns").valueChanges.subscribe((function(){t.updateValidators(!0)})),this.msgDelayConfigForm.valueChanges.subscribe((function(e){t.msgDelayConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.updateValidators=function(e){this.msgDelayConfigForm.get("useMetadataPeriodInSecondsPatterns").value?(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([o.Validators.required]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([])):(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([o.Validators.required,o.Validators.min(0)])),this.msgDelayConfigForm.get("periodInSecondsPattern").updateValueAndValidity({emitEvent:e}),this.msgDelayConfigForm.get("periodInSeconds").updateValueAndValidity({emitEvent:e})},n.prototype.validateConfig=function(){return this.msgDelayConfigForm.valid},n.decorators=[{type:t.Component,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'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var k=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.directionTypes=Object.keys(a.EntitySearchDirection),r.directionTypeTranslations=a.entitySearchDirectionTranslations,r.entityType=a.EntityType,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.deleteRelationConfigForm=this.fb.group({deleteForSingleEntity:[!!e&&e.deleteForSingleEntity,[]],direction:[e?e.direction:null,[o.Validators.required]],entityType:[e?e.entityType:null,[]],entityNamePattern:[e?e.entityNamePattern:null,[]],relationType:[e?e.relationType:null,[o.Validators.required]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[o.Validators.required,o.Validators.min(0)]]}),this.updateValidators(!1,!1),this.deleteRelationConfigForm.get("deleteForSingleEntity").valueChanges.subscribe((function(){t.updateValidators(!0,!0)})),this.deleteRelationConfigForm.get("entityType").valueChanges.subscribe((function(){t.updateValidators(!0,!1)})),this.deleteRelationConfigForm.valueChanges.subscribe((function(e){t.validateConfig()?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.updateValidators=function(e,t){var n=this.deleteRelationConfigForm.get("deleteForSingleEntity").value,r=this.deleteRelationConfigForm.get("entityType").value;n?this.deleteRelationConfigForm.get("entityType").setValidators([o.Validators.required]):this.deleteRelationConfigForm.get("entityType").setValidators([]),n&&r?this.deleteRelationConfigForm.get("entityNamePattern").setValidators([o.Validators.required]):this.deleteRelationConfigForm.get("entityNamePattern").setValidators([]),this.deleteRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:t}),this.deleteRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e})},n.prototype.validateConfig=function(){return this.deleteRelationConfigForm.valid},n.decorators=[{type:t.Component,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 \n \n
\n'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var N=function(){function e(){}return e.decorators=[{type:t.NgModule,args:[{declarations:[h,C,v,F,T,x,I,q,S,k],imports:[n.CommonModule,a.SharedModule],exports:[h,C,v,F,T,x,I,q,S,k]}]}],e}(),A=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.separatorKeysCodes=[l.ENTER,l.COMMA,l.SEMICOLON],r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.checkMessageConfigForm=this.fb.group({messageNames:[e?e.messageNames:null,[]],metadataNames:[e?e.metadataNames:null,[]],checkAllKeys:[!!e&&e.checkAllKeys,[]]}),this.checkMessageConfigForm.valueChanges.subscribe((function(e){t.validateConfig()?t.notifyConfigurationUpdated(t.checkMessageConfigForm.value):t.notifyConfigurationUpdated(null)}))},n.prototype.validateConfig=function(){var e=this.checkMessageConfigForm.get("messageNames").value,t=this.checkMessageConfigForm.get("metadataNames").value;return e.length>0||t.length>0},n.prototype.removeMessageName=function(e){var t=this.checkMessageConfigForm.get("messageNames").value,n=t.indexOf(e);n>=0&&(t.splice(n,1),this.checkMessageConfigForm.get("messageNames").setValue(t,{emitEvent:!0}))},n.prototype.removeMetadataName=function(e){var t=this.checkMessageConfigForm.get("metadataNames").value,n=t.indexOf(e);n>=0&&(t.splice(n,1),this.checkMessageConfigForm.get("metadataNames").setValue(t,{emitEvent:!0}))},n.prototype.addMessageName=function(e){var t=e.input,n=e.value;if((n||"").trim()){n=n.trim();var r=this.checkMessageConfigForm.get("messageNames").value;r&&-1!==r.indexOf(n)||(r||(r=[]),r.push(n),this.checkMessageConfigForm.get("messageNames").setValue(r,{emitEvent:!0}))}t&&(t.value="")},n.prototype.addMetadataName=function(e){var t=e.input,n=e.value;if((n||"").trim()){n=n.trim();var r=this.checkMessageConfigForm.get("metadataNames").value;r&&-1!==r.indexOf(n)||(r||(r=[]),r.push(n),this.checkMessageConfigForm.get("metadataNames").setValue(r,{emitEvent:!0}))}t&&(t.value="")},n.decorators=[{type:t.Component,args:[{selector:"tb-filter-node-check-message-config",template:'
\n \n \n \n \n \n {{messageName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n \n \n \n \n {{metadataName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\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:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var E=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.entitySearchDirection=Object.keys(a.EntitySearchDirection),r.entitySearchDirectionTranslationsMap=a.entitySearchDirectionTranslations,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.checkRelationConfigForm=this.fb.group({checkForSingleEntity:[!!e&&e.checkForSingleEntity,[]],direction:[e?e.direction:null,[]],entityType:[e?e.entityType:null,e&&e.checkForSingleEntity?[o.Validators.required]:[]],entityId:[e?e.entityId:null,e&&e.checkForSingleEntity?[o.Validators.required]:[]],relationType:[e?e.relationType:null,[o.Validators.required]]}),this.checkRelationConfigForm.get("checkForSingleEntity").valueChanges.subscribe((function(e){t.checkRelationConfigForm.get("entityType").setValidators(e?[o.Validators.required]:[]),t.checkRelationConfigForm.get("entityType").updateValueAndValidity(),t.checkRelationConfigForm.get("entityId").setValidators(e?[o.Validators.required]:[]),t.checkRelationConfigForm.get("entityId").updateValueAndValidity()})),this.checkRelationConfigForm.valueChanges.subscribe((function(e){t.validateConfig()?t.notifyConfigurationUpdated(t.checkRelationConfigForm.value):t.notifyConfigurationUpdated(null)}))},n.prototype.validateConfig=function(){return this.checkRelationConfigForm.valid},n.decorators=[{type:t.Component,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'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var V={CUSTOMER:"CUSTOMER",TENANT:"TENANT",RELATED:"RELATED",ALARM_ORIGINATOR:"ALARM_ORIGINATOR"},L=new Map([[V.CUSTOMER,"tb.rulenode.originator-customer"],[V.TENANT,"tb.rulenode.originator-tenant"],[V.RELATED,"tb.rulenode.originator-related"],[V.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator"]]),M={CIRCLE:"CIRCLE",POLYGON:"POLYGON"},O=new Map([[M.CIRCLE,"tb.rulenode.perimeter-circle"],[M.POLYGON,"tb.rulenode.perimeter-polygon"]]),R={MILLISECONDS:"MILLISECONDS",SECONDS:"SECONDS",MINUTES:"MINUTES",HOURS:"HOURS",DAYS:"DAYS"},D=new Map([[R.MILLISECONDS,"tb.rulenode.time-unit-milliseconds"],[R.SECONDS,"tb.rulenode.time-unit-seconds"],[R.MINUTES,"tb.rulenode.time-unit-minutes"],[R.HOURS,"tb.rulenode.time-unit-hours"],[R.DAYS,"tb.rulenode.time-unit-days"]]),w={METER:"METER",KILOMETER:"KILOMETER",FOOT:"FOOT",MILE:"MILE",NAUTICAL_MILE:"NAUTICAL_MILE"},P=new Map([[w.METER,"tb.rulenode.range-unit-meter"],[w.KILOMETER,"tb.rulenode.range-unit-kilometer"],[w.FOOT,"tb.rulenode.range-unit-foot"],[w.MILE,"tb.rulenode.range-unit-mile"],[w.NAUTICAL_MILE,"tb.rulenode.range-unit-nautical-mile"]]),U={TITLE:"TITLE",COUNTRY:"COUNTRY",STATE:"STATE",ZIP:"ZIP",ADDRESS:"ADDRESS",ADDRESS2:"ADDRESS2",PHONE:"PHONE",EMAIL:"EMAIL",ADDITIONAL_INFO:"ADDITIONAL_INFO"},K=new Map([[U.TITLE,"tb.rulenode.entity-details-title"],[U.COUNTRY,"tb.rulenode.entity-details-country"],[U.STATE,"tb.rulenode.entity-details-state"],[U.ZIP,"tb.rulenode.entity-details-zip"],[U.ADDRESS,"tb.rulenode.entity-details-address"],[U.ADDRESS2,"tb.rulenode.entity-details-address2"],[U.PHONE,"tb.rulenode.entity-details-phone"],[U.EMAIL,"tb.rulenode.entity-details-email"],[U.ADDITIONAL_INFO,"tb.rulenode.entity-details-additional_info"]]),j={FIRST:"FIRST",LAST:"LAST",ALL:"ALL"},G={ASC:"ASC",DESC:"DESC"},B=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.perimeterType=M,r.perimeterTypes=Object.keys(M),r.perimeterTypeTranslationMap=O,r.rangeUnits=Object.keys(w),r.rangeUnitTranslationMap=P,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.geoFilterConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[o.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[o.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType: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,[]]}),this.updateValidators(!1),this.geoFilterConfigForm.get("fetchPerimeterInfoFromMessageMetadata").valueChanges.subscribe((function(){t.updateValidators(!0)})),this.geoFilterConfigForm.get("perimeterType").valueChanges.subscribe((function(){t.updateValidators(!0)})),this.geoFilterConfigForm.valueChanges.subscribe((function(e){t.validateConfig()?t.notifyConfigurationUpdated(t.geoFilterConfigForm.value):t.notifyConfigurationUpdated(null)}))},n.prototype.updateValidators=function(e){var t=this.geoFilterConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,n=this.geoFilterConfigForm.get("perimeterType").value;t?this.geoFilterConfigForm.get("perimeterType").setValidators([]):this.geoFilterConfigForm.get("perimeterType").setValidators([o.Validators.required]),t||n!==M.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([o.Validators.required]),this.geoFilterConfigForm.get("centerLongitude").setValidators([o.Validators.required]),this.geoFilterConfigForm.get("range").setValidators([o.Validators.required]),this.geoFilterConfigForm.get("rangeUnit").setValidators([o.Validators.required])),t||n!==M.POLYGON?this.geoFilterConfigForm.get("polygonsDefinition").setValidators([]):this.geoFilterConfigForm.get("polygonsDefinition").setValidators([o.Validators.required]),this.geoFilterConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),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})},n.prototype.validateConfig=function(){return this.geoFilterConfigForm.valid},n.decorators=[{type:t.Component,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.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \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'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var H=function(e){function n(t,n,r,i){var o,s,m=e.call(this,t)||this;m.store=t,m.translate=n,m.truncate=r,m.fb=i,m.separatorKeysCodes=[l.ENTER,l.COMMA,l.SEMICOLON],m.messageTypes=[],m.messageTypesList=[],m.searchText="",m.messageTypeConfigForm=m.fb.group({messageType:[null]});try{for(var u=y(Object.keys(a.MessageType)),d=u.next();!d.done;d=u.next()){var p=d.value;m.messageTypesList.push({name:a.messageTypeNames.get(a.MessageType[p]),value:p})}}catch(e){o={error:e}}finally{try{d&&!d.done&&(s=u.return)&&s.call(u)}finally{if(o)throw o.error}}return m}return g(n,e),n.prototype.ngOnInit=function(){var t=this;e.prototype.ngOnInit.call(this),this.filteredMessageTypes=this.messageTypeConfigForm.get("messageType").valueChanges.pipe(d.startWith(""),d.map((function(e){return e||""})),d.mergeMap((function(e){return t.fetchMessageTypes(e)})),d.share())},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.messageTypes.length||e.updateModel()}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.searchText="",this.messageTypes.length=0,e&&e.messageTypes&&e.messageTypes.forEach((function(e){var n=t.messageTypesList.find((function(t){return t.value===e}));n?t.messageTypes.push({name:n.name,value:n.value}):t.messageTypes.push({name:e,value:e})})),this.messageTypeConfigForm.get("messageType").patchValue("",{emitEvent:!0})},n.prototype.displayMessageTypeFn=function(e){return e?e.name:void 0},n.prototype.textIsNotEmpty=function(e){return!!(e&&null!=e&&e.length>0)},n.prototype.createMessageType=function(e,t){e.preventDefault(),this.transformMessageType(t)},n.prototype.add=function(e){this.transformMessageType(e.value)},n.prototype.fetchMessageTypes=function(e){if(this.searchText=e,this.searchText&&this.searchText.length){var t=this.searchText.toUpperCase();return u.of(this.messageTypesList.filter((function(e){return e.name.toUpperCase().includes(t)})))}return u.of(this.messageTypesList)},n.prototype.transformMessageType=function(e){if((e||"").trim()){var t=null,n=e.trim(),r=this.messageTypesList.find((function(e){return e.name===n}));(t=r?{name:r.name,value:r.value}:{name:n,value:n})&&this.addMessageType(t)}this.clear("")},n.prototype.remove=function(e){var t=this.messageTypes.indexOf(e);t>=0&&(this.messageTypes.splice(t,1),this.updateModel())},n.prototype.selected=function(e){this.addMessageType(e.option.value),this.clear("")},n.prototype.addMessageType=function(e){-1===this.messageTypes.findIndex((function(t){return t.value===e.value}))&&(this.messageTypes.push(e),this.updateModel())},n.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.messageTypeInput.nativeElement.value=e,this.messageTypeConfigForm.get("messageType").patchValue(null,{emitEvent:!0}),setTimeout((function(){t.messageTypeInput.nativeElement.blur(),t.messageTypeInput.nativeElement.focus()}),0)},n.prototype.updateModel=function(){if(this.messageTypes.length){this.chipList.errorState=!1;var e={messageTypes:this.messageTypes.map((function(e){return e.value}))};this.notifyConfigurationUpdated(e)}else this.chipList.errorState=!0,this.notifyConfigurationUpdated(null)},n.decorators=[{type:t.Component,args:[{selector:"tb-filter-node-message-type-config",template:'\n tb.rulenode.message-types-filter\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 {{ translate.get(\'tb.rulenode.no-message-type-matching\',\n {messageType: truncate.transform(searchText, true, 6, '...')}) | async }}\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'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:r.TranslateService},{type:a.TruncatePipe},{type:o.FormBuilder}]},n.propDecorators={chipList:[{type:t.ViewChild,args:["chipList",{static:!1}]}],matAutocomplete:[{type:t.ViewChild,args:["messageTypeAutocomplete",{static:!1}]}],messageTypeInput:[{type:t.ViewChild,args:["messageTypeInput",{static:!1}]}]},n}(a.RuleNodeConfigurationComponent);var $=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.allowedEntityTypes=[a.EntityType.DEVICE,a.EntityType.ASSET,a.EntityType.ENTITY_VIEW,a.EntityType.TENANT,a.EntityType.CUSTOMER,a.EntityType.USER,a.EntityType.DASHBOARD,a.EntityType.RULE_CHAIN,a.EntityType.RULE_NODE],r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.originatorTypeConfigForm=this.fb.group({originatorTypes:[e?e.originatorTypes:null,[o.Validators.required]]}),this.originatorTypeConfigForm.valueChanges.subscribe((function(e){t.originatorTypeConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.validateConfig=function(){return this.originatorTypeConfigForm.valid},n.decorators=[{type:t.Component,args:[{selector:"tb-filter-node-originator-type-config",template:'
\n \n \n \n
\n',styles:[":host ::ng-deep tb-entity-type-list .mat-form-field-flex{padding-top:0}:host ::ng-deep tb-entity-type-list .mat-form-field-infix{border-top:0}"]}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var _=function(e){function n(t,n,r,a){var i=e.call(this,t)||this;return i.store=t,i.fb=n,i.nodeScriptTestService=r,i.translate=a,i}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.onConfigurationSet=function(e){var t=this;this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[o.Validators.required]]}),this.scriptConfigForm.valueChanges.subscribe((function(e){t.scriptConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"filter",this.translate.instant("tb.rulenode.filter"),"Filter",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},n.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},n.decorators=[{type:t.Component,args:[{selector:"tb-filter-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder},{type:s.NodeScriptTestService},{type:r.TranslateService}]},n.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},n}(a.RuleNodeConfigurationComponent);var Q=function(e){function n(t,n,r,a){var i=e.call(this,t)||this;return i.store=t,i.fb=n,i.nodeScriptTestService=r,i.translate=a,i}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.onConfigurationSet=function(e){var t=this;this.switchConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[o.Validators.required]]}),this.switchConfigForm.valueChanges.subscribe((function(e){t.switchConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.testScript=function(){var e=this,t=this.switchConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"switch",this.translate.instant("tb.rulenode.switch"),"Switch",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.switchConfigForm.get("jsScript").setValue(t)}))},n.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},n.decorators=[{type:t.Component,args:[{selector:"tb-filter-node-switch-config",template:'
\n \n \n \n
\n \n
\n
\n'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder},{type:s.NodeScriptTestService},{type:r.TranslateService}]},n.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},n}(a.RuleNodeConfigurationComponent);var z=function(){function e(){}return e.decorators=[{type:t.NgModule,args:[{declarations:[A,E,B,H,$,_,Q],imports:[n.CommonModule,a.SharedModule],exports:[A,E,B,H,$,_,Q]}]}],e}(),W=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.customerAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[o.Validators.required]]}),this.customerAttributesConfigForm.valueChanges.subscribe((function(e){t.customerAttributesConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.validateConfig=function(){return this.customerAttributesConfigForm.valid},n.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-customer-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var Y=function(e){function n(t,n,r,a){var i=e.call(this,t)||this;return i.store=t,i.translate=n,i.injector=r,i.fb=a,i.propagateChange=null,i.valueChangeSubscription=null,i}return g(n,e),Object.defineProperty(n.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=c.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),n.prototype.ngOnInit=function(){this.ngControl=this.injector.get(o.NgControl),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.kvListFormGroup=this.fb.group({}),this.kvListFormGroup.addControl("keyVals",this.fb.array([]))},n.prototype.keyValsFormArray=function(){return this.kvListFormGroup.get("keyVals")},n.prototype.registerOnChange=function(e){this.propagateChange=e},n.prototype.registerOnTouched=function(e){},n.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.kvListFormGroup.disable({emitEvent:!1}):this.kvListFormGroup.enable({emitEvent:!1})},n.prototype.writeValue=function(e){var t,n,r=this;this.valueChangeSubscription&&this.valueChangeSubscription.unsubscribe();var a=[];if(e)try{for(var i=y(Object.keys(e)),s=i.next();!s.done;s=i.next()){var l=s.value;Object.prototype.hasOwnProperty.call(e,l)&&a.push(this.fb.group({key:[l,[o.Validators.required]],value:[e[l],[o.Validators.required]]}))}}catch(e){t={error:e}}finally{try{s&&!s.done&&(n=i.return)&&n.call(i)}finally{if(t)throw t.error}}this.kvListFormGroup.setControl("keyVals",this.fb.array(a)),this.valueChangeSubscription=this.kvListFormGroup.valueChanges.subscribe((function(){r.updateModel()}))},n.prototype.removeKeyVal=function(e){this.kvListFormGroup.get("keyVals").removeAt(e)},n.prototype.addKeyVal=function(){this.kvListFormGroup.get("keyVals").push(this.fb.group({key:["",[o.Validators.required]],value:["",[o.Validators.required]]}))},n.prototype.validate=function(e){var t=!0;return this.kvListFormGroup.get("keyVals").value.length||(t=!this.required),t?null:{kvMap:{valid:!1}}},n.prototype.updateModel=function(){var e=this.kvListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.kvListFormGroup.valid)this.propagateChange(null);else{var t={};e.forEach((function(e){t[e.key]=e.value})),this.propagateChange(t)}},n.decorators=[{type:t.Component,args:[{selector:"tb-kv-map-config",template:'
\n
\n {{ keyText }}\n {{ valText }}\n \n
\n
\n
\n \n \n \n \n {{ keyRequiredText | translate }}\n \n \n \n \n \n \n {{ valRequiredText | translate }}\n \n \n \n
\n
\n \n
\n \n
\n
\n',providers:[{provide:o.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0},{provide:o.NG_VALIDATORS,useExisting:t.forwardRef((function(){return n})),multi:!0}],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:rgba(0,0,0,.54);font-size:12px;font-weight:700;white-space:nowrap}:host .tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:20px;max-height:300px;overflow:auto}:host .tb-kv-map-config .body .row{padding-top:5px;max-height:40px}:host .tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell{margin:0;max-height:40px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell .mat-form-field-infix{border-top:0}:host ::ng-deep .tb-kv-map-config .body button.mat-button{margin:0}"]}]}],n.ctorParameters=function(){return[{type:i.Store},{type:r.TranslateService},{type:t.Injector},{type:o.FormBuilder}]},n.propDecorators={disabled:[{type:t.Input}],requiredText:[{type:t.Input}],keyText:[{type:t.Input}],keyRequiredText:[{type:t.Input}],valText:[{type:t.Input}],valRequiredText:[{type:t.Input}],required:[{type:t.Input}]},n}(a.PageComponent);var J=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.directionTypes=Object.keys(a.EntitySearchDirection),r.directionTypeTranslations=a.entitySearchDirectionTranslations,r.entityType=a.EntityType,r.propagateChange=null,r}return g(n,e),Object.defineProperty(n.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=c.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),n.prototype.ngOnInit=function(){var e=this;this.deviceRelationsQueryFormGroup=this.fb.group({direction:[null,[o.Validators.required]],maxLevel:[null,[]],relationType:[null],deviceTypes:[null,[o.Validators.required]]}),this.deviceRelationsQueryFormGroup.valueChanges.subscribe((function(t){e.deviceRelationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},n.prototype.registerOnChange=function(e){this.propagateChange=e},n.prototype.registerOnTouched=function(e){},n.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.deviceRelationsQueryFormGroup.disable({emitEvent:!1}):this.deviceRelationsQueryFormGroup.enable({emitEvent:!1})},n.prototype.writeValue=function(e){this.deviceRelationsQueryFormGroup.reset(e,{emitEvent:!1})},n.decorators=[{type:t.Component,args:[{selector:"tb-device-relations-query-config",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
\n
relation.relation-type
\n \n \n
device.device-types
\n \n \n
\n',providers:[{provide:o.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0}]}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n.propDecorators={disabled:[{type:t.Input}],required:[{type:t.Input}]},n}(a.PageComponent);var Z=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.directionTypes=Object.keys(a.EntitySearchDirection),r.directionTypeTranslations=a.entitySearchDirectionTranslations,r.propagateChange=null,r}return g(n,e),Object.defineProperty(n.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=c.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),n.prototype.ngOnInit=function(){var e=this;this.relationsQueryFormGroup=this.fb.group({direction:[null,[o.Validators.required]],maxLevel:[null,[]],filters:[null]}),this.relationsQueryFormGroup.valueChanges.subscribe((function(t){e.relationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},n.prototype.registerOnChange=function(e){this.propagateChange=e},n.prototype.registerOnTouched=function(e){},n.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.relationsQueryFormGroup.disable({emitEvent:!1}):this.relationsQueryFormGroup.enable({emitEvent:!1})},n.prototype.writeValue=function(e){this.relationsQueryFormGroup.reset(e,{emitEvent:!1})},n.decorators=[{type:t.Component,args:[{selector:"tb-relations-query-config",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
\n
relation.relation-filters
\n \n
\n',providers:[{provide:o.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0}]}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n.propDecorators={disabled:[{type:t.Input}],required:[{type:t.Input}]},n}(a.PageComponent);var X=function(){function e(){}return e.decorators=[{type:t.NgModule,args:[{declarations:[Y,J,Z],imports:[n.CommonModule,a.SharedModule,p.HomeComponentsModule],exports:[Y,J,Z]}]}],e}(),ee=function(e){function n(t,n,r){var a,i,o=e.call(this,t)||this;o.store=t,o.translate=n,o.fb=r,o.entityDetailsTranslationsMap=K,o.entityDetailsList=[],o.searchText="",o.displayDetailsFn=o.displayDetails.bind(o);try{for(var s=y(Object.keys(U)),l=s.next();!l.done;l=s.next()){var m=l.value;o.entityDetailsList.push(U[m])}}catch(e){a={error:e}}finally{try{l&&!l.done&&(i=s.return)&&i.call(s)}finally{if(a)throw a.error}}return o}return g(n,e),n.prototype.ngOnInit=function(){var t=this;this.detailsFormControl=new o.FormControl(""),e.prototype.ngOnInit.call(this),this.filteredEntityDetails=this.detailsFormControl.valueChanges.pipe(d.startWith(""),d.map((function(e){return e||""})),d.mergeMap((function(e){return t.fetchEntityDetails(e)})),d.share())},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.searchText="",this.entityDetailsConfigForm=this.fb.group({detailsList:[e?e.detailsList:null,[o.Validators.required]],addToMetadata:[!!e&&e.addToMetadata,[]]}),this.entityDetailsConfigForm.valueChanges.subscribe((function(e){t.entityDetailsConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)})),this.detailsFormControl.patchValue("",{emitEvent:!0})},n.prototype.displayDetails=function(e){return e?this.translate.instant(K.get(e)):void 0},n.prototype.fetchEntityDetails=function(e){var t=this;if(this.searchText=e,this.searchText&&this.searchText.length){var n=this.searchText.toUpperCase();return u.of(this.entityDetailsList.filter((function(e){return t.translate.instant(K.get(U[e])).toUpperCase().includes(n)})))}return u.of(this.entityDetailsList)},n.prototype.detailsFieldSelected=function(e){this.addDetailsField(e.option.value),this.clear("")},n.prototype.removeDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;if(t){var n=t.indexOf(e);n>=0&&(t.splice(n,1),this.entityDetailsConfigForm.get("detailsList").setValue(t))}},n.prototype.addDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;t||(t=[]),-1===t.indexOf(e)&&(t.push(e),this.entityDetailsConfigForm.get("detailsList").setValue(t))},n.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.detailsInput.nativeElement.value=e,this.detailsFormControl.patchValue(null,{emitEvent:!0}),setTimeout((function(){t.detailsInput.nativeElement.blur(),t.detailsInput.nativeElement.focus()}),0)},n.prototype.validateConfig=function(){return this.entityDetailsConfigForm.valid},n.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-entity-details-config",template:'
\n \n \n \n \n \n {{entityDetailsTranslationsMap.get(details) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-entity-details-matching\n
\n
\n
\n
\n
\n \n \n {{ \'tb.rulenode.add-to-metadata\' | translate }}\n \n
tb.rulenode.add-to-metadata-hint
\n
\n',styles:[":host ::ng-deep mat-form-field.entity-fields-list .mat-form-field-wrapper{margin-bottom:-1.25em}"]}]}],n.ctorParameters=function(){return[{type:i.Store},{type:r.TranslateService},{type:o.FormBuilder}]},n.propDecorators={detailsInput:[{type:t.ViewChild,args:["detailsInput",{static:!1}]}]},n}(a.RuleNodeConfigurationComponent);var te=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.separatorKeysCodes=[l.ENTER,l.COMMA,l.SEMICOLON],r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.deviceAttributesConfigForm=this.fb.group({deviceRelationsQuery:[e?e.deviceRelationsQuery:null,[o.Validators.required]],tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]}),this.deviceAttributesConfigForm.valueChanges.subscribe((function(e){t.validateConfig()?t.notifyConfigurationUpdated(t.deviceAttributesConfigForm.value):t.notifyConfigurationUpdated(null)}))},n.prototype.validateConfig=function(){return this.deviceAttributesConfigForm.valid},n.prototype.removeKey=function(e,t){var n=this.deviceAttributesConfigForm.get(t).value,r=n.indexOf(e);r>=0&&(n.splice(r,1),this.deviceAttributesConfigForm.get(t).setValue(n,{emitEvent:!0}))},n.prototype.addKey=function(e,t){var n=e.input,r=e.value;if((r||"").trim()){r=r.trim();var a=this.deviceAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(r)||(a||(a=[]),a.push(r),this.deviceAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}n&&(n.value="")},n.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-device-attributes-config",template:'
\n \n \n \n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var ne=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.separatorKeysCodes=[l.ENTER,l.COMMA,l.SEMICOLON],r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.originatorAttributesConfigForm=this.fb.group({tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]}),this.originatorAttributesConfigForm.valueChanges.subscribe((function(e){t.validateConfig()?t.notifyConfigurationUpdated(t.originatorAttributesConfigForm.value):t.notifyConfigurationUpdated(null)}))},n.prototype.validateConfig=function(){return this.originatorAttributesConfigForm.valid},n.prototype.removeKey=function(e,t){var n=this.originatorAttributesConfigForm.get(t).value,r=n.indexOf(e);r>=0&&(n.splice(r,1),this.originatorAttributesConfigForm.get(t).setValue(n,{emitEvent:!0}))},n.prototype.addKey=function(e,t){var n=e.input,r=e.value;if((r||"").trim()){r=r.trim();var a=this.originatorAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(r)||(a||(a=[]),a.push(r),this.originatorAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}n&&(n.value="")},n.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-originator-attributes-config",template:'
\n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var re=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.originatorFieldsConfigForm=this.fb.group({fieldsMapping:[e?e.fieldsMapping:null,[o.Validators.required]]}),this.originatorFieldsConfigForm.valueChanges.subscribe((function(e){t.originatorFieldsConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.validateConfig=function(){return this.originatorFieldsConfigForm.valid},n.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-originator-fields-config",template:'
\n \n \n \n
\n'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var ae=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.separatorKeysCodes=[l.ENTER,l.COMMA,l.SEMICOLON],r.fetchMode=j,r.fetchModes=Object.keys(j),r.samplingOrders=Object.keys(G),r.timeUnits=Object.keys(R),r.timeUnitsTranslationMap=D,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.getTelemetryFromDatabaseConfigForm=this.fb.group({latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],fetchMode:[e?e.fetchMode:null,[o.Validators.required]],orderBy:[e?e.orderBy:null,[]],limit:[e?e.limit:null,[]],useMetadataIntervalPatterns:[!!e&&e.useMetadataIntervalPatterns,[]],startInterval:[e?e.startInterval:null,[]],startIntervalTimeUnit:[e?e.startIntervalTimeUnit:null,[]],endInterval:[e?e.endInterval:null,[]],endIntervalTimeUnit:[e?e.endIntervalTimeUnit:null,[]],startIntervalPattern:[e?e.startIntervalPattern:null,[]],endIntervalPattern:[e?e.endIntervalPattern:null,[]]}),this.updateValidators(!1),this.getTelemetryFromDatabaseConfigForm.get("fetchMode").valueChanges.subscribe((function(){t.updateValidators(!0)})),this.getTelemetryFromDatabaseConfigForm.get("useMetadataIntervalPatterns").valueChanges.subscribe((function(){t.updateValidators(!0)})),this.getTelemetryFromDatabaseConfigForm.valueChanges.subscribe((function(e){t.validateConfig()?t.notifyConfigurationUpdated(t.getTelemetryFromDatabaseConfigForm.value):t.notifyConfigurationUpdated(null)}))},n.prototype.updateValidators=function(e){var t=this.getTelemetryFromDatabaseConfigForm.get("fetchMode").value,n=this.getTelemetryFromDatabaseConfigForm.get("useMetadataIntervalPatterns").value;t&&t===j.ALL?(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([o.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([o.Validators.required,o.Validators.min(2),o.Validators.max(1e3)])):(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([])),n?(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([o.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([o.Validators.required])):(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([o.Validators.required,o.Validators.min(1),o.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([o.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([o.Validators.required,o.Validators.min(1),o.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([o.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([])),this.getTelemetryFromDatabaseConfigForm.get("orderBy").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("limit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").updateValueAndValidity({emitEvent:e})},n.prototype.validateConfig=function(){return this.getTelemetryFromDatabaseConfigForm.valid},n.prototype.removeKey=function(e,t){var n=this.getTelemetryFromDatabaseConfigForm.get(t).value,r=n.indexOf(e);r>=0&&(n.splice(r,1),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(n,{emitEvent:!0}))},n.prototype.addKey=function(e,t){var n=e.input,r=e.value;if((r||"").trim()){r=r.trim();var a=this.getTelemetryFromDatabaseConfigForm.get(t).value;a&&-1!==a.indexOf(r)||(a||(a=[]),a.push(r),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(a,{emitEvent:!0}))}n&&(n.value="")},n.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-get-telemetry-from-database",template:'
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n tb.rulenode.fetch-mode\n \n \n {{ mode }}\n \n \n tb.rulenode.fetch-mode-hint\n \n
\n \n tb.rulenode.order-by\n \n \n {{ order }}\n \n \n tb.rulenode.order-by-hint\n \n \n tb.rulenode.limit\n \n tb.rulenode.limit-hint\n \n
\n \n {{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-interval-patterns-hint
\n
\n
\n \n tb.rulenode.start-interval\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.start-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.end-interval\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.end-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n \n tb.rulenode.start-interval-pattern\n \n \n {{ \'tb.rulenode.start-interval-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.end-interval-pattern\n \n \n {{ \'tb.rulenode.end-interval-pattern-required\' | translate }}\n \n \n \n \n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var ie=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.relatedAttributesConfigForm=this.fb.group({relationsQuery:[e?e.relationsQuery:null,[o.Validators.required]],telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[o.Validators.required]]}),this.relatedAttributesConfigForm.valueChanges.subscribe((function(e){t.relatedAttributesConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.validateConfig=function(){return this.relatedAttributesConfigForm.valid},n.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-related-attributes-config",template:'
\n \n \n \n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var oe=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.tenantAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[o.Validators.required]]}),this.tenantAttributesConfigForm.valueChanges.subscribe((function(e){t.tenantAttributesConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.validateConfig=function(){return this.tenantAttributesConfigForm.valid},n.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-tenant-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var se=function(){function e(){}return e.decorators=[{type:t.NgModule,args:[{declarations:[W,ee,te,ne,re,ae,ie,oe],imports:[n.CommonModule,a.SharedModule,X],exports:[W,ee,te,ne,re,ae,ie,oe]}]}],e}(),le=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r.originatorSource=V,r.originatorSources=Object.keys(V),r.originatorSourceTranslationMap=L,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.changeOriginatorConfigForm=this.fb.group({originatorSource:[e?e.originatorSource:null,[o.Validators.required]],relationsQuery:[e?e.relationsQuery:null,[]]}),this.updateValidators(!1),this.changeOriginatorConfigForm.get("originatorSource").valueChanges.subscribe((function(){t.updateValidators(!0)})),this.changeOriginatorConfigForm.valueChanges.subscribe((function(e){t.changeOriginatorConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.updateValidators=function(e){var t=this.changeOriginatorConfigForm.get("originatorSource").value;t&&t===V.RELATED?this.changeOriginatorConfigForm.get("relationsQuery").setValidators([o.Validators.required]):this.changeOriginatorConfigForm.get("relationsQuery").setValidators([]),this.changeOriginatorConfigForm.get("relationsQuery").updateValueAndValidity({emitEvent:e})},n.prototype.validateConfig=function(){return this.changeOriginatorConfigForm.valid},n.decorators=[{type:t.Component,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
\n
\n'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var me=function(e){function n(t,n,r,a){var i=e.call(this,t)||this;return i.store=t,i.fb=n,i.nodeScriptTestService=r,i.translate=a,i}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.onConfigurationSet=function(e){var t=this;this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[o.Validators.required]]}),this.scriptConfigForm.valueChanges.subscribe((function(e){t.scriptConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"update",this.translate.instant("tb.rulenode.transformer"),"Transform",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},n.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},n.decorators=[{type:t.Component,args:[{selector:"tb-transformation-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder},{type:s.NodeScriptTestService},{type:r.TranslateService}]},n.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},n}(a.RuleNodeConfigurationComponent);var ue=function(e){function n(t,n){var r=e.call(this,t)||this;return r.store=t,r.fb=n,r}return g(n,e),n.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},n.prototype.ngAfterViewInit=function(){var e=this;setTimeout((function(){e.validateConfig()||e.notifyConfigurationUpdated(null)}),0)},n.prototype.onConfigurationSet=function(e){var t=this;this.toEmailConfigForm=this.fb.group({fromTemplate:[e?e.fromTemplate:null,[o.Validators.required]],toTemplate:[e?e.toTemplate:null,[o.Validators.required]],ccTemplate:[e?e.ccTemplate:null,[]],bccTemplate:[e?e.bccTemplate:null,[]],subjectTemplate:[e?e.subjectTemplate:null,[o.Validators.required]],bodyTemplate:[e?e.bodyTemplate:null,[o.Validators.required]]}),this.toEmailConfigForm.valueChanges.subscribe((function(e){t.toEmailConfigForm.valid?t.notifyConfigurationUpdated(e):t.notifyConfigurationUpdated(null)}))},n.prototype.validateConfig=function(){return this.toEmailConfigForm.valid},n.decorators=[{type:t.Component,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.body-template\n \n \n {{ \'tb.rulenode.body-template-required\' | translate }}\n \n \n \n
\n'}]}],n.ctorParameters=function(){return[{type:i.Store},{type:o.FormBuilder}]},n}(a.RuleNodeConfigurationComponent);var de=function(){function e(){}return e.decorators=[{type:t.NgModule,args:[{declarations:[le,me,ue],imports:[n.CommonModule,a.SharedModule,X],exports:[le,me,ue]}]}],e}(),pe=function(){function e(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-name-pattern-hint":"Name pattern, use ${metaKeyName} to substitute variables from metadata","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-type-pattern-hint":"Type pattern, use ${metaKeyName} to substitute variables from metadata","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-name-pattern-hint":"Customer name pattern, use ${metaKeyName} to substitute variables from metadata","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.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.",limit:"Limit","limit-hint":"Min limit value is 2, max - 1000. In case you want to fetch a single entry, select fetch mode 'FIRST' or 'LAST'.","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","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","latest-timeseries":"Latest timeseries","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-hint":"Relation type pattern, use ${metaKeyName} to substitute variables from metadata","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","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","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","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 metadata period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds metadata pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","period-in-seconds-pattern-hint":"Period in seconds pattern, use ${metaKeyName} to substitute variables from metadata","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",propagate:"Propagate",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","from-template-hint":"From address template, use ${metaKeyName} to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use ${metaKeyName} to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use ${metaKeyName} to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use ${metaKeyName} to substitute variables from metadata","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","endpoint-url-pattern-hint":"HTTP URL address pattern, use ${metaKeyName} to substitute variables from metadata","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory",headers:"Headers","headers-hint":"Use ${metaKeyName} in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use ${metaKeyName} to substitute variables from metadata","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","topic-arn-pattern-hint":"Topic ARN pattern, use ${metaKeyName} to substitute variables from metadata","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","queue-url-pattern-hint":"Queue URL pattern, use ${metaKeyName} to substitute variables from metadata","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 ${metaKeyName} in name/value fields to substitute variables from metadata","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","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","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"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-interval-patterns":"Use metadata interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","check-all-keys":"Check that all selected keys 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-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","start-interval-pattern-hint":"Start interval pattern, use ${metaKeyName} to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use ${metaKeyName} to substitute variables from metadata","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","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":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","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","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","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":"You should 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-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 Latest telemetry with Timestamp","get-latest-value-with-ts-hint":'If selected, latest telemetry values will be added to the outbound message metadata with timestamp, e.g: "temp": "{\\"ts\\":1574329385897,\\"value\\":42}"'},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}},!0)}(e)}return e.decorators=[{type:t.NgModule,args:[{declarations:[b],imports:[n.CommonModule,a.SharedModule],exports:[N,z,se,de,b]}]}],e.ctorParameters=function(){return[{type:r.TranslateService}]},e}();e.RuleNodeCoreConfigModule=pe,e.default=pe,e.ɵa=b,e.ɵb=N,e.ɵba=ae,e.ɵbb=ie,e.ɵbc=oe,e.ɵbd=X,e.ɵbe=Y,e.ɵbf=J,e.ɵbg=Z,e.ɵbh=de,e.ɵbi=le,e.ɵbj=me,e.ɵbk=ue,e.ɵc=h,e.ɵd=C,e.ɵe=v,e.ɵf=F,e.ɵg=T,e.ɵh=x,e.ɵi=I,e.ɵj=q,e.ɵk=S,e.ɵl=k,e.ɵm=z,e.ɵn=A,e.ɵo=E,e.ɵp=B,e.ɵq=H,e.ɵr=$,e.ɵs=_,e.ɵt=Q,e.ɵu=se,e.ɵv=W,e.ɵw=ee,e.ɵx=te,e.ɵy=ne,e.ɵz=re,Object.defineProperty(e,"__esModule",{value:!0})})); + ***************************************************************************** */var f=function(e,t){return(f=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)t.hasOwnProperty(r)&&(e[r]=t[r])})(e,t)};function g(e,t){function r(){this.constructor=e}f(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)}function y(e){var t="function"==typeof Symbol&&e[Symbol.iterator],r=0;return t?t.call(e):{next:function(){return e&&r>=e.length&&(e=void 0),{value:e&&e[r++],done:!e}}}}var b=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.emptyConfigForm},r.prototype.onConfigurationSet=function(e){this.emptyConfigForm=this.fb.group({})},r.decorators=[{type:t.Component,args:[{selector:"tb-node-empty-config",template:"
"}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var h=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.attributeScopes=Object.keys(a.AttributeScope),n.telemetryTypeTranslationsMap=a.telemetryTypeTranslations,n}return g(r,e),r.prototype.configForm=function(){return this.attributesConfigForm},r.prototype.onConfigurationSet=function(e){this.attributesConfigForm=this.fb.group({scope:[e?e.scope:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var C=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.timeseriesConfigForm},r.prototype.onConfigurationSet=function(e){this.timeseriesConfigForm=this.fb.group({defaultTTL:[e?e.defaultTTL:null,[i.Validators.required,i.Validators.min(0)]]})},r.decorators=[{type:t.Component,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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var v=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.rpcRequestConfigForm},r.prototype.onConfigurationSet=function(e){this.rpcRequestConfigForm=this.fb.group({timeoutInSeconds:[e?e.timeoutInSeconds:null,[i.Validators.required,i.Validators.min(0)]]})},r.decorators=[{type:t.Component,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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var F=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return g(r,e),r.prototype.configForm=function(){return this.logConfigForm},r.prototype.onConfigurationSet=function(e){this.logConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.logConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"string",this.translate.instant("tb.rulenode.to-string"),"ToString",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.logConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.decorators=[{type:t.Component,args:[{selector:"tb-action-node-log-config",template:'
\n \n \n \n
\n \n
\n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},r.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},r}(a.RuleNodeConfigurationComponent);var T=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.assignCustomerConfigForm},r.prototype.onConfigurationSet=function(e){this.assignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[i.Validators.required]],createCustomerIfNotExists:[!!e&&e.createCustomerIfNotExists,[]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.decorators=[{type:t.Component,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 \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var q=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return g(r,e),r.prototype.configForm=function(){return this.clearAlarmConfigForm},r.prototype.onConfigurationSet=function(e){this.clearAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[i.Validators.required]],alarmType:[e?e.alarmType:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.clearAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.clearAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.decorators=[{type:t.Component,args:[{selector:"tb-action-node-clear-alarm-config",template:'
\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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},r.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},r}(a.RuleNodeConfigurationComponent);var x=function(e){function r(t,r,n,o){var i=e.call(this,t)||this;return i.store=t,i.fb=r,i.nodeScriptTestService=n,i.translate=o,i.alarmSeverities=Object.keys(a.AlarmSeverity),i.alarmSeverityTranslationMap=a.alarmSeverityTranslations,i.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],i}return g(r,e),r.prototype.configForm=function(){return this.createAlarmConfigForm},r.prototype.onConfigurationSet=function(e){this.createAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[i.Validators.required]],useMessageAlarmData:[!!e&&e.useMessageAlarmData,[]],alarmType:[e?e.alarmType:null,[]],severity:[e?e.severity:null,[]],propagate:[!!e&&e.propagate,[]],relationTypes:[e?e.relationTypes:null,[]]})},r.prototype.validatorTriggers=function(){return["useMessageAlarmData"]},r.prototype.updateValidators=function(e){this.createAlarmConfigForm.get("useMessageAlarmData").value?(this.createAlarmConfigForm.get("alarmType").setValidators([]),this.createAlarmConfigForm.get("severity").setValidators([])):(this.createAlarmConfigForm.get("alarmType").setValidators([i.Validators.required]),this.createAlarmConfigForm.get("severity").setValidators([i.Validators.required])),this.createAlarmConfigForm.get("alarmType").updateValueAndValidity({emitEvent:e}),this.createAlarmConfigForm.get("severity").updateValueAndValidity({emitEvent:e})},r.prototype.testScript=function(){var e=this,t=this.createAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.createAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},r.prototype.removeKey=function(e,t){var r=this.createAlarmConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.createAlarmConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.createAlarmConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.createAlarmConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.decorators=[{type:t.Component,args:[{selector:"tb-action-node-create-alarm-config",template:'
\n \n \n \n
\n \n
\n \n {{ \'tb.rulenode.use-message-alarm-data\' | translate }}\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.alarm-severity\n \n \n {{ alarmSeverityTranslationMap.get(severity) | translate }}\n \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 \n \n
\n
\n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},r.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},r}(a.RuleNodeConfigurationComponent);var I=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return g(r,e),r.prototype.configForm=function(){return this.createRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.createRelationConfigForm=this.fb.group({direction:[e?e.direction:null,[i.Validators.required]],entityType:[e?e.entityType:null,[i.Validators.required]],entityNamePattern:[e?e.entityNamePattern:null,[]],entityTypePattern:[e?e.entityTypePattern:null,[]],relationType:[e?e.relationType:null,[i.Validators.required]],createEntityIfNotExists:[!!e&&e.createEntityIfNotExists,[]],removeCurrentRelations:[!!e&&e.removeCurrentRelations,[]],changeOriginatorToRelatedEntity:[!!e&&e.changeOriginatorToRelatedEntity,[]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.validatorTriggers=function(){return["entityType"]},r.prototype.updateValidators=function(e){var t=this.createRelationConfigForm.get("entityType").value;t?this.createRelationConfigForm.get("entityNamePattern").setValidators([i.Validators.required]):this.createRelationConfigForm.get("entityNamePattern").setValidators([]),!t||t!==a.EntityType.DEVICE&&t!==a.EntityType.ASSET?this.createRelationConfigForm.get("entityTypePattern").setValidators([]):this.createRelationConfigForm.get("entityTypePattern").setValidators([i.Validators.required]),this.createRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e}),this.createRelationConfigForm.get("entityTypePattern").updateValueAndValidity({emitEvent:e})},r.decorators=[{type:t.Component,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 \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var N=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.msgDelayConfigForm},r.prototype.onConfigurationSet=function(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,[i.Validators.required,i.Validators.min(1),i.Validators.max(1e5)]]})},r.prototype.validatorTriggers=function(){return["useMetadataPeriodInSecondsPatterns"]},r.prototype.updateValidators=function(e){this.msgDelayConfigForm.get("useMetadataPeriodInSecondsPatterns").value?(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([i.Validators.required]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([])):(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([i.Validators.required,i.Validators.min(0)])),this.msgDelayConfigForm.get("periodInSecondsPattern").updateValueAndValidity({emitEvent:e}),this.msgDelayConfigForm.get("periodInSeconds").updateValueAndValidity({emitEvent:e})},r.decorators=[{type:t.Component,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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var k=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return g(r,e),r.prototype.configForm=function(){return this.deleteRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.deleteRelationConfigForm=this.fb.group({deleteForSingleEntity:[!!e&&e.deleteForSingleEntity,[]],direction:[e?e.direction:null,[i.Validators.required]],entityType:[e?e.entityType:null,[]],entityNamePattern:[e?e.entityNamePattern:null,[]],relationType:[e?e.relationType:null,[i.Validators.required]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.validatorTriggers=function(){return["deleteForSingleEntity","entityType"]},r.prototype.updateValidators=function(e){var t=this.deleteRelationConfigForm.get("deleteForSingleEntity").value,r=this.deleteRelationConfigForm.get("entityType").value;t?this.deleteRelationConfigForm.get("entityType").setValidators([i.Validators.required]):this.deleteRelationConfigForm.get("entityType").setValidators([]),t&&r?this.deleteRelationConfigForm.get("entityNamePattern").setValidators([i.Validators.required]):this.deleteRelationConfigForm.get("entityNamePattern").setValidators([]),this.deleteRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:!1}),this.deleteRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e})},r.decorators=[{type:t.Component,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 \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var S=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return g(r,e),r.prototype.configForm=function(){return this.generatorConfigForm},r.prototype.onConfigurationSet=function(e){this.generatorConfigForm=this.fb.group({msgCount:[e?e.msgCount:null,[i.Validators.required,i.Validators.min(0)]],periodInSeconds:[e?e.periodInSeconds:null,[i.Validators.required,i.Validators.min(1)]],originator:[e?e.originator:null,[]],jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.prepareInputConfig=function(e){return e&&(e.originatorId&&e.originatorType?e.originator={id:e.originatorId,entityType:e.originatorType}:e.originator=null,delete e.originatorId,delete e.originatorType),e},r.prototype.prepareOutputConfig=function(e){return e.originator?(e.originatorId=e.originator.id,e.originatorType=e.originator.entityType):(e.originatorId=null,e.originatorType=null),delete e.originator,e},r.prototype.testScript=function(){var e=this,t=this.generatorConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"generate",this.translate.instant("tb.rulenode.generator"),"Generate",["prevMsg","prevMetadata","prevMsgType"],this.ruleNodeId).subscribe((function(t){t&&e.generatorConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.decorators=[{type:t.Component,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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},r.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},r}(a.RuleNodeConfigurationComponent);var V={CUSTOMER:"CUSTOMER",TENANT:"TENANT",RELATED:"RELATED",ALARM_ORIGINATOR:"ALARM_ORIGINATOR"},E=new Map([[V.CUSTOMER,"tb.rulenode.originator-customer"],[V.TENANT,"tb.rulenode.originator-tenant"],[V.RELATED,"tb.rulenode.originator-related"],[V.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator"]]),A={CIRCLE:"CIRCLE",POLYGON:"POLYGON"},L=new Map([[A.CIRCLE,"tb.rulenode.perimeter-circle"],[A.POLYGON,"tb.rulenode.perimeter-polygon"]]),M={MILLISECONDS:"MILLISECONDS",SECONDS:"SECONDS",MINUTES:"MINUTES",HOURS:"HOURS",DAYS:"DAYS"},R=new Map([[M.MILLISECONDS,"tb.rulenode.time-unit-milliseconds"],[M.SECONDS,"tb.rulenode.time-unit-seconds"],[M.MINUTES,"tb.rulenode.time-unit-minutes"],[M.HOURS,"tb.rulenode.time-unit-hours"],[M.DAYS,"tb.rulenode.time-unit-days"]]),D={METER:"METER",KILOMETER:"KILOMETER",FOOT:"FOOT",MILE:"MILE",NAUTICAL_MILE:"NAUTICAL_MILE"},P=new Map([[D.METER,"tb.rulenode.range-unit-meter"],[D.KILOMETER,"tb.rulenode.range-unit-kilometer"],[D.FOOT,"tb.rulenode.range-unit-foot"],[D.MILE,"tb.rulenode.range-unit-mile"],[D.NAUTICAL_MILE,"tb.rulenode.range-unit-nautical-mile"]]),w={TITLE:"TITLE",COUNTRY:"COUNTRY",STATE:"STATE",ZIP:"ZIP",ADDRESS:"ADDRESS",ADDRESS2:"ADDRESS2",PHONE:"PHONE",EMAIL:"EMAIL",ADDITIONAL_INFO:"ADDITIONAL_INFO"},O=new Map([[w.TITLE,"tb.rulenode.entity-details-title"],[w.COUNTRY,"tb.rulenode.entity-details-country"],[w.STATE,"tb.rulenode.entity-details-state"],[w.ZIP,"tb.rulenode.entity-details-zip"],[w.ADDRESS,"tb.rulenode.entity-details-address"],[w.ADDRESS2,"tb.rulenode.entity-details-address2"],[w.PHONE,"tb.rulenode.entity-details-phone"],[w.EMAIL,"tb.rulenode.entity-details-email"],[w.ADDITIONAL_INFO,"tb.rulenode.entity-details-additional_info"]]),K={FIRST:"FIRST",LAST:"LAST",ALL:"ALL"},j={ASC:"ASC",DESC:"DESC"},U={STANDARD:"STANDARD",FIFO:"FIFO"},G=new Map([[U.STANDARD,"tb.rulenode.sqs-queue-standard"],[U.FIFO,"tb.rulenode.sqs-queue-fifo"]]),B=["anonymous","basic","cert.PEM"],H=new Map([["anonymous","tb.rulenode.credentials-anonymous"],["basic","tb.rulenode.credentials-basic"],["cert.PEM","tb.rulenode.credentials-pem"]]),$=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.perimeterType=A,n.perimeterTypes=Object.keys(A),n.perimeterTypeTranslationMap=L,n.rangeUnits=Object.keys(D),n.rangeUnitTranslationMap=P,n.timeUnits=Object.keys(M),n.timeUnitsTranslationMap=R,n}return g(r,e),r.prototype.configForm=function(){return this.geoActionConfigForm},r.prototype.onConfigurationSet=function(e){this.geoActionConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[i.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[i.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType: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,[i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]],minInsideDurationTimeUnit:[e?e.minInsideDurationTimeUnit:null,[i.Validators.required]],minOutsideDuration:[e?e.minOutsideDuration:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]],minOutsideDurationTimeUnit:[e?e.minOutsideDurationTimeUnit:null,[i.Validators.required]]})},r.prototype.validatorTriggers=function(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]},r.prototype.updateValidators=function(e){var t=this.geoActionConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,r=this.geoActionConfigForm.get("perimeterType").value;t?this.geoActionConfigForm.get("perimeterType").setValidators([]):this.geoActionConfigForm.get("perimeterType").setValidators([i.Validators.required]),t||r!==A.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([i.Validators.required,i.Validators.min(-90),i.Validators.max(90)]),this.geoActionConfigForm.get("centerLongitude").setValidators([i.Validators.required,i.Validators.min(-180),i.Validators.max(180)]),this.geoActionConfigForm.get("range").setValidators([i.Validators.required,i.Validators.min(0)]),this.geoActionConfigForm.get("rangeUnit").setValidators([i.Validators.required])),t||r!==A.POLYGON?this.geoActionConfigForm.get("polygonsDefinition").setValidators([]):this.geoActionConfigForm.get("polygonsDefinition").setValidators([i.Validators.required]),this.geoActionConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),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})},r.decorators=[{type:t.Component,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.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var Q=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.msgCountConfigForm},r.prototype.onConfigurationSet=function(e){this.msgCountConfigForm=this.fb.group({interval:[e?e.interval:null,[i.Validators.required,i.Validators.min(1)]],telemetryPrefix:[e?e.telemetryPrefix:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var z=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.rpcReplyConfigForm},r.prototype.onConfigurationSet=function(e){this.rpcReplyConfigForm=this.fb.group({requestIdMetaDataAttribute:[e?e.requestIdMetaDataAttribute:null,[]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-action-node-rpc-reply-config",template:'
\n \n tb.rulenode.request-id-metadata-attribute\n \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var _=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.saveToCustomTableConfigForm},r.prototype.onConfigurationSet=function(e){this.saveToCustomTableConfigForm=this.fb.group({tableName:[e?e.tableName:null,[i.Validators.required]],fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,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 \n \n \n \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var W=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.translate=r,o.injector=n,o.fb=a,o.propagateChange=null,o.valueChangeSubscription=null,o}return g(r,e),Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){this.ngControl=this.injector.get(i.NgControl),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.kvListFormGroup=this.fb.group({}),this.kvListFormGroup.addControl("keyVals",this.fb.array([]))},r.prototype.keyValsFormArray=function(){return this.kvListFormGroup.get("keyVals")},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.kvListFormGroup.disable({emitEvent:!1}):this.kvListFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){var t,r,n=this;this.valueChangeSubscription&&this.valueChangeSubscription.unsubscribe();var a=[];if(e)try{for(var o=y(Object.keys(e)),l=o.next();!l.done;l=o.next()){var s=l.value;Object.prototype.hasOwnProperty.call(e,s)&&a.push(this.fb.group({key:[s,[i.Validators.required]],value:[e[s],[i.Validators.required]]}))}}catch(e){t={error:e}}finally{try{l&&!l.done&&(r=o.return)&&r.call(o)}finally{if(t)throw t.error}}this.kvListFormGroup.setControl("keyVals",this.fb.array(a)),this.valueChangeSubscription=this.kvListFormGroup.valueChanges.subscribe((function(){n.updateModel()}))},r.prototype.removeKeyVal=function(e){this.kvListFormGroup.get("keyVals").removeAt(e)},r.prototype.addKeyVal=function(){this.kvListFormGroup.get("keyVals").push(this.fb.group({key:["",[i.Validators.required]],value:["",[i.Validators.required]]}))},r.prototype.validate=function(e){return!this.kvListFormGroup.get("keyVals").value.length&&this.required?{kvMapRequired:!0}:this.kvListFormGroup.valid?null:{kvFieldsRequired:!0}},r.prototype.updateModel=function(){var e=this.kvListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.kvListFormGroup.valid)this.propagateChange(null);else{var t={};e.forEach((function(e){t[e.key]=e.value})),this.propagateChange(t)}},r.decorators=[{type:t.Component,args:[{selector:"tb-kv-map-config",template:'
\n
\n {{ keyText }}\n {{ valText }}\n \n
\n
\n
\n \n \n \n \n {{ keyRequiredText | translate }}\n \n \n \n \n \n \n {{ valRequiredText | translate }}\n \n \n \n
\n
\n \n
\n \n
\n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return r})),multi:!0},{provide:i.NG_VALIDATORS,useExisting:t.forwardRef((function(){return r})),multi:!0}],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:rgba(0,0,0,.54);font-size:12px;font-weight:700;white-space:nowrap}:host .tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:20px;max-height:300px;overflow:auto}:host .tb-kv-map-config .body .row{padding-top:5px;max-height:40px}:host .tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell{margin:0;max-height:40px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell .mat-form-field-infix{border-top:0}:host ::ng-deep .tb-kv-map-config .body button.mat-button{margin:0}"]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:t.Injector},{type:i.FormBuilder}]},r.propDecorators={disabled:[{type:t.Input}],requiredText:[{type:t.Input}],keyText:[{type:t.Input}],keyRequiredText:[{type:t.Input}],valText:[{type:t.Input}],valRequiredText:[{type:t.Input}],required:[{type:t.Input}]},r}(a.PageComponent);var Y=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n.propagateChange=null,n}return g(r,e),Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.deviceRelationsQueryFormGroup=this.fb.group({direction:[null,[i.Validators.required]],maxLevel:[null,[]],relationType:[null],deviceTypes:[null,[i.Validators.required]]}),this.deviceRelationsQueryFormGroup.valueChanges.subscribe((function(t){e.deviceRelationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.deviceRelationsQueryFormGroup.disable({emitEvent:!1}):this.deviceRelationsQueryFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){this.deviceRelationsQueryFormGroup.reset(e,{emitEvent:!1})},r.decorators=[{type:t.Component,args:[{selector:"tb-device-relations-query-config",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
\n
relation.relation-type
\n \n \n
device.device-types
\n \n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return r})),multi:!0}]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r.propDecorators={disabled:[{type:t.Input}],required:[{type:t.Input}]},r}(a.PageComponent);var J=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.propagateChange=null,n}return g(r,e),Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.relationsQueryFormGroup=this.fb.group({direction:[null,[i.Validators.required]],maxLevel:[null,[]],filters:[null]}),this.relationsQueryFormGroup.valueChanges.subscribe((function(t){e.relationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.relationsQueryFormGroup.disable({emitEvent:!1}):this.relationsQueryFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){this.relationsQueryFormGroup.reset(e,{emitEvent:!1})},r.decorators=[{type:t.Component,args:[{selector:"tb-relations-query-config",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
\n
relation.relation-filters
\n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return r})),multi:!0}]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r.propDecorators={disabled:[{type:t.Input}],required:[{type:t.Input}]},r}(a.PageComponent);var Z=function(e){function r(t,r,n,o){var i,l,m=e.call(this,t)||this;m.store=t,m.translate=r,m.truncate=n,m.fb=o,m.placeholder="tb.rulenode.message-type",m.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],m.messageTypes=[],m.messageTypesList=[],m.searchText="",m.propagateChange=function(e){},m.messageTypeConfigForm=m.fb.group({messageType:[null]});try{for(var u=y(Object.keys(a.MessageType)),d=u.next();!d.done;d=u.next()){var p=d.value;m.messageTypesList.push({name:a.messageTypeNames.get(a.MessageType[p]),value:p})}}catch(e){i={error:e}}finally{try{d&&!d.done&&(l=u.return)&&l.call(u)}finally{if(i)throw i.error}}return m}return g(r,e),Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.ngOnInit=function(){var e=this;this.filteredMessageTypes=this.messageTypeConfigForm.get("messageType").valueChanges.pipe(c.startWith(""),c.map((function(e){return e||""})),c.mergeMap((function(t){return e.fetchMessageTypes(t)})),c.share())},r.prototype.ngAfterViewInit=function(){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.messageTypeConfigForm.disable({emitEvent:!1}):this.messageTypeConfigForm.enable({emitEvent:!1})},r.prototype.writeValue=function(e){var t=this;this.searchText="",this.messageTypes.length=0,e&&e.forEach((function(e){var r=t.messageTypesList.find((function(t){return t.value===e}));r?t.messageTypes.push({name:r.name,value:r.value}):t.messageTypes.push({name:e,value:e})}))},r.prototype.displayMessageTypeFn=function(e){return e?e.name:void 0},r.prototype.textIsNotEmpty=function(e){return!!(e&&null!=e&&e.length>0)},r.prototype.createMessageType=function(e,t){e.preventDefault(),this.transformMessageType(t)},r.prototype.add=function(e){this.transformMessageType(e.value)},r.prototype.fetchMessageTypes=function(e){if(this.searchText=e,this.searchText&&this.searchText.length){var t=this.searchText.toUpperCase();return p.of(this.messageTypesList.filter((function(e){return e.name.toUpperCase().includes(t)})))}return p.of(this.messageTypesList)},r.prototype.transformMessageType=function(e){if((e||"").trim()){var t=null,r=e.trim(),n=this.messageTypesList.find((function(e){return e.name===r}));(t=n?{name:n.name,value:n.value}:{name:r,value:r})&&this.addMessageType(t)}this.clear("")},r.prototype.remove=function(e){var t=this.messageTypes.indexOf(e);t>=0&&(this.messageTypes.splice(t,1),this.updateModel())},r.prototype.selected=function(e){this.addMessageType(e.option.value),this.clear("")},r.prototype.addMessageType=function(e){-1===this.messageTypes.findIndex((function(t){return t.value===e.value}))&&(this.messageTypes.push(e),this.updateModel())},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.messageTypeInput.nativeElement.value=e,this.messageTypeConfigForm.get("messageType").patchValue(null,{emitEvent:!0}),setTimeout((function(){t.messageTypeInput.nativeElement.blur(),t.messageTypeInput.nativeElement.focus()}),0)},r.prototype.updateModel=function(){var e=this.messageTypes.map((function(e){return e.value}));this.required?(this.chipList.errorState=!e.length,this.propagateChange(e.length>0?e:null)):(this.chipList.errorState=!1,this.propagateChange(e))},r.decorators=[{type:t.Component,args:[{selector:"tb-message-types-config",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 {{ translate.get(\'tb.rulenode.no-message-type-matching\',\n {messageType: truncate.transform(searchText, true, 6, '...')}) | async }}\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',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return r})),multi:!0}]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:a.TruncatePipe},{type:i.FormBuilder}]},r.propDecorators={required:[{type:t.Input}],label:[{type:t.Input}],placeholder:[{type:t.Input}],disabled:[{type:t.Input}],chipList:[{type:t.ViewChild,args:["chipList",{static:!1}]}],matAutocomplete:[{type:t.ViewChild,args:["messageTypeAutocomplete",{static:!1}]}],messageTypeInput:[{type:t.ViewChild,args:["messageTypeInput",{static:!1}]}]},r}(a.PageComponent);var X=function(){function e(){}return e.decorators=[{type:t.NgModule,args:[{declarations:[W,Y,J,Z],imports:[r.CommonModule,a.SharedModule,m.HomeComponentsModule],exports:[W,Y,J,Z]}]}],e}(),ee=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.unassignCustomerConfigForm},r.prototype.onConfigurationSet=function(e){this.unassignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[i.Validators.required]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.decorators=[{type:t.Component,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 \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var te=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.snsConfigForm},r.prototype.onConfigurationSet=function(e){this.snsConfigForm=this.fb.group({topicArnPattern:[e?e.topicArnPattern:null,[i.Validators.required]],accessKeyId:[e?e.accessKeyId:null,[i.Validators.required]],secretAccessKey:[e?e.secretAccessKey:null,[i.Validators.required]],region:[e?e.region:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-action-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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var re=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.sqsQueueType=U,n.sqsQueueTypes=Object.keys(U),n.sqsQueueTypeTranslationsMap=G,n}return g(r,e),r.prototype.configForm=function(){return this.sqsConfigForm},r.prototype.onConfigurationSet=function(e){this.sqsConfigForm=this.fb.group({queueType:[e?e.queueType:null,[i.Validators.required]],queueUrlPattern:[e?e.queueUrlPattern:null,[i.Validators.required]],delaySeconds:[e?e.delaySeconds:null,[i.Validators.min(0),i.Validators.max(900)]],messageAttributes:[e?e.messageAttributes:null,[]],accessKeyId:[e?e.accessKeyId:null,[i.Validators.required]],secretAccessKey:[e?e.secretAccessKey:null,[i.Validators.required]],region:[e?e.region:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-action-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
tb.rulenode.message-attributes-hint
\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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var ne=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.pubSubConfigForm},r.prototype.onConfigurationSet=function(e){this.pubSubConfigForm=this.fb.group({projectId:[e?e.projectId:null,[i.Validators.required]],topicName:[e?e.topicName:null,[i.Validators.required]],serviceAccountKey:[e?e.serviceAccountKey:null,[i.Validators.required]],serviceAccountKeyFileName:[e?e.serviceAccountKeyFileName:null,[i.Validators.required]],messageAttributes:[e?e.messageAttributes:null,[]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-action-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
tb.rulenode.message-attributes-hint
\n \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var ae=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.ackValues=["all","-1","0","1"],n}return g(r,e),r.prototype.configForm=function(){return this.kafkaConfigForm},r.prototype.onConfigurationSet=function(e){this.kafkaConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],bootstrapServers:[e?e.bootstrapServers:null,[i.Validators.required]],retries:[e?e.retries:null,[i.Validators.min(0)]],batchSize:[e?e.batchSize:null,[i.Validators.min(0)]],linger:[e?e.linger:null,[i.Validators.min(0)]],bufferMemory:[e?e.bufferMemory:null,[i.Validators.min(0)]],acks:[e?e.acks:null,[i.Validators.required]],keySerializer:[e?e.keySerializer:null,[i.Validators.required]],valueSerializer:[e?e.valueSerializer:null,[i.Validators.required]],otherProperties:[e?e.otherProperties:null,[]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-action-node-kafka-config",template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var oe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allMqttCredentialsTypes=B,n.mqttCredentialsTypeTranslationsMap=H,n}return g(r,e),r.prototype.configForm=function(){return this.mqttConfigForm},r.prototype.onConfigurationSet=function(e){this.mqttConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(200)]],clientId:[e?e.clientId:null,[]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:this.fb.group({type:[e&&e.credentials?e.credentials.type:null,[i.Validators.required]],username:[e&&e.credentials?e.credentials.username:null,[]],password:[e&&e.credentials?e.credentials.password: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,[]]})})},r.prototype.prepareOutputConfig=function(e){var t=e.credentials.type;switch(t){case"anonymous":e.credentials={type:t};break;case"basic":e.credentials={type:t,username:e.credentials.username,password:e.credentials.password};break;case"cert.PEM":delete e.credentials.username}return e},r.prototype.validatorTriggers=function(){return["credentials.type"]},r.prototype.updateValidators=function(e){var t=this.mqttConfigForm.get("credentials"),r=t.get("type").value;switch(e&&t.reset({type:r},{emitEvent:!1}),t.get("username").setValidators([]),t.get("password").setValidators([]),t.get("caCert").setValidators([]),t.get("caCertFileName").setValidators([]),t.get("privateKey").setValidators([]),t.get("privateKeyFileName").setValidators([]),t.get("cert").setValidators([]),t.get("certFileName").setValidators([]),r){case"anonymous":break;case"basic":t.get("username").setValidators([i.Validators.required]),t.get("password").setValidators([i.Validators.required]);break;case"cert.PEM":t.get("caCert").setValidators([i.Validators.required]),t.get("caCertFileName").setValidators([i.Validators.required]),t.get("privateKey").setValidators([i.Validators.required]),t.get("privateKeyFileName").setValidators([i.Validators.required]),t.get("cert").setValidators([i.Validators.required]),t.get("certFileName").setValidators([i.Validators.required])}t.get("username").updateValueAndValidity({emitEvent:e}),t.get("password").updateValueAndValidity({emitEvent:e}),t.get("caCert").updateValueAndValidity({emitEvent:e}),t.get("caCertFileName").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})},r.decorators=[{type:t.Component,args:[{selector:"tb-action-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 \n \n {{ \'tb.rulenode.clean-session\' | translate }}\n \n \n {{ \'tb.rulenode.enable-ssl\' | translate }}\n \n \n \n tb.rulenode.credentials\n \n {{ mqttCredentialsTypeTranslationsMap.get(mqttConfigForm.get(\'credentials\').get(\'type\').value) | translate }}\n \n \n
\n \n tb.rulenode.credentials-type\n \n \n {{ mqttCredentialsTypeTranslationsMap.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 {{ \'tb.rulenode.password-required\' | translate }}\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',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}"]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var ie=function(){function e(){}return e.decorators=[{type:t.NgModule,args:[{declarations:[h,C,v,F,T,q,x,I,N,k,S,$,Q,z,_,ee,te,re,ne,ae,oe],imports:[r.CommonModule,a.SharedModule,X],exports:[h,C,v,F,T,q,x,I,N,k,S,$,Q,z,_,ee,te,re,ne,ae,oe]}]}],e}(),le=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n}return g(r,e),r.prototype.configForm=function(){return this.checkMessageConfigForm},r.prototype.onConfigurationSet=function(e){this.checkMessageConfigForm=this.fb.group({messageNames:[e?e.messageNames:null,[]],metadataNames:[e?e.metadataNames:null,[]],checkAllKeys:[!!e&&e.checkAllKeys,[]]})},r.prototype.validateConfig=function(){var e=this.checkMessageConfigForm.get("messageNames").value,t=this.checkMessageConfigForm.get("metadataNames").value;return e.length>0||t.length>0},r.prototype.removeMessageName=function(e){var t=this.checkMessageConfigForm.get("messageNames").value,r=t.indexOf(e);r>=0&&(t.splice(r,1),this.checkMessageConfigForm.get("messageNames").setValue(t,{emitEvent:!0}))},r.prototype.removeMetadataName=function(e){var t=this.checkMessageConfigForm.get("metadataNames").value,r=t.indexOf(e);r>=0&&(t.splice(r,1),this.checkMessageConfigForm.get("metadataNames").setValue(t,{emitEvent:!0}))},r.prototype.addMessageName=function(e){var t=e.input,r=e.value;if((r||"").trim()){r=r.trim();var n=this.checkMessageConfigForm.get("messageNames").value;n&&-1!==n.indexOf(r)||(n||(n=[]),n.push(r),this.checkMessageConfigForm.get("messageNames").setValue(n,{emitEvent:!0}))}t&&(t.value="")},r.prototype.addMetadataName=function(e){var t=e.input,r=e.value;if((r||"").trim()){r=r.trim();var n=this.checkMessageConfigForm.get("metadataNames").value;n&&-1!==n.indexOf(r)||(n||(n=[]),n.push(r),this.checkMessageConfigForm.get("metadataNames").setValue(n,{emitEvent:!0}))}t&&(t.value="")},r.decorators=[{type:t.Component,args:[{selector:"tb-filter-node-check-message-config",template:'
\n \n \n \n \n \n {{messageName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n \n \n \n \n {{metadataName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\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}"]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.entitySearchDirection=Object.keys(a.EntitySearchDirection),n.entitySearchDirectionTranslationsMap=a.entitySearchDirectionTranslations,n}return g(r,e),r.prototype.configForm=function(){return this.checkRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.checkRelationConfigForm=this.fb.group({checkForSingleEntity:[!!e&&e.checkForSingleEntity,[]],direction:[e?e.direction:null,[]],entityType:[e?e.entityType:null,e&&e.checkForSingleEntity?[i.Validators.required]:[]],entityId:[e?e.entityId:null,e&&e.checkForSingleEntity?[i.Validators.required]:[]],relationType:[e?e.relationType:null,[i.Validators.required]]})},r.prototype.validatorTriggers=function(){return["checkForSingleEntity"]},r.prototype.updateValidators=function(e){var t=this.checkRelationConfigForm.get("checkForSingleEntity").value;this.checkRelationConfigForm.get("entityType").setValidators(t?[i.Validators.required]:[]),this.checkRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:e}),this.checkRelationConfigForm.get("entityId").setValidators(t?[i.Validators.required]:[]),this.checkRelationConfigForm.get("entityId").updateValueAndValidity({emitEvent:e})},r.decorators=[{type:t.Component,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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var me=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.perimeterType=A,n.perimeterTypes=Object.keys(A),n.perimeterTypeTranslationMap=L,n.rangeUnits=Object.keys(D),n.rangeUnitTranslationMap=P,n}return g(r,e),r.prototype.configForm=function(){return this.geoFilterConfigForm},r.prototype.onConfigurationSet=function(e){this.geoFilterConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[i.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[i.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType: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,[]]})},r.prototype.validatorTriggers=function(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]},r.prototype.updateValidators=function(e){var t=this.geoFilterConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,r=this.geoFilterConfigForm.get("perimeterType").value;t?this.geoFilterConfigForm.get("perimeterType").setValidators([]):this.geoFilterConfigForm.get("perimeterType").setValidators([i.Validators.required]),t||r!==A.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([i.Validators.required,i.Validators.min(-90),i.Validators.max(90)]),this.geoFilterConfigForm.get("centerLongitude").setValidators([i.Validators.required,i.Validators.min(-180),i.Validators.max(180)]),this.geoFilterConfigForm.get("range").setValidators([i.Validators.required,i.Validators.min(0)]),this.geoFilterConfigForm.get("rangeUnit").setValidators([i.Validators.required])),t||r!==A.POLYGON?this.geoFilterConfigForm.get("polygonsDefinition").setValidators([]):this.geoFilterConfigForm.get("polygonsDefinition").setValidators([i.Validators.required]),this.geoFilterConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),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})},r.decorators=[{type:t.Component,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.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var ue=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.messageTypeConfigForm},r.prototype.onConfigurationSet=function(e){this.messageTypeConfigForm=this.fb.group({messageTypes:[e?e.messageTypes:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-filter-node-message-type-config",template:'
\n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var de=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allowedEntityTypes=[a.EntityType.DEVICE,a.EntityType.ASSET,a.EntityType.ENTITY_VIEW,a.EntityType.TENANT,a.EntityType.CUSTOMER,a.EntityType.USER,a.EntityType.DASHBOARD,a.EntityType.RULE_CHAIN,a.EntityType.RULE_NODE],n}return g(r,e),r.prototype.configForm=function(){return this.originatorTypeConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorTypeConfigForm=this.fb.group({originatorTypes:[e?e.originatorTypes:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-filter-node-originator-type-config",template:'
\n \n \n \n
\n',styles:[":host ::ng-deep tb-entity-type-list .mat-form-field-flex{padding-top:0}:host ::ng-deep tb-entity-type-list .mat-form-field-infix{border-top:0}"]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var pe=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return g(r,e),r.prototype.configForm=function(){return this.scriptConfigForm},r.prototype.onConfigurationSet=function(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"filter",this.translate.instant("tb.rulenode.filter"),"Filter",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.decorators=[{type:t.Component,args:[{selector:"tb-filter-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},r.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},r}(a.RuleNodeConfigurationComponent);var ce=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return g(r,e),r.prototype.configForm=function(){return this.switchConfigForm},r.prototype.onConfigurationSet=function(e){this.switchConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.switchConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"switch",this.translate.instant("tb.rulenode.switch"),"Switch",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.switchConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.decorators=[{type:t.Component,args:[{selector:"tb-filter-node-switch-config",template:'
\n \n \n \n
\n \n
\n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},r.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},r}(a.RuleNodeConfigurationComponent);var fe=function(){function e(){}return e.decorators=[{type:t.NgModule,args:[{declarations:[le,se,me,ue,de,pe,ce],imports:[r.CommonModule,a.SharedModule,X],exports:[le,se,me,ue,de,pe,ce]}]}],e}(),ge=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.customerAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.customerAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-customer-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var ye=function(e){function r(t,r,n){var a,o,i=e.call(this,t)||this;i.store=t,i.translate=r,i.fb=n,i.entityDetailsTranslationsMap=O,i.entityDetailsList=[],i.searchText="",i.displayDetailsFn=i.displayDetails.bind(i);try{for(var l=y(Object.keys(w)),s=l.next();!s.done;s=l.next()){var m=s.value;i.entityDetailsList.push(w[m])}}catch(e){a={error:e}}finally{try{s&&!s.done&&(o=l.return)&&o.call(l)}finally{if(a)throw a.error}}return i}return g(r,e),r.prototype.ngOnInit=function(){var t=this;this.detailsFormControl=new i.FormControl(""),e.prototype.ngOnInit.call(this),this.filteredEntityDetails=this.detailsFormControl.valueChanges.pipe(c.startWith(""),c.map((function(e){return e||""})),c.mergeMap((function(e){return t.fetchEntityDetails(e)})),c.share())},r.prototype.configForm=function(){return this.entityDetailsConfigForm},r.prototype.prepareInputConfig=function(e){return this.searchText="",this.detailsFormControl.patchValue("",{emitEvent:!0}),e},r.prototype.onConfigurationSet=function(e){this.entityDetailsConfigForm=this.fb.group({detailsList:[e?e.detailsList:null,[i.Validators.required]],addToMetadata:[!!e&&e.addToMetadata,[]]})},r.prototype.displayDetails=function(e){return e?this.translate.instant(O.get(e)):void 0},r.prototype.fetchEntityDetails=function(e){var t=this;if(this.searchText=e,this.searchText&&this.searchText.length){var r=this.searchText.toUpperCase();return p.of(this.entityDetailsList.filter((function(e){return t.translate.instant(O.get(w[e])).toUpperCase().includes(r)})))}return p.of(this.entityDetailsList)},r.prototype.detailsFieldSelected=function(e){this.addDetailsField(e.option.value),this.clear("")},r.prototype.removeDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;if(t){var r=t.indexOf(e);r>=0&&(t.splice(r,1),this.entityDetailsConfigForm.get("detailsList").setValue(t))}},r.prototype.addDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;t||(t=[]),-1===t.indexOf(e)&&(t.push(e),this.entityDetailsConfigForm.get("detailsList").setValue(t))},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.detailsInput.nativeElement.value=e,this.detailsFormControl.patchValue(null,{emitEvent:!0}),setTimeout((function(){t.detailsInput.nativeElement.blur(),t.detailsInput.nativeElement.focus()}),0)},r.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-entity-details-config",template:'
\n \n \n \n \n \n {{entityDetailsTranslationsMap.get(details) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-entity-details-matching\n
\n
\n
\n
\n
\n \n \n {{ \'tb.rulenode.add-to-metadata\' | translate }}\n \n
tb.rulenode.add-to-metadata-hint
\n
\n',styles:[":host ::ng-deep mat-form-field.entity-fields-list .mat-form-field-wrapper{margin-bottom:-1.25em}"]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:i.FormBuilder}]},r.propDecorators={detailsInput:[{type:t.ViewChild,args:["detailsInput",{static:!1}]}]},r}(a.RuleNodeConfigurationComponent);var be=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n}return g(r,e),r.prototype.configForm=function(){return this.deviceAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.deviceAttributesConfigForm=this.fb.group({deviceRelationsQuery:[e?e.deviceRelationsQuery:null,[i.Validators.required]],tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})},r.prototype.removeKey=function(e,t){var r=this.deviceAttributesConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.deviceAttributesConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.deviceAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.deviceAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-device-attributes-config",template:'
\n \n \n \n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var he=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n}return g(r,e),r.prototype.configForm=function(){return this.originatorAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorAttributesConfigForm=this.fb.group({tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})},r.prototype.removeKey=function(e,t){var r=this.originatorAttributesConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.originatorAttributesConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.originatorAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.originatorAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-originator-attributes-config",template:'
\n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var Ce=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.originatorFieldsConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorFieldsConfigForm=this.fb.group({fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-originator-fields-config",template:'
\n \n \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var ve=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n.fetchMode=K,n.fetchModes=Object.keys(K),n.samplingOrders=Object.keys(j),n.timeUnits=Object.keys(M),n.timeUnitsTranslationMap=R,n}return g(r,e),r.prototype.configForm=function(){return this.getTelemetryFromDatabaseConfigForm},r.prototype.onConfigurationSet=function(e){this.getTelemetryFromDatabaseConfigForm=this.fb.group({latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],fetchMode:[e?e.fetchMode:null,[i.Validators.required]],orderBy:[e?e.orderBy:null,[]],limit:[e?e.limit:null,[]],useMetadataIntervalPatterns:[!!e&&e.useMetadataIntervalPatterns,[]],startInterval:[e?e.startInterval:null,[]],startIntervalTimeUnit:[e?e.startIntervalTimeUnit:null,[]],endInterval:[e?e.endInterval:null,[]],endIntervalTimeUnit:[e?e.endIntervalTimeUnit:null,[]],startIntervalPattern:[e?e.startIntervalPattern:null,[]],endIntervalPattern:[e?e.endIntervalPattern:null,[]]})},r.prototype.validatorTriggers=function(){return["fetchMode","useMetadataIntervalPatterns"]},r.prototype.updateValidators=function(e){var t=this.getTelemetryFromDatabaseConfigForm.get("fetchMode").value,r=this.getTelemetryFromDatabaseConfigForm.get("useMetadataIntervalPatterns").value;t&&t===K.ALL?(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([i.Validators.required,i.Validators.min(2),i.Validators.max(1e3)])):(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([])),r?(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([i.Validators.required])):(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([])),this.getTelemetryFromDatabaseConfigForm.get("orderBy").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("limit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").updateValueAndValidity({emitEvent:e})},r.prototype.removeKey=function(e,t){var r=this.getTelemetryFromDatabaseConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.getTelemetryFromDatabaseConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-get-telemetry-from-database",template:'
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n tb.rulenode.fetch-mode\n \n \n {{ mode }}\n \n \n tb.rulenode.fetch-mode-hint\n \n
\n \n tb.rulenode.order-by\n \n \n {{ order }}\n \n \n tb.rulenode.order-by-hint\n \n \n tb.rulenode.limit\n \n tb.rulenode.limit-hint\n \n
\n \n {{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-interval-patterns-hint
\n
\n
\n \n tb.rulenode.start-interval\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.start-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.end-interval\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.end-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n \n tb.rulenode.start-interval-pattern\n \n \n {{ \'tb.rulenode.start-interval-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.end-interval-pattern\n \n \n {{ \'tb.rulenode.end-interval-pattern-required\' | translate }}\n \n \n \n \n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var Fe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.relatedAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.relatedAttributesConfigForm=this.fb.group({relationsQuery:[e?e.relationsQuery:null,[i.Validators.required]],telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-related-attributes-config",template:'
\n \n \n \n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var Te=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.tenantAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.tenantAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-tenant-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var qe=function(){function e(){}return e.decorators=[{type:t.NgModule,args:[{declarations:[ge,ye,be,he,Ce,ve,Fe,Te],imports:[r.CommonModule,a.SharedModule,X],exports:[ge,ye,be,he,Ce,ve,Fe,Te]}]}],e}(),xe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.originatorSource=V,n.originatorSources=Object.keys(V),n.originatorSourceTranslationMap=E,n}return g(r,e),r.prototype.configForm=function(){return this.changeOriginatorConfigForm},r.prototype.onConfigurationSet=function(e){this.changeOriginatorConfigForm=this.fb.group({originatorSource:[e?e.originatorSource:null,[i.Validators.required]],relationsQuery:[e?e.relationsQuery:null,[]]})},r.prototype.validatorTriggers=function(){return["originatorSource"]},r.prototype.updateValidators=function(e){var t=this.changeOriginatorConfigForm.get("originatorSource").value;t&&t===V.RELATED?this.changeOriginatorConfigForm.get("relationsQuery").setValidators([i.Validators.required]):this.changeOriginatorConfigForm.get("relationsQuery").setValidators([]),this.changeOriginatorConfigForm.get("relationsQuery").updateValueAndValidity({emitEvent:e})},r.decorators=[{type:t.Component,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
\n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var Ie=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return g(r,e),r.prototype.configForm=function(){return this.scriptConfigForm},r.prototype.onConfigurationSet=function(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"update",this.translate.instant("tb.rulenode.transformer"),"Transform",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.decorators=[{type:t.Component,args:[{selector:"tb-transformation-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},r.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},r}(a.RuleNodeConfigurationComponent);var Ne=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.toEmailConfigForm},r.prototype.onConfigurationSet=function(e){this.toEmailConfigForm=this.fb.group({fromTemplate:[e?e.fromTemplate:null,[i.Validators.required]],toTemplate:[e?e.toTemplate:null,[i.Validators.required]],ccTemplate:[e?e.ccTemplate:null,[]],bccTemplate:[e?e.bccTemplate:null,[]],subjectTemplate:[e?e.subjectTemplate:null,[i.Validators.required]],bodyTemplate:[e?e.bodyTemplate:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,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.body-template\n \n \n {{ \'tb.rulenode.body-template-required\' | translate }}\n \n \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var ke=function(){function e(){}return e.decorators=[{type:t.NgModule,args:[{declarations:[xe,Ie,Ne],imports:[r.CommonModule,a.SharedModule,X],exports:[xe,Ie,Ne]}]}],e}(),Se=function(){function e(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-name-pattern-hint":"Name pattern, use ${metaKeyName} to substitute variables from metadata","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-type-pattern-hint":"Type pattern, use ${metaKeyName} to substitute variables from metadata","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-name-pattern-hint":"Customer name pattern, use ${metaKeyName} to substitute variables from metadata","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.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.",limit:"Limit","limit-hint":"Min limit value is 2, max - 1000. In case you want to fetch a single entry, select fetch mode 'FIRST' or 'LAST'.","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","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","latest-timeseries":"Latest timeseries","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-hint":"Relation type pattern, use ${metaKeyName} to substitute variables from metadata","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","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","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","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 metadata period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds metadata pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","period-in-seconds-pattern-hint":"Period in seconds pattern, use ${metaKeyName} to substitute variables from metadata","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",propagate:"Propagate",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","from-template-hint":"From address template, use ${metaKeyName} to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use ${metaKeyName} to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use ${metaKeyName} to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use ${metaKeyName} to substitute variables from metadata","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","endpoint-url-pattern-hint":"HTTP URL address pattern, use ${metaKeyName} to substitute variables from metadata","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory",headers:"Headers","headers-hint":"Use ${metaKeyName} in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use ${metaKeyName} to substitute variables from metadata","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","topic-arn-pattern-hint":"Topic ARN pattern, use ${metaKeyName} to substitute variables from metadata","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","queue-url-pattern-hint":"Queue URL pattern, use ${metaKeyName} to substitute variables from metadata","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 ${metaKeyName} in name/value fields to substitute variables from metadata","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","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","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"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-interval-patterns":"Use metadata interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","check-all-keys":"Check that all selected keys 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-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","start-interval-pattern-hint":"Start interval pattern, use ${metaKeyName} to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use ${metaKeyName} to substitute variables from metadata","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","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":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","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","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","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":"You should 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-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 Latest telemetry with Timestamp","get-latest-value-with-ts-hint":'If selected, latest telemetry values will be added to the outbound message metadata with timestamp, e.g: "temp": "{\\"ts\\":1574329385897,\\"value\\":42}"'},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}},!0)}(e)}return e.decorators=[{type:t.NgModule,args:[{declarations:[b],imports:[r.CommonModule,a.SharedModule],exports:[ie,fe,qe,ke,b]}]}],e.ctorParameters=function(){return[{type:n.TranslateService}]},e}();e.RuleNodeCoreConfigModule=Se,e.default=Se,e.ɵa=b,e.ɵb=ie,e.ɵba=J,e.ɵbb=Z,e.ɵbc=fe,e.ɵbd=le,e.ɵbe=se,e.ɵbf=me,e.ɵbg=ue,e.ɵbh=de,e.ɵbi=pe,e.ɵbj=ce,e.ɵbk=qe,e.ɵbl=ge,e.ɵbm=ye,e.ɵbn=be,e.ɵbo=he,e.ɵbp=Ce,e.ɵbq=ve,e.ɵbr=Fe,e.ɵbs=Te,e.ɵbt=ke,e.ɵbu=xe,e.ɵbv=Ie,e.ɵbw=Ne,e.ɵc=h,e.ɵd=C,e.ɵe=v,e.ɵf=F,e.ɵg=T,e.ɵh=q,e.ɵi=x,e.ɵj=I,e.ɵk=N,e.ɵl=k,e.ɵm=S,e.ɵn=$,e.ɵo=Q,e.ɵp=z,e.ɵq=_,e.ɵr=ee,e.ɵs=te,e.ɵt=re,e.ɵu=ne,e.ɵv=ae,e.ɵw=oe,e.ɵx=X,e.ɵy=W,e.ɵz=Y,Object.defineProperty(e,"__esModule",{value:!0})})); //# sourceMappingURL=rulenode-core-config.umd.min.js.map \ No newline at end of file From 832c45fded02140c3fe5b5159175a93328e4f8ce Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 16 Jan 2020 14:50:00 +0200 Subject: [PATCH 070/133] Update travis settings --- .travis.settings.xml | 29 +++++++++++++++++++++++++++++ .travis.yml | 5 +++-- pom.xml | 4 ++-- 3 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 .travis.settings.xml diff --git a/.travis.settings.xml b/.travis.settings.xml new file mode 100644 index 0000000000..4d71bd7796 --- /dev/null +++ b/.travis.settings.xml @@ -0,0 +1,29 @@ + + + + + + gradle-fix + Gradle fix + https://repo.gradle.org/gradle/libs-releases-local + gradle + + + diff --git a/.travis.yml b/.travis.yml index 6f22da9139..516f25ae3b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,10 +3,11 @@ before_install: - export M2_HOME=/usr/local/maven - export MAVEN_OPTS="-Dmaven.repo.local=$HOME/.m2/repository -Xms1024m -Xmx3072m" - export HTTP_LOG_CONTROLLER_ERROR_STACK_TRACE=false + - cp .travis.settings.xml $HOME/.m2/settings.xml jdk: - - oraclejdk8 + - openjdk8 language: java sudo: required services: - docker -script: mvn clean verify -Ddockerfile.skip=false -DblackBoxTests.skip=false -DblackBoxTests.skipTailChildContainers=true +script: mvn clean verify -Ddockerfile.skip=false diff --git a/pom.xml b/pom.xml index 8301cb782f..6b8c51ef55 100755 --- a/pom.xml +++ b/pom.xml @@ -893,7 +893,7 @@ central - http://repo1.maven.org/maven2/ + https://repo1.maven.org/maven2/ spring-snapshots @@ -914,7 +914,7 @@ typesafe Typesafe Repository - http://repo.typesafe.com/typesafe/releases/ + https://repo.typesafe.com/typesafe/releases/ sonatype From a0238fe17864a9496267d05578d8645128d5cc77 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 16 Jan 2020 16:02:09 +0200 Subject: [PATCH 071/133] Increase memory for ui prod build --- ui-ngx/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/package.json b/ui-ngx/package.json index c952c89c51..07d0d21c81 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -5,7 +5,7 @@ "ng": "ng", "start": "ng serve --host 0.0.0.0 --open", "build": "ng build", - "build:prod": "ng build --prod --vendor-chunk", + "build:prod": "node --max_old_space_size=8048 ./node_modules/@angular/cli/bin/ng build --prod --vendor-chunk", "test": "ng test", "lint": "ng lint", "e2e": "ng e2e" From 0b63930f08dd5b9869bf41c3970b644a27b00771 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 16 Jan 2020 18:58:45 +0200 Subject: [PATCH 072/133] Use gradle-maven-plugin from org.thingsboard groupId --- .travis.settings.xml | 29 ----------------------------- .travis.yml | 1 - msa/js-executor/package-lock.json | 23 +++++++++++++++++------ msa/js-executor/pom.xml | 2 +- msa/web-ui/pom.xml | 2 +- pom.xml | 4 ++-- transport/coap/pom.xml | 2 +- transport/http/pom.xml | 2 +- transport/mqtt/pom.xml | 2 +- 9 files changed, 24 insertions(+), 43 deletions(-) delete mode 100644 .travis.settings.xml diff --git a/.travis.settings.xml b/.travis.settings.xml deleted file mode 100644 index 4d71bd7796..0000000000 --- a/.travis.settings.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - gradle-fix - Gradle fix - https://repo.gradle.org/gradle/libs-releases-local - gradle - - - diff --git a/.travis.yml b/.travis.yml index 516f25ae3b..f635da5a55 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,6 @@ before_install: - export M2_HOME=/usr/local/maven - export MAVEN_OPTS="-Dmaven.repo.local=$HOME/.m2/repository -Xms1024m -Xmx3072m" - export HTTP_LOG_CONTROLLER_ERROR_STACK_TRACE=false - - cp .travis.settings.xml $HOME/.m2/settings.xml jdk: - openjdk8 language: java diff --git a/msa/js-executor/package-lock.json b/msa/js-executor/package-lock.json index c61ef011a2..bc9fdf512d 100644 --- a/msa/js-executor/package-lock.json +++ b/msa/js-executor/package-lock.json @@ -1407,12 +1407,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1427,17 +1429,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -1554,7 +1559,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -1566,6 +1572,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -1580,6 +1587,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -1691,7 +1699,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -1703,6 +1712,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -1824,6 +1834,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", diff --git a/msa/js-executor/pom.xml b/msa/js-executor/pom.xml index e4faee234c..c047b56827 100644 --- a/msa/js-executor/pom.xml +++ b/msa/js-executor/pom.xml @@ -231,7 +231,7 @@ - org.fortasoft + org.thingsboard gradle-maven-plugin diff --git a/msa/web-ui/pom.xml b/msa/web-ui/pom.xml index 73fa4f922f..a735bf99b8 100644 --- a/msa/web-ui/pom.xml +++ b/msa/web-ui/pom.xml @@ -255,7 +255,7 @@ - org.fortasoft + org.thingsboard gradle-maven-plugin diff --git a/pom.xml b/pom.xml index 6b8c51ef55..27447b73f5 100755 --- a/pom.xml +++ b/pom.xml @@ -160,9 +160,9 @@ ${spring-boot.version} - org.fortasoft + org.thingsboard gradle-maven-plugin - 1.0.8 + 1.0.9 org.apache.maven.plugins diff --git a/transport/coap/pom.xml b/transport/coap/pom.xml index 67fbee67f8..b78cffac17 100644 --- a/transport/coap/pom.xml +++ b/transport/coap/pom.xml @@ -260,7 +260,7 @@ - org.fortasoft + org.thingsboard gradle-maven-plugin diff --git a/transport/http/pom.xml b/transport/http/pom.xml index 54b95ea122..eb50b53b34 100644 --- a/transport/http/pom.xml +++ b/transport/http/pom.xml @@ -260,7 +260,7 @@ - org.fortasoft + org.thingsboard gradle-maven-plugin diff --git a/transport/mqtt/pom.xml b/transport/mqtt/pom.xml index 63dede47ff..04a78cae99 100644 --- a/transport/mqtt/pom.xml +++ b/transport/mqtt/pom.xml @@ -260,7 +260,7 @@ - org.fortasoft + org.thingsboard gradle-maven-plugin From 08e5a7e09d78f018dd70797533e800dd65c97d84 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 16 Jan 2020 20:40:53 +0200 Subject: [PATCH 073/133] Rule nodes UI --- .../resources/public/static/rulenode/rulenode-core-config.js | 2 +- ui-ngx/proxy.conf.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js index 796b064749..91cf4cdfaa 100644 --- a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js +++ b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js @@ -12,5 +12,5 @@ See the Apache Version 2.0 License for specific language governing permissions and limitations under the License. - ***************************************************************************** */var f=function(e,t){return(f=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)t.hasOwnProperty(r)&&(e[r]=t[r])})(e,t)};function g(e,t){function r(){this.constructor=e}f(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)}function y(e){var t="function"==typeof Symbol&&e[Symbol.iterator],r=0;return t?t.call(e):{next:function(){return e&&r>=e.length&&(e=void 0),{value:e&&e[r++],done:!e}}}}var b=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.emptyConfigForm},r.prototype.onConfigurationSet=function(e){this.emptyConfigForm=this.fb.group({})},r.decorators=[{type:t.Component,args:[{selector:"tb-node-empty-config",template:"
"}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var h=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.attributeScopes=Object.keys(a.AttributeScope),n.telemetryTypeTranslationsMap=a.telemetryTypeTranslations,n}return g(r,e),r.prototype.configForm=function(){return this.attributesConfigForm},r.prototype.onConfigurationSet=function(e){this.attributesConfigForm=this.fb.group({scope:[e?e.scope:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var C=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.timeseriesConfigForm},r.prototype.onConfigurationSet=function(e){this.timeseriesConfigForm=this.fb.group({defaultTTL:[e?e.defaultTTL:null,[i.Validators.required,i.Validators.min(0)]]})},r.decorators=[{type:t.Component,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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var v=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.rpcRequestConfigForm},r.prototype.onConfigurationSet=function(e){this.rpcRequestConfigForm=this.fb.group({timeoutInSeconds:[e?e.timeoutInSeconds:null,[i.Validators.required,i.Validators.min(0)]]})},r.decorators=[{type:t.Component,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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var F=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return g(r,e),r.prototype.configForm=function(){return this.logConfigForm},r.prototype.onConfigurationSet=function(e){this.logConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.logConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"string",this.translate.instant("tb.rulenode.to-string"),"ToString",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.logConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.decorators=[{type:t.Component,args:[{selector:"tb-action-node-log-config",template:'
\n \n \n \n
\n \n
\n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},r.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},r}(a.RuleNodeConfigurationComponent);var T=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.assignCustomerConfigForm},r.prototype.onConfigurationSet=function(e){this.assignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[i.Validators.required]],createCustomerIfNotExists:[!!e&&e.createCustomerIfNotExists,[]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.decorators=[{type:t.Component,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 \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var q=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return g(r,e),r.prototype.configForm=function(){return this.clearAlarmConfigForm},r.prototype.onConfigurationSet=function(e){this.clearAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[i.Validators.required]],alarmType:[e?e.alarmType:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.clearAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.clearAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.decorators=[{type:t.Component,args:[{selector:"tb-action-node-clear-alarm-config",template:'
\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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},r.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},r}(a.RuleNodeConfigurationComponent);var x=function(e){function r(t,r,n,o){var i=e.call(this,t)||this;return i.store=t,i.fb=r,i.nodeScriptTestService=n,i.translate=o,i.alarmSeverities=Object.keys(a.AlarmSeverity),i.alarmSeverityTranslationMap=a.alarmSeverityTranslations,i.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],i}return g(r,e),r.prototype.configForm=function(){return this.createAlarmConfigForm},r.prototype.onConfigurationSet=function(e){this.createAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[i.Validators.required]],useMessageAlarmData:[!!e&&e.useMessageAlarmData,[]],alarmType:[e?e.alarmType:null,[]],severity:[e?e.severity:null,[]],propagate:[!!e&&e.propagate,[]],relationTypes:[e?e.relationTypes:null,[]]})},r.prototype.validatorTriggers=function(){return["useMessageAlarmData"]},r.prototype.updateValidators=function(e){this.createAlarmConfigForm.get("useMessageAlarmData").value?(this.createAlarmConfigForm.get("alarmType").setValidators([]),this.createAlarmConfigForm.get("severity").setValidators([])):(this.createAlarmConfigForm.get("alarmType").setValidators([i.Validators.required]),this.createAlarmConfigForm.get("severity").setValidators([i.Validators.required])),this.createAlarmConfigForm.get("alarmType").updateValueAndValidity({emitEvent:e}),this.createAlarmConfigForm.get("severity").updateValueAndValidity({emitEvent:e})},r.prototype.testScript=function(){var e=this,t=this.createAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.createAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},r.prototype.removeKey=function(e,t){var r=this.createAlarmConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.createAlarmConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.createAlarmConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.createAlarmConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.decorators=[{type:t.Component,args:[{selector:"tb-action-node-create-alarm-config",template:'
\n \n \n \n
\n \n
\n \n {{ \'tb.rulenode.use-message-alarm-data\' | translate }}\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.alarm-severity\n \n \n {{ alarmSeverityTranslationMap.get(severity) | translate }}\n \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 \n \n
\n
\n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},r.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},r}(a.RuleNodeConfigurationComponent);var I=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return g(r,e),r.prototype.configForm=function(){return this.createRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.createRelationConfigForm=this.fb.group({direction:[e?e.direction:null,[i.Validators.required]],entityType:[e?e.entityType:null,[i.Validators.required]],entityNamePattern:[e?e.entityNamePattern:null,[]],entityTypePattern:[e?e.entityTypePattern:null,[]],relationType:[e?e.relationType:null,[i.Validators.required]],createEntityIfNotExists:[!!e&&e.createEntityIfNotExists,[]],removeCurrentRelations:[!!e&&e.removeCurrentRelations,[]],changeOriginatorToRelatedEntity:[!!e&&e.changeOriginatorToRelatedEntity,[]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.validatorTriggers=function(){return["entityType"]},r.prototype.updateValidators=function(e){var t=this.createRelationConfigForm.get("entityType").value;t?this.createRelationConfigForm.get("entityNamePattern").setValidators([i.Validators.required]):this.createRelationConfigForm.get("entityNamePattern").setValidators([]),!t||t!==a.EntityType.DEVICE&&t!==a.EntityType.ASSET?this.createRelationConfigForm.get("entityTypePattern").setValidators([]):this.createRelationConfigForm.get("entityTypePattern").setValidators([i.Validators.required]),this.createRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e}),this.createRelationConfigForm.get("entityTypePattern").updateValueAndValidity({emitEvent:e})},r.decorators=[{type:t.Component,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 \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var N=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.msgDelayConfigForm},r.prototype.onConfigurationSet=function(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,[i.Validators.required,i.Validators.min(1),i.Validators.max(1e5)]]})},r.prototype.validatorTriggers=function(){return["useMetadataPeriodInSecondsPatterns"]},r.prototype.updateValidators=function(e){this.msgDelayConfigForm.get("useMetadataPeriodInSecondsPatterns").value?(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([i.Validators.required]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([])):(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([i.Validators.required,i.Validators.min(0)])),this.msgDelayConfigForm.get("periodInSecondsPattern").updateValueAndValidity({emitEvent:e}),this.msgDelayConfigForm.get("periodInSeconds").updateValueAndValidity({emitEvent:e})},r.decorators=[{type:t.Component,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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var k=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return g(r,e),r.prototype.configForm=function(){return this.deleteRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.deleteRelationConfigForm=this.fb.group({deleteForSingleEntity:[!!e&&e.deleteForSingleEntity,[]],direction:[e?e.direction:null,[i.Validators.required]],entityType:[e?e.entityType:null,[]],entityNamePattern:[e?e.entityNamePattern:null,[]],relationType:[e?e.relationType:null,[i.Validators.required]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.validatorTriggers=function(){return["deleteForSingleEntity","entityType"]},r.prototype.updateValidators=function(e){var t=this.deleteRelationConfigForm.get("deleteForSingleEntity").value,r=this.deleteRelationConfigForm.get("entityType").value;t?this.deleteRelationConfigForm.get("entityType").setValidators([i.Validators.required]):this.deleteRelationConfigForm.get("entityType").setValidators([]),t&&r?this.deleteRelationConfigForm.get("entityNamePattern").setValidators([i.Validators.required]):this.deleteRelationConfigForm.get("entityNamePattern").setValidators([]),this.deleteRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:!1}),this.deleteRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e})},r.decorators=[{type:t.Component,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 \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var S=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return g(r,e),r.prototype.configForm=function(){return this.generatorConfigForm},r.prototype.onConfigurationSet=function(e){this.generatorConfigForm=this.fb.group({msgCount:[e?e.msgCount:null,[i.Validators.required,i.Validators.min(0)]],periodInSeconds:[e?e.periodInSeconds:null,[i.Validators.required,i.Validators.min(1)]],originator:[e?e.originator:null,[]],jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.prepareInputConfig=function(e){return e&&(e.originatorId&&e.originatorType?e.originator={id:e.originatorId,entityType:e.originatorType}:e.originator=null,delete e.originatorId,delete e.originatorType),e},r.prototype.prepareOutputConfig=function(e){return e.originator?(e.originatorId=e.originator.id,e.originatorType=e.originator.entityType):(e.originatorId=null,e.originatorType=null),delete e.originator,e},r.prototype.testScript=function(){var e=this,t=this.generatorConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"generate",this.translate.instant("tb.rulenode.generator"),"Generate",["prevMsg","prevMetadata","prevMsgType"],this.ruleNodeId).subscribe((function(t){t&&e.generatorConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.decorators=[{type:t.Component,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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},r.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},r}(a.RuleNodeConfigurationComponent);var V={CUSTOMER:"CUSTOMER",TENANT:"TENANT",RELATED:"RELATED",ALARM_ORIGINATOR:"ALARM_ORIGINATOR"},E=new Map([[V.CUSTOMER,"tb.rulenode.originator-customer"],[V.TENANT,"tb.rulenode.originator-tenant"],[V.RELATED,"tb.rulenode.originator-related"],[V.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator"]]),A={CIRCLE:"CIRCLE",POLYGON:"POLYGON"},L=new Map([[A.CIRCLE,"tb.rulenode.perimeter-circle"],[A.POLYGON,"tb.rulenode.perimeter-polygon"]]),M={MILLISECONDS:"MILLISECONDS",SECONDS:"SECONDS",MINUTES:"MINUTES",HOURS:"HOURS",DAYS:"DAYS"},R=new Map([[M.MILLISECONDS,"tb.rulenode.time-unit-milliseconds"],[M.SECONDS,"tb.rulenode.time-unit-seconds"],[M.MINUTES,"tb.rulenode.time-unit-minutes"],[M.HOURS,"tb.rulenode.time-unit-hours"],[M.DAYS,"tb.rulenode.time-unit-days"]]),D={METER:"METER",KILOMETER:"KILOMETER",FOOT:"FOOT",MILE:"MILE",NAUTICAL_MILE:"NAUTICAL_MILE"},P=new Map([[D.METER,"tb.rulenode.range-unit-meter"],[D.KILOMETER,"tb.rulenode.range-unit-kilometer"],[D.FOOT,"tb.rulenode.range-unit-foot"],[D.MILE,"tb.rulenode.range-unit-mile"],[D.NAUTICAL_MILE,"tb.rulenode.range-unit-nautical-mile"]]),w={TITLE:"TITLE",COUNTRY:"COUNTRY",STATE:"STATE",ZIP:"ZIP",ADDRESS:"ADDRESS",ADDRESS2:"ADDRESS2",PHONE:"PHONE",EMAIL:"EMAIL",ADDITIONAL_INFO:"ADDITIONAL_INFO"},O=new Map([[w.TITLE,"tb.rulenode.entity-details-title"],[w.COUNTRY,"tb.rulenode.entity-details-country"],[w.STATE,"tb.rulenode.entity-details-state"],[w.ZIP,"tb.rulenode.entity-details-zip"],[w.ADDRESS,"tb.rulenode.entity-details-address"],[w.ADDRESS2,"tb.rulenode.entity-details-address2"],[w.PHONE,"tb.rulenode.entity-details-phone"],[w.EMAIL,"tb.rulenode.entity-details-email"],[w.ADDITIONAL_INFO,"tb.rulenode.entity-details-additional_info"]]),K={FIRST:"FIRST",LAST:"LAST",ALL:"ALL"},j={ASC:"ASC",DESC:"DESC"},U={STANDARD:"STANDARD",FIFO:"FIFO"},G=new Map([[U.STANDARD,"tb.rulenode.sqs-queue-standard"],[U.FIFO,"tb.rulenode.sqs-queue-fifo"]]),B=["anonymous","basic","cert.PEM"],H=new Map([["anonymous","tb.rulenode.credentials-anonymous"],["basic","tb.rulenode.credentials-basic"],["cert.PEM","tb.rulenode.credentials-pem"]]),$=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.perimeterType=A,n.perimeterTypes=Object.keys(A),n.perimeterTypeTranslationMap=L,n.rangeUnits=Object.keys(D),n.rangeUnitTranslationMap=P,n.timeUnits=Object.keys(M),n.timeUnitsTranslationMap=R,n}return g(r,e),r.prototype.configForm=function(){return this.geoActionConfigForm},r.prototype.onConfigurationSet=function(e){this.geoActionConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[i.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[i.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType: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,[i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]],minInsideDurationTimeUnit:[e?e.minInsideDurationTimeUnit:null,[i.Validators.required]],minOutsideDuration:[e?e.minOutsideDuration:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]],minOutsideDurationTimeUnit:[e?e.minOutsideDurationTimeUnit:null,[i.Validators.required]]})},r.prototype.validatorTriggers=function(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]},r.prototype.updateValidators=function(e){var t=this.geoActionConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,r=this.geoActionConfigForm.get("perimeterType").value;t?this.geoActionConfigForm.get("perimeterType").setValidators([]):this.geoActionConfigForm.get("perimeterType").setValidators([i.Validators.required]),t||r!==A.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([i.Validators.required,i.Validators.min(-90),i.Validators.max(90)]),this.geoActionConfigForm.get("centerLongitude").setValidators([i.Validators.required,i.Validators.min(-180),i.Validators.max(180)]),this.geoActionConfigForm.get("range").setValidators([i.Validators.required,i.Validators.min(0)]),this.geoActionConfigForm.get("rangeUnit").setValidators([i.Validators.required])),t||r!==A.POLYGON?this.geoActionConfigForm.get("polygonsDefinition").setValidators([]):this.geoActionConfigForm.get("polygonsDefinition").setValidators([i.Validators.required]),this.geoActionConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),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})},r.decorators=[{type:t.Component,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.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var Q=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.msgCountConfigForm},r.prototype.onConfigurationSet=function(e){this.msgCountConfigForm=this.fb.group({interval:[e?e.interval:null,[i.Validators.required,i.Validators.min(1)]],telemetryPrefix:[e?e.telemetryPrefix:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var z=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.rpcReplyConfigForm},r.prototype.onConfigurationSet=function(e){this.rpcReplyConfigForm=this.fb.group({requestIdMetaDataAttribute:[e?e.requestIdMetaDataAttribute:null,[]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-action-node-rpc-reply-config",template:'
\n \n tb.rulenode.request-id-metadata-attribute\n \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var _=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.saveToCustomTableConfigForm},r.prototype.onConfigurationSet=function(e){this.saveToCustomTableConfigForm=this.fb.group({tableName:[e?e.tableName:null,[i.Validators.required]],fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,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 \n \n \n \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var W=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.translate=r,o.injector=n,o.fb=a,o.propagateChange=null,o.valueChangeSubscription=null,o}return g(r,e),Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){this.ngControl=this.injector.get(i.NgControl),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.kvListFormGroup=this.fb.group({}),this.kvListFormGroup.addControl("keyVals",this.fb.array([]))},r.prototype.keyValsFormArray=function(){return this.kvListFormGroup.get("keyVals")},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.kvListFormGroup.disable({emitEvent:!1}):this.kvListFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){var t,r,n=this;this.valueChangeSubscription&&this.valueChangeSubscription.unsubscribe();var a=[];if(e)try{for(var o=y(Object.keys(e)),l=o.next();!l.done;l=o.next()){var s=l.value;Object.prototype.hasOwnProperty.call(e,s)&&a.push(this.fb.group({key:[s,[i.Validators.required]],value:[e[s],[i.Validators.required]]}))}}catch(e){t={error:e}}finally{try{l&&!l.done&&(r=o.return)&&r.call(o)}finally{if(t)throw t.error}}this.kvListFormGroup.setControl("keyVals",this.fb.array(a)),this.valueChangeSubscription=this.kvListFormGroup.valueChanges.subscribe((function(){n.updateModel()}))},r.prototype.removeKeyVal=function(e){this.kvListFormGroup.get("keyVals").removeAt(e)},r.prototype.addKeyVal=function(){this.kvListFormGroup.get("keyVals").push(this.fb.group({key:["",[i.Validators.required]],value:["",[i.Validators.required]]}))},r.prototype.validate=function(e){return!this.kvListFormGroup.get("keyVals").value.length&&this.required?{kvMapRequired:!0}:this.kvListFormGroup.valid?null:{kvFieldsRequired:!0}},r.prototype.updateModel=function(){var e=this.kvListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.kvListFormGroup.valid)this.propagateChange(null);else{var t={};e.forEach((function(e){t[e.key]=e.value})),this.propagateChange(t)}},r.decorators=[{type:t.Component,args:[{selector:"tb-kv-map-config",template:'
\n
\n {{ keyText }}\n {{ valText }}\n \n
\n
\n
\n \n \n \n \n {{ keyRequiredText | translate }}\n \n \n \n \n \n \n {{ valRequiredText | translate }}\n \n \n \n
\n
\n \n
\n \n
\n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return r})),multi:!0},{provide:i.NG_VALIDATORS,useExisting:t.forwardRef((function(){return r})),multi:!0}],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:rgba(0,0,0,.54);font-size:12px;font-weight:700;white-space:nowrap}:host .tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:20px;max-height:300px;overflow:auto}:host .tb-kv-map-config .body .row{padding-top:5px;max-height:40px}:host .tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell{margin:0;max-height:40px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell .mat-form-field-infix{border-top:0}:host ::ng-deep .tb-kv-map-config .body button.mat-button{margin:0}"]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:t.Injector},{type:i.FormBuilder}]},r.propDecorators={disabled:[{type:t.Input}],requiredText:[{type:t.Input}],keyText:[{type:t.Input}],keyRequiredText:[{type:t.Input}],valText:[{type:t.Input}],valRequiredText:[{type:t.Input}],required:[{type:t.Input}]},r}(a.PageComponent);var Y=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n.propagateChange=null,n}return g(r,e),Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.deviceRelationsQueryFormGroup=this.fb.group({direction:[null,[i.Validators.required]],maxLevel:[null,[]],relationType:[null],deviceTypes:[null,[i.Validators.required]]}),this.deviceRelationsQueryFormGroup.valueChanges.subscribe((function(t){e.deviceRelationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.deviceRelationsQueryFormGroup.disable({emitEvent:!1}):this.deviceRelationsQueryFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){this.deviceRelationsQueryFormGroup.reset(e,{emitEvent:!1})},r.decorators=[{type:t.Component,args:[{selector:"tb-device-relations-query-config",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
\n
relation.relation-type
\n \n \n
device.device-types
\n \n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return r})),multi:!0}]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r.propDecorators={disabled:[{type:t.Input}],required:[{type:t.Input}]},r}(a.PageComponent);var J=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.propagateChange=null,n}return g(r,e),Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.relationsQueryFormGroup=this.fb.group({direction:[null,[i.Validators.required]],maxLevel:[null,[]],filters:[null]}),this.relationsQueryFormGroup.valueChanges.subscribe((function(t){e.relationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.relationsQueryFormGroup.disable({emitEvent:!1}):this.relationsQueryFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){this.relationsQueryFormGroup.reset(e,{emitEvent:!1})},r.decorators=[{type:t.Component,args:[{selector:"tb-relations-query-config",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
\n
relation.relation-filters
\n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return r})),multi:!0}]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r.propDecorators={disabled:[{type:t.Input}],required:[{type:t.Input}]},r}(a.PageComponent);var Z=function(e){function r(t,r,n,o){var i,l,m=e.call(this,t)||this;m.store=t,m.translate=r,m.truncate=n,m.fb=o,m.placeholder="tb.rulenode.message-type",m.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],m.messageTypes=[],m.messageTypesList=[],m.searchText="",m.propagateChange=function(e){},m.messageTypeConfigForm=m.fb.group({messageType:[null]});try{for(var u=y(Object.keys(a.MessageType)),d=u.next();!d.done;d=u.next()){var p=d.value;m.messageTypesList.push({name:a.messageTypeNames.get(a.MessageType[p]),value:p})}}catch(e){i={error:e}}finally{try{d&&!d.done&&(l=u.return)&&l.call(u)}finally{if(i)throw i.error}}return m}return g(r,e),Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.ngOnInit=function(){var e=this;this.filteredMessageTypes=this.messageTypeConfigForm.get("messageType").valueChanges.pipe(c.startWith(""),c.map((function(e){return e||""})),c.mergeMap((function(t){return e.fetchMessageTypes(t)})),c.share())},r.prototype.ngAfterViewInit=function(){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.messageTypeConfigForm.disable({emitEvent:!1}):this.messageTypeConfigForm.enable({emitEvent:!1})},r.prototype.writeValue=function(e){var t=this;this.searchText="",this.messageTypes.length=0,e&&e.forEach((function(e){var r=t.messageTypesList.find((function(t){return t.value===e}));r?t.messageTypes.push({name:r.name,value:r.value}):t.messageTypes.push({name:e,value:e})}))},r.prototype.displayMessageTypeFn=function(e){return e?e.name:void 0},r.prototype.textIsNotEmpty=function(e){return!!(e&&null!=e&&e.length>0)},r.prototype.createMessageType=function(e,t){e.preventDefault(),this.transformMessageType(t)},r.prototype.add=function(e){this.transformMessageType(e.value)},r.prototype.fetchMessageTypes=function(e){if(this.searchText=e,this.searchText&&this.searchText.length){var t=this.searchText.toUpperCase();return p.of(this.messageTypesList.filter((function(e){return e.name.toUpperCase().includes(t)})))}return p.of(this.messageTypesList)},r.prototype.transformMessageType=function(e){if((e||"").trim()){var t=null,r=e.trim(),n=this.messageTypesList.find((function(e){return e.name===r}));(t=n?{name:n.name,value:n.value}:{name:r,value:r})&&this.addMessageType(t)}this.clear("")},r.prototype.remove=function(e){var t=this.messageTypes.indexOf(e);t>=0&&(this.messageTypes.splice(t,1),this.updateModel())},r.prototype.selected=function(e){this.addMessageType(e.option.value),this.clear("")},r.prototype.addMessageType=function(e){-1===this.messageTypes.findIndex((function(t){return t.value===e.value}))&&(this.messageTypes.push(e),this.updateModel())},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.messageTypeInput.nativeElement.value=e,this.messageTypeConfigForm.get("messageType").patchValue(null,{emitEvent:!0}),setTimeout((function(){t.messageTypeInput.nativeElement.blur(),t.messageTypeInput.nativeElement.focus()}),0)},r.prototype.updateModel=function(){var e=this.messageTypes.map((function(e){return e.value}));this.required?(this.chipList.errorState=!e.length,this.propagateChange(e.length>0?e:null)):(this.chipList.errorState=!1,this.propagateChange(e))},r.decorators=[{type:t.Component,args:[{selector:"tb-message-types-config",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 {{ translate.get(\'tb.rulenode.no-message-type-matching\',\n {messageType: truncate.transform(searchText, true, 6, '...')}) | async }}\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',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return r})),multi:!0}]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:a.TruncatePipe},{type:i.FormBuilder}]},r.propDecorators={required:[{type:t.Input}],label:[{type:t.Input}],placeholder:[{type:t.Input}],disabled:[{type:t.Input}],chipList:[{type:t.ViewChild,args:["chipList",{static:!1}]}],matAutocomplete:[{type:t.ViewChild,args:["messageTypeAutocomplete",{static:!1}]}],messageTypeInput:[{type:t.ViewChild,args:["messageTypeInput",{static:!1}]}]},r}(a.PageComponent);var X=function(){function e(){}return e.decorators=[{type:t.NgModule,args:[{declarations:[W,Y,J,Z],imports:[r.CommonModule,a.SharedModule,m.HomeComponentsModule],exports:[W,Y,J,Z]}]}],e}(),ee=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.unassignCustomerConfigForm},r.prototype.onConfigurationSet=function(e){this.unassignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[i.Validators.required]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.decorators=[{type:t.Component,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 \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var te=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.snsConfigForm},r.prototype.onConfigurationSet=function(e){this.snsConfigForm=this.fb.group({topicArnPattern:[e?e.topicArnPattern:null,[i.Validators.required]],accessKeyId:[e?e.accessKeyId:null,[i.Validators.required]],secretAccessKey:[e?e.secretAccessKey:null,[i.Validators.required]],region:[e?e.region:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-action-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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var re=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.sqsQueueType=U,n.sqsQueueTypes=Object.keys(U),n.sqsQueueTypeTranslationsMap=G,n}return g(r,e),r.prototype.configForm=function(){return this.sqsConfigForm},r.prototype.onConfigurationSet=function(e){this.sqsConfigForm=this.fb.group({queueType:[e?e.queueType:null,[i.Validators.required]],queueUrlPattern:[e?e.queueUrlPattern:null,[i.Validators.required]],delaySeconds:[e?e.delaySeconds:null,[i.Validators.min(0),i.Validators.max(900)]],messageAttributes:[e?e.messageAttributes:null,[]],accessKeyId:[e?e.accessKeyId:null,[i.Validators.required]],secretAccessKey:[e?e.secretAccessKey:null,[i.Validators.required]],region:[e?e.region:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-action-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
tb.rulenode.message-attributes-hint
\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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var ne=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.pubSubConfigForm},r.prototype.onConfigurationSet=function(e){this.pubSubConfigForm=this.fb.group({projectId:[e?e.projectId:null,[i.Validators.required]],topicName:[e?e.topicName:null,[i.Validators.required]],serviceAccountKey:[e?e.serviceAccountKey:null,[i.Validators.required]],serviceAccountKeyFileName:[e?e.serviceAccountKeyFileName:null,[i.Validators.required]],messageAttributes:[e?e.messageAttributes:null,[]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-action-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
tb.rulenode.message-attributes-hint
\n \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var ae=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.ackValues=["all","-1","0","1"],n}return g(r,e),r.prototype.configForm=function(){return this.kafkaConfigForm},r.prototype.onConfigurationSet=function(e){this.kafkaConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],bootstrapServers:[e?e.bootstrapServers:null,[i.Validators.required]],retries:[e?e.retries:null,[i.Validators.min(0)]],batchSize:[e?e.batchSize:null,[i.Validators.min(0)]],linger:[e?e.linger:null,[i.Validators.min(0)]],bufferMemory:[e?e.bufferMemory:null,[i.Validators.min(0)]],acks:[e?e.acks:null,[i.Validators.required]],keySerializer:[e?e.keySerializer:null,[i.Validators.required]],valueSerializer:[e?e.valueSerializer:null,[i.Validators.required]],otherProperties:[e?e.otherProperties:null,[]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-action-node-kafka-config",template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var oe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allMqttCredentialsTypes=B,n.mqttCredentialsTypeTranslationsMap=H,n}return g(r,e),r.prototype.configForm=function(){return this.mqttConfigForm},r.prototype.onConfigurationSet=function(e){this.mqttConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(200)]],clientId:[e?e.clientId:null,[]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:this.fb.group({type:[e&&e.credentials?e.credentials.type:null,[i.Validators.required]],username:[e&&e.credentials?e.credentials.username:null,[]],password:[e&&e.credentials?e.credentials.password: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,[]]})})},r.prototype.prepareOutputConfig=function(e){var t=e.credentials.type;switch(t){case"anonymous":e.credentials={type:t};break;case"basic":e.credentials={type:t,username:e.credentials.username,password:e.credentials.password};break;case"cert.PEM":delete e.credentials.username}return e},r.prototype.validatorTriggers=function(){return["credentials.type"]},r.prototype.updateValidators=function(e){var t=this.mqttConfigForm.get("credentials"),r=t.get("type").value;switch(e&&t.reset({type:r},{emitEvent:!1}),t.get("username").setValidators([]),t.get("password").setValidators([]),t.get("caCert").setValidators([]),t.get("caCertFileName").setValidators([]),t.get("privateKey").setValidators([]),t.get("privateKeyFileName").setValidators([]),t.get("cert").setValidators([]),t.get("certFileName").setValidators([]),r){case"anonymous":break;case"basic":t.get("username").setValidators([i.Validators.required]),t.get("password").setValidators([i.Validators.required]);break;case"cert.PEM":t.get("caCert").setValidators([i.Validators.required]),t.get("caCertFileName").setValidators([i.Validators.required]),t.get("privateKey").setValidators([i.Validators.required]),t.get("privateKeyFileName").setValidators([i.Validators.required]),t.get("cert").setValidators([i.Validators.required]),t.get("certFileName").setValidators([i.Validators.required])}t.get("username").updateValueAndValidity({emitEvent:e}),t.get("password").updateValueAndValidity({emitEvent:e}),t.get("caCert").updateValueAndValidity({emitEvent:e}),t.get("caCertFileName").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})},r.decorators=[{type:t.Component,args:[{selector:"tb-action-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 \n \n {{ \'tb.rulenode.clean-session\' | translate }}\n \n \n {{ \'tb.rulenode.enable-ssl\' | translate }}\n \n \n \n tb.rulenode.credentials\n \n {{ mqttCredentialsTypeTranslationsMap.get(mqttConfigForm.get(\'credentials\').get(\'type\').value) | translate }}\n \n \n
\n \n tb.rulenode.credentials-type\n \n \n {{ mqttCredentialsTypeTranslationsMap.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 {{ \'tb.rulenode.password-required\' | translate }}\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',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}"]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var ie=function(){function e(){}return e.decorators=[{type:t.NgModule,args:[{declarations:[h,C,v,F,T,q,x,I,N,k,S,$,Q,z,_,ee,te,re,ne,ae,oe],imports:[r.CommonModule,a.SharedModule,X],exports:[h,C,v,F,T,q,x,I,N,k,S,$,Q,z,_,ee,te,re,ne,ae,oe]}]}],e}(),le=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n}return g(r,e),r.prototype.configForm=function(){return this.checkMessageConfigForm},r.prototype.onConfigurationSet=function(e){this.checkMessageConfigForm=this.fb.group({messageNames:[e?e.messageNames:null,[]],metadataNames:[e?e.metadataNames:null,[]],checkAllKeys:[!!e&&e.checkAllKeys,[]]})},r.prototype.validateConfig=function(){var e=this.checkMessageConfigForm.get("messageNames").value,t=this.checkMessageConfigForm.get("metadataNames").value;return e.length>0||t.length>0},r.prototype.removeMessageName=function(e){var t=this.checkMessageConfigForm.get("messageNames").value,r=t.indexOf(e);r>=0&&(t.splice(r,1),this.checkMessageConfigForm.get("messageNames").setValue(t,{emitEvent:!0}))},r.prototype.removeMetadataName=function(e){var t=this.checkMessageConfigForm.get("metadataNames").value,r=t.indexOf(e);r>=0&&(t.splice(r,1),this.checkMessageConfigForm.get("metadataNames").setValue(t,{emitEvent:!0}))},r.prototype.addMessageName=function(e){var t=e.input,r=e.value;if((r||"").trim()){r=r.trim();var n=this.checkMessageConfigForm.get("messageNames").value;n&&-1!==n.indexOf(r)||(n||(n=[]),n.push(r),this.checkMessageConfigForm.get("messageNames").setValue(n,{emitEvent:!0}))}t&&(t.value="")},r.prototype.addMetadataName=function(e){var t=e.input,r=e.value;if((r||"").trim()){r=r.trim();var n=this.checkMessageConfigForm.get("metadataNames").value;n&&-1!==n.indexOf(r)||(n||(n=[]),n.push(r),this.checkMessageConfigForm.get("metadataNames").setValue(n,{emitEvent:!0}))}t&&(t.value="")},r.decorators=[{type:t.Component,args:[{selector:"tb-filter-node-check-message-config",template:'
\n \n \n \n \n \n {{messageName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n \n \n \n \n {{metadataName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\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}"]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.entitySearchDirection=Object.keys(a.EntitySearchDirection),n.entitySearchDirectionTranslationsMap=a.entitySearchDirectionTranslations,n}return g(r,e),r.prototype.configForm=function(){return this.checkRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.checkRelationConfigForm=this.fb.group({checkForSingleEntity:[!!e&&e.checkForSingleEntity,[]],direction:[e?e.direction:null,[]],entityType:[e?e.entityType:null,e&&e.checkForSingleEntity?[i.Validators.required]:[]],entityId:[e?e.entityId:null,e&&e.checkForSingleEntity?[i.Validators.required]:[]],relationType:[e?e.relationType:null,[i.Validators.required]]})},r.prototype.validatorTriggers=function(){return["checkForSingleEntity"]},r.prototype.updateValidators=function(e){var t=this.checkRelationConfigForm.get("checkForSingleEntity").value;this.checkRelationConfigForm.get("entityType").setValidators(t?[i.Validators.required]:[]),this.checkRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:e}),this.checkRelationConfigForm.get("entityId").setValidators(t?[i.Validators.required]:[]),this.checkRelationConfigForm.get("entityId").updateValueAndValidity({emitEvent:e})},r.decorators=[{type:t.Component,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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var me=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.perimeterType=A,n.perimeterTypes=Object.keys(A),n.perimeterTypeTranslationMap=L,n.rangeUnits=Object.keys(D),n.rangeUnitTranslationMap=P,n}return g(r,e),r.prototype.configForm=function(){return this.geoFilterConfigForm},r.prototype.onConfigurationSet=function(e){this.geoFilterConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[i.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[i.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType: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,[]]})},r.prototype.validatorTriggers=function(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]},r.prototype.updateValidators=function(e){var t=this.geoFilterConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,r=this.geoFilterConfigForm.get("perimeterType").value;t?this.geoFilterConfigForm.get("perimeterType").setValidators([]):this.geoFilterConfigForm.get("perimeterType").setValidators([i.Validators.required]),t||r!==A.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([i.Validators.required,i.Validators.min(-90),i.Validators.max(90)]),this.geoFilterConfigForm.get("centerLongitude").setValidators([i.Validators.required,i.Validators.min(-180),i.Validators.max(180)]),this.geoFilterConfigForm.get("range").setValidators([i.Validators.required,i.Validators.min(0)]),this.geoFilterConfigForm.get("rangeUnit").setValidators([i.Validators.required])),t||r!==A.POLYGON?this.geoFilterConfigForm.get("polygonsDefinition").setValidators([]):this.geoFilterConfigForm.get("polygonsDefinition").setValidators([i.Validators.required]),this.geoFilterConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),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})},r.decorators=[{type:t.Component,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.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var ue=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.messageTypeConfigForm},r.prototype.onConfigurationSet=function(e){this.messageTypeConfigForm=this.fb.group({messageTypes:[e?e.messageTypes:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-filter-node-message-type-config",template:'
\n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var de=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allowedEntityTypes=[a.EntityType.DEVICE,a.EntityType.ASSET,a.EntityType.ENTITY_VIEW,a.EntityType.TENANT,a.EntityType.CUSTOMER,a.EntityType.USER,a.EntityType.DASHBOARD,a.EntityType.RULE_CHAIN,a.EntityType.RULE_NODE],n}return g(r,e),r.prototype.configForm=function(){return this.originatorTypeConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorTypeConfigForm=this.fb.group({originatorTypes:[e?e.originatorTypes:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-filter-node-originator-type-config",template:'
\n \n \n \n
\n',styles:[":host ::ng-deep tb-entity-type-list .mat-form-field-flex{padding-top:0}:host ::ng-deep tb-entity-type-list .mat-form-field-infix{border-top:0}"]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var pe=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return g(r,e),r.prototype.configForm=function(){return this.scriptConfigForm},r.prototype.onConfigurationSet=function(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"filter",this.translate.instant("tb.rulenode.filter"),"Filter",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.decorators=[{type:t.Component,args:[{selector:"tb-filter-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},r.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},r}(a.RuleNodeConfigurationComponent);var ce=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return g(r,e),r.prototype.configForm=function(){return this.switchConfigForm},r.prototype.onConfigurationSet=function(e){this.switchConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.switchConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"switch",this.translate.instant("tb.rulenode.switch"),"Switch",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.switchConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.decorators=[{type:t.Component,args:[{selector:"tb-filter-node-switch-config",template:'
\n \n \n \n
\n \n
\n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},r.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},r}(a.RuleNodeConfigurationComponent);var fe=function(){function e(){}return e.decorators=[{type:t.NgModule,args:[{declarations:[le,se,me,ue,de,pe,ce],imports:[r.CommonModule,a.SharedModule,X],exports:[le,se,me,ue,de,pe,ce]}]}],e}(),ge=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.customerAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.customerAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-customer-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var ye=function(e){function r(t,r,n){var a,o,i=e.call(this,t)||this;i.store=t,i.translate=r,i.fb=n,i.entityDetailsTranslationsMap=O,i.entityDetailsList=[],i.searchText="",i.displayDetailsFn=i.displayDetails.bind(i);try{for(var l=y(Object.keys(w)),s=l.next();!s.done;s=l.next()){var m=s.value;i.entityDetailsList.push(w[m])}}catch(e){a={error:e}}finally{try{s&&!s.done&&(o=l.return)&&o.call(l)}finally{if(a)throw a.error}}return i}return g(r,e),r.prototype.ngOnInit=function(){var t=this;this.detailsFormControl=new i.FormControl(""),e.prototype.ngOnInit.call(this),this.filteredEntityDetails=this.detailsFormControl.valueChanges.pipe(c.startWith(""),c.map((function(e){return e||""})),c.mergeMap((function(e){return t.fetchEntityDetails(e)})),c.share())},r.prototype.configForm=function(){return this.entityDetailsConfigForm},r.prototype.prepareInputConfig=function(e){return this.searchText="",this.detailsFormControl.patchValue("",{emitEvent:!0}),e},r.prototype.onConfigurationSet=function(e){this.entityDetailsConfigForm=this.fb.group({detailsList:[e?e.detailsList:null,[i.Validators.required]],addToMetadata:[!!e&&e.addToMetadata,[]]})},r.prototype.displayDetails=function(e){return e?this.translate.instant(O.get(e)):void 0},r.prototype.fetchEntityDetails=function(e){var t=this;if(this.searchText=e,this.searchText&&this.searchText.length){var r=this.searchText.toUpperCase();return p.of(this.entityDetailsList.filter((function(e){return t.translate.instant(O.get(w[e])).toUpperCase().includes(r)})))}return p.of(this.entityDetailsList)},r.prototype.detailsFieldSelected=function(e){this.addDetailsField(e.option.value),this.clear("")},r.prototype.removeDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;if(t){var r=t.indexOf(e);r>=0&&(t.splice(r,1),this.entityDetailsConfigForm.get("detailsList").setValue(t))}},r.prototype.addDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;t||(t=[]),-1===t.indexOf(e)&&(t.push(e),this.entityDetailsConfigForm.get("detailsList").setValue(t))},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.detailsInput.nativeElement.value=e,this.detailsFormControl.patchValue(null,{emitEvent:!0}),setTimeout((function(){t.detailsInput.nativeElement.blur(),t.detailsInput.nativeElement.focus()}),0)},r.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-entity-details-config",template:'
\n \n \n \n \n \n {{entityDetailsTranslationsMap.get(details) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-entity-details-matching\n
\n
\n
\n
\n
\n \n \n {{ \'tb.rulenode.add-to-metadata\' | translate }}\n \n
tb.rulenode.add-to-metadata-hint
\n
\n',styles:[":host ::ng-deep mat-form-field.entity-fields-list .mat-form-field-wrapper{margin-bottom:-1.25em}"]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:i.FormBuilder}]},r.propDecorators={detailsInput:[{type:t.ViewChild,args:["detailsInput",{static:!1}]}]},r}(a.RuleNodeConfigurationComponent);var be=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n}return g(r,e),r.prototype.configForm=function(){return this.deviceAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.deviceAttributesConfigForm=this.fb.group({deviceRelationsQuery:[e?e.deviceRelationsQuery:null,[i.Validators.required]],tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})},r.prototype.removeKey=function(e,t){var r=this.deviceAttributesConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.deviceAttributesConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.deviceAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.deviceAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-device-attributes-config",template:'
\n \n \n \n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var he=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n}return g(r,e),r.prototype.configForm=function(){return this.originatorAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorAttributesConfigForm=this.fb.group({tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})},r.prototype.removeKey=function(e,t){var r=this.originatorAttributesConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.originatorAttributesConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.originatorAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.originatorAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-originator-attributes-config",template:'
\n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var Ce=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.originatorFieldsConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorFieldsConfigForm=this.fb.group({fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-originator-fields-config",template:'
\n \n \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var ve=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n.fetchMode=K,n.fetchModes=Object.keys(K),n.samplingOrders=Object.keys(j),n.timeUnits=Object.keys(M),n.timeUnitsTranslationMap=R,n}return g(r,e),r.prototype.configForm=function(){return this.getTelemetryFromDatabaseConfigForm},r.prototype.onConfigurationSet=function(e){this.getTelemetryFromDatabaseConfigForm=this.fb.group({latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],fetchMode:[e?e.fetchMode:null,[i.Validators.required]],orderBy:[e?e.orderBy:null,[]],limit:[e?e.limit:null,[]],useMetadataIntervalPatterns:[!!e&&e.useMetadataIntervalPatterns,[]],startInterval:[e?e.startInterval:null,[]],startIntervalTimeUnit:[e?e.startIntervalTimeUnit:null,[]],endInterval:[e?e.endInterval:null,[]],endIntervalTimeUnit:[e?e.endIntervalTimeUnit:null,[]],startIntervalPattern:[e?e.startIntervalPattern:null,[]],endIntervalPattern:[e?e.endIntervalPattern:null,[]]})},r.prototype.validatorTriggers=function(){return["fetchMode","useMetadataIntervalPatterns"]},r.prototype.updateValidators=function(e){var t=this.getTelemetryFromDatabaseConfigForm.get("fetchMode").value,r=this.getTelemetryFromDatabaseConfigForm.get("useMetadataIntervalPatterns").value;t&&t===K.ALL?(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([i.Validators.required,i.Validators.min(2),i.Validators.max(1e3)])):(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([])),r?(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([i.Validators.required])):(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([])),this.getTelemetryFromDatabaseConfigForm.get("orderBy").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("limit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").updateValueAndValidity({emitEvent:e})},r.prototype.removeKey=function(e,t){var r=this.getTelemetryFromDatabaseConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.getTelemetryFromDatabaseConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-get-telemetry-from-database",template:'
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n tb.rulenode.fetch-mode\n \n \n {{ mode }}\n \n \n tb.rulenode.fetch-mode-hint\n \n
\n \n tb.rulenode.order-by\n \n \n {{ order }}\n \n \n tb.rulenode.order-by-hint\n \n \n tb.rulenode.limit\n \n tb.rulenode.limit-hint\n \n
\n \n {{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-interval-patterns-hint
\n
\n
\n \n tb.rulenode.start-interval\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.start-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.end-interval\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.end-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n \n tb.rulenode.start-interval-pattern\n \n \n {{ \'tb.rulenode.start-interval-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.end-interval-pattern\n \n \n {{ \'tb.rulenode.end-interval-pattern-required\' | translate }}\n \n \n \n \n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var Fe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.relatedAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.relatedAttributesConfigForm=this.fb.group({relationsQuery:[e?e.relationsQuery:null,[i.Validators.required]],telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-related-attributes-config",template:'
\n \n \n \n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var Te=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.tenantAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.tenantAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-tenant-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var qe=function(){function e(){}return e.decorators=[{type:t.NgModule,args:[{declarations:[ge,ye,be,he,Ce,ve,Fe,Te],imports:[r.CommonModule,a.SharedModule,X],exports:[ge,ye,be,he,Ce,ve,Fe,Te]}]}],e}(),xe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.originatorSource=V,n.originatorSources=Object.keys(V),n.originatorSourceTranslationMap=E,n}return g(r,e),r.prototype.configForm=function(){return this.changeOriginatorConfigForm},r.prototype.onConfigurationSet=function(e){this.changeOriginatorConfigForm=this.fb.group({originatorSource:[e?e.originatorSource:null,[i.Validators.required]],relationsQuery:[e?e.relationsQuery:null,[]]})},r.prototype.validatorTriggers=function(){return["originatorSource"]},r.prototype.updateValidators=function(e){var t=this.changeOriginatorConfigForm.get("originatorSource").value;t&&t===V.RELATED?this.changeOriginatorConfigForm.get("relationsQuery").setValidators([i.Validators.required]):this.changeOriginatorConfigForm.get("relationsQuery").setValidators([]),this.changeOriginatorConfigForm.get("relationsQuery").updateValueAndValidity({emitEvent:e})},r.decorators=[{type:t.Component,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
\n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var Ie=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return g(r,e),r.prototype.configForm=function(){return this.scriptConfigForm},r.prototype.onConfigurationSet=function(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"update",this.translate.instant("tb.rulenode.transformer"),"Transform",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.decorators=[{type:t.Component,args:[{selector:"tb-transformation-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},r.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},r}(a.RuleNodeConfigurationComponent);var Ne=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.toEmailConfigForm},r.prototype.onConfigurationSet=function(e){this.toEmailConfigForm=this.fb.group({fromTemplate:[e?e.fromTemplate:null,[i.Validators.required]],toTemplate:[e?e.toTemplate:null,[i.Validators.required]],ccTemplate:[e?e.ccTemplate:null,[]],bccTemplate:[e?e.bccTemplate:null,[]],subjectTemplate:[e?e.subjectTemplate:null,[i.Validators.required]],bodyTemplate:[e?e.bodyTemplate:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,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.body-template\n \n \n {{ \'tb.rulenode.body-template-required\' | translate }}\n \n \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var ke=function(){function e(){}return e.decorators=[{type:t.NgModule,args:[{declarations:[xe,Ie,Ne],imports:[r.CommonModule,a.SharedModule,X],exports:[xe,Ie,Ne]}]}],e}(),Se=function(){function e(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-name-pattern-hint":"Name pattern, use ${metaKeyName} to substitute variables from metadata","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-type-pattern-hint":"Type pattern, use ${metaKeyName} to substitute variables from metadata","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-name-pattern-hint":"Customer name pattern, use ${metaKeyName} to substitute variables from metadata","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.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.",limit:"Limit","limit-hint":"Min limit value is 2, max - 1000. In case you want to fetch a single entry, select fetch mode 'FIRST' or 'LAST'.","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","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","latest-timeseries":"Latest timeseries","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-hint":"Relation type pattern, use ${metaKeyName} to substitute variables from metadata","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","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","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","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 metadata period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds metadata pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","period-in-seconds-pattern-hint":"Period in seconds pattern, use ${metaKeyName} to substitute variables from metadata","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",propagate:"Propagate",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","from-template-hint":"From address template, use ${metaKeyName} to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use ${metaKeyName} to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use ${metaKeyName} to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use ${metaKeyName} to substitute variables from metadata","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","endpoint-url-pattern-hint":"HTTP URL address pattern, use ${metaKeyName} to substitute variables from metadata","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory",headers:"Headers","headers-hint":"Use ${metaKeyName} in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use ${metaKeyName} to substitute variables from metadata","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","topic-arn-pattern-hint":"Topic ARN pattern, use ${metaKeyName} to substitute variables from metadata","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","queue-url-pattern-hint":"Queue URL pattern, use ${metaKeyName} to substitute variables from metadata","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 ${metaKeyName} in name/value fields to substitute variables from metadata","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","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","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"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-interval-patterns":"Use metadata interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","check-all-keys":"Check that all selected keys 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-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","start-interval-pattern-hint":"Start interval pattern, use ${metaKeyName} to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use ${metaKeyName} to substitute variables from metadata","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","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":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","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","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","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":"You should 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-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 Latest telemetry with Timestamp","get-latest-value-with-ts-hint":'If selected, latest telemetry values will be added to the outbound message metadata with timestamp, e.g: "temp": "{\\"ts\\":1574329385897,\\"value\\":42}"'},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}},!0)}(e)}return e.decorators=[{type:t.NgModule,args:[{declarations:[b],imports:[r.CommonModule,a.SharedModule],exports:[ie,fe,qe,ke,b]}]}],e.ctorParameters=function(){return[{type:n.TranslateService}]},e}();e.RuleNodeCoreConfigModule=Se,e.default=Se,e.ɵa=b,e.ɵb=ie,e.ɵba=J,e.ɵbb=Z,e.ɵbc=fe,e.ɵbd=le,e.ɵbe=se,e.ɵbf=me,e.ɵbg=ue,e.ɵbh=de,e.ɵbi=pe,e.ɵbj=ce,e.ɵbk=qe,e.ɵbl=ge,e.ɵbm=ye,e.ɵbn=be,e.ɵbo=he,e.ɵbp=Ce,e.ɵbq=ve,e.ɵbr=Fe,e.ɵbs=Te,e.ɵbt=ke,e.ɵbu=xe,e.ɵbv=Ie,e.ɵbw=Ne,e.ɵc=h,e.ɵd=C,e.ɵe=v,e.ɵf=F,e.ɵg=T,e.ɵh=q,e.ɵi=x,e.ɵj=I,e.ɵk=N,e.ɵl=k,e.ɵm=S,e.ɵn=$,e.ɵo=Q,e.ɵp=z,e.ɵq=_,e.ɵr=ee,e.ɵs=te,e.ɵt=re,e.ɵu=ne,e.ɵv=ae,e.ɵw=oe,e.ɵx=X,e.ɵy=W,e.ɵz=Y,Object.defineProperty(e,"__esModule",{value:!0})})); + ***************************************************************************** */var f=function(e,t){return(f=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)t.hasOwnProperty(r)&&(e[r]=t[r])})(e,t)};function g(e,t){function r(){this.constructor=e}f(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)}function y(e){var t="function"==typeof Symbol&&e[Symbol.iterator],r=0;return t?t.call(e):{next:function(){return e&&r>=e.length&&(e=void 0),{value:e&&e[r++],done:!e}}}}var b=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.emptyConfigForm},r.prototype.onConfigurationSet=function(e){this.emptyConfigForm=this.fb.group({})},r.decorators=[{type:t.Component,args:[{selector:"tb-node-empty-config",template:"
"}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var h=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.attributeScopes=Object.keys(a.AttributeScope),n.telemetryTypeTranslationsMap=a.telemetryTypeTranslations,n}return g(r,e),r.prototype.configForm=function(){return this.attributesConfigForm},r.prototype.onConfigurationSet=function(e){this.attributesConfigForm=this.fb.group({scope:[e?e.scope:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var C=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.timeseriesConfigForm},r.prototype.onConfigurationSet=function(e){this.timeseriesConfigForm=this.fb.group({defaultTTL:[e?e.defaultTTL:null,[i.Validators.required,i.Validators.min(0)]]})},r.decorators=[{type:t.Component,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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var v=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.rpcRequestConfigForm},r.prototype.onConfigurationSet=function(e){this.rpcRequestConfigForm=this.fb.group({timeoutInSeconds:[e?e.timeoutInSeconds:null,[i.Validators.required,i.Validators.min(0)]]})},r.decorators=[{type:t.Component,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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var F=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return g(r,e),r.prototype.configForm=function(){return this.logConfigForm},r.prototype.onConfigurationSet=function(e){this.logConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.logConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"string",this.translate.instant("tb.rulenode.to-string"),"ToString",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.logConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.decorators=[{type:t.Component,args:[{selector:"tb-action-node-log-config",template:'
\n \n \n \n
\n \n
\n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},r.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},r}(a.RuleNodeConfigurationComponent);var T=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.assignCustomerConfigForm},r.prototype.onConfigurationSet=function(e){this.assignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[i.Validators.required]],createCustomerIfNotExists:[!!e&&e.createCustomerIfNotExists,[]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.decorators=[{type:t.Component,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 \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var q=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return g(r,e),r.prototype.configForm=function(){return this.clearAlarmConfigForm},r.prototype.onConfigurationSet=function(e){this.clearAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[i.Validators.required]],alarmType:[e?e.alarmType:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.clearAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.clearAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.decorators=[{type:t.Component,args:[{selector:"tb-action-node-clear-alarm-config",template:'
\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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},r.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},r}(a.RuleNodeConfigurationComponent);var x=function(e){function r(t,r,n,o){var i=e.call(this,t)||this;return i.store=t,i.fb=r,i.nodeScriptTestService=n,i.translate=o,i.alarmSeverities=Object.keys(a.AlarmSeverity),i.alarmSeverityTranslationMap=a.alarmSeverityTranslations,i.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],i}return g(r,e),r.prototype.configForm=function(){return this.createAlarmConfigForm},r.prototype.onConfigurationSet=function(e){this.createAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[i.Validators.required]],useMessageAlarmData:[!!e&&e.useMessageAlarmData,[]],alarmType:[e?e.alarmType:null,[]],severity:[e?e.severity:null,[]],propagate:[!!e&&e.propagate,[]],relationTypes:[e?e.relationTypes:null,[]]})},r.prototype.validatorTriggers=function(){return["useMessageAlarmData"]},r.prototype.updateValidators=function(e){this.createAlarmConfigForm.get("useMessageAlarmData").value?(this.createAlarmConfigForm.get("alarmType").setValidators([]),this.createAlarmConfigForm.get("severity").setValidators([])):(this.createAlarmConfigForm.get("alarmType").setValidators([i.Validators.required]),this.createAlarmConfigForm.get("severity").setValidators([i.Validators.required])),this.createAlarmConfigForm.get("alarmType").updateValueAndValidity({emitEvent:e}),this.createAlarmConfigForm.get("severity").updateValueAndValidity({emitEvent:e})},r.prototype.testScript=function(){var e=this,t=this.createAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.createAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},r.prototype.removeKey=function(e,t){var r=this.createAlarmConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.createAlarmConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.createAlarmConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.createAlarmConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.decorators=[{type:t.Component,args:[{selector:"tb-action-node-create-alarm-config",template:'
\n \n \n \n
\n \n
\n \n {{ \'tb.rulenode.use-message-alarm-data\' | translate }}\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.alarm-severity\n \n \n {{ alarmSeverityTranslationMap.get(severity) | translate }}\n \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 \n \n
\n
\n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},r.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},r}(a.RuleNodeConfigurationComponent);var I=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return g(r,e),r.prototype.configForm=function(){return this.createRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.createRelationConfigForm=this.fb.group({direction:[e?e.direction:null,[i.Validators.required]],entityType:[e?e.entityType:null,[i.Validators.required]],entityNamePattern:[e?e.entityNamePattern:null,[]],entityTypePattern:[e?e.entityTypePattern:null,[]],relationType:[e?e.relationType:null,[i.Validators.required]],createEntityIfNotExists:[!!e&&e.createEntityIfNotExists,[]],removeCurrentRelations:[!!e&&e.removeCurrentRelations,[]],changeOriginatorToRelatedEntity:[!!e&&e.changeOriginatorToRelatedEntity,[]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.validatorTriggers=function(){return["entityType"]},r.prototype.updateValidators=function(e){var t=this.createRelationConfigForm.get("entityType").value;t?this.createRelationConfigForm.get("entityNamePattern").setValidators([i.Validators.required]):this.createRelationConfigForm.get("entityNamePattern").setValidators([]),!t||t!==a.EntityType.DEVICE&&t!==a.EntityType.ASSET?this.createRelationConfigForm.get("entityTypePattern").setValidators([]):this.createRelationConfigForm.get("entityTypePattern").setValidators([i.Validators.required]),this.createRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e}),this.createRelationConfigForm.get("entityTypePattern").updateValueAndValidity({emitEvent:e})},r.decorators=[{type:t.Component,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 \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var k=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.msgDelayConfigForm},r.prototype.onConfigurationSet=function(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,[i.Validators.required,i.Validators.min(1),i.Validators.max(1e5)]]})},r.prototype.validatorTriggers=function(){return["useMetadataPeriodInSecondsPatterns"]},r.prototype.updateValidators=function(e){this.msgDelayConfigForm.get("useMetadataPeriodInSecondsPatterns").value?(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([i.Validators.required]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([])):(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([i.Validators.required,i.Validators.min(0)])),this.msgDelayConfigForm.get("periodInSecondsPattern").updateValueAndValidity({emitEvent:e}),this.msgDelayConfigForm.get("periodInSeconds").updateValueAndValidity({emitEvent:e})},r.decorators=[{type:t.Component,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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var N=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return g(r,e),r.prototype.configForm=function(){return this.deleteRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.deleteRelationConfigForm=this.fb.group({deleteForSingleEntity:[!!e&&e.deleteForSingleEntity,[]],direction:[e?e.direction:null,[i.Validators.required]],entityType:[e?e.entityType:null,[]],entityNamePattern:[e?e.entityNamePattern:null,[]],relationType:[e?e.relationType:null,[i.Validators.required]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.validatorTriggers=function(){return["deleteForSingleEntity","entityType"]},r.prototype.updateValidators=function(e){var t=this.deleteRelationConfigForm.get("deleteForSingleEntity").value,r=this.deleteRelationConfigForm.get("entityType").value;t?this.deleteRelationConfigForm.get("entityType").setValidators([i.Validators.required]):this.deleteRelationConfigForm.get("entityType").setValidators([]),t&&r?this.deleteRelationConfigForm.get("entityNamePattern").setValidators([i.Validators.required]):this.deleteRelationConfigForm.get("entityNamePattern").setValidators([]),this.deleteRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:!1}),this.deleteRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e})},r.decorators=[{type:t.Component,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 \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var S=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return g(r,e),r.prototype.configForm=function(){return this.generatorConfigForm},r.prototype.onConfigurationSet=function(e){this.generatorConfigForm=this.fb.group({msgCount:[e?e.msgCount:null,[i.Validators.required,i.Validators.min(0)]],periodInSeconds:[e?e.periodInSeconds:null,[i.Validators.required,i.Validators.min(1)]],originator:[e?e.originator:null,[]],jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.prepareInputConfig=function(e){return e&&(e.originatorId&&e.originatorType?e.originator={id:e.originatorId,entityType:e.originatorType}:e.originator=null,delete e.originatorId,delete e.originatorType),e},r.prototype.prepareOutputConfig=function(e){return e.originator?(e.originatorId=e.originator.id,e.originatorType=e.originator.entityType):(e.originatorId=null,e.originatorType=null),delete e.originator,e},r.prototype.testScript=function(){var e=this,t=this.generatorConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"generate",this.translate.instant("tb.rulenode.generator"),"Generate",["prevMsg","prevMetadata","prevMsgType"],this.ruleNodeId).subscribe((function(t){t&&e.generatorConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.decorators=[{type:t.Component,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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},r.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},r}(a.RuleNodeConfigurationComponent);var E={CUSTOMER:"CUSTOMER",TENANT:"TENANT",RELATED:"RELATED",ALARM_ORIGINATOR:"ALARM_ORIGINATOR"},V=new Map([[E.CUSTOMER,"tb.rulenode.originator-customer"],[E.TENANT,"tb.rulenode.originator-tenant"],[E.RELATED,"tb.rulenode.originator-related"],[E.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator"]]),A={CIRCLE:"CIRCLE",POLYGON:"POLYGON"},L=new Map([[A.CIRCLE,"tb.rulenode.perimeter-circle"],[A.POLYGON,"tb.rulenode.perimeter-polygon"]]),M={MILLISECONDS:"MILLISECONDS",SECONDS:"SECONDS",MINUTES:"MINUTES",HOURS:"HOURS",DAYS:"DAYS"},P=new Map([[M.MILLISECONDS,"tb.rulenode.time-unit-milliseconds"],[M.SECONDS,"tb.rulenode.time-unit-seconds"],[M.MINUTES,"tb.rulenode.time-unit-minutes"],[M.HOURS,"tb.rulenode.time-unit-hours"],[M.DAYS,"tb.rulenode.time-unit-days"]]),R={METER:"METER",KILOMETER:"KILOMETER",FOOT:"FOOT",MILE:"MILE",NAUTICAL_MILE:"NAUTICAL_MILE"},w=new Map([[R.METER,"tb.rulenode.range-unit-meter"],[R.KILOMETER,"tb.rulenode.range-unit-kilometer"],[R.FOOT,"tb.rulenode.range-unit-foot"],[R.MILE,"tb.rulenode.range-unit-mile"],[R.NAUTICAL_MILE,"tb.rulenode.range-unit-nautical-mile"]]),D={TITLE:"TITLE",COUNTRY:"COUNTRY",STATE:"STATE",ZIP:"ZIP",ADDRESS:"ADDRESS",ADDRESS2:"ADDRESS2",PHONE:"PHONE",EMAIL:"EMAIL",ADDITIONAL_INFO:"ADDITIONAL_INFO"},O=new Map([[D.TITLE,"tb.rulenode.entity-details-title"],[D.COUNTRY,"tb.rulenode.entity-details-country"],[D.STATE,"tb.rulenode.entity-details-state"],[D.ZIP,"tb.rulenode.entity-details-zip"],[D.ADDRESS,"tb.rulenode.entity-details-address"],[D.ADDRESS2,"tb.rulenode.entity-details-address2"],[D.PHONE,"tb.rulenode.entity-details-phone"],[D.EMAIL,"tb.rulenode.entity-details-email"],[D.ADDITIONAL_INFO,"tb.rulenode.entity-details-additional_info"]]),K={FIRST:"FIRST",LAST:"LAST",ALL:"ALL"},j={ASC:"ASC",DESC:"DESC"},U={STANDARD:"STANDARD",FIFO:"FIFO"},G=new Map([[U.STANDARD,"tb.rulenode.sqs-queue-standard"],[U.FIFO,"tb.rulenode.sqs-queue-fifo"]]),B=["anonymous","basic","cert.PEM"],H=new Map([["anonymous","tb.rulenode.credentials-anonymous"],["basic","tb.rulenode.credentials-basic"],["cert.PEM","tb.rulenode.credentials-pem"]]),Q={GET:"GET",POST:"POST",PUT:"PUT",DELETE:"DELETE"},z=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.perimeterType=A,n.perimeterTypes=Object.keys(A),n.perimeterTypeTranslationMap=L,n.rangeUnits=Object.keys(R),n.rangeUnitTranslationMap=w,n.timeUnits=Object.keys(M),n.timeUnitsTranslationMap=P,n}return g(r,e),r.prototype.configForm=function(){return this.geoActionConfigForm},r.prototype.onConfigurationSet=function(e){this.geoActionConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[i.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[i.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType: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,[i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]],minInsideDurationTimeUnit:[e?e.minInsideDurationTimeUnit:null,[i.Validators.required]],minOutsideDuration:[e?e.minOutsideDuration:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]],minOutsideDurationTimeUnit:[e?e.minOutsideDurationTimeUnit:null,[i.Validators.required]]})},r.prototype.validatorTriggers=function(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]},r.prototype.updateValidators=function(e){var t=this.geoActionConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,r=this.geoActionConfigForm.get("perimeterType").value;t?this.geoActionConfigForm.get("perimeterType").setValidators([]):this.geoActionConfigForm.get("perimeterType").setValidators([i.Validators.required]),t||r!==A.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([i.Validators.required,i.Validators.min(-90),i.Validators.max(90)]),this.geoActionConfigForm.get("centerLongitude").setValidators([i.Validators.required,i.Validators.min(-180),i.Validators.max(180)]),this.geoActionConfigForm.get("range").setValidators([i.Validators.required,i.Validators.min(0)]),this.geoActionConfigForm.get("rangeUnit").setValidators([i.Validators.required])),t||r!==A.POLYGON?this.geoActionConfigForm.get("polygonsDefinition").setValidators([]):this.geoActionConfigForm.get("polygonsDefinition").setValidators([i.Validators.required]),this.geoActionConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),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})},r.decorators=[{type:t.Component,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.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var $=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.msgCountConfigForm},r.prototype.onConfigurationSet=function(e){this.msgCountConfigForm=this.fb.group({interval:[e?e.interval:null,[i.Validators.required,i.Validators.min(1)]],telemetryPrefix:[e?e.telemetryPrefix:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var _=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.rpcReplyConfigForm},r.prototype.onConfigurationSet=function(e){this.rpcReplyConfigForm=this.fb.group({requestIdMetaDataAttribute:[e?e.requestIdMetaDataAttribute:null,[]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-action-node-rpc-reply-config",template:'
\n \n tb.rulenode.request-id-metadata-attribute\n \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var W=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.saveToCustomTableConfigForm},r.prototype.onConfigurationSet=function(e){this.saveToCustomTableConfigForm=this.fb.group({tableName:[e?e.tableName:null,[i.Validators.required]],fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,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 \n \n \n \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var Y=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.translate=r,o.injector=n,o.fb=a,o.propagateChange=null,o.valueChangeSubscription=null,o}return g(r,e),Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){this.ngControl=this.injector.get(i.NgControl),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.kvListFormGroup=this.fb.group({}),this.kvListFormGroup.addControl("keyVals",this.fb.array([]))},r.prototype.keyValsFormArray=function(){return this.kvListFormGroup.get("keyVals")},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.kvListFormGroup.disable({emitEvent:!1}):this.kvListFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){var t,r,n=this;this.valueChangeSubscription&&this.valueChangeSubscription.unsubscribe();var a=[];if(e)try{for(var o=y(Object.keys(e)),l=o.next();!l.done;l=o.next()){var s=l.value;Object.prototype.hasOwnProperty.call(e,s)&&a.push(this.fb.group({key:[s,[i.Validators.required]],value:[e[s],[i.Validators.required]]}))}}catch(e){t={error:e}}finally{try{l&&!l.done&&(r=o.return)&&r.call(o)}finally{if(t)throw t.error}}this.kvListFormGroup.setControl("keyVals",this.fb.array(a)),this.valueChangeSubscription=this.kvListFormGroup.valueChanges.subscribe((function(){n.updateModel()}))},r.prototype.removeKeyVal=function(e){this.kvListFormGroup.get("keyVals").removeAt(e)},r.prototype.addKeyVal=function(){this.kvListFormGroup.get("keyVals").push(this.fb.group({key:["",[i.Validators.required]],value:["",[i.Validators.required]]}))},r.prototype.validate=function(e){return!this.kvListFormGroup.get("keyVals").value.length&&this.required?{kvMapRequired:!0}:this.kvListFormGroup.valid?null:{kvFieldsRequired:!0}},r.prototype.updateModel=function(){var e=this.kvListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.kvListFormGroup.valid)this.propagateChange(null);else{var t={};e.forEach((function(e){t[e.key]=e.value})),this.propagateChange(t)}},r.decorators=[{type:t.Component,args:[{selector:"tb-kv-map-config",template:'
\n
\n {{ keyText }}\n {{ valText }}\n \n
\n
\n
\n \n \n \n \n {{ keyRequiredText | translate }}\n \n \n \n \n \n \n {{ valRequiredText | translate }}\n \n \n \n
\n
\n \n
\n \n
\n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return r})),multi:!0},{provide:i.NG_VALIDATORS,useExisting:t.forwardRef((function(){return r})),multi:!0}],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:rgba(0,0,0,.54);font-size:12px;font-weight:700;white-space:nowrap}:host .tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:20px;max-height:300px;overflow:auto}:host .tb-kv-map-config .body .row{padding-top:5px;max-height:40px}:host .tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell{margin:0;max-height:40px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell .mat-form-field-infix{border-top:0}:host ::ng-deep .tb-kv-map-config .body button.mat-button{margin:0}"]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:t.Injector},{type:i.FormBuilder}]},r.propDecorators={disabled:[{type:t.Input}],requiredText:[{type:t.Input}],keyText:[{type:t.Input}],keyRequiredText:[{type:t.Input}],valText:[{type:t.Input}],valRequiredText:[{type:t.Input}],required:[{type:t.Input}]},r}(a.PageComponent);var J=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n.propagateChange=null,n}return g(r,e),Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.deviceRelationsQueryFormGroup=this.fb.group({direction:[null,[i.Validators.required]],maxLevel:[null,[]],relationType:[null],deviceTypes:[null,[i.Validators.required]]}),this.deviceRelationsQueryFormGroup.valueChanges.subscribe((function(t){e.deviceRelationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.deviceRelationsQueryFormGroup.disable({emitEvent:!1}):this.deviceRelationsQueryFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){this.deviceRelationsQueryFormGroup.reset(e,{emitEvent:!1})},r.decorators=[{type:t.Component,args:[{selector:"tb-device-relations-query-config",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
\n
relation.relation-type
\n \n \n
device.device-types
\n \n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return r})),multi:!0}]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r.propDecorators={disabled:[{type:t.Input}],required:[{type:t.Input}]},r}(a.PageComponent);var Z=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.propagateChange=null,n}return g(r,e),Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.relationsQueryFormGroup=this.fb.group({direction:[null,[i.Validators.required]],maxLevel:[null,[]],filters:[null]}),this.relationsQueryFormGroup.valueChanges.subscribe((function(t){e.relationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.relationsQueryFormGroup.disable({emitEvent:!1}):this.relationsQueryFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){this.relationsQueryFormGroup.reset(e,{emitEvent:!1})},r.decorators=[{type:t.Component,args:[{selector:"tb-relations-query-config",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
\n
relation.relation-filters
\n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return r})),multi:!0}]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r.propDecorators={disabled:[{type:t.Input}],required:[{type:t.Input}]},r}(a.PageComponent);var X=function(e){function r(t,r,n,o){var i,l,m=e.call(this,t)||this;m.store=t,m.translate=r,m.truncate=n,m.fb=o,m.placeholder="tb.rulenode.message-type",m.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],m.messageTypes=[],m.messageTypesList=[],m.searchText="",m.propagateChange=function(e){},m.messageTypeConfigForm=m.fb.group({messageType:[null]});try{for(var u=y(Object.keys(a.MessageType)),d=u.next();!d.done;d=u.next()){var p=d.value;m.messageTypesList.push({name:a.messageTypeNames.get(a.MessageType[p]),value:p})}}catch(e){i={error:e}}finally{try{d&&!d.done&&(l=u.return)&&l.call(u)}finally{if(i)throw i.error}}return m}return g(r,e),Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.ngOnInit=function(){var e=this;this.filteredMessageTypes=this.messageTypeConfigForm.get("messageType").valueChanges.pipe(c.startWith(""),c.map((function(e){return e||""})),c.mergeMap((function(t){return e.fetchMessageTypes(t)})),c.share())},r.prototype.ngAfterViewInit=function(){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.messageTypeConfigForm.disable({emitEvent:!1}):this.messageTypeConfigForm.enable({emitEvent:!1})},r.prototype.writeValue=function(e){var t=this;this.searchText="",this.messageTypes.length=0,e&&e.forEach((function(e){var r=t.messageTypesList.find((function(t){return t.value===e}));r?t.messageTypes.push({name:r.name,value:r.value}):t.messageTypes.push({name:e,value:e})}))},r.prototype.displayMessageTypeFn=function(e){return e?e.name:void 0},r.prototype.textIsNotEmpty=function(e){return!!(e&&null!=e&&e.length>0)},r.prototype.createMessageType=function(e,t){e.preventDefault(),this.transformMessageType(t)},r.prototype.add=function(e){this.transformMessageType(e.value)},r.prototype.fetchMessageTypes=function(e){if(this.searchText=e,this.searchText&&this.searchText.length){var t=this.searchText.toUpperCase();return p.of(this.messageTypesList.filter((function(e){return e.name.toUpperCase().includes(t)})))}return p.of(this.messageTypesList)},r.prototype.transformMessageType=function(e){if((e||"").trim()){var t=null,r=e.trim(),n=this.messageTypesList.find((function(e){return e.name===r}));(t=n?{name:n.name,value:n.value}:{name:r,value:r})&&this.addMessageType(t)}this.clear("")},r.prototype.remove=function(e){var t=this.messageTypes.indexOf(e);t>=0&&(this.messageTypes.splice(t,1),this.updateModel())},r.prototype.selected=function(e){this.addMessageType(e.option.value),this.clear("")},r.prototype.addMessageType=function(e){-1===this.messageTypes.findIndex((function(t){return t.value===e.value}))&&(this.messageTypes.push(e),this.updateModel())},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.messageTypeInput.nativeElement.value=e,this.messageTypeConfigForm.get("messageType").patchValue(null,{emitEvent:!0}),setTimeout((function(){t.messageTypeInput.nativeElement.blur(),t.messageTypeInput.nativeElement.focus()}),0)},r.prototype.updateModel=function(){var e=this.messageTypes.map((function(e){return e.value}));this.required?(this.chipList.errorState=!e.length,this.propagateChange(e.length>0?e:null)):(this.chipList.errorState=!1,this.propagateChange(e))},r.decorators=[{type:t.Component,args:[{selector:"tb-message-types-config",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 {{ translate.get(\'tb.rulenode.no-message-type-matching\',\n {messageType: truncate.transform(searchText, true, 6, '...')}) | async }}\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',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return r})),multi:!0}]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:a.TruncatePipe},{type:i.FormBuilder}]},r.propDecorators={required:[{type:t.Input}],label:[{type:t.Input}],placeholder:[{type:t.Input}],disabled:[{type:t.Input}],chipList:[{type:t.ViewChild,args:["chipList",{static:!1}]}],matAutocomplete:[{type:t.ViewChild,args:["messageTypeAutocomplete",{static:!1}]}],messageTypeInput:[{type:t.ViewChild,args:["messageTypeInput",{static:!1}]}]},r}(a.PageComponent);var ee=function(){function e(){}return e.decorators=[{type:t.NgModule,args:[{declarations:[Y,J,Z,X],imports:[r.CommonModule,a.SharedModule,m.HomeComponentsModule],exports:[Y,J,Z,X]}]}],e}(),te=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.unassignCustomerConfigForm},r.prototype.onConfigurationSet=function(e){this.unassignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[i.Validators.required]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.decorators=[{type:t.Component,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 \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var re=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.snsConfigForm},r.prototype.onConfigurationSet=function(e){this.snsConfigForm=this.fb.group({topicArnPattern:[e?e.topicArnPattern:null,[i.Validators.required]],accessKeyId:[e?e.accessKeyId:null,[i.Validators.required]],secretAccessKey:[e?e.secretAccessKey:null,[i.Validators.required]],region:[e?e.region:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-action-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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var ne=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.sqsQueueType=U,n.sqsQueueTypes=Object.keys(U),n.sqsQueueTypeTranslationsMap=G,n}return g(r,e),r.prototype.configForm=function(){return this.sqsConfigForm},r.prototype.onConfigurationSet=function(e){this.sqsConfigForm=this.fb.group({queueType:[e?e.queueType:null,[i.Validators.required]],queueUrlPattern:[e?e.queueUrlPattern:null,[i.Validators.required]],delaySeconds:[e?e.delaySeconds:null,[i.Validators.min(0),i.Validators.max(900)]],messageAttributes:[e?e.messageAttributes:null,[]],accessKeyId:[e?e.accessKeyId:null,[i.Validators.required]],secretAccessKey:[e?e.secretAccessKey:null,[i.Validators.required]],region:[e?e.region:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-action-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
tb.rulenode.message-attributes-hint
\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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var ae=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.pubSubConfigForm},r.prototype.onConfigurationSet=function(e){this.pubSubConfigForm=this.fb.group({projectId:[e?e.projectId:null,[i.Validators.required]],topicName:[e?e.topicName:null,[i.Validators.required]],serviceAccountKey:[e?e.serviceAccountKey:null,[i.Validators.required]],serviceAccountKeyFileName:[e?e.serviceAccountKeyFileName:null,[i.Validators.required]],messageAttributes:[e?e.messageAttributes:null,[]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-action-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
tb.rulenode.message-attributes-hint
\n \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var oe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.ackValues=["all","-1","0","1"],n}return g(r,e),r.prototype.configForm=function(){return this.kafkaConfigForm},r.prototype.onConfigurationSet=function(e){this.kafkaConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],bootstrapServers:[e?e.bootstrapServers:null,[i.Validators.required]],retries:[e?e.retries:null,[i.Validators.min(0)]],batchSize:[e?e.batchSize:null,[i.Validators.min(0)]],linger:[e?e.linger:null,[i.Validators.min(0)]],bufferMemory:[e?e.bufferMemory:null,[i.Validators.min(0)]],acks:[e?e.acks:null,[i.Validators.required]],keySerializer:[e?e.keySerializer:null,[i.Validators.required]],valueSerializer:[e?e.valueSerializer:null,[i.Validators.required]],otherProperties:[e?e.otherProperties:null,[]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-action-node-kafka-config",template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var ie=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allMqttCredentialsTypes=B,n.mqttCredentialsTypeTranslationsMap=H,n}return g(r,e),r.prototype.configForm=function(){return this.mqttConfigForm},r.prototype.onConfigurationSet=function(e){this.mqttConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(200)]],clientId:[e?e.clientId:null,[]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:this.fb.group({type:[e&&e.credentials?e.credentials.type:null,[i.Validators.required]],username:[e&&e.credentials?e.credentials.username:null,[]],password:[e&&e.credentials?e.credentials.password: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,[]]})})},r.prototype.prepareOutputConfig=function(e){var t=e.credentials.type;switch(t){case"anonymous":e.credentials={type:t};break;case"basic":e.credentials={type:t,username:e.credentials.username,password:e.credentials.password};break;case"cert.PEM":delete e.credentials.username}return e},r.prototype.validatorTriggers=function(){return["credentials.type"]},r.prototype.updateValidators=function(e){var t=this.mqttConfigForm.get("credentials"),r=t.get("type").value;switch(e&&t.reset({type:r},{emitEvent:!1}),t.get("username").setValidators([]),t.get("password").setValidators([]),t.get("caCert").setValidators([]),t.get("caCertFileName").setValidators([]),t.get("privateKey").setValidators([]),t.get("privateKeyFileName").setValidators([]),t.get("cert").setValidators([]),t.get("certFileName").setValidators([]),r){case"anonymous":break;case"basic":t.get("username").setValidators([i.Validators.required]),t.get("password").setValidators([i.Validators.required]);break;case"cert.PEM":t.get("caCert").setValidators([i.Validators.required]),t.get("caCertFileName").setValidators([i.Validators.required]),t.get("privateKey").setValidators([i.Validators.required]),t.get("privateKeyFileName").setValidators([i.Validators.required]),t.get("cert").setValidators([i.Validators.required]),t.get("certFileName").setValidators([i.Validators.required])}t.get("username").updateValueAndValidity({emitEvent:e}),t.get("password").updateValueAndValidity({emitEvent:e}),t.get("caCert").updateValueAndValidity({emitEvent:e}),t.get("caCertFileName").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})},r.decorators=[{type:t.Component,args:[{selector:"tb-action-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 \n \n {{ \'tb.rulenode.clean-session\' | translate }}\n \n \n {{ \'tb.rulenode.enable-ssl\' | translate }}\n \n \n \n tb.rulenode.credentials\n \n {{ mqttCredentialsTypeTranslationsMap.get(mqttConfigForm.get(\'credentials\').get(\'type\').value) | translate }}\n \n \n
\n \n tb.rulenode.credentials-type\n \n \n {{ mqttCredentialsTypeTranslationsMap.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 {{ \'tb.rulenode.password-required\' | translate }}\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',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}"]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var le=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],n}return g(r,e),r.prototype.configForm=function(){return this.rabbitMqConfigForm},r.prototype.onConfigurationSet=function(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,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.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,[i.Validators.min(0)]],handshakeTimeout:[e?e.handshakeTimeout:null,[i.Validators.min(0)]],clientProperties:[e?e.clientProperties:null,[]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-action-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 {{ \'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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.httpRequestTypes=Object.keys(Q),n}return g(r,e),r.prototype.configForm=function(){return this.restApiCallConfigForm},r.prototype.onConfigurationSet=function(e){this.restApiCallConfigForm=this.fb.group({restEndpointUrlPattern:[e?e.restEndpointUrlPattern:null,[i.Validators.required]],requestMethod:[e?e.requestMethod:null,[i.Validators.required]],useSimpleClientHttpFactory:[!!e&&e.useSimpleClientHttpFactory,[]],readTimeoutMs:[e?e.readTimeoutMs:null,[]],maxParallelRequestsCount:[e?e.maxParallelRequestsCount:null,[i.Validators.min(0)]],headers:[e?e.headers:null,[]],useRedisQueueForMsgPersistence:[!!e&&e.useRedisQueueForMsgPersistence,[]],trimQueue:[!!e&&e.trimQueue,[]],maxQueueSize:[e?e.maxQueueSize:null,[]]})},r.prototype.validatorTriggers=function(){return["useSimpleClientHttpFactory","useRedisQueueForMsgPersistence"]},r.prototype.updateValidators=function(e){var t=this.restApiCallConfigForm.get("useSimpleClientHttpFactory").value,r=this.restApiCallConfigForm.get("useRedisQueueForMsgPersistence").value;t?this.restApiCallConfigForm.get("readTimeoutMs").setValidators([]):this.restApiCallConfigForm.get("readTimeoutMs").setValidators([i.Validators.min(0)]),r?this.restApiCallConfigForm.get("maxQueueSize").setValidators([i.Validators.min(0)]):this.restApiCallConfigForm.get("maxQueueSize").setValidators([]),this.restApiCallConfigForm.get("readTimeoutMs").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("maxQueueSize").updateValueAndValidity({emitEvent:e})},r.decorators=[{type:t.Component,args:[{selector:"tb-action-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.use-simple-client-http-factory\' | translate }}\n \n \n tb.rulenode.read-timeout\n \n \n \n \n tb.rulenode.max-parallel-requests-count\n \n \n \n \n
tb.rulenode.headers-hint
\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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var me=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.smtpProtocols=["smtp","smtps"],n}return g(r,e),r.prototype.configForm=function(){return this.sendEmailConfigForm},r.prototype.onConfigurationSet=function(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,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]]})},r.prototype.validatorTriggers=function(){return["useSystemSmtpSettings"]},r.prototype.updateValidators=function(e){this.sendEmailConfigForm.get("useSystemSmtpSettings").value?(this.sendEmailConfigForm.get("smtpProtocol").setValidators([]),this.sendEmailConfigForm.get("smtpHost").setValidators([]),this.sendEmailConfigForm.get("smtpPort").setValidators([]),this.sendEmailConfigForm.get("timeout").setValidators([])):(this.sendEmailConfigForm.get("smtpProtocol").setValidators([i.Validators.required]),this.sendEmailConfigForm.get("smtpHost").setValidators([i.Validators.required]),this.sendEmailConfigForm.get("smtpPort").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]),this.sendEmailConfigForm.get("timeout").setValidators([i.Validators.required,i.Validators.min(0)])),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})},r.decorators=[{type:t.Component,args:[{selector:"tb-action-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.username\n \n \n \n tb.rulenode.password\n \n \n
\n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var ue=function(){function e(){}return e.decorators=[{type:t.NgModule,args:[{declarations:[h,C,v,F,T,q,x,I,k,N,S,z,$,_,W,te,re,ne,ae,oe,ie,le,se,me],imports:[r.CommonModule,a.SharedModule,ee],exports:[h,C,v,F,T,q,x,I,k,N,S,z,$,_,W,te,re,ne,ae,oe,ie,le,se,me]}]}],e}(),de=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n}return g(r,e),r.prototype.configForm=function(){return this.checkMessageConfigForm},r.prototype.onConfigurationSet=function(e){this.checkMessageConfigForm=this.fb.group({messageNames:[e?e.messageNames:null,[]],metadataNames:[e?e.metadataNames:null,[]],checkAllKeys:[!!e&&e.checkAllKeys,[]]})},r.prototype.validateConfig=function(){var e=this.checkMessageConfigForm.get("messageNames").value,t=this.checkMessageConfigForm.get("metadataNames").value;return e.length>0||t.length>0},r.prototype.removeMessageName=function(e){var t=this.checkMessageConfigForm.get("messageNames").value,r=t.indexOf(e);r>=0&&(t.splice(r,1),this.checkMessageConfigForm.get("messageNames").setValue(t,{emitEvent:!0}))},r.prototype.removeMetadataName=function(e){var t=this.checkMessageConfigForm.get("metadataNames").value,r=t.indexOf(e);r>=0&&(t.splice(r,1),this.checkMessageConfigForm.get("metadataNames").setValue(t,{emitEvent:!0}))},r.prototype.addMessageName=function(e){var t=e.input,r=e.value;if((r||"").trim()){r=r.trim();var n=this.checkMessageConfigForm.get("messageNames").value;n&&-1!==n.indexOf(r)||(n||(n=[]),n.push(r),this.checkMessageConfigForm.get("messageNames").setValue(n,{emitEvent:!0}))}t&&(t.value="")},r.prototype.addMetadataName=function(e){var t=e.input,r=e.value;if((r||"").trim()){r=r.trim();var n=this.checkMessageConfigForm.get("metadataNames").value;n&&-1!==n.indexOf(r)||(n||(n=[]),n.push(r),this.checkMessageConfigForm.get("metadataNames").setValue(n,{emitEvent:!0}))}t&&(t.value="")},r.decorators=[{type:t.Component,args:[{selector:"tb-filter-node-check-message-config",template:'
\n \n \n \n \n \n {{messageName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n \n \n \n \n {{metadataName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\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}"]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var pe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.entitySearchDirection=Object.keys(a.EntitySearchDirection),n.entitySearchDirectionTranslationsMap=a.entitySearchDirectionTranslations,n}return g(r,e),r.prototype.configForm=function(){return this.checkRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.checkRelationConfigForm=this.fb.group({checkForSingleEntity:[!!e&&e.checkForSingleEntity,[]],direction:[e?e.direction:null,[]],entityType:[e?e.entityType:null,e&&e.checkForSingleEntity?[i.Validators.required]:[]],entityId:[e?e.entityId:null,e&&e.checkForSingleEntity?[i.Validators.required]:[]],relationType:[e?e.relationType:null,[i.Validators.required]]})},r.prototype.validatorTriggers=function(){return["checkForSingleEntity"]},r.prototype.updateValidators=function(e){var t=this.checkRelationConfigForm.get("checkForSingleEntity").value;this.checkRelationConfigForm.get("entityType").setValidators(t?[i.Validators.required]:[]),this.checkRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:e}),this.checkRelationConfigForm.get("entityId").setValidators(t?[i.Validators.required]:[]),this.checkRelationConfigForm.get("entityId").updateValueAndValidity({emitEvent:e})},r.decorators=[{type:t.Component,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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var ce=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.perimeterType=A,n.perimeterTypes=Object.keys(A),n.perimeterTypeTranslationMap=L,n.rangeUnits=Object.keys(R),n.rangeUnitTranslationMap=w,n}return g(r,e),r.prototype.configForm=function(){return this.geoFilterConfigForm},r.prototype.onConfigurationSet=function(e){this.geoFilterConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[i.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[i.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType: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,[]]})},r.prototype.validatorTriggers=function(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]},r.prototype.updateValidators=function(e){var t=this.geoFilterConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,r=this.geoFilterConfigForm.get("perimeterType").value;t?this.geoFilterConfigForm.get("perimeterType").setValidators([]):this.geoFilterConfigForm.get("perimeterType").setValidators([i.Validators.required]),t||r!==A.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([i.Validators.required,i.Validators.min(-90),i.Validators.max(90)]),this.geoFilterConfigForm.get("centerLongitude").setValidators([i.Validators.required,i.Validators.min(-180),i.Validators.max(180)]),this.geoFilterConfigForm.get("range").setValidators([i.Validators.required,i.Validators.min(0)]),this.geoFilterConfigForm.get("rangeUnit").setValidators([i.Validators.required])),t||r!==A.POLYGON?this.geoFilterConfigForm.get("polygonsDefinition").setValidators([]):this.geoFilterConfigForm.get("polygonsDefinition").setValidators([i.Validators.required]),this.geoFilterConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),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})},r.decorators=[{type:t.Component,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.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var fe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.messageTypeConfigForm},r.prototype.onConfigurationSet=function(e){this.messageTypeConfigForm=this.fb.group({messageTypes:[e?e.messageTypes:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-filter-node-message-type-config",template:'
\n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var ge=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allowedEntityTypes=[a.EntityType.DEVICE,a.EntityType.ASSET,a.EntityType.ENTITY_VIEW,a.EntityType.TENANT,a.EntityType.CUSTOMER,a.EntityType.USER,a.EntityType.DASHBOARD,a.EntityType.RULE_CHAIN,a.EntityType.RULE_NODE],n}return g(r,e),r.prototype.configForm=function(){return this.originatorTypeConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorTypeConfigForm=this.fb.group({originatorTypes:[e?e.originatorTypes:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-filter-node-originator-type-config",template:'
\n \n \n \n
\n',styles:[":host ::ng-deep tb-entity-type-list .mat-form-field-flex{padding-top:0}:host ::ng-deep tb-entity-type-list .mat-form-field-infix{border-top:0}"]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var ye=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return g(r,e),r.prototype.configForm=function(){return this.scriptConfigForm},r.prototype.onConfigurationSet=function(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"filter",this.translate.instant("tb.rulenode.filter"),"Filter",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.decorators=[{type:t.Component,args:[{selector:"tb-filter-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},r.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},r}(a.RuleNodeConfigurationComponent);var be=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return g(r,e),r.prototype.configForm=function(){return this.switchConfigForm},r.prototype.onConfigurationSet=function(e){this.switchConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.switchConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"switch",this.translate.instant("tb.rulenode.switch"),"Switch",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.switchConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.decorators=[{type:t.Component,args:[{selector:"tb-filter-node-switch-config",template:'
\n \n \n \n
\n \n
\n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},r.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},r}(a.RuleNodeConfigurationComponent);var he=function(){function e(){}return e.decorators=[{type:t.NgModule,args:[{declarations:[de,pe,ce,fe,ge,ye,be],imports:[r.CommonModule,a.SharedModule,ee],exports:[de,pe,ce,fe,ge,ye,be]}]}],e}(),Ce=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.customerAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.customerAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-customer-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var ve=function(e){function r(t,r,n){var a,o,i=e.call(this,t)||this;i.store=t,i.translate=r,i.fb=n,i.entityDetailsTranslationsMap=O,i.entityDetailsList=[],i.searchText="",i.displayDetailsFn=i.displayDetails.bind(i);try{for(var l=y(Object.keys(D)),s=l.next();!s.done;s=l.next()){var m=s.value;i.entityDetailsList.push(D[m])}}catch(e){a={error:e}}finally{try{s&&!s.done&&(o=l.return)&&o.call(l)}finally{if(a)throw a.error}}return i}return g(r,e),r.prototype.ngOnInit=function(){var t=this;this.detailsFormControl=new i.FormControl(""),e.prototype.ngOnInit.call(this),this.filteredEntityDetails=this.detailsFormControl.valueChanges.pipe(c.startWith(""),c.map((function(e){return e||""})),c.mergeMap((function(e){return t.fetchEntityDetails(e)})),c.share())},r.prototype.configForm=function(){return this.entityDetailsConfigForm},r.prototype.prepareInputConfig=function(e){return this.searchText="",this.detailsFormControl.patchValue("",{emitEvent:!0}),e},r.prototype.onConfigurationSet=function(e){this.entityDetailsConfigForm=this.fb.group({detailsList:[e?e.detailsList:null,[i.Validators.required]],addToMetadata:[!!e&&e.addToMetadata,[]]})},r.prototype.displayDetails=function(e){return e?this.translate.instant(O.get(e)):void 0},r.prototype.fetchEntityDetails=function(e){var t=this;if(this.searchText=e,this.searchText&&this.searchText.length){var r=this.searchText.toUpperCase();return p.of(this.entityDetailsList.filter((function(e){return t.translate.instant(O.get(D[e])).toUpperCase().includes(r)})))}return p.of(this.entityDetailsList)},r.prototype.detailsFieldSelected=function(e){this.addDetailsField(e.option.value),this.clear("")},r.prototype.removeDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;if(t){var r=t.indexOf(e);r>=0&&(t.splice(r,1),this.entityDetailsConfigForm.get("detailsList").setValue(t))}},r.prototype.addDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;t||(t=[]),-1===t.indexOf(e)&&(t.push(e),this.entityDetailsConfigForm.get("detailsList").setValue(t))},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.detailsInput.nativeElement.value=e,this.detailsFormControl.patchValue(null,{emitEvent:!0}),setTimeout((function(){t.detailsInput.nativeElement.blur(),t.detailsInput.nativeElement.focus()}),0)},r.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-entity-details-config",template:'
\n \n \n \n \n \n {{entityDetailsTranslationsMap.get(details) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-entity-details-matching\n
\n
\n
\n
\n
\n \n \n {{ \'tb.rulenode.add-to-metadata\' | translate }}\n \n
tb.rulenode.add-to-metadata-hint
\n
\n',styles:[":host ::ng-deep mat-form-field.entity-fields-list .mat-form-field-wrapper{margin-bottom:-1.25em}"]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:i.FormBuilder}]},r.propDecorators={detailsInput:[{type:t.ViewChild,args:["detailsInput",{static:!1}]}]},r}(a.RuleNodeConfigurationComponent);var Fe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n}return g(r,e),r.prototype.configForm=function(){return this.deviceAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.deviceAttributesConfigForm=this.fb.group({deviceRelationsQuery:[e?e.deviceRelationsQuery:null,[i.Validators.required]],tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})},r.prototype.removeKey=function(e,t){var r=this.deviceAttributesConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.deviceAttributesConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.deviceAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.deviceAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-device-attributes-config",template:'
\n \n \n \n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var Te=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n}return g(r,e),r.prototype.configForm=function(){return this.originatorAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorAttributesConfigForm=this.fb.group({tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})},r.prototype.removeKey=function(e,t){var r=this.originatorAttributesConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.originatorAttributesConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.originatorAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.originatorAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-originator-attributes-config",template:'
\n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var qe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.originatorFieldsConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorFieldsConfigForm=this.fb.group({fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-originator-fields-config",template:'
\n \n \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var xe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n.fetchMode=K,n.fetchModes=Object.keys(K),n.samplingOrders=Object.keys(j),n.timeUnits=Object.keys(M),n.timeUnitsTranslationMap=P,n}return g(r,e),r.prototype.configForm=function(){return this.getTelemetryFromDatabaseConfigForm},r.prototype.onConfigurationSet=function(e){this.getTelemetryFromDatabaseConfigForm=this.fb.group({latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],fetchMode:[e?e.fetchMode:null,[i.Validators.required]],orderBy:[e?e.orderBy:null,[]],limit:[e?e.limit:null,[]],useMetadataIntervalPatterns:[!!e&&e.useMetadataIntervalPatterns,[]],startInterval:[e?e.startInterval:null,[]],startIntervalTimeUnit:[e?e.startIntervalTimeUnit:null,[]],endInterval:[e?e.endInterval:null,[]],endIntervalTimeUnit:[e?e.endIntervalTimeUnit:null,[]],startIntervalPattern:[e?e.startIntervalPattern:null,[]],endIntervalPattern:[e?e.endIntervalPattern:null,[]]})},r.prototype.validatorTriggers=function(){return["fetchMode","useMetadataIntervalPatterns"]},r.prototype.updateValidators=function(e){var t=this.getTelemetryFromDatabaseConfigForm.get("fetchMode").value,r=this.getTelemetryFromDatabaseConfigForm.get("useMetadataIntervalPatterns").value;t&&t===K.ALL?(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([i.Validators.required,i.Validators.min(2),i.Validators.max(1e3)])):(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([])),r?(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([i.Validators.required])):(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([])),this.getTelemetryFromDatabaseConfigForm.get("orderBy").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("limit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").updateValueAndValidity({emitEvent:e})},r.prototype.removeKey=function(e,t){var r=this.getTelemetryFromDatabaseConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.getTelemetryFromDatabaseConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-get-telemetry-from-database",template:'
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n tb.rulenode.fetch-mode\n \n \n {{ mode }}\n \n \n tb.rulenode.fetch-mode-hint\n \n
\n \n tb.rulenode.order-by\n \n \n {{ order }}\n \n \n tb.rulenode.order-by-hint\n \n \n tb.rulenode.limit\n \n tb.rulenode.limit-hint\n \n
\n \n {{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-interval-patterns-hint
\n
\n
\n \n tb.rulenode.start-interval\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.start-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.end-interval\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.end-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n \n tb.rulenode.start-interval-pattern\n \n \n {{ \'tb.rulenode.start-interval-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.end-interval-pattern\n \n \n {{ \'tb.rulenode.end-interval-pattern-required\' | translate }}\n \n \n \n \n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var Ie=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.relatedAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.relatedAttributesConfigForm=this.fb.group({relationsQuery:[e?e.relationsQuery:null,[i.Validators.required]],telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-related-attributes-config",template:'
\n \n \n \n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var ke=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.tenantAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.tenantAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-tenant-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var Ne=function(){function e(){}return e.decorators=[{type:t.NgModule,args:[{declarations:[Ce,ve,Fe,Te,qe,xe,Ie,ke],imports:[r.CommonModule,a.SharedModule,ee],exports:[Ce,ve,Fe,Te,qe,xe,Ie,ke]}]}],e}(),Se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.originatorSource=E,n.originatorSources=Object.keys(E),n.originatorSourceTranslationMap=V,n}return g(r,e),r.prototype.configForm=function(){return this.changeOriginatorConfigForm},r.prototype.onConfigurationSet=function(e){this.changeOriginatorConfigForm=this.fb.group({originatorSource:[e?e.originatorSource:null,[i.Validators.required]],relationsQuery:[e?e.relationsQuery:null,[]]})},r.prototype.validatorTriggers=function(){return["originatorSource"]},r.prototype.updateValidators=function(e){var t=this.changeOriginatorConfigForm.get("originatorSource").value;t&&t===E.RELATED?this.changeOriginatorConfigForm.get("relationsQuery").setValidators([i.Validators.required]):this.changeOriginatorConfigForm.get("relationsQuery").setValidators([]),this.changeOriginatorConfigForm.get("relationsQuery").updateValueAndValidity({emitEvent:e})},r.decorators=[{type:t.Component,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
\n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var Ee=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return g(r,e),r.prototype.configForm=function(){return this.scriptConfigForm},r.prototype.onConfigurationSet=function(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"update",this.translate.instant("tb.rulenode.transformer"),"Transform",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.decorators=[{type:t.Component,args:[{selector:"tb-transformation-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},r.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},r}(a.RuleNodeConfigurationComponent);var Ve=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.toEmailConfigForm},r.prototype.onConfigurationSet=function(e){this.toEmailConfigForm=this.fb.group({fromTemplate:[e?e.fromTemplate:null,[i.Validators.required]],toTemplate:[e?e.toTemplate:null,[i.Validators.required]],ccTemplate:[e?e.ccTemplate:null,[]],bccTemplate:[e?e.bccTemplate:null,[]],subjectTemplate:[e?e.subjectTemplate:null,[i.Validators.required]],bodyTemplate:[e?e.bodyTemplate:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,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.body-template\n \n \n {{ \'tb.rulenode.body-template-required\' | translate }}\n \n \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var Ae=function(){function e(){}return e.decorators=[{type:t.NgModule,args:[{declarations:[Se,Ee,Ve],imports:[r.CommonModule,a.SharedModule,ee],exports:[Se,Ee,Ve]}]}],e}(),Le=function(){function e(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-name-pattern-hint":"Name pattern, use ${metaKeyName} to substitute variables from metadata","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-type-pattern-hint":"Type pattern, use ${metaKeyName} to substitute variables from metadata","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-name-pattern-hint":"Customer name pattern, use ${metaKeyName} to substitute variables from metadata","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.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.",limit:"Limit","limit-hint":"Min limit value is 2, max - 1000. In case you want to fetch a single entry, select fetch mode 'FIRST' or 'LAST'.","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","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","latest-timeseries":"Latest timeseries","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-hint":"Relation type pattern, use ${metaKeyName} to substitute variables from metadata","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","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","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","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 metadata period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds metadata pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","period-in-seconds-pattern-hint":"Period in seconds pattern, use ${metaKeyName} to substitute variables from metadata","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",propagate:"Propagate",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","from-template-hint":"From address template, use ${metaKeyName} to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use ${metaKeyName} to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use ${metaKeyName} to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use ${metaKeyName} to substitute variables from metadata","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","endpoint-url-pattern-hint":"HTTP URL address pattern, use ${metaKeyName} to substitute variables from metadata","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory","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 ${metaKeyName} in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use ${metaKeyName} to substitute variables from metadata","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","topic-arn-pattern-hint":"Topic ARN pattern, use ${metaKeyName} to substitute variables from metadata","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","queue-url-pattern-hint":"Queue URL pattern, use ${metaKeyName} to substitute variables from metadata","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 ${metaKeyName} in name/value fields to substitute variables from metadata","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","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","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"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-interval-patterns":"Use metadata interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","check-all-keys":"Check that all selected keys 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-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","start-interval-pattern-hint":"Start interval pattern, use ${metaKeyName} to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use ${metaKeyName} to substitute variables from metadata","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","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":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","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","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","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":"You should 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-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 Latest telemetry with Timestamp","get-latest-value-with-ts-hint":'If selected, latest telemetry values will be added to the outbound message metadata with timestamp, e.g: "temp": "{\\"ts\\":1574329385897,\\"value\\":42}"',"use-redis-queue":"Use redis queue for message persistence","trim-redis-queue":"Trim redis queue","redis-queue-max-size":"Redis queue max size"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}},!0)}(e)}return e.decorators=[{type:t.NgModule,args:[{declarations:[b],imports:[r.CommonModule,a.SharedModule],exports:[ue,he,Ne,Ae,b]}]}],e.ctorParameters=function(){return[{type:n.TranslateService}]},e}();e.RuleNodeCoreConfigModule=Le,e.default=Le,e.ɵa=b,e.ɵb=ue,e.ɵba=ee,e.ɵbb=Y,e.ɵbc=J,e.ɵbd=Z,e.ɵbe=X,e.ɵbf=he,e.ɵbg=de,e.ɵbh=pe,e.ɵbi=ce,e.ɵbj=fe,e.ɵbk=ge,e.ɵbl=ye,e.ɵbm=be,e.ɵbn=Ne,e.ɵbo=Ce,e.ɵbp=ve,e.ɵbq=Fe,e.ɵbr=Te,e.ɵbs=qe,e.ɵbt=xe,e.ɵbu=Ie,e.ɵbv=ke,e.ɵbw=Ae,e.ɵbx=Se,e.ɵby=Ee,e.ɵbz=Ve,e.ɵc=h,e.ɵd=C,e.ɵe=v,e.ɵf=F,e.ɵg=T,e.ɵh=q,e.ɵi=x,e.ɵj=I,e.ɵk=k,e.ɵl=N,e.ɵm=S,e.ɵn=z,e.ɵo=$,e.ɵp=_,e.ɵq=W,e.ɵr=te,e.ɵs=re,e.ɵt=ne,e.ɵu=ae,e.ɵv=oe,e.ɵw=ie,e.ɵx=le,e.ɵy=se,e.ɵz=me,Object.defineProperty(e,"__esModule",{value:!0})})); //# sourceMappingURL=rulenode-core-config.umd.min.js.map \ No newline at end of file diff --git a/ui-ngx/proxy.conf.js b/ui-ngx/proxy.conf.js index f7d53fae70..6b45570da1 100644 --- a/ui-ngx/proxy.conf.js +++ b/ui-ngx/proxy.conf.js @@ -15,7 +15,7 @@ */ const ruleNodeUiforwardHost = 'localhost'; -const ruleNodeUiforwardPort = 5000; +const ruleNodeUiforwardPort = 8080; const PROXY_CONFIG = { '/api': { From 53b6aeb4fa1b9a976affa96d66563d7d6a939d12 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 20 Jan 2020 15:14:08 +0200 Subject: [PATCH 074/133] Rule chain page. Inprove hotkeys handling --- msa/js-executor/package-lock.json | 23 +- ui-ngx/src/app/core/api/alias-controller.ts | 2 + .../app/core/services/item-buffer.service.ts | 120 ++++- .../components/widget/widget.component.ts | 10 +- .../dashboard/dashboard-page.component.html | 3 + .../layout/dashboard-layout.component.html | 5 +- .../layout/dashboard-layout.component.ts | 29 +- .../pages/rulechain/link-labels.component.ts | 27 +- .../pages/rulechain/rule-node-colors.scss | 46 ++ .../rulechain/rule-node-details.component.ts | 4 +- .../rulechain/rule-node-link.component.ts | 26 +- .../rulechain/rulechain-page.component.html | 39 +- .../rulechain/rulechain-page.component.scss | 59 +++ .../rulechain/rulechain-page.component.ts | 480 +++++++++++++++++- .../pages/rulechain/rulechain-page.models.ts | 43 +- .../pages/rulechain/rulenode.component.scss | 30 +- .../pages/widget/widget-editor.component.html | 4 +- .../pages/widget/widget-editor.component.ts | 16 +- .../shared/components/cheatsheet.component.ts | 165 ++++++ .../shared/components/hotkeys.directive.ts | 86 ++++ .../src/app/shared/models/rule-node.models.ts | 39 +- ui-ngx/src/app/shared/shared.module.ts | 6 + 22 files changed, 1076 insertions(+), 186 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/pages/rulechain/rule-node-colors.scss create mode 100644 ui-ngx/src/app/shared/components/cheatsheet.component.ts create mode 100644 ui-ngx/src/app/shared/components/hotkeys.directive.ts diff --git a/msa/js-executor/package-lock.json b/msa/js-executor/package-lock.json index bc9fdf512d..c61ef011a2 100644 --- a/msa/js-executor/package-lock.json +++ b/msa/js-executor/package-lock.json @@ -1407,14 +1407,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1429,20 +1427,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -1559,8 +1554,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -1572,7 +1566,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -1587,7 +1580,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -1699,8 +1691,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -1712,7 +1703,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -1834,7 +1824,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", diff --git a/ui-ngx/src/app/core/api/alias-controller.ts b/ui-ngx/src/app/core/api/alias-controller.ts index 07ecb519eb..2f174c80e6 100644 --- a/ui-ngx/src/app/core/api/alias-controller.ts +++ b/ui-ngx/src/app/core/api/alias-controller.ts @@ -186,7 +186,9 @@ export class AliasController implements IAliasController { ); } else { resolvedAliasSubject.error(null); + const res = this.resolvedAliasesObservable[aliasId]; delete this.resolvedAliasesObservable[aliasId]; + return res; } return this.resolvedAliasesObservable[aliasId]; } diff --git a/ui-ngx/src/app/core/services/item-buffer.service.ts b/ui-ngx/src/app/core/services/item-buffer.service.ts index 2c1e041176..82232fda1f 100644 --- a/ui-ngx/src/app/core/services/item-buffer.service.ts +++ b/ui-ngx/src/app/core/services/item-buffer.service.ts @@ -24,6 +24,8 @@ import * as equal from 'deep-equal'; import { UtilsService } from '@core/services/utils.service'; import { Observable, of, throwError } from 'rxjs'; import { map } from 'rxjs/operators'; +import { FcRuleEdge, FcRuleNode, ruleNodeTypeDescriptors } from '@shared/models/rule-node.models'; +import { RuleChainService } from '@core/http/rule-chain.service'; const WIDGET_ITEM = 'widget_item'; const WIDGET_REFERENCE = 'widget_reference'; @@ -45,6 +47,21 @@ export interface WidgetReference { originalColumns: number; } +export interface RuleNodeConnection { + isInputSource: boolean; + fromIndex: number; + toIndex: number; + label: string; + labels: string[]; +} + +export interface RuleNodesReference { + nodes: FcRuleNode[]; + connections: RuleNodeConnection[]; + originX?: number; + originY?: number; +} + @Injectable({ providedIn: 'root' }) @@ -54,6 +71,7 @@ export class ItemBufferService { private delimiter = '.'; constructor(private dashboardUtils: DashboardUtilsService, + private ruleChainService: RuleChainService, private utils: UtilsService) {} public prepareWidgetItem(dashboard: Dashboard, sourceState: string, sourceLayout: DashboardLayoutId, widget: Widget): WidgetItem { @@ -99,12 +117,12 @@ export class ItemBufferService { public copyWidget(dashboard: Dashboard, sourceState: string, sourceLayout: DashboardLayoutId, widget: Widget): void { const widgetItem = this.prepareWidgetItem(dashboard, sourceState, sourceLayout, widget); - this.storeSet(WIDGET_ITEM, JSON.stringify(widgetItem)); + this.storeSet(WIDGET_ITEM, widgetItem); } public copyWidgetReference(dashboard: Dashboard, sourceState: string, sourceLayout: DashboardLayoutId, widget: Widget): void { const widgetReference = this.prepareWidgetReference(dashboard, sourceState, sourceLayout, widget); - this.storeSet(WIDGET_REFERENCE, JSON.stringify(widgetReference)); + this.storeSet(WIDGET_REFERENCE, widgetReference); } public hasWidget(): boolean { @@ -112,9 +130,8 @@ export class ItemBufferService { } public canPasteWidgetReference(dashboard: Dashboard, state: string, layout: DashboardLayoutId): boolean { - const widgetReferenceJson = this.storeGet(WIDGET_REFERENCE); - if (widgetReferenceJson) { - const widgetReference: WidgetReference = JSON.parse(widgetReferenceJson); + const widgetReference: WidgetReference = this.storeGet(WIDGET_REFERENCE); + if (widgetReference) { if (widgetReference.dashboardId === dashboard.id.id) { if ((widgetReference.sourceState !== state || widgetReference.sourceLayout !== layout) && dashboard.configuration.widgets[widgetReference.widgetId]) { @@ -128,9 +145,8 @@ export class ItemBufferService { public pasteWidget(targetDashboard: Dashboard, targetState: string, targetLayout: DashboardLayoutId, position: WidgetPosition, onAliasesUpdateFunction: () => void): Observable { - const widgetItemJson = this.storeGet(WIDGET_ITEM); - if (widgetItemJson) { - const widgetItem: WidgetItem = JSON.parse(widgetItemJson); + const widgetItem: WidgetItem = this.storeGet(WIDGET_ITEM); + if (widgetItem) { const widget = widgetItem.widget; const aliasesInfo = widgetItem.aliasesInfo; const originalColumns = widgetItem.originalColumns; @@ -155,9 +171,8 @@ export class ItemBufferService { public pasteWidgetReference(targetDashboard: Dashboard, targetState: string, targetLayout: DashboardLayoutId, position: WidgetPosition): Observable { - const widgetReferenceJson = this.storeGet(WIDGET_REFERENCE); - if (widgetReferenceJson) { - const widgetReference: WidgetReference = JSON.parse(widgetReferenceJson); + const widgetReference: WidgetReference = this.storeGet(WIDGET_REFERENCE); + if (widgetReference) { const widget = targetDashboard.configuration.widgets[widgetReference.widgetId]; if (widget) { const originalColumns = widgetReference.originalColumns; @@ -216,6 +231,89 @@ export class ItemBufferService { return of(theDashboard); } + public copyRuleNodes(nodes: FcRuleNode[], connections: RuleNodeConnection[]) { + const ruleNodes: RuleNodesReference = { + nodes: [], + connections: [] + }; + let top = -1, left = -1, bottom = -1, right = -1; + for (let i = 0; i < nodes.length; i++) { + const origNode = nodes[i]; + const node: FcRuleNode = { + id: '', + connectors: [], + additionalInfo: origNode.additionalInfo, + configuration: origNode.configuration, + debugMode: origNode.debugMode, + x: origNode.x, + y: origNode.y, + name: origNode.name, + componentClazz: origNode.component.clazz, + } + if (origNode.targetRuleChainId) { + node.targetRuleChainId = origNode.targetRuleChainId; + } + if (origNode.error) { + node.error = origNode.error; + } + ruleNodes.nodes.push(node); + if (i==0) { + top = node.y; + left = node.x; + bottom = node.y + 50; + right = node.x + 170; + } else { + top = Math.min(top, node.y); + left = Math.min(left, node.x); + bottom = Math.max(bottom, node.y + 50); + right = Math.max(right, node.x + 170); + } + } + ruleNodes.originX = left + (right-left)/2; + ruleNodes.originY = top + (bottom-top)/2; + connections.forEach(connection => { + ruleNodes.connections.push(connection); + }); + this.storeSet(RULE_NODES, ruleNodes); + } + + public hasRuleNodes(): boolean { + return this.storeHas(RULE_NODES); + } + + public pasteRuleNodes(x: number, y: number): RuleNodesReference { + const ruleNodes: RuleNodesReference = this.storeGet(RULE_NODES); + if (ruleNodes) { + const deltaX = x - ruleNodes.originX; + const deltaY = y - ruleNodes.originY; + for (const node of ruleNodes.nodes) { + const component = this.ruleChainService.getRuleNodeComponentByClazz(node.componentClazz); + if (component) { + let icon = ruleNodeTypeDescriptors.get(component.type).icon; + let iconUrl: string = null; + if (component.configurationDescriptor.nodeDefinition.icon) { + icon = component.configurationDescriptor.nodeDefinition.icon; + } + if (component.configurationDescriptor.nodeDefinition.iconUrl) { + iconUrl = component.configurationDescriptor.nodeDefinition.iconUrl; + } + delete node.componentClazz; + node.component = component; + node.nodeClass = ruleNodeTypeDescriptors.get(component.type).nodeClass; + node.icon = icon; + node.iconUrl = iconUrl; + node.connectors = []; + node.x = Math.round(node.x + deltaX); + node.y = Math.round(node.y + deltaY); + } else { + return null; + } + } + return ruleNodes; + } + return null; + } + private getOriginalColumns(dashboard: Dashboard, sourceState: string, sourceLayout: DashboardLayoutId): number { let originalColumns = 24; let gridSettings = null; 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 fc9a7b5eb6..a803741bf1 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 @@ -737,18 +737,22 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI dataLoading: (subscription) => { if (this.loadingData !== subscription.loadingData) { this.loadingData = subscription.loadingData; - this.cd.detectChanges(); + if (!this.destroyed) { + this.cd.detectChanges(); + } } }, legendDataUpdated: (subscription, detectChanges) => { - if (detectChanges) { + if (detectChanges && !this.destroyed) { this.cd.detectChanges(); } }, timeWindowUpdated: (subscription, timeWindowConfig) => { this.ngZone.run(() => { this.widget.config.timewindow = timeWindowConfig; - this.cd.detectChanges(); + if (!this.destroyed) { + this.cd.detectChanges(); + } }); } }; diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.html b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.html index cae9b0f86d..af1df0dbff 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.html +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.html @@ -17,6 +17,7 @@ -->
+
@@ -146,6 +147,7 @@ [mode]="isMobile ? 'over' : 'side'" [(opened)]="rightLayoutOpened"> -
-
(); constructor(protected store: Store, - private hotkeysService: HotkeysService, private translate: TranslateService, private itembuffer: ItemBufferService, private sanitizer: DomSanitizer) { super(store); + this.initHotKeys(); } ngOnInit(): void { @@ -95,7 +97,6 @@ export class DashboardLayoutComponent extends PageComponent implements ILayoutCo this.dashboardCtx.runChangeDetection(); }) ); - this.initHotKeys(); } ngOnDestroy(): void { @@ -106,7 +107,7 @@ export class DashboardLayoutComponent extends PageComponent implements ILayoutCo } private initHotKeys(): void { - this.hotkeysService.add( + this.hotKeys.push( new Hotkey('ctrl+c', (event: KeyboardEvent) => { if (this.isEdit && !this.isEditingWidget && !this.widgetEditMode) { const widget = this.dashboard.getSelectedWidget(); @@ -119,7 +120,7 @@ export class DashboardLayoutComponent extends PageComponent implements ILayoutCo }, null, this.translate.instant('action.copy')) ); - this.hotkeysService.add( + this.hotKeys.push( new Hotkey('ctrl+r', (event: KeyboardEvent) => { if (this.isEdit && !this.isEditingWidget && !this.widgetEditMode) { const widget = this.dashboard.getSelectedWidget(); @@ -132,7 +133,7 @@ export class DashboardLayoutComponent extends PageComponent implements ILayoutCo }, null, this.translate.instant('action.copy-reference')) ); - this.hotkeysService.add( + this.hotKeys.push( new Hotkey('ctrl+v', (event: KeyboardEvent) => { if (this.isEdit && !this.isEditingWidget && !this.widgetEditMode) { if (this.itembuffer.hasWidget()) { @@ -144,7 +145,7 @@ export class DashboardLayoutComponent extends PageComponent implements ILayoutCo }, null, this.translate.instant('action.paste')) ); - this.hotkeysService.add( + this.hotKeys.push( new Hotkey('ctrl+i', (event: KeyboardEvent) => { if (this.isEdit && !this.isEditingWidget && !this.widgetEditMode) { if (this.itembuffer.canPasteWidgetReference(this.dashboardCtx.getDashboard(), @@ -157,7 +158,7 @@ export class DashboardLayoutComponent extends PageComponent implements ILayoutCo }, null, this.translate.instant('action.paste-reference')) ); - this.hotkeysService.add( + this.hotKeys.push( new Hotkey('ctrl+x', (event: KeyboardEvent) => { if (this.isEdit && !this.isEditingWidget && !this.widgetEditMode) { const widget = this.dashboard.getSelectedWidget(); diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/link-labels.component.ts b/ui-ngx/src/app/modules/home/pages/rulechain/link-labels.component.ts index ca165caa4d..cb8bd71383 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/link-labels.component.ts +++ b/ui-ngx/src/app/modules/home/pages/rulechain/link-labels.component.ts @@ -14,31 +14,14 @@ /// limitations under the License. /// -import { - AfterViewInit, - Component, ElementRef, - EventEmitter, forwardRef, - 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'; -import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, NgForm, Validators } from '@angular/forms'; -import { FcRuleNode, FcRuleEdge } from './rulechain-page.models'; -import { RuleNodeType, LinkLabel } from '@shared/models/rule-node.models'; -import { EntityType } from '@shared/models/entity-type.models'; -import { Observable, of, Subscription } from 'rxjs'; -import { RuleChainService } from '@core/http/rule-chain.service'; +import { Component, ElementRef, forwardRef, Input, OnChanges, OnInit, SimpleChanges, ViewChild } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { LinkLabel } from '@shared/models/rule-node.models'; +import { Observable, of } from 'rxjs'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { deepClone } from '@core/utils'; -import { EntityAlias } from '@shared/models/alias.models'; import { TruncatePipe } from '@shared/pipe/truncate.pipe'; -import { MatChipList, MatAutocomplete, MatChipInputEvent, MatAutocompleteSelectedEvent } from '@angular/material'; +import { MatAutocomplete, MatAutocompleteSelectedEvent, MatChipInputEvent, MatChipList } from '@angular/material'; import { TranslateService } from '@ngx-translate/core'; import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes'; import { map, mergeMap, share, startWith } from 'rxjs/operators'; diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-colors.scss b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-colors.scss new file mode 100644 index 0000000000..44ff80a37b --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-colors.scss @@ -0,0 +1,46 @@ +/** + * Copyright © 2016-2019 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. + */ + +@mixin rule-node-colors { + &.tb-filter-type { + background-color: #f1e861; + } + + &.tb-enrichment-type { + background-color: #cdf14e; + } + + &.tb-transformation-type { + background-color: #79cef1; + } + + &.tb-action-type { + background-color: #f1928f; + } + + &.tb-external-type { + background-color: #fbc766; + } + + &.tb-rule-chain-type { + background-color: #d6c4f1; + } + + &.tb-unknown-type { + background-color: #f16c29; + } + +} 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 e87fde4b45..1182ad925b 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 @@ -19,12 +19,10 @@ import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms'; -import { FcRuleNode } from './rulechain-page.models'; -import { RuleNodeType } from '@shared/models/rule-node.models'; +import { FcRuleNode, RuleNodeType } from '@shared/models/rule-node.models'; import { EntityType } from '@shared/models/entity-type.models'; import { Subscription } from 'rxjs'; import { RuleChainService } from '@core/http/rule-chain.service'; -import { JsonObjectEditComponent } from '@shared/components/json-object-edit.component'; import { RuleNodeConfigComponent } from './rule-node-config.component'; @Component({ diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-link.component.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-link.component.ts index 7b3f6ead9d..84c7eb45d7 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-link.component.ts +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-link.component.ts @@ -14,34 +14,12 @@ /// limitations under the License. /// -import { - AfterViewInit, - Component, ElementRef, - EventEmitter, forwardRef, - 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'; +import { Component, forwardRef, Input, OnInit, ViewChild } from '@angular/core'; import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, NgForm, Validators } from '@angular/forms'; -import { FcRuleNode, FcRuleEdge } from './rulechain-page.models'; -import { RuleNodeType, LinkLabel } from '@shared/models/rule-node.models'; -import { EntityType } from '@shared/models/entity-type.models'; -import { Observable, of, Subscription } from 'rxjs'; -import { RuleChainService } from '@core/http/rule-chain.service'; +import { FcRuleEdge, LinkLabel } from '@shared/models/rule-node.models'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; -import { deepClone } from '@core/utils'; -import { EntityAlias } from '@shared/models/alias.models'; import { TruncatePipe } from '@shared/pipe/truncate.pipe'; -import { MatChipList, MatAutocomplete, MatChipInputEvent, MatAutocompleteSelectedEvent } from '@angular/material'; import { TranslateService } from '@ngx-translate/core'; -import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes'; -import { map, mergeMap, share } from 'rxjs/operators'; @Component({ selector: 'tb-rule-node-link', 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 b62a9d1afc..99e40cd539 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 @@ -15,8 +15,10 @@ limitations under the License. --> -
+
{{ isFullscreen ? 'fullscreen_exit' : 'fullscreen' }} -
+
+
+
+ + +
+
+ {{contextInfo.icon}} + +
+
{{contextInfo.title}}
+
{{contextInfo.subtitle}}
+
+
+
+ + +
+
+
+
; + @ViewChild('ruleChainMenuTrigger', {static: true}) ruleChainMenuTrigger: MatMenuTrigger; + + ruleChainMenuPosition = { x: '0px', y: '0px' }; + + contextMenuEvent: MouseEvent; + ruleNodeTypeDescriptorsMap = ruleNodeTypeDescriptors; ruleNodeTypesLibraryArray = ruleNodeTypesLibrary; @@ -116,6 +122,9 @@ export class RuleChainPageComponent extends PageComponent isEditingRuleNodeLink = false; editingRuleNodeLinkIndex = -1; + hotKeys: Hotkey[] = []; + + enableHotKeys = true; isLibraryOpen = true; ruleNodeSearch = ''; @@ -173,7 +182,11 @@ export class RuleChainPageComponent extends PageComponent } else { const labels = this.ruleChainService.getRuleNodeSupportedLinks(sourceNode.component); const allowCustomLabels = this.ruleChainService.ruleNodeAllowCustomLinks(sourceNode.component); + this.enableHotKeys = false; return this.addRuleNodeLink(edge, labels, allowCustomLabels).pipe( + tap(() => { + this.enableHotKeys = true; + }), mergeMap((res) => { if (res) { return of(res); @@ -216,6 +229,7 @@ export class RuleChainPageComponent extends PageComponent private ruleChainService: RuleChainService, private authService: AuthService, private translate: TranslateService, + private itembuffer: ItemBufferService, public dialog: MatDialog, public dialogService: DialogService, public fb: FormBuilder) { @@ -236,6 +250,7 @@ export class RuleChainPageComponent extends PageComponent }) ) .subscribe(); + this.ruleChainCanvas.adjustCanvasSize(true); } onSearchTextUpdated(searchText: string) { @@ -244,6 +259,7 @@ export class RuleChainPageComponent extends PageComponent } private init() { + this.initHotKeys(); this.ruleChain = this.route.snapshot.data.ruleChain; if (this.route.snapshot.data.import && !this.ruleChain) { this.router.navigateByUrl('ruleChains'); @@ -268,6 +284,89 @@ export class RuleChainPageComponent extends PageComponent this.createRuleChainModel(); } + private initHotKeys(): void { + this.hotKeys.push( + new Hotkey('ctrl+a', (event: KeyboardEvent) => { + if (this.enableHotKeys) { + event.preventDefault(); + this.ruleChainCanvas.modelService.selectAll(); + return false; + } + return true; + }, ['INPUT', 'SELECT', 'TEXTAREA'], + this.translate.instant('rulenode.select-all-objects')) + ); + this.hotKeys.push( + new Hotkey('ctrl+c', (event: KeyboardEvent) => { + if (this.enableHotKeys) { + event.preventDefault(); + this.copyRuleNodes(); + return false; + } + return true; + }, ['INPUT', 'SELECT', 'TEXTAREA'], + this.translate.instant('rulenode.copy-selected')) + ); + this.hotKeys.push( + new Hotkey('ctrl+v', (event: KeyboardEvent) => { + if (this.enableHotKeys) { + event.preventDefault(); + if (this.itembuffer.hasRuleNodes()) { + this.pasteRuleNodes(); + } + return false; + } + return true; + }, ['INPUT', 'SELECT', 'TEXTAREA'], + this.translate.instant('action.paste')) + ); + this.hotKeys.push( + new Hotkey('esc', (event: KeyboardEvent) => { + if (this.enableHotKeys) { + event.preventDefault(); + event.stopPropagation(); + this.ruleChainCanvas.modelService.deselectAll(); + return false; + } + return true; + }, ['INPUT', 'SELECT', 'TEXTAREA'], + this.translate.instant('rulenode.deselect-all-objects')) + ); + this.hotKeys.push( + new Hotkey('ctrl+s', (event: KeyboardEvent) => { + if (this.enableHotKeys) { + event.preventDefault(); + this.saveRuleChain(); + return false; + } + return true; + }, ['INPUT', 'SELECT', 'TEXTAREA'], + this.translate.instant('action.apply')) + ); + this.hotKeys.push( + new Hotkey('ctrl+z', (event: KeyboardEvent) => { + if (this.enableHotKeys) { + event.preventDefault(); + this.revertRuleChain(); + return false; + } + return true; + }, ['INPUT', 'SELECT', 'TEXTAREA'], + this.translate.instant('action.decline-changes')) + ); + this.hotKeys.push( + new Hotkey('del', (event: KeyboardEvent) => { + if (this.enableHotKeys) { + event.preventDefault(); + this.ruleChainCanvas.modelService.deleteSelected(); + return false; + } + return true; + }, ['INPUT', 'SELECT', 'TEXTAREA'], + this.translate.instant('rulenode.delete-selected-objects')) + ); + } + updateRuleChainLibrary() { const search = this.ruleNodeTypeSearch.toUpperCase(); const res = this.ruleNodeComponents.filter( @@ -510,11 +609,229 @@ export class RuleChainPageComponent extends PageComponent } }); } + if (this.ruleChainCanvas) { + this.ruleChainCanvas.adjustCanvasSize(true); + } this.isDirtyValue = false; this.updateRuleNodesHighlight(); this.validate(); } + openRuleChainContextMenu($event: MouseEvent) { + if (this.ruleChainCanvas.modelService && !$event.ctrlKey && !$event.metaKey) { + const x = $event.clientX; + const y = $event.clientY; + const item = this.ruleChainCanvas.modelService.getItemInfoAtPoint(x, y); + const contextInfo = this.prepareContextMenu(item); + if (contextInfo.menuItems && contextInfo.menuItems.length > 0) { + $event.preventDefault(); + $event.stopPropagation(); + this.contextMenuEvent = $event; + this.ruleChainMenuPosition.x = x + 'px'; + this.ruleChainMenuPosition.y = y + 'px'; + this.ruleChainMenuTrigger.menuData = { contextInfo }; + this.ruleChainMenuTrigger.openMenu(); + } + } + } + + onRuleChainContextMenuMouseLeave() { + this.ruleChainMenuTrigger.closeMenu(); + } + + private prepareContextMenu(item: FcItemInfo): RuleChainMenuContextInfo { + if (this.objectsSelected() || (!item.node && !item.edge)) { + return this.prepareRuleChainContextMenu(); + } else if (item.node) { + return this.prepareRuleNodeContextMenu(item.node); + } else if (item.edge) { + return this.prepareEdgeContextMenu(item.edge); + } + } + + private prepareRuleChainContextMenu(): RuleChainMenuContextInfo { + const contextInfo: RuleChainMenuContextInfo = { + headerClass: 'tb-rulechain-header', + icon: 'settings_ethernet', + title: this.ruleChain.name, + subtitle: this.translate.instant('rulechain.rulechain'), + menuItems: [] + }; + if (this.ruleChainCanvas.modelService.nodes.getSelectedNodes().length) { + contextInfo.menuItems.push( + { + action: () => { + this.copyRuleNodes(); + }, + enabled: true, + value: 'rulenode.copy-selected', + icon: 'content_copy', + shortcut: 'M-C' + } + ); + } + contextInfo.menuItems.push( + { + action: ($event) => { + this.pasteRuleNodes($event); + }, + enabled: this.itembuffer.hasRuleNodes(), + value: 'action.paste', + icon: 'content_paste', + shortcut: 'M-V' + } + ); + contextInfo.menuItems.push( + { + divider: true + } + ); + if (this.objectsSelected()) { + contextInfo.menuItems.push( + { + action: () => { + this.ruleChainCanvas.modelService.deselectAll(); + }, + enabled: true, + value: 'rulenode.deselect-all', + icon: 'tab_unselected', + shortcut: 'Esc' + } + ); + contextInfo.menuItems.push( + { + action: () => { + this.ruleChainCanvas.modelService.deleteSelected(); + }, + enabled: true, + value: 'rulenode.delete-selected', + icon: 'clear', + shortcut: 'Del' + } + ); + } else { + contextInfo.menuItems.push( + { + action: () => { + this.ruleChainCanvas.modelService.selectAll(); + }, + enabled: true, + value: 'rulenode.select-all', + icon: 'select_all', + shortcut: 'M-A' + } + ); + } + contextInfo.menuItems.push( + { + divider: true + } + ); + contextInfo.menuItems.push( + { + action: () => { + this.saveRuleChain(); + }, + enabled: !(this.isInvalid || (!this.isDirty && !this.isImport)), + value: 'action.apply-changes', + icon: 'done', + shortcut: 'M-S' + } + ); + contextInfo.menuItems.push( + { + action: () => { + this.revertRuleChain(); + }, + enabled: this.isDirty, + value: 'action.decline-changes', + icon: 'close', + shortcut: 'M-Z' + } + ); + return contextInfo; + } + + private prepareRuleNodeContextMenu(node: FcRuleNode): RuleChainMenuContextInfo { + const contextInfo: RuleChainMenuContextInfo = { + headerClass: node.nodeClass, + icon: node.icon, + iconUrl: node.iconUrl, + title: node.name, + subtitle: node.component.name, + menuItems: [] + }; + if (!node.readonly) { + contextInfo.menuItems.push( + { + action: () => { + this.openNodeDetails(node); + }, + enabled: true, + value: 'rulenode.details', + icon: 'menu' + } + ); + contextInfo.menuItems.push( + { + action: () => { + this.copyNode(node); + }, + enabled: true, + value: 'action.copy', + icon: 'content_copy' + } + ); + contextInfo.menuItems.push( + { + action: () => { + this.ruleChainCanvas.modelService.nodes.delete(node); + }, + enabled: true, + value: 'action.delete', + icon: 'clear', + shortcut: 'M-X' + } + ); + } + return contextInfo; + } + + private prepareEdgeContextMenu(edge: FcRuleEdge): RuleChainMenuContextInfo { + const contextInfo: RuleChainMenuContextInfo = { + headerClass: 'tb-link-header', + icon: 'trending_flat', + title: edge.label, + subtitle: this.translate.instant('rulenode.link'), + menuItems: [] + }; + const sourceNode: FcRuleNode = this.ruleChainCanvas.modelService.nodes.getNodeByConnectorId(edge.source); + if (sourceNode.component.type != RuleNodeType.INPUT) { + contextInfo.menuItems.push( + { + action: () => { + this.openLinkDetails(edge); + }, + enabled: true, + value: 'rulenode.details', + icon: 'menu' + } + ); + } + contextInfo.menuItems.push( + { + action: () => { + this.ruleChainCanvas.modelService.edges.delete(edge); + }, + enabled: true, + value: 'action.delete', + icon: 'clear', + shortcut: 'M-X' + } + ); + return contextInfo; + } + onModelChanged() { console.log('Model changed!'); this.isDirtyValue = true; @@ -531,6 +848,8 @@ export class RuleChainPageComponent extends PageComponent openNodeDetails(node: FcRuleNode) { if (node.component.type !== RuleNodeType.INPUT) { + this.enableHotKeys = false; + this.updateErrorTooltips(true); this.isEditingRuleNodeLink = false; this.editingRuleNodeLink = null; this.isEditingRuleNode = true; @@ -545,6 +864,8 @@ export class RuleChainPageComponent extends PageComponent openLinkDetails(edge: FcRuleEdge) { const sourceNode: FcRuleNode = this.ruleChainCanvas.modelService.nodes.getNodeByConnectorId(edge.source) as FcRuleNode; if (sourceNode.component.type !== RuleNodeType.INPUT) { + this.enableHotKeys = false; + this.updateErrorTooltips(true); this.isEditingRuleNode = false; this.editingRuleNode = null; this.editingRuleNodeLinkLabels = this.ruleChainService.getRuleNodeSupportedLinks(sourceNode.component); @@ -558,9 +879,121 @@ export class RuleChainPageComponent extends PageComponent } } + private copyNode(node: FcRuleNode) { + this.itembuffer.copyRuleNodes([node], []); + } + + private copyRuleNodes() { + const nodes: FcRuleNode[] = this.ruleChainCanvas.modelService.nodes.getSelectedNodes(); + const edges: FcRuleEdge[] = this.ruleChainCanvas.modelService.edges.getSelectedEdges(); + const connections: RuleNodeConnection[] = []; + edges.forEach((edge) => { + const sourceNode = this.ruleChainCanvas.modelService.nodes.getNodeByConnectorId(edge.source); + const destNode = this.ruleChainCanvas.modelService.nodes.getNodeByConnectorId(edge.destination); + const isInputSource = sourceNode.component.type == RuleNodeType.INPUT; + const fromIndex = nodes.indexOf(sourceNode); + const toIndex = nodes.indexOf(destNode); + if ( (isInputSource || fromIndex > -1) && toIndex > -1 ) { + const connection: RuleNodeConnection = { + isInputSource: isInputSource, + fromIndex: fromIndex, + toIndex: toIndex, + label: edge.label, + labels: edge.labels + }; + connections.push(connection); + } + }); + this.itembuffer.copyRuleNodes(nodes, connections); + } + + private pasteRuleNodes(event?: MouseEvent) { + const canvas = $(this.ruleChainCanvas.modelService.canvasHtmlElement); + let x: number; + let y: number; + if (event) { + const offset = canvas.offset(); + x = Math.round(event.clientX - offset.left); + y = Math.round(event.clientY - offset.top); + } else { + const scrollParent = canvas.parent(); + const scrollTop = scrollParent.scrollTop(); + const scrollLeft = scrollParent.scrollLeft(); + x = scrollLeft + scrollParent.width()/2; + y = scrollTop + scrollParent.height()/2; + } + const ruleNodes = this.itembuffer.pasteRuleNodes(x, y); + if (ruleNodes) { + this.ruleChainCanvas.modelService.deselectAll(); + const nodes: FcRuleNode[] = []; + ruleNodes.nodes.forEach((node) => { + node.id = 'rule-chain-node-' + this.nextNodeID++; + const component = node.component; + if (component.configurationDescriptor.nodeDefinition.inEnabled) { + node.connectors.push( + { + type: FlowchartConstants.leftConnectorType, + id: (this.nextConnectorID++) + '' + } + ); + } + if (component.configurationDescriptor.nodeDefinition.outEnabled) { + node.connectors.push( + { + type: FlowchartConstants.rightConnectorType, + id: (this.nextConnectorID++) + '' + } + ); + } + nodes.push(node); + this.ruleChainModel.nodes.push(node); + this.ruleChainCanvas.modelService.nodes.select(node); + }); + ruleNodes.connections.forEach((connection) => { + const sourceNode = nodes[connection.fromIndex]; + const destNode = nodes[connection.toIndex]; + if ( (connection.isInputSource || sourceNode) && destNode ) { + let source: string; + let destination: string; + if (connection.isInputSource) { + source = this.inputConnectorId + ''; + const found = this.ruleChainModel.edges.find(theEdge => theEdge.source === (this.inputConnectorId + '')); + if (found) { + this.ruleChainCanvas.modelService.edges.delete(found); + } + } else { + const sourceConnectors = this.ruleChainCanvas.modelService.nodes.getConnectorsByType(sourceNode, FlowchartConstants.rightConnectorType); + if (sourceConnectors && sourceConnectors.length) { + source = sourceConnectors[0].id; + } + } + const destConnectors = this.ruleChainCanvas.modelService.nodes.getConnectorsByType(destNode, FlowchartConstants.leftConnectorType); + if (destConnectors && destConnectors.length) { + destination = destConnectors[0].id; + } + if (source && destination) { + const edge: FcRuleEdge = { + source: source, + destination: destination, + label: connection.label, + labels: connection.labels + }; + this.ruleChainModel.edges.push(edge); + this.ruleChainCanvas.modelService.edges.select(edge); + } + } + }); + this.updateRuleNodesHighlight(); + this.validate(); + this.onModelChanged(); + } + } + onDetailsDrawerClosed() { this.onEditRuleNodeClosed(); this.onEditRuleNodeLinkClosed(); + this.enableHotKeys = true; + this.updateErrorTooltips(false); } onEditRuleNodeClosed() { @@ -739,6 +1172,7 @@ export class RuleChainPageComponent extends PageComponent addRuleNode(ruleNode: FcRuleNode) { ruleNode.configuration = deepClone(ruleNode.component.configurationDescriptor.nodeDefinition.defaultConfiguration); const ruleChainId = this.ruleChain.id ? this.ruleChain.id.id : null; + this.enableHotKeys = false; this.dialog.open(AddRuleNodeDialogComponent, { disableClose: true, @@ -772,6 +1206,7 @@ export class RuleChainPageComponent extends PageComponent this.onModelChanged(); this.updateRuleNodesHighlight(); } + this.enableHotKeys = true; } ); } @@ -836,6 +1271,17 @@ export class RuleChainPageComponent extends PageComponent } } + private updateErrorTooltips(hide: boolean) { + for (const nodeId of Object.keys(this.errorTooltips)) { + const tooltip = this.errorTooltips[nodeId]; + if (hide) { + tooltip.close(); + } else { + tooltip.open(); + } + } + } + private displayTooltip(event: MouseEvent, content: string) { this.destroyTooltips(); this.tooltipTimeout = setTimeout(() => { diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.models.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.models.ts index 20cb4d1dba..485f7b8a72 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.models.ts +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.models.ts @@ -14,37 +14,32 @@ /// limitations under the License. /// -import { FcNode, FcEdge, FcModel } from 'ngx-flowchart/dist/ngx-flowchart'; -import { RuleNodeComponentDescriptor, RuleNodeConfiguration } from '@shared/models/rule-node.models'; -import { RuleNodeId } from '@app/shared/models/id/rule-node-id'; -import { RuleChainId } from '@shared/models/id/rule-chain-id'; - -export interface FcRuleNodeType extends FcNode { - component: RuleNodeComponentDescriptor; - nodeClass: string; - icon: string; - iconUrl?: string; -} +import { FcModel } from 'ngx-flowchart/dist/ngx-flowchart'; +import { FcRuleEdge, FcRuleNode, FcRuleNodeType } from '@shared/models/rule-node.models'; export interface FcRuleNodeTypeModel extends FcModel { nodes: Array; } -export interface FcRuleNode extends FcRuleNodeType { - ruleNodeId?: RuleNodeId; - additionalInfo?: any; - configuration?: RuleNodeConfiguration; - debugMode?: boolean; - targetRuleChainId?: string; - error?: string; - highlighted?: boolean; +export interface FcRuleNodeModel extends FcModel { + nodes: Array; + edges: Array; } -export interface FcRuleEdge extends FcEdge { - labels?: string[]; +export interface RuleChainMenuItem { + action?: ($event: MouseEvent) => void; + enabled?: boolean; + value?: string; + icon?: string; + shortcut?: string; + divider?: boolean; } -export interface FcRuleNodeModel extends FcModel { - nodes: Array; - edges: Array; +export interface RuleChainMenuContextInfo { + headerClass: string; + icon: string; + iconUrl?: string; + title: string; + subtitle: string; + menuItems: RuleChainMenuItem[]; } 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 55587dd5db..83aefcd05e 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 @@ -14,6 +14,8 @@ * limitations under the License. */ +@import './rule-node-colors'; + :host { .fc-node-overlay { @@ -63,33 +65,7 @@ border: solid 1px #777; border-radius: 5px; - &.tb-filter-type { - background-color: #f1e861; - } - - &.tb-enrichment-type { - background-color: #cdf14e; - } - - &.tb-transformation-type { - background-color: #79cef1; - } - - &.tb-action-type { - background-color: #f1928f; - } - - &.tb-external-type { - background-color: #fbc766; - } - - &.tb-rule-chain-type { - background-color: #d6c4f1; - } - - &.tb-unknown-type { - background-color: #f16c29; - } + @include rule-node-colors(); &.tb-rule-node-highlighted:not(.tb-rule-node-invalid) { box-shadow: 0 0 10px 6px #51cbee; diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html index 3031652eae..e530d85203 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html @@ -15,9 +15,9 @@ limitations under the License. --> -
-
+
+ diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts index 846bb53641..1450d3bbe7 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts @@ -139,6 +139,8 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe saveWidgetTimeout: Timeout; + hotKeys: Hotkey[] = []; + private rxSubscriptions = new Array(); constructor(protected store: Store, @@ -146,7 +148,6 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe private route: ActivatedRoute, private router: Router, private widgetService: WidgetService, - private hotkeysService: HotkeysService, private translate: TranslateService, private raf: RafService, private dialog: MatDialog) { @@ -159,6 +160,8 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe this.init(data); } )); + + this.initHotKeys(); } private init(data: any) { @@ -181,7 +184,6 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe } ngOnInit(): void { - this.initHotKeys(); this.initSplitLayout(); this.initAceEditors(); this.iframe = $(this.widgetIFrameElmRef.nativeElement); @@ -203,7 +205,7 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe } private initHotKeys(): void { - this.hotkeysService.add( + this.hotKeys.push( new Hotkey('ctrl+q', (event: KeyboardEvent) => { if (!getCurrentIsLoading(this.store) && !this.undoDisabled()) { event.preventDefault(); @@ -213,7 +215,7 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe }, ['INPUT', 'SELECT', 'TEXTAREA'], this.translate.instant('widget.undo')) ); - this.hotkeysService.add( + this.hotKeys.push( new Hotkey('ctrl+s', (event: KeyboardEvent) => { if (!getCurrentIsLoading(this.store) && !this.saveDisabled()) { event.preventDefault(); @@ -223,7 +225,7 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe }, ['INPUT', 'SELECT', 'TEXTAREA'], this.translate.instant('widget.save')) ); - this.hotkeysService.add( + this.hotKeys.push( new Hotkey('shift+ctrl+s', (event: KeyboardEvent) => { if (!getCurrentIsLoading(this.store) && !this.saveAsDisabled()) { event.preventDefault(); @@ -233,7 +235,7 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe }, ['INPUT', 'SELECT', 'TEXTAREA'], this.translate.instant('widget.saveAs')) ); - this.hotkeysService.add( + this.hotKeys.push( new Hotkey('shift+ctrl+f', (event: KeyboardEvent) => { event.preventDefault(); this.fullscreen = !this.fullscreen; @@ -241,7 +243,7 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe }, ['INPUT', 'SELECT', 'TEXTAREA'], this.translate.instant('widget.toggle-fullscreen')) ); - this.hotkeysService.add( + this.hotKeys.push( new Hotkey('ctrl+enter', (event: KeyboardEvent) => { event.preventDefault(); this.applyWidgetScript(); diff --git a/ui-ngx/src/app/shared/components/cheatsheet.component.ts b/ui-ngx/src/app/shared/components/cheatsheet.component.ts new file mode 100644 index 0000000000..3c7412add8 --- /dev/null +++ b/ui-ngx/src/app/shared/components/cheatsheet.component.ts @@ -0,0 +1,165 @@ +/// +/// Copyright © 2016-2019 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, Input, OnDestroy, OnInit } from '@angular/core'; +import { Hotkey, HotkeysService } from 'angular2-hotkeys'; + +@Component({ + selector : 'tb-hotkeys-cheatsheet', + styles : [` +.tb-hotkeys-container { + display: table !important; + position: fixed; + width: 100%; + height: 100%; + top: 0; + left: 0; + color: #333; + font-size: 1em; + background-color: rgba(255,255,255,0.9); + outline: 0; +} +.tb-hotkeys-container.fade { + z-index: -1024; + visibility: hidden; + opacity: 0; + -webkit-transition: opacity 0.15s linear; + -moz-transition: opacity 0.15s linear; + -o-transition: opacity 0.15s linear; + transition: opacity 0.15s linear; +} +.tb-hotkeys-container.fade.in { + z-index: 10002; + visibility: visible; + opacity: 1; +} +.tb-hotkeys-title { + font-weight: bold; + text-align: center; + font-size: 1.2em; +} +.tb-hotkeys { + width: 100%; + height: 100%; + display: table-cell; + vertical-align: middle; +} +.tb-hotkeys table { + margin: auto; + color: #333; +} +.tb-content { + display: table-cell; + vertical-align: middle; +} +.tb-hotkeys-keys { + padding: 5px; + text-align: right; +} +.tb-hotkeys-key { + display: inline-block; + color: #fff; + background-color: #333; + border: 1px solid #333; + border-radius: 5px; + text-align: center; + margin-right: 5px; + box-shadow: inset 0 1px 0 #666, 0 1px 0 #bbb; + padding: 5px 9px; + font-size: 1em; +} +.tb-hotkeys-text { + padding-left: 10px; + font-size: 1em; +} +.tb-hotkeys-close { + position: fixed; + top: 20px; + right: 20px; + font-size: 2em; + font-weight: bold; + padding: 5px 10px; + border: 1px solid #ddd; + border-radius: 5px; + min-height: 45px; + min-width: 45px; + text-align: center; +} +.tb-hotkeys-close:hover { + background-color: #fff; + cursor: pointer; +} +@media all and (max-width: 500px) { + .tb-hotkeys { + font-size: 0.8em; + } +} +@media all and (min-width: 750px) { + .tb-hotkeys { + font-size: 1.2em; + } +} `], + template : ``, +}) +export class TbCheatSheetComponent implements OnInit, OnDestroy { + + helpVisible = false; + @Input() title: string = 'Keyboard Shortcuts:'; + + @Input() + hotkeys: Hotkey[]; + + hotkeysList: Hotkey[]; + + private mousetrap: MousetrapInstance; + + constructor(private _elementRef: ElementRef, + private hotkeysService: HotkeysService) { + this.mousetrap = new Mousetrap(this._elementRef.nativeElement); + this.mousetrap.bind('?', (event: KeyboardEvent, combo: string) => { + this.toggleCheatSheet(); + }); + } + + public ngOnInit(): void { + if (this.hotkeys) { + this.hotkeysList = this.hotkeys.filter(hotkey => hotkey.description); + } + } + + public setHotKeys(hotkeys: Hotkey[]) { + this.hotkeysList = hotkeys.filter(hotkey => hotkey.description); + } + + public toggleCheatSheet(): void { + this.helpVisible = !this.helpVisible; + } + + ngOnDestroy() { + this.mousetrap.unbind('?'); + } +} diff --git a/ui-ngx/src/app/shared/components/hotkeys.directive.ts b/ui-ngx/src/app/shared/components/hotkeys.directive.ts new file mode 100644 index 0000000000..c39aaed204 --- /dev/null +++ b/ui-ngx/src/app/shared/components/hotkeys.directive.ts @@ -0,0 +1,86 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import {Directive, Input, OnInit, OnDestroy, ElementRef} from '@angular/core'; +import {Hotkey, ExtendedKeyboardEvent} from 'angular2-hotkeys'; +import 'mousetrap'; +import { TbCheatSheetComponent } from '@shared/components/cheatsheet.component'; + +@Directive({ + selector : '[tb-hotkeys]' +}) +export class TbHotkeysDirective implements OnInit, OnDestroy { + @Input() hotkeys: Hotkey[] = []; + @Input() cheatSheet: TbCheatSheetComponent; + + private mousetrap: MousetrapInstance; + private hotkeysList: Hotkey[] = []; + + private _preventIn = ['INPUT', 'SELECT', 'TEXTAREA']; + + constructor(private _elementRef: ElementRef) { + this.mousetrap = new Mousetrap(this._elementRef.nativeElement); + (this._elementRef.nativeElement as HTMLElement).tabIndex = -1; + (this._elementRef.nativeElement as HTMLElement).style.outline = '0'; + } + + ngOnInit() { + for (let hotkey of this.hotkeys) { + this.hotkeysList.push(hotkey); + this.bindEvent(hotkey); + } + if (this.cheatSheet) { + let hotkeyObj: Hotkey = new Hotkey( + '?', + (event: KeyboardEvent) => { + this.cheatSheet.toggleCheatSheet(); + return false; + }, + [], + 'Show / hide this help menu', + ); + this.hotkeysList.unshift(hotkeyObj); + this.bindEvent(hotkeyObj); + this.cheatSheet.setHotKeys(this.hotkeysList); + } + } + + private bindEvent(hotkey: Hotkey): void { + this.mousetrap.bind((hotkey).combo, (event: KeyboardEvent, combo: string) => { + let shouldExecute = true; + if(event) { + let target: HTMLElement = (event.target || event.srcElement); + let nodeName: string = target.nodeName.toUpperCase(); + if((' ' + target.className + ' ').indexOf(' mousetrap ') > -1) { + shouldExecute = true; + } else if(this._preventIn.indexOf(nodeName) > -1 && (hotkey).allowIn.map(allow => allow.toUpperCase()).indexOf(nodeName) === -1) { + shouldExecute = false; + } + } + + if(shouldExecute) { + return (hotkey).callback.apply(this, [event, combo]); + } + }); + } + + ngOnDestroy() { + for (let hotkey of this.hotkeysList) { + this.mousetrap.unbind(hotkey.combo); + } + } + +} 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 dd68fea554..e80e43a03b 100644 --- a/ui-ngx/src/app/shared/models/rule-node.models.ts +++ b/ui-ngx/src/app/shared/models/rule-node.models.ts @@ -14,18 +14,14 @@ /// limitations under the License. /// -import {BaseData} from '@shared/models/base-data'; -import {AssetId} from '@shared/models/id/asset-id'; -import {TenantId} from '@shared/models/id/tenant-id'; -import {CustomerId} from '@shared/models/id/customer-id'; -import {RuleChainId} from '@shared/models/id/rule-chain-id'; -import {RuleNodeId} from '@shared/models/id/rule-node-id'; -import { ComponentDescriptor, ComponentType } from '@shared/models/component-descriptor.models'; -import { EntityType, EntityTypeResource } from '@shared/models/entity-type.models'; +import { BaseData } from '@shared/models/base-data'; +import { RuleChainId } from '@shared/models/id/rule-chain-id'; +import { RuleNodeId } from '@shared/models/id/rule-node-id'; +import { ComponentDescriptor } from '@shared/models/component-descriptor.models'; +import { FcEdge, FcNode } from 'ngx-flowchart/dist/ngx-flowchart'; import { Observable } from 'rxjs'; import { PageComponent } from '@shared/components/page.component'; -import { AfterViewInit, ComponentFactory, EventEmitter, Inject, OnDestroy, OnInit } from '@angular/core'; -import { RafService } from '@core/services/raf.service'; +import { AfterViewInit, EventEmitter, Inject, OnInit } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { AbstractControl, FormGroup } from '@angular/forms'; @@ -38,7 +34,6 @@ export enum MsgDataType { export interface RuleNodeConfiguration { [key: string]: any; - // TODO: } export interface RuleNode extends BaseData { @@ -307,6 +302,28 @@ export interface RuleNodeComponentDescriptor extends ComponentDescriptor { configurationDescriptor?: RuleNodeConfigurationDescriptor; } +export interface FcRuleNodeType extends FcNode { + component?: RuleNodeComponentDescriptor; + nodeClass?: string; + icon?: string; + iconUrl?: string; +} + +export interface FcRuleNode extends FcRuleNodeType { + ruleNodeId?: RuleNodeId; + additionalInfo?: any; + configuration?: RuleNodeConfiguration; + debugMode?: boolean; + targetRuleChainId?: string; + error?: string; + highlighted?: boolean; + componentClazz?: string; +} + +export interface FcRuleEdge extends FcEdge { + labels?: string[]; +} + export interface TestScriptInputParams { script: string; scriptType: string; diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index 73657a320e..1334836c67 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -118,6 +118,8 @@ import { NodeScriptTestDialogComponent } from '@shared/components/dialog/node-sc import { MessageTypeAutocompleteComponent } from './components/message-type-autocomplete.component'; import { JsonContentComponent } from './components/json-content.component'; import { KeyValMapComponent } from './components/kv-map.component'; +import { TbCheatSheetComponent } from '@shared/components/cheatsheet.component'; +import { TbHotkeysDirective } from '@shared/components/hotkeys.directive'; @NgModule({ providers: [ @@ -149,11 +151,13 @@ import { KeyValMapComponent } from './components/kv-map.component'; FullscreenDirective, CircularProgressDirective, MatChipDraggableDirective, + TbHotkeysDirective, TbAnchorComponent, HelpComponent, TbCheckboxComponent, TbSnackBarComponent, TbErrorComponent, + TbCheatSheetComponent, BreadcrumbComponent, UserMenuComponent, TimewindowComponent, @@ -256,10 +260,12 @@ import { KeyValMapComponent } from './components/kv-map.component'; FullscreenDirective, CircularProgressDirective, MatChipDraggableDirective, + TbHotkeysDirective, TbAnchorComponent, HelpComponent, TbCheckboxComponent, TbErrorComponent, + TbCheatSheetComponent, BreadcrumbComponent, UserMenuComponent, TimewindowComponent, From 4982f0e6a4260abb0a4763cdca12834dc4f5e662 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 22 Jan 2020 20:05:30 +0200 Subject: [PATCH 075/133] Finish RuleChain page. Implement missing widget subscription methods. Implement Entities table widget. --- .../json/system/widget_bundles/cards.json | 10 +- .../rule/engine/action/TbCreateAlarmNode.java | 4 + .../TbCreateAlarmNodeConfiguration.java | 6 + ui-ngx/src/app/core/api/alias-controller.ts | 51 +- ui-ngx/src/app/core/api/widget-api.models.ts | 1 - .../src/app/core/api/widget-subscription.ts | 216 +++++- ui-ngx/src/app/core/http/alarm.service.ts | 153 +++- ui-ngx/src/app/core/http/device.service.ts | 8 + .../src/app/core/http/rule-chain.service.ts | 25 +- .../app/core/services/item-buffer.service.ts | 18 + .../attribute/attribute-table.component.ts | 7 +- .../audit-log-details-dialog.component.ts | 13 +- .../dashboard/dashboard.component.ts | 1 + .../entity/entities-table.component.html | 28 +- .../entity/entities-table.component.ts | 51 +- .../event/event-content-dialog.component.html | 40 + .../event/event-content-dialog.component.scss | 23 + .../event/event-content-dialog.component.ts | 116 +++ .../components/event/event-table-config.ts | 75 +- .../home/components/home-components.module.ts | 3 + .../import-export/import-export.models.ts | 1 + .../import-export/import-export.service.ts | 83 +- .../widget/dynamic-widget.component.ts | 22 +- .../lib/display-columns-panel.component.html | 24 + .../lib/display-columns-panel.component.scss | 36 + .../lib/display-columns-panel.component.ts | 43 ++ .../lib/entities-table-widget.component.html | 96 +++ .../lib/entities-table-widget.component.scss | 39 + .../lib/entities-table-widget.component.ts | 710 ++++++++++++++++++ .../widget/lib/table-widget.models.ts | 55 ++ .../widget/widget-components.module.ts | 6 + .../components/widget/widget.component.ts | 65 +- .../home/models/dashboard-component.models.ts | 8 +- .../entity/entities-table-config.models.ts | 4 +- .../home/models/widget-component.models.ts | 32 +- .../rulechain/rule-node-config.component.ts | 5 +- .../rulechain/rulechain-page.component.html | 11 +- .../rulechain/rulechain-page.component.scss | 16 + .../rulechain/rulechain-page.component.ts | 111 ++- .../rulechain/rulechain-routing.module.ts | 45 +- .../rulechains-table-config.resolver.ts | 15 +- .../pages/widget/widget-library.component.ts | 31 +- .../entity/entity-type-list.component.ts | 2 +- ui-ngx/src/app/shared/models/alarm.models.ts | 23 + ui-ngx/src/app/shared/models/event.models.ts | 8 +- .../src/app/shared/models/page/page-link.ts | 11 +- .../src/app/shared/models/page/sort-order.ts | 16 + .../app/shared/models/rule-chain.models.ts | 5 +- .../src/app/shared/models/rule-node.models.ts | 8 +- .../models/telemetry/telemetry.models.ts | 2 +- ui-ngx/src/app/shared/models/widget.models.ts | 2 - ui-ngx/src/styles.scss | 7 +- 52 files changed, 2123 insertions(+), 268 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/event/event-content-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/components/event/event-content-dialog.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/event/event-content-dialog.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts 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 60124c0fc3..0a6b0b0d47 100644 --- a/application/src/main/data/json/system/widget_bundles/cards.json +++ b/application/src/main/data/json/system/widget_bundles/cards.json @@ -29,11 +29,11 @@ "sizeX": 7.5, "sizeY": 6.5, "resources": [], - "templateHtml": "\n", + "templateHtml": "\n", "templateCss": "", - "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n var id = self.ctx.$scope.$injector.get('utils').guid();\n scope.tableId = \"table-\"+id;\n scope.ctx = self.ctx;\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.$broadcast('entities-table-data-updated', self.ctx.$scope.tableId);\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n dataKeysOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n", - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"entitiesTitle\": {\n \"title\": \"Entities table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSearch\": {\n \"title\": \"Enable entities search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayEntityName\": {\n \"title\": \"Display entity name column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"entityNameColumnTitle\": {\n \"title\": \"Entity name column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityType\": {\n \"title\": \"Display entity type column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"entityName\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"entitiesTitle\",\n \"enableSearch\",\n \"displayEntityName\",\n \"entityNameColumnTitle\",\n \"displayEntityType\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\"\n ]\n}", - "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, entity, filter)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", + "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.entitiesTableWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n dataKeysOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"entitiesTitle\": {\n \"title\": \"Entities table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSearch\": {\n \"title\": \"Enable entities search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSelectColumnDisplay\": {\n \"title\": \"Enable select columns to display\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayEntityName\": {\n \"title\": \"Display entity name column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"entityNameColumnTitle\": {\n \"title\": \"Entity name column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityType\": {\n \"title\": \"Display entity type column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"entityName\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"entitiesTitle\",\n \"enableSearch\",\n \"enableSelectColumnDisplay\",\n \"displayEntityName\",\n \"entityNameColumnTitle\",\n \"displayEntityType\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\"\n ]\n}", + "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, entity, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSelection\":true,\"enableSearch\":true,\"displayDetails\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"entityName\",\"displayEntityName\":true,\"displayEntityType\":true},\"title\":\"Entities table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#2196f3\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.472295003170325,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Cos\",\"color\":\"#4caf50\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.8926244886945558,\"funcBody\":\"return Math.round(1000*Math.cos(time/5000));\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"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;\"}]}]}" } }, @@ -134,4 +134,4 @@ } } ] -} +} \ No newline at end of file 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 dcf7b6987d..fe409ce7c0 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 @@ -33,6 +33,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; import java.io.IOException; +import java.util.List; @Slf4j @RuleNode( @@ -53,9 +54,12 @@ import java.io.IOException; public class TbCreateAlarmNode extends TbAbstractAlarmNode { private static ObjectMapper mapper = new ObjectMapper(); + private List relationTypes; @Override protected TbCreateAlarmNodeConfiguration loadAlarmNodeConfig(TbNodeConfiguration configuration) throws TbNodeException { + TbCreateAlarmNodeConfiguration nodeConfiguration = TbNodeUtils.convert(configuration, TbCreateAlarmNodeConfiguration.class); + relationTypes = nodeConfiguration.getRelationTypes(); return TbNodeUtils.convert(configuration, TbCreateAlarmNodeConfiguration.class); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNodeConfiguration.java index f93c879acf..3fb0800e07 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNodeConfiguration.java @@ -19,6 +19,9 @@ import lombok.Data; import org.thingsboard.rule.engine.api.NodeConfiguration; import org.thingsboard.server.common.data.alarm.AlarmSeverity; +import java.util.Collections; +import java.util.List; + @Data public class TbCreateAlarmNodeConfiguration extends TbAbstractAlarmNodeConfiguration implements NodeConfiguration { @@ -26,6 +29,8 @@ public class TbCreateAlarmNodeConfiguration extends TbAbstractAlarmNodeConfigura private boolean propagate; private boolean useMessageAlarmData; + private List relationTypes; + @Override public TbCreateAlarmNodeConfiguration defaultConfiguration() { TbCreateAlarmNodeConfiguration configuration = new TbCreateAlarmNodeConfiguration(); @@ -38,6 +43,7 @@ public class TbCreateAlarmNodeConfiguration extends TbAbstractAlarmNodeConfigura configuration.setSeverity(AlarmSeverity.CRITICAL); configuration.setPropagate(false); configuration.setUseMessageAlarmData(false); + configuration.setRelationTypes(Collections.emptyList()); return configuration; } } diff --git a/ui-ngx/src/app/core/api/alias-controller.ts b/ui-ngx/src/app/core/api/alias-controller.ts index 2f174c80e6..c7fe014d86 100644 --- a/ui-ngx/src/app/core/api/alias-controller.ts +++ b/ui-ngx/src/app/core/api/alias-controller.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { AliasInfo, IAliasController, IStateController, StateEntityInfo, StateControllerHolder } from '@core/api/widget-api.models'; +import { AliasInfo, IAliasController, StateControllerHolder, StateEntityInfo } from '@core/api/widget-api.models'; import { forkJoin, Observable, of, ReplaySubject, Subject } from 'rxjs'; import { DataKey, Datasource, DatasourceType } from '@app/shared/models/widget.models'; import { deepClone } from '@core/utils'; @@ -23,54 +23,7 @@ import { UtilsService } from '@core/services/utils.service'; import { EntityAliases } from '@shared/models/alias.models'; import { EntityInfo } from '@shared/models/entity.models'; import * as equal from 'deep-equal'; -import { map, tap } from 'rxjs/operators'; - -export class DummyAliasController implements IAliasController { - - entityAliasesChanged: Observable>; - entityAliasResolved: Observable; - - [key: string]: any | null; - - constructor() { - this.entityAliasesChanged = new Subject>().asObservable(); - this.entityAliasResolved = new Subject().asObservable(); - } - - getAliasInfo(aliasId): Observable { - return of(null); - } - - resolveAlarmSource(alarmSource: Datasource): Observable { - return of(deepClone(alarmSource)); - } - - resolveDatasources(datasources: Array): Observable> { - return of(deepClone(datasources)); - } - - getEntityAliases(): EntityAliases { - return undefined; - } - - getInstantAliasInfo(aliasId: string): AliasInfo { - return undefined; - } - - updateCurrentAliasEntity(aliasId: string, currentEntity: EntityInfo) { - } - - updateEntityAliases(entityAliases: EntityAliases) { - } - - dashboardStateChanged() { - } - - getEntityAliasId(aliasName: string): string { - return null; - } - -} +import { map } from 'rxjs/operators'; export class AliasController implements IAliasController { diff --git a/ui-ngx/src/app/core/api/widget-api.models.ts b/ui-ngx/src/app/core/api/widget-api.models.ts index 6d5a05e4cc..de0d82f592 100644 --- a/ui-ngx/src/app/core/api/widget-api.models.ts +++ b/ui-ngx/src/app/core/api/widget-api.models.ts @@ -259,5 +259,4 @@ export interface IWidgetSubscription { destroy(): void; [key: string]: any; - // TODO: } diff --git a/ui-ngx/src/app/core/api/widget-subscription.ts b/ui-ngx/src/app/core/api/widget-subscription.ts index bb9ecc560c..693ecc22c0 100644 --- a/ui-ngx/src/app/core/api/widget-subscription.ts +++ b/ui-ngx/src/app/core/api/widget-subscription.ts @@ -41,7 +41,7 @@ import { toHistoryTimewindow, WidgetTimewindow } from '@app/shared/models/time/time.models'; -import { Observable, of, ReplaySubject, Subject } from 'rxjs'; +import { Observable, ReplaySubject, Subject, throwError } from 'rxjs'; import { CancelAnimationFrame } from '@core/services/raf.service'; import { EntityType } from '@shared/models/entity-type.models'; import { AlarmInfo, AlarmSearchStatus } from '@shared/models/alarm.models'; @@ -78,7 +78,20 @@ export class WidgetSubscription implements IWidgetSubscription { alarms: Array; alarmSource: Datasource; - alarmSearchStatus: AlarmSearchStatus; + + private alarmSearchStatusValue: AlarmSearchStatus; + + set alarmSearchStatus(value: AlarmSearchStatus) { + if (this.alarmSearchStatusValue !== value) { + this.alarmSearchStatusValue = value; + this.onAlarmSearchStatusChanged(); + } + } + + get alarmSearchStatus(): AlarmSearchStatus { + return this.alarmSearchStatusValue; + } + alarmsPollingInterval: number; alarmSourceListener: AlarmSourceListener; @@ -136,7 +149,7 @@ export class WidgetSubscription implements IWidgetSubscription { this.callbacks.dataLoading = this.callbacks.dataLoading || (() => {}); this.callbacks.timeWindowUpdated = this.callbacks.timeWindowUpdated || (() => {}); this.alarmSource = options.alarmSource; - this.alarmSearchStatus = isDefined(options.alarmSearchStatus) ? + this.alarmSearchStatusValue = isDefined(options.alarmSearchStatus) ? options.alarmSearchStatus : AlarmSearchStatus.ANY; this.alarmsPollingInterval = isDefined(options.alarmsPollingInterval) ? options.alarmsPollingInterval : 5000; @@ -262,8 +275,30 @@ export class WidgetSubscription implements IWidgetSubscription { } private initAlarmSubscription(): Observable { - // TODO: - return of(null); + const initAlarmSubscriptionSubject = new ReplaySubject(1); + this.loadStDiff().subscribe(() => { + if (!this.ctx.aliasController) { + this.configureAlarmsData(); + initAlarmSubscriptionSubject.next(); + initAlarmSubscriptionSubject.complete(); + } else { + this.ctx.aliasController.resolveAlarmSource(this.alarmSource).subscribe( + (alarmSource) => { + this.alarmSource = alarmSource; + this.configureAlarmsData(); + initAlarmSubscriptionSubject.next(); + initAlarmSubscriptionSubject.complete(); + }, + (err) => { + initAlarmSubscriptionSubject.error(err); + } + ); + } + }); + return initAlarmSubscriptionSubject.asObservable(); + } + + private configureAlarmsData() { } private initDataSubscription(): Observable { @@ -417,6 +452,12 @@ export class WidgetSubscription implements IWidgetSubscription { } } + private onAlarmSearchStatusChanged() { + if (this.type === widgetType.alarm) { + this.update(); + } + } + updateDataVisibility(index: number): void { if (this.displayLegend) { const hidden = this.legendData.keys[index].dataKey.hidden; @@ -465,17 +506,125 @@ export class WidgetSubscription implements IWidgetSubscription { } sendOneWayCommand(method: string, params?: any, timeout?: number): Observable { - // TODO: - return undefined; + return this.sendCommand(true, method, params, timeout); } sendTwoWayCommand(method: string, params?: any, timeout?: number): Observable { - // TODO: - return undefined; + return this.sendCommand(false, method, params, timeout); } clearRpcError(): void { - // TODO: + this.rpcRejection = null; + this.rpcErrorText = null; + this.callbacks.onRpcErrorCleared(this); + } + + sendCommand(oneWayElseTwoWay: boolean, method: string, params?: any, timeout?: number): Observable { + if (!this.rpcEnabled) { + return throwError(new Error('Rpc disabled!')); + } else { + if (this.rpcRejection && this.rpcRejection.status !== 408) { + this.rpcRejection = null; + this.rpcErrorText = null; + this.callbacks.onRpcErrorCleared(this); + } + const requestBody: any = { + method, + params + }; + if (timeout && timeout > 0) { + requestBody.timeout = timeout; + } + const rpcSubject: Subject = new ReplaySubject(); + this.executingRpcRequest = true; + this.callbacks.rpcStateChanged(this); + if (this.ctx.utils.widgetEditMode) { + setTimeout(() => { + this.executingRpcRequest = false; + this.callbacks.rpcStateChanged(this); + if (oneWayElseTwoWay) { + rpcSubject.next(); + rpcSubject.complete(); + } else { + rpcSubject.next(requestBody); + rpcSubject.complete(); + } + }, 500); + } else { + this.executingSubjects.push(rpcSubject); + const targetSendFunction = oneWayElseTwoWay ? this.ctx.deviceService.sendOneWayRpcCommand : this.ctx.deviceService.sendTwoWayRpcCommand; + targetSendFunction(this.targetDeviceId, requestBody).subscribe((responseBody) => { + this.rpcRejection = null; + this.rpcErrorText = null; + const index = this.executingSubjects.indexOf(rpcSubject); + if (index >= 0) { + this.executingSubjects.splice( index, 1 ); + } + this.executingRpcRequest = this.executingSubjects.length > 0; + this.callbacks.onRpcSuccess(this); + rpcSubject.next(responseBody); + rpcSubject.complete(); + }, + (rejection: HttpErrorResponse) => { + const index = this.executingSubjects.indexOf(rpcSubject); + if (index >= 0) { + this.executingSubjects.splice( index, 1 ); + } + this.executingRpcRequest = this.executingSubjects.length > 0; + this.callbacks.rpcStateChanged(this); + if (!this.executingRpcRequest || rejection.status === 408) { + this.rpcRejection = rejection; + if (rejection.status === 408) { + this.rpcErrorText = 'Device is offline.'; + } else { + this.rpcErrorText = 'Error : ' + rejection.status + ' - ' + rejection.statusText; + const error = this.extractRejectionErrorText(rejection); + if (error) { + this.rpcErrorText += '
'; + this.rpcErrorText += error; + } + } + this.callbacks.onRpcFailed(this); + } + rpcSubject.error(rejection); + }); + } + return rpcSubject.asObservable(); + } + } + + private extractRejectionErrorText(rejection: HttpErrorResponse) { + let error = null; + if (rejection.error) { + error = rejection.error; + try { + error = rejection.error ? JSON.parse(rejection.error) : null; + } catch (e) {} + } + if (error && !error.message) { + error = this.prepareMessageFromData(error); + } else if (error && error.message) { + error = error.message; + } + return error; + } + + private prepareMessageFromData(data) { + if (typeof data === 'object' && data.constructor === ArrayBuffer) { + const msg = String.fromCharCode.apply(null, new Uint8Array(data)); + try { + const msgObj = JSON.parse(msg); + if (msgObj.message) { + return msgObj.message; + } else { + return msg; + } + } catch (e) { + return msg; + } + } else { + return data; + } } update() { @@ -550,8 +699,34 @@ export class WidgetSubscription implements IWidgetSubscription { } private alarmsSubscribe() { - // TODO: - this.notifyDataLoaded(); + this.notifyDataLoading(); + if (this.timeWindowConfig) { + this.updateRealtimeSubscription(); + if (this.subscriptionTimewindow.fixedWindow) { + this.onDataUpdated(); + } + } + this.alarmSourceListener = { + subscriptionTimewindow: this.subscriptionTimewindow, + alarmSource: this.alarmSource, + alarmSearchStatus: this.alarmSearchStatus, + alarmsPollingInterval: this.alarmsPollingInterval, + alarmsUpdated: alarms => this.alarmsUpdated(alarms) + }; + this.alarms = null; + + this.ctx.alarmService.subscribeForAlarms(this.alarmSourceListener); + + let forceUpdate = false; + if (this.alarmSource.unresolvedStateEntity || + (this.alarmSource.type === DatasourceType.entity && !this.alarmSource.entityId) + ) { + forceUpdate = true; + } + if (forceUpdate) { + this.notifyDataLoaded(); + this.onDataUpdated(); + } } @@ -570,7 +745,10 @@ export class WidgetSubscription implements IWidgetSubscription { } private alarmsUnsubscribe() { - // TODO: + if (this.alarmSourceListener) { + this.ctx.alarmService.unsubscribeFromAlarms(this.alarmSourceListener); + this.alarmSourceListener = null; + } } private checkRpcTarget(aliasIds: Array): boolean { @@ -684,6 +862,18 @@ export class WidgetSubscription implements IWidgetSubscription { } } + private alarmsUpdated(alarms: Array) { + this.notifyDataLoaded(); + const updated = !this.alarms || !deepEqual(this.alarms, alarms); + this.alarms = alarms; + if (this.subscriptionTimewindow && this.subscriptionTimewindow.realtimeWindowMs) { + this.updateTimewindow(); + } + if (updated) { + this.onDataUpdated(); + } + } + private updateLegend(dataIndex: number, data: DataSet, detectChanges: boolean) { const dataKey = this.legendData.keys[dataIndex].dataKey; const decimals = isDefined(dataKey.decimals) ? dataKey.decimals : this.decimals; diff --git a/ui-ngx/src/app/core/http/alarm.service.ts b/ui-ngx/src/app/core/http/alarm.service.ts index a1fcdb9810..61337ab385 100644 --- a/ui-ngx/src/app/core/http/alarm.service.ts +++ b/ui-ngx/src/app/core/http/alarm.service.ts @@ -15,8 +15,8 @@ /// import { Injectable } from '@angular/core'; -import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; -import { Observable } from 'rxjs/index'; +import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; +import { Observable } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { PageData } from '@shared/models/page/page-data'; import { EntityId } from '@shared/models/id/entity-id'; @@ -26,24 +26,42 @@ import { AlarmQuery, AlarmSearchStatus, AlarmSeverity, - AlarmStatus + AlarmStatus, + simulatedAlarm } from '@shared/models/alarm.models'; import { EntityType } from '@shared/models/entity-type.models'; -import { Datasource } from '@shared/models/widget.models'; +import { Datasource, DatasourceType } from '@shared/models/widget.models'; +import { SubscriptionTimewindow } from '@shared/models/time/time.models'; +import { UtilsService } from '@core/services/utils.service'; +import { TimePageLink } from '@shared/models/page/page-link'; +import { Direction, SortOrder } from '@shared/models/page/sort-order'; +import { concatMap, expand, map, toArray } from 'rxjs/operators'; +import { EMPTY } from 'rxjs'; +import Timeout = NodeJS.Timeout; + +interface AlarmSourceListenerQuery { + entityType: EntityType; + entityId: string; + alarmSearchStatus: AlarmSearchStatus; + alarmStatus: AlarmStatus; + fetchOriginator?: boolean; + limit?: number; + interval?: number; + startTime?: number; + endTime?: number; + onAlarms?: (alarms: Array) => void; +} export interface AlarmSourceListener { id?: string; + subscriptionTimewindow: SubscriptionTimewindow; alarmSource: Datasource; alarmsPollingInterval: number; alarmSearchStatus: AlarmSearchStatus; - alarmsQuery: { - entityType: EntityType; - entityId: string; - alarmSearchStatus: AlarmSearchStatus; - alarmStatus: AlarmStatus; - fetchOriginator?: boolean; - onAlarms?: (alarms: Array) => void; - }; + alarmsUpdated: (alarms: Array) => void; + lastUpdateTs?: number; + alarmsQuery?: AlarmSourceListenerQuery; + pollTimer?: Timeout; } @Injectable({ @@ -51,8 +69,11 @@ export interface AlarmSourceListener { }) export class AlarmService { + private alarmSourceListeners: {[id: string]: AlarmSourceListener} = {}; + constructor( - private http: HttpClient + private http: HttpClient, + private utils: UtilsService ) { } public getAlarm(alarmId: string, config?: RequestConfig): Observable { @@ -92,4 +113,110 @@ export class AlarmService { return this.http.get(url, defaultHttpOptionsFromConfig(config)); } + + public subscribeForAlarms(alarmSourceListener: AlarmSourceListener): void { + alarmSourceListener.id = this.utils.guid(); + this.alarmSourceListeners[alarmSourceListener.id] = alarmSourceListener; + const alarmSource = alarmSourceListener.alarmSource; + if (alarmSource.type === DatasourceType.function) { + setTimeout(() => { + alarmSourceListener.alarmsUpdated([simulatedAlarm]); + }, 0); + } else if (alarmSource.entityType && alarmSource.entityId) { + const pollingInterval = alarmSourceListener.alarmsPollingInterval; + alarmSourceListener.alarmsQuery = { + entityType: alarmSource.entityType, + entityId: alarmSource.entityId, + alarmSearchStatus: alarmSourceListener.alarmSearchStatus, + alarmStatus: null + }; + const originatorKeys = alarmSource.dataKeys.filter(dataKey => dataKey.name.toLocaleLowerCase().includes('originator')); + if (originatorKeys.length) { + alarmSourceListener.alarmsQuery.fetchOriginator = true; + } + const subscriptionTimewindow = alarmSourceListener.subscriptionTimewindow; + if (subscriptionTimewindow.realtimeWindowMs) { + alarmSourceListener.alarmsQuery.startTime = subscriptionTimewindow.startTs; + } else { + alarmSourceListener.alarmsQuery.startTime = subscriptionTimewindow.fixedWindow.startTimeMs; + alarmSourceListener.alarmsQuery.endTime = subscriptionTimewindow.fixedWindow.endTimeMs; + } + alarmSourceListener.alarmsQuery.onAlarms = (alarms) => { + if (subscriptionTimewindow.realtimeWindowMs) { + const now = Date.now(); + if (alarmSourceListener.lastUpdateTs) { + const interval = now - alarmSourceListener.lastUpdateTs; + alarmSourceListener.alarmsQuery.startTime += interval; + } + alarmSourceListener.lastUpdateTs = now; + } + alarmSourceListener.alarmsUpdated(alarms); + }; + this.onPollAlarms(alarmSourceListener.alarmsQuery); + alarmSourceListener.pollTimer = setInterval(this.onPollAlarms.bind(this), pollingInterval, alarmSourceListener.alarmsQuery); + } + } + + public unsubscribeFromAlarms(alarmSourceListener: AlarmSourceListener): void { + if (alarmSourceListener && alarmSourceListener.id) { + if (alarmSourceListener.pollTimer) { + clearInterval(alarmSourceListener.pollTimer); + alarmSourceListener.pollTimer = null; + } + delete this.alarmSourceListeners[alarmSourceListener.id]; + } + } + + private onPollAlarms(alarmsQuery: AlarmSourceListenerQuery): void { + this.getAlarmsByAlarmSourceQuery(alarmsQuery).subscribe((alarms) => { + alarmsQuery.onAlarms(alarms); + }); + } + + private getAlarmsByAlarmSourceQuery(alarmsQuery: AlarmSourceListenerQuery): Observable> { + const time = Date.now(); + let pageLink: TimePageLink; + const sortOrder: SortOrder = {property: 'createdTime', direction: Direction.DESC}; + if (alarmsQuery.limit) { + pageLink = new TimePageLink(alarmsQuery.limit, 0, + null, + sortOrder); + } else if (alarmsQuery.interval) { + pageLink = new TimePageLink(100, 0, + null, + sortOrder, time - alarmsQuery.interval); + } else if (alarmsQuery.startTime) { + pageLink = new TimePageLink(100, 0, + null, + sortOrder, Math.round(alarmsQuery.startTime)); + if (alarmsQuery.endTime) { + pageLink.endTime = Math.round(alarmsQuery.endTime); + } + } + return this.fetchAlarms(alarmsQuery, pageLink); + } + + private fetchAlarms(query: AlarmSourceListenerQuery, + pageLink: TimePageLink): Observable> { + const alarmQuery = new AlarmQuery( + {id: query.entityId, entityType: query.entityType}, + pageLink, + query.alarmSearchStatus, + query.alarmStatus, + query.fetchOriginator); + return this.getAlarms(alarmQuery, {ignoreLoading: true}).pipe( + expand((data) => { + if (data.hasNext && !query.limit) { + alarmQuery.pageLink.page += 1; + return this.getAlarms(alarmQuery, {ignoreLoading: true}); + } else { + return EMPTY; + } + }), + map((data) => data.data), + concatMap((data) => data), + toArray(), + map((data) => data.sort((a, b) => alarmQuery.pageLink.sort(a, b))), + ); + } } diff --git a/ui-ngx/src/app/core/http/device.service.ts b/ui-ngx/src/app/core/http/device.service.ts index d09fcd72c1..216dafd4ec 100644 --- a/ui-ngx/src/app/core/http/device.service.ts +++ b/ui-ngx/src/app/core/http/device.service.ts @@ -113,6 +113,14 @@ export class DeviceService { return this.http.delete(`/api/customer/device/${deviceId}`, defaultHttpOptionsFromConfig(config)); } + public sendOneWayRpcCommand(deviceId: string, requestBody: any, config?: RequestConfig): Observable { + return this.http.post(`/api/plugins/rpc/oneway/${deviceId}`, requestBody, defaultHttpOptionsFromConfig(config)); + } + + public sendTwoWayRpcCommand(deviceId: string, requestBody: any, config?: RequestConfig): Observable { + return this.http.post(`/api/plugins/rpc/twoway/${deviceId}`, requestBody, defaultHttpOptionsFromConfig(config)); + } + public findByQuery(query: DeviceSearchQuery, config?: RequestConfig): Observable> { return this.http.post>('/api/devices', query, defaultHttpOptionsFromConfig(config)); diff --git a/ui-ngx/src/app/core/http/rule-chain.service.ts b/ui-ngx/src/app/core/http/rule-chain.service.ts index ce7b23c47c..1c32171bbc 100644 --- a/ui-ngx/src/app/core/http/rule-chain.service.ts +++ b/ui-ngx/src/app/core/http/rule-chain.service.ts @@ -82,14 +82,7 @@ export class RuleChainService { public getResolvedRuleChainMetadata(ruleChainId: string, config?: RequestConfig): Observable { return this.getRuleChainMetadata(ruleChainId, config).pipe( - mergeMap((ruleChainMetaData) => { - return this.resolveTargetRuleChains(ruleChainMetaData.ruleChainConnections).pipe( - map((targetRuleChainsMap) => { - const resolvedRuleChainMetadata: ResolvedRuleChainMetaData = {...ruleChainMetaData, targetRuleChainsMap}; - return resolvedRuleChainMetadata; - }) - ); - }) + mergeMap((ruleChainMetaData) => this.resolveRuleChainMetadata(ruleChainMetaData)) ); } @@ -100,13 +93,15 @@ export class RuleChainService { public saveAndGetResolvedRuleChainMetadata(ruleChainMetaData: RuleChainMetaData, config?: RequestConfig): Observable { return this.saveRuleChainMetadata(ruleChainMetaData, config).pipe( - mergeMap((savedRuleChainMetaData) => { - return this.resolveTargetRuleChains(savedRuleChainMetaData.ruleChainConnections).pipe( - map((targetRuleChainsMap) => { - const resolvedRuleChainMetadata: ResolvedRuleChainMetaData = {...savedRuleChainMetaData, targetRuleChainsMap}; - return resolvedRuleChainMetadata; - }) - ); + mergeMap((savedRuleChainMetaData) => this.resolveRuleChainMetadata(savedRuleChainMetaData)) + ); + } + + public resolveRuleChainMetadata(ruleChainMetaData: RuleChainMetaData): Observable { + return this.resolveTargetRuleChains(ruleChainMetaData.ruleChainConnections).pipe( + map((targetRuleChainsMap) => { + const resolvedRuleChainMetadata: ResolvedRuleChainMetaData = {...ruleChainMetaData, targetRuleChainsMap}; + return resolvedRuleChainMetadata; }) ); } diff --git a/ui-ngx/src/app/core/services/item-buffer.service.ts b/ui-ngx/src/app/core/services/item-buffer.service.ts index 82232fda1f..48b3e851b5 100644 --- a/ui-ngx/src/app/core/services/item-buffer.service.ts +++ b/ui-ngx/src/app/core/services/item-buffer.service.ts @@ -26,10 +26,14 @@ import { Observable, of, throwError } from 'rxjs'; import { map } from 'rxjs/operators'; import { FcRuleEdge, FcRuleNode, ruleNodeTypeDescriptors } from '@shared/models/rule-node.models'; import { RuleChainService } from '@core/http/rule-chain.service'; +import { RuleChainImport } from '@shared/models/rule-chain.models'; +import { Simulate } from 'react-dom/test-utils'; +import error = Simulate.error; const WIDGET_ITEM = 'widget_item'; const WIDGET_REFERENCE = 'widget_reference'; const RULE_NODES = 'rule_nodes'; +const RULE_CHAIN_IMPORT = 'rule_chain_import'; export interface WidgetItem { widget: Widget; @@ -314,6 +318,20 @@ export class ItemBufferService { return null; } + public hasRuleChainImport(): boolean { + return this.storeHas(RULE_CHAIN_IMPORT); + } + + public storeRuleChainImport(ruleChainImport: RuleChainImport): void { + this.storeSet(RULE_CHAIN_IMPORT, ruleChainImport); + } + + public getRuleChainImport(): RuleChainImport { + const ruleChainImport: RuleChainImport = this.storeGet(RULE_CHAIN_IMPORT); + this.storeRemove(RULE_CHAIN_IMPORT); + return ruleChainImport; + } + private getOriginalColumns(dashboard: Dashboard, sourceState: string, sourceLayout: DashboardLayoutId): number { let originalColumns = 24; let gridSettings = null; diff --git a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts index d2fcbe6297..48dd608d5c 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts @@ -19,7 +19,8 @@ import { ChangeDetectionStrategy, Component, ElementRef, - Input, NgZone, + Input, + NgZone, OnInit, ViewChild, ViewContainerRef @@ -65,7 +66,7 @@ import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service' import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; import { DataKey, Datasource, DatasourceType, Widget, widgetType } from '@shared/models/widget.models'; import { IAliasController, IStateController, StateParams } from '@core/api/widget-api.models'; -import { AliasController, DummyAliasController } from '@core/api/alias-controller'; +import { AliasController } from '@core/api/alias-controller'; import { EntityAlias, EntityAliases } from '@shared/models/alias.models'; import { UtilsService } from '@core/services/utils.service'; import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; @@ -73,8 +74,6 @@ import { NULL_UUID } from '@shared/models/id/has-uuid'; import { WidgetService } from '@core/http/widget.service'; import { toWidgetInfo } from '../../models/widget-component.models'; import { EntityService } from '@core/http/entity.service'; -import { SelectTargetLayoutDialogComponent } from '@home/pages/dashboard/layout/select-target-layout-dialog.component'; -import { DashboardLayoutId } from '@shared/models/dashboard.models'; import { AddWidgetToDashboardDialogComponent, AddWidgetToDashboardDialogData diff --git a/ui-ngx/src/app/modules/home/components/audit-log/audit-log-details-dialog.component.ts b/ui-ngx/src/app/modules/home/components/audit-log/audit-log-details-dialog.component.ts index bdd869ca94..7ca9bec6cd 100644 --- a/ui-ngx/src/app/modules/home/components/audit-log/audit-log-details-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/audit-log/audit-log-details-dialog.component.ts @@ -14,19 +14,10 @@ /// limitations under the License. /// -import { - Component, - ElementRef, - Inject, - OnInit, - Renderer2, - ViewChild -} from '@angular/core'; +import { Component, ElementRef, Inject, OnInit, Renderer2, ViewChild } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; -import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; import { ActionStatus, AuditLog } from '@shared/models/audit-log.models'; import * as ace from 'ace-builds'; @@ -57,8 +48,6 @@ export class AuditLogDetailsDialogComponent extends DialogComponent, protected router: Router, @Inject(MAT_DIALOG_DATA) public data: AuditLogDetailsDialogData, diff --git a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts index a69331e670..9e38e75ce6 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts @@ -275,6 +275,7 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo private updateWidgets() { this.dashboardWidgets.setWidgets(this.widgets, this.widgetLayouts); + this.dashboardWidgets.doCheck(); this.dashboardLoading = false; } 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 075f34411d..23eec24073 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 @@ -152,18 +152,26 @@ - - {{ column.title | translate }} + + {{ column.title | translate }} - 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 d3eb695f44..23cb82d889 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 @@ -43,7 +43,7 @@ import { EntityTableConfig, GroupActionDescriptor, HeaderActionDescriptor, - EntityColumn + EntityColumn, EntityActionTableColumn } from '@home/models/entity/entities-table-config.models'; import { EntityTypeTranslation } from '@shared/models/entity-type.models'; import { DialogService } from '@core/services/dialog.service'; @@ -55,6 +55,8 @@ import { import { Timewindow, historyInterval } from '@shared/models/time/time.models'; import {DomSanitizer, SafeHtml} from '@angular/platform-browser'; import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; +import { instanceOf } from 'prop-types'; +import { isDefined, isDefinedAndNotNull, isUndefined } from '@core/utils'; @Component({ selector: 'tb-entities-table', @@ -73,12 +75,15 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn groupActionDescriptors: Array>>; cellActionDescriptors: Array>>; - columns: Array>>; + actionColumns: Array>>; + entityColumns: Array>>; + displayedColumns: string[]; headerCellStyleCache: Array = []; cellContentCache: Array = []; + cellTooltipCache: Array = []; cellStyleCache: Array = []; @@ -230,6 +235,7 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn private dataLoaded() { this.headerCellStyleCache.length = 0; this.cellContentCache.length = 0; + this.cellTooltipCache.length = 0; this.cellStyleCache.length = 0; } @@ -360,14 +366,19 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn } columnsUpdated(resetData: boolean = false) { - this.columns = [...this.entitiesTableConfig.columns]; + this.entityColumns = this.entitiesTableConfig.columns.filter( + (column) => column instanceof EntityTableColumn) + .map(column => column as EntityTableColumn>); + this.actionColumns = this.entitiesTableConfig.columns.filter( + (column) => column instanceof EntityActionTableColumn) + .map(column => column as EntityActionTableColumn>); this.displayedColumns = []; if (this.selectionEnabled) { this.displayedColumns.push('select'); } - this.columns.forEach( + this.entitiesTableConfig.columns.forEach( (column) => { this.displayedColumns.push(column.key); } @@ -375,14 +386,15 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn this.displayedColumns.push('actions'); this.headerCellStyleCache.length = 0; this.cellContentCache.length = 0; + this.cellTooltipCache.length = 0; this.cellStyleCache.length = 0; if (resetData) { this.dataSource.reset(); } } - headerCellStyle(column: EntityColumn>, col: number) { - const index = col; + headerCellStyle(column: EntityColumn>) { + const index = this.entitiesTableConfig.columns.indexOf(column); let res = this.headerCellStyleCache[index]; if (!res) { if (column instanceof EntityTableColumn) { @@ -395,9 +407,10 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn return res; } - cellContent(entity: BaseData, column: EntityColumn>, row: number, col: number) { + cellContent(entity: BaseData, column: EntityColumn>, row: number) { if (column instanceof EntityTableColumn) { - const index = row * this.columns.length + col; + const col = this.entitiesTableConfig.columns.indexOf(column); + const index = row * this.entitiesTableConfig.columns.length + col; let res = this.cellContentCache[index]; if (!res) { res = this.domSanitizer.bypassSecurityTrustHtml(column.cellContentFunction(entity, column.key)); @@ -409,8 +422,26 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn } } - cellStyle(entity: BaseData, column: EntityColumn>, row: number, col: number) { - const index = row * this.columns.length + col; + cellTooltip(entity: BaseData, column: EntityColumn>, row: number) { + if (column instanceof EntityTableColumn) { + const col = this.entitiesTableConfig.columns.indexOf(column); + const index = row * this.entitiesTableConfig.columns.length + col; + let res = this.cellTooltipCache[index]; + if (isUndefined(res)) { + res = column.cellTooltipFunction(entity, column.key); + res = isDefined(res) ? res : null; + this.cellTooltipCache[index] = res; + } else { + return res !== null ? res : undefined; + } + } else { + return undefined; + } + } + + cellStyle(entity: BaseData, column: EntityColumn>, row: number) { + const col = this.entitiesTableConfig.columns.indexOf(column); + const index = row * this.entitiesTableConfig.columns.length + col; let res = this.cellStyleCache[index]; if (!res) { if (column instanceof EntityTableColumn) { diff --git a/ui-ngx/src/app/modules/home/components/event/event-content-dialog.component.html b/ui-ngx/src/app/modules/home/components/event/event-content-dialog.component.html new file mode 100644 index 0000000000..40c43923ef --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/event/event-content-dialog.component.html @@ -0,0 +1,40 @@ + + +

{{ title }}

+ + +
+
+
+
+
+
+ + +
diff --git a/ui-ngx/src/app/modules/home/components/event/event-content-dialog.component.scss b/ui-ngx/src/app/modules/home/components/event/event-content-dialog.component.scss new file mode 100644 index 0000000000..6960dd7e53 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/event/event-content-dialog.component.scss @@ -0,0 +1,23 @@ +/** + * Copyright © 2016-2019 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-event-content { + width: 100%; + min-width: 400px; + height: 100%; + min-height: 50px; + } +} diff --git a/ui-ngx/src/app/modules/home/components/event/event-content-dialog.component.ts b/ui-ngx/src/app/modules/home/components/event/event-content-dialog.component.ts new file mode 100644 index 0000000000..4a9c58826b --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/event/event-content-dialog.component.ts @@ -0,0 +1,116 @@ +/// +/// Copyright © 2016-2019 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, Inject, OnInit, Renderer2, ViewChild } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; + +import * as ace from 'ace-builds'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { Router } from '@angular/router'; +import { ContentType, contentTypesMap } from '@shared/models/constants'; + +export interface EventContentDialogData { + content: string; + title: string; + contentType: ContentType; +} + +@Component({ + selector: 'tb-event-content-dialog', + templateUrl: './event-content-dialog.component.html', + styleUrls: ['./event-content-dialog.component.scss'] +}) +export class EventContentDialogComponent extends DialogComponent implements OnInit { + + @ViewChild('eventContentEditor', {static: true}) + eventContentEditorElmRef: ElementRef; + private eventContentEditor: ace.Ace.Editor; + + content: string; + title: string; + contentType: ContentType; + + constructor(protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) public data: EventContentDialogData, + public dialogRef: MatDialogRef, + private renderer: Renderer2) { + super(store, router, dialogRef); + } + + ngOnInit(): void { + this.content = this.data.content; + this.title = this.data.title; + this.contentType = this.data.contentType; + + this.eventContentEditor = this.createEditor(this.eventContentEditorElmRef, this.content); + } + + createEditor(editorElementRef: ElementRef, content: string): ace.Ace.Editor { + const editorElement = editorElementRef.nativeElement; + let mode = 'java'; + if (this.contentType) { + mode = contentTypesMap.get(this.contentType).code; + if (this.contentType === ContentType.JSON && content) { + content = js_beautify(content, {indent_size: 4}); + } + } + let editorOptions: Partial = { + mode: `ace/mode/${mode}`, + theme: 'ace/theme/github', + showGutter: false, + showPrintMargin: false, + readOnly: true + }; + + const advancedOptions = { + enableSnippets: false, + enableBasicAutocompletion: false, + enableLiveAutocompletion: false + }; + + editorOptions = {...editorOptions, ...advancedOptions}; + const editor = ace.edit(editorElement, editorOptions); + editor.session.setUseWrapMode(false); + editor.setValue(content, -1); + this.updateEditorSize(editorElement, content, editor); + return editor; + } + + updateEditorSize(editorElement: any, content: string, editor: ace.Ace.Editor) { + let newHeight = 400; + let newWidth = 600; + if (content && content.length > 0) { + const lines = content.split('\n'); + newHeight = 16 * lines.length + 16; + let maxLineLength = 0; + lines.forEach((row) => { + const line = row.replace(/\t/g, ' ').replace(/\n/g, ''); + const lineLength = line.length; + maxLineLength = Math.max(maxLineLength, lineLength); + }); + newWidth = 8 * maxLineLength + 16; + } + // newHeight = Math.min(400, newHeight); + this.renderer.setStyle(editorElement, 'minHeight', newHeight.toString() + 'px'); + this.renderer.setStyle(editorElement, 'height', newHeight.toString() + 'px'); + this.renderer.setStyle(editorElement, 'width', newWidth.toString() + 'px'); + editor.resize(); + } + +} 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 9e2973a264..687e74ecb7 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 @@ -32,8 +32,12 @@ import { EntityTypeResource } from '@shared/models/entity-type.models'; import { Observable } from 'rxjs'; import { PageData } from '@shared/models/page/page-data'; import { Direction } from '@shared/models/page/sort-order'; -import { MsgDataType } from '@shared/models/rule-node.models'; import { DialogService } from '@core/services/dialog.service'; +import { ContentType } from '@shared/models/constants'; +import { + EventContentDialogComponent, + EventContentDialogData +} from '@home/components/event/event-content-dialog.component'; export class EventTableConfig extends EntityTableConfig { @@ -157,34 +161,62 @@ export class EventTableConfig extends EntityTableConfig { case DebugEventType.DEBUG_RULE_NODE: case DebugEventType.DEBUG_RULE_CHAIN: this.columns.push( - new EntityTableColumn('type', 'event.type', '100%', - (entity) => entity.body.type, entity => ({}), false), - new EntityTableColumn('entity', 'event.entity', '100%', - (entity) => entity.body.entityName, entity => ({}), false), + new EntityTableColumn('type', 'event.type', '40px', + (entity) => entity.body.type, entity => ({ + padding: '0 12px 0 0', + }), false, key => ({ + padding: '0 12px 0 0' + })), + new EntityTableColumn('entity', 'event.entity', '100px', + (entity) => entity.body.entityName, entity => ({ + padding: '0 12px 0 0', + }), false, key => ({ + padding: '0 12px 0 0' + })), new EntityTableColumn('msgId', 'event.message-id', '100%', - (entity) => entity.body.msgId, entity => ({}), false), + (entity) => entity.body.msgId, entity => ({ + whiteSpace: 'nowrap', + padding: '0 12px 0 0', + textOverflow: 'ellipsis', + display: 'inline-block', + lineHeight: '48px', + }), false, key => ({ + padding: '0 12px 0 0' + }), + entity => entity.body.msgId), new EntityTableColumn('msgType', 'event.message-type', '100%', - (entity) => entity.body.msgType, entity => ({}), false), - new EntityTableColumn('relationType', 'event.relation-type', '100%', - (entity) => entity.body.relationType, entity => ({}), false), + (entity) => entity.body.msgType, entity => ({ + whiteSpace: 'nowrap', + padding: '0 12px 0 0', + textOverflow: 'ellipsis', + display: 'inline-block', + lineHeight: '48px', + }), false, key => ({ + padding: '0 12px 0 0' + }), + entity => entity.body.msgType), + new EntityTableColumn('relationType', 'event.relation-type', '100px', + (entity) => entity.body.relationType, entity => ({padding: '0 12px 0 0',}),false, key => ({ + padding: '0 12px 0 0' + })), new EntityActionTableColumn('data', 'event.data', { name: this.translate.instant('action.view'), icon: 'more_horiz', - isEnabled: (entity) => entity.body.data && entity.body.data.length > 0, + isEnabled: (entity) => entity.body.data ? entity.body.data.length > 0 : false, onAction: ($event, entity) => this.showContent($event, entity.body.data, 'event.data', entity.body.dataType) }, - '60px'), + '40px'), new EntityActionTableColumn('metadata', 'event.metadata', { name: this.translate.instant('action.view'), icon: 'more_horiz', - isEnabled: (entity) => entity.body.metadata && entity.body.metadata.length > 0, + isEnabled: (entity) => entity.body.metadata ? entity.body.metadata.length > 0 : false, onAction: ($event, entity) => this.showContent($event, entity.body.metadata, - 'event.metadata', MsgDataType.JSON) + 'event.metadata', ContentType.JSON) }, - '60px'), + '40px'), new EntityActionTableColumn('error', 'event.error', { name: this.translate.instant('action.view'), @@ -193,7 +225,7 @@ export class EventTableConfig extends EntityTableConfig { onAction: ($event, entity) => this.showContent($event, entity.body.error, 'event.error') }, - '60px') + '40px') ); break; } @@ -202,11 +234,18 @@ export class EventTableConfig extends EntityTableConfig { } } - showContent($event: MouseEvent, content: string, title: string, contentType: MsgDataType = null): void { + showContent($event: MouseEvent, content: string, title: string, contentType: ContentType = null): void { if ($event) { $event.stopPropagation(); } - // TODO: - this.dialogService.todo(); + this.dialog.open(EventContentDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + content, + title, + contentType + } + }); } } diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index 65c32d68bf..5fce77ab5e 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -63,11 +63,13 @@ import { ImportDialogComponent } from './import-export/import-dialog.component'; import { AddWidgetToDashboardDialogComponent } from './attribute/add-widget-to-dashboard-dialog.component'; import { ImportDialogCsvComponent } from './import-export/import-dialog-csv.component'; import { TableColumnsAssignmentComponent } from './import-export/table-columns-assignment.component'; +import { EventContentDialogComponent } from '@home/components/event/event-content-dialog.component'; @NgModule({ entryComponents: [ AddEntityDialogComponent, AuditLogDetailsDialogComponent, + EventContentDialogComponent, EventTableHeaderComponent, RelationDialogComponent, AlarmTableHeaderComponent, @@ -94,6 +96,7 @@ import { TableColumnsAssignmentComponent } from './import-export/table-columns-a ContactComponent, AuditLogTableComponent, AuditLogDetailsDialogComponent, + EventContentDialogComponent, EventTableHeaderComponent, EventTableComponent, RelationTableComponent, diff --git a/ui-ngx/src/app/modules/home/components/import-export/import-export.models.ts b/ui-ngx/src/app/modules/home/components/import-export/import-export.models.ts index 1d41c4da02..d0ebe90003 100644 --- a/ui-ngx/src/app/modules/home/components/import-export/import-export.models.ts +++ b/ui-ngx/src/app/modules/home/components/import-export/import-export.models.ts @@ -19,6 +19,7 @@ import { DashboardLayoutId } from '@shared/models/dashboard.models'; import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; import { ActionNotificationShow } from '@core/notification/notification.actions'; import { ActionType } from '@shared/models/audit-log.models'; +import { RuleChain, RuleChainMetaData } from '@shared/models/rule-chain.models'; export interface ImportWidgetResult { widget: Widget; diff --git a/ui-ngx/src/app/modules/home/components/import-export/import-export.service.ts b/ui-ngx/src/app/modules/home/components/import-export/import-export.service.ts index b396caf6ee..af388baeb5 100644 --- a/ui-ngx/src/app/modules/home/components/import-export/import-export.service.ts +++ b/ui-ngx/src/app/modules/home/components/import-export/import-export.service.ts @@ -44,15 +44,16 @@ import { EntityAliasesDialogData } from '@home/components/alias/entity-aliases-dialog.component'; import { ItemBufferService, WidgetItem } from '@core/services/item-buffer.service'; -import { CsvToJsonConfig, ImportWidgetResult, WidgetsBundleItem, CsvToJsonResult } from './import-export.models'; +import { ImportWidgetResult, WidgetsBundleItem } from './import-export.models'; import { EntityType } from '@shared/models/entity-type.models'; import { UtilsService } from '@core/services/utils.service'; import { WidgetService } from '@core/http/widget.service'; import { NULL_UUID } from '@shared/models/id/has-uuid'; import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; -import { ImportDialogCsvComponent, ImportDialogCsvData } from './import-dialog-csv.component'; -import { ImportEntityData, ImportEntitiesResultInfo } from '@shared/models/entity.models'; +import { ImportEntitiesResultInfo, ImportEntityData } from '@shared/models/entity.models'; import { RequestConfig } from '@core/http/http-utils'; +import { RuleChain, RuleChainImport, RuleChainMetaData } from '@shared/models/rule-chain.models'; +import { RuleChainService } from '@core/http/rule-chain.service'; // @dynamic @Injectable() @@ -66,6 +67,7 @@ export class ImportExportService { private dashboardUtils: DashboardUtilsService, private widgetService: WidgetService, private entityService: EntityService, + private ruleChainService: RuleChainService, private utils: UtilsService, private itembuffer: ItemBufferService, private dialog: MatDialog) { @@ -365,6 +367,81 @@ export class ImportExportService { ); } + public exportRuleChain(ruleChainId: string) { + this.ruleChainService.getRuleChain(ruleChainId).pipe( + mergeMap(ruleChain => { + return this.ruleChainService.getRuleChainMetadata(ruleChainId).pipe( + map((ruleChainMetaData) => { + const ruleChainExport: RuleChainImport = { + ruleChain: this.prepareRuleChain(ruleChain), + metadata: this.prepareRuleChainMetaData(ruleChainMetaData) + }; + return ruleChainExport; + }) + ); + }) + ).subscribe((ruleChainExport) => { + let name = ruleChainExport.ruleChain.name; + name = name.toLowerCase().replace(/\W/g, '_'); + this.exportToPc(ruleChainExport, name + '.json'); + }, + (e) => { + this.handleExportError(e, 'rulechain.export-failed-error'); + } + ); + } + + public importRuleChain(): Observable { + return this.openImportDialog('rulechain.import', 'rulechain.rulechain-file').pipe( + mergeMap((ruleChainImport: RuleChainImport) => { + if (!this.validateImportedRuleChain(ruleChainImport)) { + this.store.dispatch(new ActionNotificationShow( + {message: this.translate.instant('rulechain.invalid-rulechain-file-error'), + type: 'error'})); + throw new Error('Invalid rule chain file'); + } else { + return this.ruleChainService.resolveRuleChainMetadata(ruleChainImport.metadata).pipe( + map((resolvedMetadata) => { + ruleChainImport.resolvedMetadata = resolvedMetadata; + return ruleChainImport; + }) + ); + } + }), + catchError((err) => { + return of(null); + }) + ); + } + + private prepareRuleChain(ruleChain: RuleChain): RuleChain { + ruleChain = this.prepareExport(ruleChain); + if (ruleChain.firstRuleNodeId) { + ruleChain.firstRuleNodeId = null; + } + ruleChain.root = false; + return ruleChain; + } + + private prepareRuleChainMetaData(ruleChainMetaData: RuleChainMetaData) { + delete ruleChainMetaData.ruleChainId; + for (let i = 0; i < ruleChainMetaData.nodes.length; i++) { + var node = ruleChainMetaData.nodes[i]; + delete node.ruleChainId; + ruleChainMetaData.nodes[i] = this.prepareExport(node); + } + return ruleChainMetaData; + } + + private validateImportedRuleChain(ruleChainImport: RuleChainImport): boolean { + if (isUndefined(ruleChainImport.ruleChain) + || isUndefined(ruleChainImport.metadata) + || isUndefined(ruleChainImport.ruleChain.name)) { + return false; + } + return true; + } + private sumObject(obj1: any, obj2: any): any { Object.keys(obj2).map((key) => { if (isObject(obj2[key])) { diff --git a/ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts index 9b56a5c806..b70f694c36 100644 --- a/ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts @@ -15,22 +15,17 @@ /// import { PageComponent } from '@shared/components/page.component'; -import { Inject, Input, OnDestroy, OnInit } from '@angular/core'; +import { Inject, Injector, Input, OnDestroy, OnInit } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { WidgetContext, IDynamicWidgetComponent } from '@home/models/widget-component.models'; import { ExceptionData } from '@shared/models/error.models'; import { HttpErrorResponse } from '@angular/common/http'; import { RafService } from '@core/services/raf.service'; +import { DeviceService } from '@core/http/device.service'; export class DynamicWidgetComponent extends PageComponent implements IDynamicWidgetComponent, OnInit, OnDestroy { - @Input() - widgetContext: WidgetContext; - - @Input() - errorMessages: string[]; - executingRpcRequest: boolean; rpcEnabled: boolean; rpcErrorText: string; @@ -39,8 +34,19 @@ export class DynamicWidgetComponent extends PageComponent implements IDynamicWid [key: string]: any; constructor(@Inject(RafService) public raf: RafService, - @Inject(Store) protected store: Store) { + @Inject(Store) protected store: Store, + @Inject(Injector) private $injector: Injector, + @Inject('widgetContext') public readonly ctx: WidgetContext, + @Inject('errorMessages') public readonly errorMessages: string[]) { super(store); + this.ctx.$injector = $injector; + this.ctx.$scope = this; + if (this.ctx.defaultSubscription) { + this.executingRpcRequest = this.ctx.defaultSubscription.executingRpcRequest; + this.rpcEnabled = this.ctx.defaultSubscription.rpcEnabled; + this.rpcErrorText = this.ctx.defaultSubscription.rpcErrorText; + this.rpcRejection = this.ctx.defaultSubscription.rpcRejection; + } } ngOnInit() { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.html new file mode 100644 index 0000000000..97f7e8be8b --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.html @@ -0,0 +1,24 @@ + +
+ + + {{ column.title }} + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.scss new file mode 100644 index 0000000000..0ead8c7992 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.scss @@ -0,0 +1,36 @@ +/** + * Copyright © 2016-2019 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 { + width: 100%; + height: 100%; + min-width: 300px; + overflow: hidden; + background: #fff; + border-radius: 4px; + box-shadow: + 0 7px 8px -4px rgba(0, 0, 0, .2), + 0 13px 19px 2px rgba(0, 0, 0, .14), + 0 5px 24px 4px rgba(0, 0, 0, .12); + + .mat-content { + overflow: hidden; + background-color: #fff; + } + + .mat-padding { + padding: 16px; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.ts new file mode 100644 index 0000000000..4d6a9c37ea --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.ts @@ -0,0 +1,43 @@ +/// +/// Copyright © 2016-2019 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, InjectionToken } from '@angular/core'; +import { DisplayColumn } from '@home/components/widget/lib/table-widget.models'; + +export const DISPLAY_COLUMNS_PANEL_DATA = new InjectionToken('DisplayColumnsPanelData'); + +export interface DisplayColumnsPanelData { + columns: DisplayColumn[]; + columnsUpdated: (columns: DisplayColumn[]) => void; +} + +@Component({ + selector: 'tb-display-columns-panel', + templateUrl: './display-columns-panel.component.html', + styleUrls: ['./display-columns-panel.component.scss'] +}) +export class DisplayColumnsPanelComponent { + + columns: DisplayColumn[]; + + constructor(@Inject(DISPLAY_COLUMNS_PANEL_DATA) public data: DisplayColumnsPanelData) { + this.columns = this.data.columns; + } + + public update() { + this.data.columnsUpdated(this.columns); + } +} 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 new file mode 100644 index 0000000000..ae779f55be --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.html @@ -0,0 +1,96 @@ + +
+
+ +
+ + +   + + + +
+
+
+ + + {{ column.title }} + + + + + + + +
+ +
+
+ + + + +
+
+
+ + +
+ entity.no-entities-prompt +
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.scss new file mode 100644 index 0000000000..1333a90161 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.scss @@ -0,0 +1,39 @@ +/** + * Copyright © 2016-2019 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 { + width: 100%; + height: 100%; + .tb-entity-table { + .mat-table, .mat-paginator, mat-toolbar.mat-table-toolbar { + background: transparent; + } + mat-toolbar { + height: 39px; + max-height: 39px; + .mat-toolbar-tools { + height: 39px; + max-height: 39px; + } + } + .table-container { + overflow: auto; + } + } +} + +:host ::ng-deep .mat-sort-header-sorted .mat-sort-header-arrow { + opacity: 1 !important; +} 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 new file mode 100644 index 0000000000..666e14de3d --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts @@ -0,0 +1,710 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { AfterViewInit, Component, ElementRef, Input, OnInit, ViewChild, ViewContainerRef } from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { WidgetAction, WidgetContext } from '@home/models/widget-component.models'; +import { + DataKey, + Datasource, + DatasourceData, + DatasourceType, + WidgetActionDescriptor, + WidgetConfig +} from '@shared/models/widget.models'; +import { IWidgetSubscription } from '@core/api/widget-api.models'; +import { UtilsService } from '@core/services/utils.service'; +import { TranslateService } from '@ngx-translate/core'; +import { deepClone, isDefined, isNumber } from '@core/utils'; +import * as tinycolor_ from 'tinycolor2'; +import cssjs from '@core/css/css'; +import { PageLink } from '@shared/models/page/page-link'; +import { Direction, SortOrder, sortOrderFromString } from '@shared/models/page/sort-order'; +import { DataSource } from '@angular/cdk/typings/collections'; +import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; +import { CollectionViewer } from '@angular/cdk/collections'; +import { BehaviorSubject, fromEvent, merge, Observable, of } from 'rxjs'; +import { emptyPageData, PageData } from '@shared/models/page/page-data'; +import { EntityId } from '@shared/models/id/entity-id'; +import { entityTypeTranslations } from '@shared/models/entity-type.models'; +import { catchError, debounceTime, distinctUntilChanged, map, tap } from 'rxjs/operators'; +import { MatPaginator } from '@angular/material/paginator'; +import { MatSort } from '@angular/material/sort'; +import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; +import { + CellContentInfo, + CellStyleInfo, + DisplayColumn, + EntityColumn, + EntityData, + getEntityValue +} from '@home/components/widget/lib/table-widget.models'; +import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; +import { ComponentPortal, PortalInjector } from '@angular/cdk/portal'; +import { + DISPLAY_COLUMNS_PANEL_DATA, + DisplayColumnsPanelComponent, + DisplayColumnsPanelData +} from '@home/components/widget/lib/display-columns-panel.component'; + +const tinycolor = tinycolor_; + +interface EntitiesTableWidgetSettings { + entitiesTitle: string; + enableSearch: boolean; + enableSelectColumnDisplay: boolean; + displayEntityName: boolean; + entityNameColumnTitle: string; + displayEntityType: boolean; + displayPagination: boolean; + defaultPageSize: number; + defaultSortOrder: string; +} + +interface EntitiesTableDataKeySettings { + columnWidth: string; + useCellStyleFunction: boolean; + cellStyleFunction: string; + useCellContentFunction: boolean; + cellContentFunction: string; +} + +@Component({ + selector: 'tb-entities-table-widget', + templateUrl: './entities-table-widget.component.html', + styleUrls: ['./entities-table-widget.component.scss'] +}) +export class EntitiesTableWidgetComponent extends PageComponent implements OnInit, AfterViewInit { + + @Input() + ctx: WidgetContext; + + @ViewChild('searchInput', {static: false}) searchInputField: ElementRef; + @ViewChild(MatPaginator, {static: false}) paginator: MatPaginator; + @ViewChild(MatSort, {static: false}) sort: MatSort; + + public displayPagination = true; + public pageSizeOptions; + public pageLink: PageLink; + public textSearchMode = false; + public columns: Array = []; + public displayedColumns: string[] = []; + public actionCellDescriptors: WidgetActionDescriptor[]; + public entityDatasource: EntityDatasource; + + private settings: EntitiesTableWidgetSettings; + private widgetConfig: WidgetConfig; + private subscription: IWidgetSubscription; + + private defaultPageSize = 10; + private defaultSortOrder = 'entityName'; + + private contentsInfo: {[key: string]: CellContentInfo} = {}; + private stylesInfo: {[key: string]: CellStyleInfo} = {}; + private columnWidth: {[key: string]: string} = {}; + + private searchAction: WidgetAction = { + name: 'action.search', + show: true, + icon: 'search', + onAction: () => { + this.enterFilterMode(); + } + }; + + private columnDisplayAction: WidgetAction = { + name: 'entity.columns-to-display', + show: true, + icon: 'view_column', + onAction: ($event) => { + this.editColumnsToDisplay($event); + } + }; + + constructor(protected store: Store, + private elementRef: ElementRef, + private overlay: Overlay, + private viewContainerRef: ViewContainerRef, + private utils: UtilsService, + private translate: TranslateService, + private domSanitizer: DomSanitizer) { + super(store); + + const sortOrder: SortOrder = sortOrderFromString(this.defaultSortOrder); + this.pageLink = new PageLink(this.defaultPageSize, 0, null, sortOrder); + } + + ngOnInit(): void { + this.ctx.$scope.entitiesTableWidget = this; + this.settings = this.ctx.settings; + this.widgetConfig = this.ctx.widgetConfig; + this.subscription = this.ctx.defaultSubscription; + this.initializeConfig(); + this.updateDatasources(); + this.ctx.updateWidgetParams(); + } + + ngAfterViewInit(): void { + fromEvent(this.searchInputField.nativeElement, 'keyup') + .pipe( + debounceTime(150), + distinctUntilChanged(), + tap(() => { + if (this.displayPagination) { + this.paginator.pageIndex = 0; + } + this.updateData(); + }) + ) + .subscribe(); + + if (this.displayPagination) { + this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0); + } + (this.displayPagination ? merge(this.sort.sortChange, this.paginator.page) : this.sort.sortChange) + .pipe( + tap(() => this.updateData()) + ) + .subscribe(); + this.updateData(); + } + + public onDataUpdated() { + this.entityDatasource.updateEntitiesData(this.subscription.data); + this.ctx.detectChanges(); + } + + private initializeConfig() { + this.ctx.widgetActions = [this.searchAction, this.columnDisplayAction]; + + let entitiesTitle: string; + + if (this.settings.entitiesTitle && this.settings.entitiesTitle.length) { + entitiesTitle = this.utils.customTranslation(this.settings.entitiesTitle, this.settings.entitiesTitle); + } else { + entitiesTitle = this.translate.instant('entity.entities'); + } + + const datasource = this.subscription.datasources[0]; + this.ctx.widgetTitle = this.utils.createLabelFromDatasource(datasource, entitiesTitle); + + this.searchAction.show = isDefined(this.settings.enableSearch) ? this.settings.enableSearch : true; + this.displayPagination = isDefined(this.settings.displayPagination) ? this.settings.displayPagination : true; + this.columnDisplayAction.show = isDefined(this.settings.enableSelectColumnDisplay) ? this.settings.enableSelectColumnDisplay : true; + + const pageSize = this.settings.defaultPageSize; + if (isDefined(pageSize) && isNumber(pageSize) && pageSize > 0) { + this.defaultPageSize = pageSize; + } + this.pageSizeOptions = [this.defaultPageSize, this.defaultPageSize*2, this.defaultPageSize*3]; + + if (this.settings.defaultSortOrder && this.settings.defaultSortOrder.length) { + this.defaultSortOrder = this.settings.defaultSortOrder; + } + + this.pageLink.pageSize = this.displayPagination ? this.defaultPageSize : Number.POSITIVE_INFINITY; + this.pageLink.sortOrder = sortOrderFromString(this.defaultSortOrder); + + const origColor = this.widgetConfig.color || 'rgba(0, 0, 0, 0.87)'; + const origBackgroundColor = this.widgetConfig.backgroundColor || 'rgb(255, 255, 255)'; + const defaultColor = tinycolor(origColor); + const mdDark = defaultColor.setAlpha(0.87).toRgbString(); + const mdDarkSecondary = defaultColor.setAlpha(0.54).toRgbString(); + const mdDarkDisabled = defaultColor.setAlpha(0.26).toRgbString(); + const mdDarkDivider = defaultColor.setAlpha(0.12).toRgbString(); + + const cssString = + '.mat-input-element::placeholder {\n' + + ' color: ' + mdDarkSecondary + ';\n'+ + '}\n' + + '.mat-input-element::-moz-placeholder {\n' + + ' color: ' + mdDarkSecondary + ';\n'+ + '}\n' + + '.mat-input-element::-webkit-input-placeholder {\n' + + ' color: ' + mdDarkSecondary + ';\n'+ + '}\n' + + '.mat-input-element:-ms-input-placeholder {\n' + + ' color: ' + mdDarkSecondary + ';\n'+ + '}\n' + + 'mat-toolbar.mat-table-toolbar {\n'+ + 'color: ' + mdDark + ';\n'+ + '}\n'+ + 'mat-toolbar.mat-table-toolbar button.mat-icon-button mat-icon {\n'+ + 'color: ' + mdDarkSecondary + ';\n'+ + '}\n'+ + '.mat-table .mat-header-row {\n'+ + 'background-color: ' + origBackgroundColor + ';\n'+ + '}\n'+ + '.mat-table .mat-header-cell {\n'+ + 'color: ' + mdDarkSecondary + ';\n'+ + '}\n'+ + '.mat-table .mat-header-cell .mat-sort-header-arrow {\n'+ + 'color: ' + mdDarkDisabled + ';\n'+ + '}\n'+ + '.mat-table .mat-row, .mat-table .mat-header-row {\n'+ + 'border-bottom-color: '+mdDarkDivider+';\n'+ + '}\n'+ + '.mat-table .mat-row:not(.tb-current-entity):not(:hover) .mat-cell.mat-table-sticky, .mat-table .mat-header-cell.mat-table-sticky {\n'+ + 'background-color: ' + origBackgroundColor + ';\n'+ + '}\n'+ + '.mat-table .mat-cell {\n'+ + 'color: ' + mdDark + ';\n'+ + '}\n'+ + '.mat-table .mat-cell button.mat-icon-button mat-icon {\n'+ + 'color: ' + mdDarkSecondary + ';\n'+ + '}\n'+ + '.mat-divider {\n'+ + 'border-top-color: ' + mdDarkDivider + ';\n'+ + '}\n'+ + '.mat-paginator {\n'+ + 'color: ' + mdDarkSecondary + ';\n'+ + '}\n'+ + '.mat-paginator button.mat-icon-button {\n'+ + 'color: ' + mdDarkSecondary + ';\n'+ + '}\n'+ + '.mat-paginator button.mat-icon-button[disabled][disabled] {\n'+ + 'color: ' + mdDarkDisabled + ';\n'+ + '}\n'+ + '.mat-paginator .mat-select-value {\n'+ + 'color: ' + mdDarkSecondary + ';\n'+ + '}'; + + const cssParser = new cssjs(); + cssParser.testMode = false; + const namespace = 'entities-table-' + this.utils.hashCode(cssString); + cssParser.cssPreviewNamespace = namespace; + cssParser.createStyleElement(namespace, cssString); + $(this.elementRef.nativeElement).addClass(namespace); + } + + private updateDatasources() { + + this.actionCellDescriptors = this.ctx.actionsApi.getActionDescriptors('actionCellButton'); + + const displayEntityName = isDefined(this.settings.displayEntityName) ? this.settings.displayEntityName : true; + let entityNameColumnTitle: string; + if (this.settings.entityNameColumnTitle && this.settings.entityNameColumnTitle.length) { + entityNameColumnTitle = this.utils.customTranslation(this.settings.entityNameColumnTitle, this.settings.entityNameColumnTitle); + } else { + entityNameColumnTitle = this.translate.instant('entity.entity-name'); + } + const displayEntityType = isDefined(this.settings.displayEntityType) ? this.settings.displayEntityType : true; + + if (displayEntityName) { + this.displayedColumns.push('entityName'); + this.columns.push( + { + name: 'entityName', + label: 'entityName', + title: entityNameColumnTitle + } as EntityColumn + ); + this.contentsInfo['entityName'] = { + useCellContentFunction: false + }; + this.stylesInfo['entityName'] = { + useCellStyleFunction: false + }; + this.columnWidth['entityName'] = '100px'; + } + if (displayEntityType) { + this.displayedColumns.push('entityType'); + this.columns.push( + { + name: 'entityType', + label: 'entityType', + title: this.translate.instant('entity.entity-type'), + } as EntityColumn + ); + this.contentsInfo['entityType'] = { + useCellContentFunction: false + }; + this.stylesInfo['entityType'] = { + useCellStyleFunction: false + }; + this.columnWidth['entityType'] = '100px'; + } + + const dataKeys: Array = []; + + const datasource = this.subscription.datasources[0]; + + if (datasource) { + datasource.dataKeys.forEach((_dataKey) => { + const dataKey: EntityColumn = deepClone(_dataKey) as EntityColumn; + if (dataKey.type === DataKeyType.function) { + dataKey.name = dataKey.label; + } + dataKeys.push(dataKey); + + dataKey.title = this.utils.customTranslation(dataKey.label, dataKey.label); + const keySettings: EntitiesTableDataKeySettings = dataKey.settings; + + let cellStyleFunction: Function = null; + let useCellStyleFunction = false; + + if (keySettings.useCellStyleFunction === true) { + if (isDefined(keySettings.cellStyleFunction) && keySettings.cellStyleFunction.length > 0) { + try { + cellStyleFunction = new Function('value', keySettings.cellStyleFunction); + useCellStyleFunction = true; + } catch (e) { + cellStyleFunction = null; + useCellStyleFunction = false; + } + } + } + this.stylesInfo[dataKey.label] = { + useCellStyleFunction, + cellStyleFunction + }; + + let cellContentFunction: Function = null; + let useCellContentFunction = false; + + if (keySettings.useCellContentFunction === true) { + if (isDefined(keySettings.cellContentFunction) && keySettings.cellContentFunction.length > 0) { + try { + cellContentFunction = new Function('value, entity, ctx', keySettings.cellContentFunction); + useCellContentFunction = true; + } catch (e) { + cellContentFunction = null; + useCellContentFunction = false; + } + } + } + + this.contentsInfo[dataKey.label] = { + useCellContentFunction, + cellContentFunction, + units: dataKey.units, + decimals: dataKey.decimals + }; + + const columnWidth = isDefined(keySettings.columnWidth) ? keySettings.columnWidth : '0px'; + this.columnWidth[dataKey.label] = columnWidth; + this.displayedColumns.push(dataKey.label); + this.columns.push(dataKey); + }); + } + + if (this.actionCellDescriptors.length) { + this.displayedColumns.push('actions'); + } + this.entityDatasource = new EntityDatasource( + this.translate, dataKeys, this.subscription.datasources); + } + + private editColumnsToDisplay($event: Event) { + if ($event) { + $event.stopPropagation(); + } + const target = $event.target || $event.srcElement || $event.currentTarget; + const config = new OverlayConfig(); + config.backdropClass = 'cdk-overlay-transparent-backdrop'; + config.hasBackdrop = true; + const connectedPosition: ConnectedPosition = { + originX: 'end', + originY: 'bottom', + overlayX: 'end', + overlayY: 'top' + }; + config.positionStrategy = this.overlay.position().flexibleConnectedTo(target as HTMLElement) + .withPositions([connectedPosition]); + + const overlayRef = this.overlay.create(config); + overlayRef.backdropClick().subscribe(() => { + overlayRef.dispose(); + }); + + const columns: DisplayColumn[] = this.columns.map(column => { + return { + title: column.title, + label: column.label, + display: this.displayedColumns.indexOf(column.label) > -1 + } + }); + + const injectionTokens = new WeakMap([ + [DISPLAY_COLUMNS_PANEL_DATA, { + columns, + columnsUpdated: (newColumns) => { + this.displayedColumns = newColumns.filter(column => column.display).map(column => column.label); + this.displayedColumns.push('actions'); + } + } as DisplayColumnsPanelData], + [OverlayRef, overlayRef] + ]); + const injector = new PortalInjector(this.viewContainerRef.injector, injectionTokens); + overlayRef.attach(new ComponentPortal(DisplayColumnsPanelComponent, + this.viewContainerRef, injector)); + this.ctx.detectChanges(); + } + + private enterFilterMode() { + this.textSearchMode = true; + this.pageLink.textSearch = ''; + this.ctx.hideTitlePanel = true; + this.ctx.detectChanges(true); + setTimeout(() => { + this.searchInputField.nativeElement.focus(); + this.searchInputField.nativeElement.setSelectionRange(0, 0); + }, 10); + } + + exitFilterMode() { + this.textSearchMode = false; + this.pageLink.textSearch = null; + if (this.displayPagination) { + this.paginator.pageIndex = 0; + } + this.updateData(); + this.ctx.hideTitlePanel = false; + this.ctx.detectChanges(true); + } + + private updateData() { + if (this.displayPagination) { + this.pageLink.page = this.paginator.pageIndex; + this.pageLink.pageSize = this.paginator.pageSize; + } else { + this.pageLink.page = 0; + } + this.pageLink.sortOrder.property = this.sort.active; + this.pageLink.sortOrder.direction = Direction[this.sort.direction.toUpperCase()]; + this.entityDatasource.loadEntities(this.pageLink); + this.ctx.detectChanges(); + } + + public trackByColumnLabel(index, column: EntityColumn) { + return column.label; + } + + public headerStyle(key: EntityColumn): any { + return this.widthStyle(key); + } + + public cellStyle(entity: EntityData, key: EntityColumn): any { + let style: any = {}; + if (entity && key) { + const styleInfo = this.stylesInfo[key.label]; + const value = getEntityValue(entity, key); + if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) { + try { + style = styleInfo.cellStyleFunction(value); + } catch (e) { + style = {}; + } + } else { + style = this.defaultStyle(key, value); + } + } + const widthStyle = this.widthStyle(key); + style = {...style, ...widthStyle}; + return style; + } + + private widthStyle(key: EntityColumn): any { + let style: any = {}; + const columnWidth = this.columnWidth[key.label]; + if (columnWidth !== "0px") { + style.minWidth = columnWidth; + style.width = columnWidth; + } else { + style.minWidth = "auto"; + style.width = "auto"; + } + return style; + } + + public cellContent(entity: EntityData, key: EntityColumn): SafeHtml { + let strContent = ''; + if (entity && key) { + const contentInfo = this.contentsInfo[key.label]; + const value = getEntityValue(entity, key); + if (contentInfo.useCellContentFunction && contentInfo.cellContentFunction) { + if (isDefined(value)) { + strContent = '' + value; + } + var content = strContent; + try { + content = contentInfo.cellContentFunction(value, entity, this.ctx); + } catch (e) { + content = strContent; + } + } else { + const decimals = (contentInfo.decimals || contentInfo.decimals === 0) ? contentInfo.decimals : this.ctx.widgetConfig.decimals; + const units = contentInfo.units || this.ctx.widgetConfig.units; + content = this.ctx.utils.formatValue(value, decimals, units, true); + } + return this.domSanitizer.bypassSecurityTrustHtml(content); + } else { + return strContent; + } + } + + public onRowClick($event: Event, entity: EntityData) { + if ($event) { + $event.stopPropagation(); + } + this.entityDatasource.toggleCurrentEntity(entity); + const descriptors = this.ctx.actionsApi.getActionDescriptors('rowClick'); + if (descriptors.length) { + let entityId; + let entityName; + if (entity) { + entityId = entity.id; + entityName = entity.entityName; + } + this.ctx.actionsApi.handleWidgetAction($event, descriptors[0], entityId, entityName); + } + } + + public onActionButtonClick($event: Event, entity: EntityData, actionDescriptor: WidgetActionDescriptor) { + if ($event) { + $event.stopPropagation(); + } + let entityId; + let entityName; + if (entity) { + entityId = entity.id; + entityName = entity.entityName; + } + this.ctx.actionsApi.handleWidgetAction($event, actionDescriptor, entityId, entityName); + } + + private defaultStyle(key: EntityColumn, value: any): any { + return {}; + } + +} + + + +class EntityDatasource implements DataSource { + + private entitiesSubject = new BehaviorSubject([]); + private pageDataSubject = new BehaviorSubject>(emptyPageData()); + + private allEntities: Array = []; + private allEntitiesSubject = new BehaviorSubject([]); + private allEntities$: Observable> = this.allEntitiesSubject.asObservable(); + + private currentEntity: EntityData = null; + + constructor( + private translate: TranslateService, + private dataKeys: Array, + datasources: Array + ) { + + for (const datasource of datasources) { + if (datasource.type === DatasourceType.entity && !datasource.entityId) { + continue; + } + const entity: EntityData = { + id: {} as EntityId, + entityName: datasource.entityName + }; + if (datasource.entityId) { + entity.id.id = datasource.entityId; + } + if (datasource.entityType) { + entity.id.entityType = datasource.entityType; + entity.entityType = this.translate.instant(entityTypeTranslations.get(datasource.entityType).type); + } else { + entity.entityType = ''; + } + this.dataKeys.forEach((dataKey) => { + entity[dataKey.label] = ''; + }); + this.allEntities.push(entity); + } + this.allEntitiesSubject.next(this.allEntities); + } + + connect(collectionViewer: CollectionViewer): Observable> { + return this.entitiesSubject.asObservable(); + } + + disconnect(collectionViewer: CollectionViewer): void { + this.entitiesSubject.complete(); + this.pageDataSubject.complete(); + } + + loadEntities(pageLink: PageLink) { + this.fetchEntities(pageLink).pipe( + catchError(() => of(emptyPageData())), + ).subscribe( + (pageData) => { + this.entitiesSubject.next(pageData.data); + this.pageDataSubject.next(pageData); + } + ); + } + + updateEntitiesData(data: DatasourceData[]) { + for (let i = 0; i < this.allEntities.length; i++) { + const entity = this.allEntities[i]; + for (let a = 0; a < this.dataKeys.length; a++) { + const dataKey = this.dataKeys[a]; + const index = i * this.dataKeys.length + a; + const keyData = data[index].data; + if (keyData && keyData.length && keyData[0].length > 1) { + const value = keyData[0][1]; + entity[dataKey.label] = value; + } else { + entity[dataKey.label] = ''; + } + } + } + this.allEntitiesSubject.next(this.allEntities); + } + + isEmpty(): Observable { + return this.entitiesSubject.pipe( + map((entities) => !entities.length) + ); + } + + total(): Observable { + return this.pageDataSubject.pipe( + map((pageData) => pageData.totalElements) + ); + } + + public toggleCurrentEntity(entity: EntityData): boolean { + if (this.currentEntity !== entity) { + this.currentEntity = entity; + return true; + } else { + return false; + } + } + + public isCurrentEntity(entity: EntityData): boolean { + return (this.currentEntity && entity && this.currentEntity.id && entity.id) && + (this.currentEntity.id.id === entity.id.id); + } + + private fetchEntities(pageLink: PageLink): Observable> { + return this.allEntities$.pipe( + map((data) => pageLink.filterData(data)) + ); + } +} 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 new file mode 100644 index 0000000000..07ec5671d0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts @@ -0,0 +1,55 @@ +/// +/// Copyright © 2016-2019 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 { EntityId } from '@shared/models/id/entity-id'; +import { DataKey } from '@shared/models/widget.models'; + +export interface EntityData { + id: EntityId; + entityName: string; + entityType?: string; + [key: string]: any; +} + +export interface EntityColumn extends DataKey { + title: string; +} + +export interface DisplayColumn { + title: string; + label: string; + display: boolean; +} + +export interface CellContentInfo { + useCellContentFunction: boolean; + cellContentFunction?: Function; + units?: string; + decimals?: number; +} + +export interface CellStyleInfo { + useCellStyleFunction: boolean; + cellStyleFunction?: Function; +} + +export function getEntityValue(entity: any, key: DataKey): any { + return getDescendantProp(entity, key.label); +} + +export function getDescendantProp(obj: any, path: string): any { + return path.split('.').reduce((acc, part) => acc && acc[part], obj) +} 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 d81dcca350..cce9d797cb 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 @@ -19,18 +19,24 @@ import { CommonModule } from '@angular/common'; import { SharedModule } from '@app/shared/shared.module'; import { AlarmDetailsDialogComponent } from '@home/components/alarm/alarm-details-dialog.component'; import { LegendComponent } from '@home/components/widget/legend.component'; +import { EntitiesTableWidgetComponent } from '@home/components/widget/lib/entities-table-widget.component'; +import { DisplayColumnsPanelComponent } from '@home/components/widget/lib/display-columns-panel.component'; @NgModule({ entryComponents: [ + DisplayColumnsPanelComponent ], declarations: [ + DisplayColumnsPanelComponent, + EntitiesTableWidgetComponent ], imports: [ CommonModule, SharedModule ], exports: [ + EntitiesTableWidgetComponent ] }) export class WidgetComponentsModule { } 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 a803741bf1..5fc43e8d03 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 @@ -27,7 +27,7 @@ import { NgZone, OnChanges, OnDestroy, - OnInit, + OnInit, ReflectiveInjector, SimpleChanges, Type, ViewChild, ViewContainerRef, @@ -93,12 +93,14 @@ import { EntityService } from '@core/http/entity.service'; import { AssetService } from '@core/http/asset.service'; import { DialogService } from '@core/services/dialog.service'; import { CustomDialogService } from '@home/components/widget/dialog/custom-dialog.service'; +import { DatePipe } from '@angular/common'; const ServicesMap = new Map>(); ServicesMap.set('deviceService', DeviceService); ServicesMap.set('assetService', AssetService); ServicesMap.set('dialogs', DialogService); ServicesMap.set('customDialog', CustomDialogService); +ServicesMap.set('date', DatePipe); @Component({ selector: 'tb-widget', @@ -249,6 +251,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI } this.widgetContext = this.dashboardWidget.widgetContext; + this.widgetContext.changeDetector = this.cd; this.widgetContext.servicesMap = ServicesMap; this.widgetContext.isEdit = this.isEdit; this.widgetContext.isMobile = this.isMobile; @@ -420,6 +423,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI this.initialize().subscribe( () => { + this.cd.detectChanges(); this.onInit(); }, (err) => { @@ -579,13 +583,11 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI } } )); - - this.configureDynamicWidgetComponent(); if (!this.typeParameters.useCustomDatasources) { - // this.cre this.createDefaultSubscription().subscribe( () => { this.subscriptionInited = true; + this.configureDynamicWidgetComponent(); initSubject.next(); initSubject.complete(); }, @@ -597,6 +599,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI } else { this.loadingData = false; this.subscriptionInited = true; + this.configureDynamicWidgetComponent(); initSubject.next(); initSubject.complete(); } @@ -621,15 +624,24 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI private configureDynamicWidgetComponent() { this.widgetContentContainer.clear(); - this.dynamicWidgetComponentRef = this.widgetContentContainer.createComponent(this.widgetInfo.componentFactory); + const injector: Injector = Injector.create( + { + providers: [ + { + provide: 'widgetContext', + useValue: this.widgetContext + }, + { + provide: 'errorMessages', + useValue: this.errorMessages + } + ], + parent: this.injector + } + ); + this.dynamicWidgetComponentRef = this.widgetContentContainer.createComponent(this.widgetInfo.componentFactory, 0, injector); this.dynamicWidgetComponent = this.dynamicWidgetComponentRef.instance; - this.dynamicWidgetComponent.widgetContext = this.widgetContext; - this.dynamicWidgetComponent.errorMessages = this.errorMessages; - - this.widgetContext.$scope = this.dynamicWidgetComponent; - this.widgetContext.$scope.$injector = this.injector; - const containerElement = $(this.elementRef.nativeElement.querySelector('#widget-container')); // this.widgetContext.$container = $('> ng-component:not([id="container"])', containerElement); @@ -810,22 +822,30 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI }; options.callbacks = { rpcStateChanged: (subscription) => { - this.dynamicWidgetComponent.rpcEnabled = subscription.rpcEnabled; - this.dynamicWidgetComponent.executingRpcRequest = subscription.executingRpcRequest; + if (this.dynamicWidgetComponent) { + this.dynamicWidgetComponent.rpcEnabled = subscription.rpcEnabled; + this.dynamicWidgetComponent.executingRpcRequest = subscription.executingRpcRequest; + } }, onRpcSuccess: (subscription) => { - this.dynamicWidgetComponent.executingRpcRequest = subscription.executingRpcRequest; - this.dynamicWidgetComponent.rpcErrorText = subscription.rpcErrorText; - this.dynamicWidgetComponent.rpcRejection = subscription.rpcRejection; + if (this.dynamicWidgetComponent) { + this.dynamicWidgetComponent.executingRpcRequest = subscription.executingRpcRequest; + this.dynamicWidgetComponent.rpcErrorText = subscription.rpcErrorText; + this.dynamicWidgetComponent.rpcRejection = subscription.rpcRejection; + } }, onRpcFailed: (subscription) => { - this.dynamicWidgetComponent.executingRpcRequest = subscription.executingRpcRequest; - this.dynamicWidgetComponent.rpcErrorText = subscription.rpcErrorText; - this.dynamicWidgetComponent.rpcRejection = subscription.rpcRejection; + if (this.dynamicWidgetComponent) { + this.dynamicWidgetComponent.executingRpcRequest = subscription.executingRpcRequest; + this.dynamicWidgetComponent.rpcErrorText = subscription.rpcErrorText; + this.dynamicWidgetComponent.rpcRejection = subscription.rpcRejection; + } }, onRpcErrorCleared: (subscription) => { - this.dynamicWidgetComponent.rpcErrorText = null; - this.dynamicWidgetComponent.rpcRejection = null; + if (this.dynamicWidgetComponent) { + this.dynamicWidgetComponent.rpcErrorText = null; + this.dynamicWidgetComponent.rpcRejection = null; + } } }; this.createSubscription(options).subscribe( @@ -897,12 +917,11 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI } else { url = `/dashboards/${targetDashboardId}?state=${state}`; } - const urlTree = this.router.parseUrl(url); this.router.navigateByUrl(url); break; case WidgetActionType.custom: const customFunction = descriptor.customFunction; - if (isDefined(customFunction) && customFunction.length > 0) { + if (customFunction && customFunction.length > 0) { try { if (!additionalParams) { additionalParams = {}; 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 a3c237b115..6a44e5bafb 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 @@ -17,11 +17,11 @@ import { GridsterComponent, GridsterConfig, GridsterItem, GridsterItemComponentInterface } from 'angular-gridster2'; import { Widget, widgetType, WidgetPosition } from '@app/shared/models/widget.models'; import { WidgetLayout, WidgetLayouts } from '@app/shared/models/dashboard.models'; -import { WidgetAction, WidgetContext, WidgetHeaderAction } from './widget-component.models'; +import { IDashboardWidget, WidgetAction, WidgetContext, WidgetHeaderAction } from './widget-component.models'; import { Timewindow } from '@shared/models/time/time.models'; import { Observable, of, Subject } from 'rxjs'; import { guid, isDefined, isUndefined } from '@app/core/utils'; -import { IterableDiffer, KeyValueDiffer } from '@angular/core'; +import { IterableDiffer, KeyValueDiffer, NgZone } from '@angular/core'; import { IAliasController, IStateController } from '@app/core/api/widget-api.models'; import * as deepEqual from 'deep-equal'; @@ -264,7 +264,7 @@ export class DashboardWidgets implements Iterable { } -export class DashboardWidget implements GridsterItem { +export class DashboardWidget implements GridsterItem, IDashboardWidget { highlighted = false; selected = false; @@ -302,7 +302,7 @@ export class DashboardWidget implements GridsterItem { customHeaderActions: Array; widgetActions: Array; - widgetContext = new WidgetContext(this.dashboard, this.widget); + widgetContext = new WidgetContext(this.dashboard, this, this.widget); widgetId: string; 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 18236ca35b..4c4982d357 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 @@ -41,6 +41,7 @@ export type EntityActionFunction> = (action: EntityAct export type CreateEntityOperation> = () => Observable; export type CellContentFunction> = (entity: T, key: string) => string; +export type CellTooltipFunction> = (entity: T, key: string) => string | undefined; export type HeaderCellStyleFunction> = (key: string) => object; export type CellStyleFunction> = (entity: T, key: string) => object; @@ -86,7 +87,8 @@ export class EntityTableColumn> extends BaseEntityTabl public cellContentFunction: CellContentFunction = (entity, property) => entity[property], public cellStyleFunction: CellStyleFunction = () => ({}), public sortable: boolean = true, - public headerCellStyleFunction: HeaderCellStyleFunction = () => ({})) { + public headerCellStyleFunction: HeaderCellStyleFunction = () => ({}), + public cellTooltipFunction: CellTooltipFunction = () => undefined) { super('content', key, title, maxWidth, sortable); } } 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 4a9701f839..020242e54c 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 @@ -40,7 +40,7 @@ import { WidgetActionsApi, WidgetSubscriptionApi, WidgetSubscriptionContext, WidgetSubscriptionOptions } from '@core/api/widget-api.models'; -import { ComponentFactory, Type } from '@angular/core'; +import { ChangeDetectorRef, ComponentFactory, Injector, NgZone, Type } from '@angular/core'; import { HttpErrorResponse } from '@angular/common/http'; import { RafService } from '@core/services/raf.service'; import { WidgetTypeId } from '@shared/models/id/widget-type-id'; @@ -69,9 +69,14 @@ export interface WidgetAction extends IWidgetAction { show: boolean; } +export interface IDashboardWidget { + updateWidgetParams(); +} + export class WidgetContext { constructor(public dashboard: IDashboardComponent, + private dashboardWidget: IDashboardWidget, private widget: Widget) {} get stateController(): IStateController { @@ -102,6 +107,25 @@ export class WidgetContext { return isDefined(this.widget.config.decimals) ? this.widget.config.decimals : 2; } + set changeDetector(cd: ChangeDetectorRef) { + this._changeDetector = cd; + } + + private _changeDetector: ChangeDetectorRef; + + detectChanges(updateWidgetParams: boolean = false) { + if (updateWidgetParams) { + this.dashboardWidget.updateWidgetParams(); + } + this._changeDetector.detectChanges(); + } + + updateWidgetParams() { + setTimeout(() => { + this.dashboardWidget.updateWidgetParams(); + }, 0); + } + inited = false; subscriptions: {[id: string]: IWidgetSubscription} = {}; @@ -167,11 +191,13 @@ export class WidgetContext { widgetActions?: Array; servicesMap?: Map>; + + $injector?: Injector; } export interface IDynamicWidgetComponent { - widgetContext: WidgetContext; - errorMessages: string[]; + readonly ctx: WidgetContext; + readonly errorMessages: string[]; executingRpcRequest: boolean; rpcEnabled: boolean; rpcErrorText: string; 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 26c41289ae..d3d4fba033 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 @@ -36,6 +36,7 @@ import { RuleChainService } from '@core/http/rule-chain.service'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { TranslateService } from '@ngx-translate/core'; import { JsonObjectEditComponent } from '@shared/components/json-object-edit.component'; +import { deepClone } from '@core/utils'; @Component({ selector: 'tb-rule-node-config', @@ -134,7 +135,7 @@ export class RuleNodeConfigComponent implements ControlValueAccessor, OnInit, On } writeValue(value: RuleNodeConfiguration): void { - this.configuration = value; + this.configuration = deepClone(value); if (this.changeSubscription) { this.changeSubscription.unsubscribe(); this.changeSubscription = null; @@ -145,7 +146,7 @@ export class RuleNodeConfigComponent implements ControlValueAccessor, OnInit, On this.updateModel(configuration); }); } else { - this.ruleNodeConfigFormGroup.get('configuration').patchValue(value, {emitEvent: false}); + this.ruleNodeConfigFormGroup.get('configuration').patchValue(this.configuration, {emitEvent: false}); this.changeSubscription = this.ruleNodeConfigFormGroup.get('configuration').valueChanges.subscribe( (configuration: RuleNodeConfiguration) => { this.updateModel(configuration); 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 99e40cd539..dbab9a2d6b 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 @@ -111,16 +111,21 @@
- + - + +
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 462554287c..399105d3a9 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 @@ -19,6 +19,22 @@ .tb-rulechain { width: 100%; height: 100%; + + .mat-tab-group.tb-rulenode-details { + > .mat-tab-body-wrapper { + position: absolute; + top: 49px; + left: 0; + right: 0; + bottom: 0; + } + > .mat-tab-header { + .mat-tab-label { + min-width: 40px; + } + } + } + button.mat-button.mat-icon-button.tb-fullscreen-button { position: absolute; top: 10px; 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 516057a1d9..1eac5e3b67 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 @@ -38,9 +38,11 @@ import { DialogService } from '@core/services/dialog.service'; import { AuthService } from '@core/auth/auth.service'; import { ActivatedRoute, Router } from '@angular/router'; import { - inputNodeComponent, + inputNodeComponent, NodeConnectionInfo, ResolvedRuleChainMetaData, RuleChain, + RuleChainConnectionInfo, RuleChainImport, + RuleChainMetaData, ruleChainNodeComponent } from '@shared/models/rule-chain.models'; import { FcItemInfo, FlowchartConstants, NgxFlowchartComponent, UserCallbacks } from 'ngx-flowchart/dist/ngx-flowchart'; @@ -50,6 +52,7 @@ import { FcRuleNodeType, getRuleNodeHelpLink, LinkLabel, + RuleNode, RuleNodeComponentDescriptor, RuleNodeType, ruleNodeTypeDescriptors, @@ -66,8 +69,10 @@ import { RuleNodeLinkComponent } from './rule-node-link.component'; 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 { EntityType } from '@shared/models/entity-type.models'; import Timeout = NodeJS.Timeout; -import { Hotkey, HotkeysService } from 'angular2-hotkeys'; +import { DebugEventType, EventType } from '@shared/models/event.models'; @Component({ selector: 'tb-rulechain-page', @@ -94,6 +99,10 @@ export class RuleChainPageComponent extends PageComponent @ViewChild('ruleChainMenuTrigger', {static: true}) ruleChainMenuTrigger: MatMenuTrigger; + eventTypes = EventType; + + debugEventTypes = DebugEventType; + ruleChainMenuPosition = { x: '0px', y: '0px' }; contextMenuEvent: MouseEvent; @@ -260,13 +269,15 @@ export class RuleChainPageComponent extends PageComponent private init() { this.initHotKeys(); - this.ruleChain = this.route.snapshot.data.ruleChain; - if (this.route.snapshot.data.import && !this.ruleChain) { - this.router.navigateByUrl('ruleChains'); - return; - } this.isImport = this.route.snapshot.data.import; - this.ruleChainMetaData = this.route.snapshot.data.ruleChainMetaData; + if (this.isImport) { + const ruleChainImport: RuleChainImport = this.itembuffer.getRuleChainImport(); + this.ruleChain = ruleChainImport.ruleChain; + this.ruleChainMetaData = ruleChainImport.resolvedMetadata; + } else { + this.ruleChain = this.route.snapshot.data.ruleChain; + this.ruleChainMetaData = this.route.snapshot.data.ruleChainMetaData; + } this.ruleNodeComponents = this.route.snapshot.data.ruleNodeComponents; for (const type of ruleNodeTypesLibrary) { const desc = ruleNodeTypeDescriptors.get(type); @@ -564,7 +575,7 @@ export class RuleChainPageComponent extends PageComponent if (!ruleChainNode) { ruleChainNode = { id: 'rule-chain-node-' + this.nextNodeID++, - name: ruleChain.name ? name : 'Unresolved', + name: ruleChain.name ? ruleChain.name : 'Unresolved', targetRuleChainId: ruleChain.name ? ruleChainConnection.targetRuleChainId.id : null, error: ruleChain.name ? undefined : this.translate.instant('rulenode.invalid-target-rulechain'), additionalInfo: ruleChainConnection.additionalInfo, @@ -833,7 +844,6 @@ export class RuleChainPageComponent extends PageComponent } onModelChanged() { - console.log('Model changed!'); this.isDirtyValue = true; this.validate(); } @@ -1162,7 +1172,86 @@ export class RuleChainPageComponent extends PageComponent } saveRuleChain() { - // TODO: + let saveRuleChainObservable: Observable; + if (this.isImport) { + saveRuleChainObservable = this.ruleChainService.saveRuleChain(this.ruleChain); + } else { + saveRuleChainObservable = of(this.ruleChain); + } + saveRuleChainObservable.subscribe((ruleChain) => { + this.ruleChain = ruleChain; + const ruleChainMetaData: RuleChainMetaData = { + ruleChainId: this.ruleChain.id, + nodes: [], + connections: [], + ruleChainConnections: [] + }; + const nodes: FcRuleNode[] = []; + this.ruleChainModel.nodes.forEach((node) => { + if (node.component.type !== RuleNodeType.INPUT && node.component.type !== RuleNodeType.RULE_CHAIN) { + const ruleNode: RuleNode = { + id: node.ruleNodeId, + type: node.component.clazz, + name: node.name, + configuration: node.configuration, + additionalInfo: node.additionalInfo ? node.additionalInfo : {}, + debugMode: node.debugMode + }; + ruleNode.additionalInfo.layoutX = Math.round(node.x); + ruleNode.additionalInfo.layoutY = Math.round(node.y); + ruleChainMetaData.nodes.push(ruleNode); + nodes.push(node); + } + }); + const firstNodeEdge = this.ruleChainModel.edges.find((edge) => edge.source === this.inputConnectorId+''); + if (firstNodeEdge) { + const firstNode = this.ruleChainCanvas.modelService.nodes.getNodeByConnectorId(firstNodeEdge.destination); + ruleChainMetaData.firstNodeIndex = nodes.indexOf(firstNode); + } + this.ruleChainModel.edges.forEach((edge) => { + const sourceNode = this.ruleChainCanvas.modelService.nodes.getNodeByConnectorId(edge.source); + const destNode = this.ruleChainCanvas.modelService.nodes.getNodeByConnectorId(edge.destination); + if (sourceNode.component.type !== RuleNodeType.INPUT) { + const fromIndex = nodes.indexOf(sourceNode); + if (destNode.component.type === RuleNodeType.RULE_CHAIN) { + const ruleChainConnection = { + fromIndex, + targetRuleChainId: {entityType: EntityType.RULE_CHAIN, id: destNode.targetRuleChainId}, + additionalInfo: destNode.additionalInfo ? destNode.additionalInfo : {} + } as RuleChainConnectionInfo; + ruleChainConnection.additionalInfo.layoutX = Math.round(destNode.x); + ruleChainConnection.additionalInfo.layoutY = Math.round(destNode.y); + ruleChainConnection.additionalInfo.ruleChainNodeId = destNode.id; + edge.labels.forEach((label) => { + const newRuleChainConnection = deepClone(ruleChainConnection); + newRuleChainConnection.type = label; + ruleChainMetaData.ruleChainConnections.push(newRuleChainConnection); + }); + } else { + const toIndex = nodes.indexOf(destNode); + const nodeConnection = { + fromIndex, + toIndex + } as NodeConnectionInfo; + edge.labels.forEach((label) => { + const newNodeConnection = deepClone(nodeConnection); + newNodeConnection.type = label; + ruleChainMetaData.connections.push(newNodeConnection); + }); + } + } + }); + this.ruleChainService.saveAndGetResolvedRuleChainMetadata(ruleChainMetaData).subscribe((ruleChainMetaData) => { + this.ruleChainMetaData = ruleChainMetaData; + if (this.isImport) { + this.isDirtyValue = false; + this.isImport = false; + this.router.navigateByUrl(`ruleChains/${this.ruleChain.id.id}`); + } else { + this.createRuleChainModel(); + } + }); + }); } revertRuleChain() { diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts index 16dc106694..9fb8b17daa 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts @@ -16,7 +16,14 @@ import * as AngularCore from '@angular/core'; import { Injectable, NgModule } from '@angular/core'; -import { ActivatedRouteSnapshot, Resolve, RouterModule, Routes } from '@angular/router'; +import { + ActivatedRouteSnapshot, + CanActivate, + Resolve, Router, + RouterModule, + RouterStateSnapshot, + Routes, UrlTree +} from '@angular/router'; import { EntitiesTableComponent } from '../../components/entity/entities-table.component'; import { Authority } from '@shared/models/authority.enum'; @@ -41,6 +48,7 @@ import * as TranslateCore from '@ngx-translate/core'; import * as TbCore from '@core/public-api'; import * as TbShared from '@shared/public-api'; import * as TbHomeComponents from '@home/components/public-api'; +import { ItemBufferService } from '@core/public-api'; declare const SystemJS; @@ -98,6 +106,23 @@ export class RuleNodeComponentsResolver implements Resolve | Promise | boolean | UrlTree { + if (this.itembuffer.hasRuleChainImport()) { + return true; + } else { + return this.router.parseUrl('ruleChains'); + } + } + +} + export const ruleChainBreadcumbLabelFunction: BreadCrumbLabelFunction = ((route, translate, component) => { let label: string = component.ruleChain.name; if (component.ruleChain.root) { @@ -153,6 +178,7 @@ const routes: Routes = [ { path: 'ruleChain/import', component: RuleChainPageComponent, + canActivate: [RuleChainImportGuard], canDeactivate: [ConfirmOnExitGuard], data: { breadcrumb: { @@ -164,8 +190,6 @@ const routes: Routes = [ import: true }, resolve: { - ruleChain: 'importRuleChain', - ruleChainMetaData: 'importRuleChainMetadata', ruleNodeComponents: RuleNodeComponentsResolver } } @@ -182,20 +206,7 @@ const routes: Routes = [ RuleChainResolver, ResolvedRuleChainMetaDataResolver, RuleNodeComponentsResolver, - { - provide: 'importRuleChain', - useValue: (route: ActivatedRouteSnapshot) => { - const ruleChainImport: RuleChainImport = route.params.ruleChainImport; - return ruleChainImport.ruleChain; - } - }, - { - provide: 'importRuleChainMetadata', - useValue: (route: ActivatedRouteSnapshot) => { - const ruleChainImport: RuleChainImport = route.params.ruleChainImport; - return ruleChainImport.metadata; - } - } + RuleChainImportGuard ] }) export class RuleChainRoutingModule { } diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts index e7c7c4d76a..e725ed8f9a 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts @@ -32,6 +32,8 @@ import {RuleChainService} from '@core/http/rule-chain.service'; import {RuleChainComponent} from '@modules/home/pages/rulechain/rulechain.component'; import {DialogService} from '@core/services/dialog.service'; import { RuleChainTabsComponent } from '@home/pages/rulechain/rulechain-tabs.component'; +import { ImportExportService } from '@home/components/import-export/import-export.service'; +import { ItemBufferService } from '@core/services/item-buffer.service'; @Injectable() export class RuleChainsTableConfigResolver implements Resolve> { @@ -40,6 +42,8 @@ export class RuleChainsTableConfigResolver implements Resolve { + if (ruleChainImport) { + this.itembuffer.storeRuleChainImport(ruleChainImport); + this.router.navigateByUrl(`ruleChains/ruleChain/import`); + } + }); } openRuleChain($event: Event, ruleChain: RuleChain) { @@ -135,8 +143,7 @@ export class RuleChainsTableConfigResolver implements Resolve { return { + getStateParams(): StateParams { + return {}; + } + } as IStateController}, + {}); @ViewChild('dashboard', {static: true}) dashboard: IDashboardComponent; @@ -97,7 +100,9 @@ export class WidgetLibraryComponent extends PageComponent implements OnInit { private dialogService: DialogService, private importExport: ImportExportService, private dialog: MatDialog, - private translate: TranslateService) { + private translate: TranslateService, + private utils: UtilsService, + private entityService: EntityService) { super(store); this.authUser = getCurrentAuthUser(this.store); diff --git a/ui-ngx/src/app/shared/components/entity/entity-type-list.component.ts b/ui-ngx/src/app/shared/components/entity/entity-type-list.component.ts index 97cec76f72..829f066c02 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-type-list.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-type-list.component.ts @@ -190,7 +190,7 @@ export class EntityTypeListComponent implements ControlValueAccessor, OnInit, Af this.entityTypeList = []; value.forEach((entityType) => { this.entityTypeList.push({ - name: this.translate.instant(entityTypeTranslations.get(entityType).type), + name: entityTypeTranslations.has(entityType) ? this.translate.instant(entityTypeTranslations.get(entityType).type) : 'Unknown', value: entityType }); }); diff --git a/ui-ngx/src/app/shared/models/alarm.models.ts b/ui-ngx/src/app/shared/models/alarm.models.ts index 6d408e1d22..f014f865a7 100644 --- a/ui-ngx/src/app/shared/models/alarm.models.ts +++ b/ui-ngx/src/app/shared/models/alarm.models.ts @@ -22,6 +22,8 @@ import {AlarmId} from '@shared/models/id/alarm-id'; import {EntityId} from '@shared/models/id/entity-id'; import { ActionStatus } from '@shared/models/audit-log.models'; import { TimePageLink } from '@shared/models/page/page-link'; +import { NULL_UUID } from '@shared/models/id/has-uuid'; +import { EntityType } from '@shared/models/entity-type.models'; export enum AlarmSeverity { CRITICAL = 'CRITICAL', @@ -103,6 +105,27 @@ export interface AlarmInfo extends Alarm { originatorName: string; } +export const simulatedAlarm: AlarmInfo = { + tenantId: new TenantId(NULL_UUID), + createdTime: new Date().getTime(), + startTs: new Date().getTime(), + endTs: 0, + ackTs: 0, + clearTs: 0, + originatorName: 'Simulated', + originator: { + entityType: EntityType.DEVICE, + id: "1" + }, + type: 'TEMPERATURE', + severity: AlarmSeverity.MAJOR, + status: AlarmStatus.ACTIVE_UNACK, + details: { + message: "Temperature is high!" + }, + propagate: false +}; + export interface AlarmField { keyName: string; value: string; diff --git a/ui-ngx/src/app/shared/models/event.models.ts b/ui-ngx/src/app/shared/models/event.models.ts index a8f8927a99..b19a376109 100644 --- a/ui-ngx/src/app/shared/models/event.models.ts +++ b/ui-ngx/src/app/shared/models/event.models.ts @@ -14,15 +14,11 @@ /// limitations under the License. /// -import { ActionStatus, ActionType } from '@shared/models/audit-log.models'; import { BaseData } from '@shared/models/base-data'; -import { AuditLogId } from '@shared/models/id/audit-log-id'; import { TenantId } from '@shared/models/id/tenant-id'; -import { CustomerId } from '@shared/models/id/customer-id'; import { EntityId } from '@shared/models/id/entity-id'; -import { UserId } from '@shared/models/id/user-id'; import { EventId } from './id/event-id'; -import { MsgDataType } from './rule-node.models'; +import { ContentType } from '@shared/models/constants'; export enum EventType { ERROR = 'ERROR', @@ -72,7 +68,7 @@ export interface DebugRuleNodeEventBody extends BaseEventBody { msgId: string; msgType: string; relationType: string; - dataType: MsgDataType; + dataType: ContentType; data: string; metadata: string; error: string; diff --git a/ui-ngx/src/app/shared/models/page/page-link.ts b/ui-ngx/src/app/shared/models/page/page-link.ts index e4e35a481e..307d1e4e3f 100644 --- a/ui-ngx/src/app/shared/models/page/page-link.ts +++ b/ui-ngx/src/app/shared/models/page/page-link.ts @@ -94,14 +94,15 @@ export class PageLink { pageData.data = pageData.data.filter((entity) => searchFunction(entity, this.textSearch)); } pageData.totalElements = pageData.data.length; - pageData.totalPages = Math.ceil(pageData.totalElements / this.pageSize); + pageData.totalPages = this.pageSize === Number.POSITIVE_INFINITY ? 1 : Math.ceil(pageData.totalElements / this.pageSize); if (this.sortOrder) { pageData.data = pageData.data.sort((a, b) => this.sort(a, b)); } - const startIndex = this.pageSize * this.page; - const endIndex = startIndex + this.pageSize; - pageData.data = pageData.data.slice(startIndex, startIndex + this.pageSize); - pageData.hasNext = pageData.totalElements > startIndex + pageData.data.length; + if (this.pageSize !== Number.POSITIVE_INFINITY) { + const startIndex = this.pageSize * this.page; + pageData.data = pageData.data.slice(startIndex, startIndex + this.pageSize); + pageData.hasNext = pageData.totalElements > startIndex + pageData.data.length; + } return pageData; } diff --git a/ui-ngx/src/app/shared/models/page/sort-order.ts b/ui-ngx/src/app/shared/models/page/sort-order.ts index b51791ca7d..c1511e07f8 100644 --- a/ui-ngx/src/app/shared/models/page/sort-order.ts +++ b/ui-ngx/src/app/shared/models/page/sort-order.ts @@ -24,3 +24,19 @@ export enum Direction { ASC = 'ASC', DESC = 'DESC' } + +export function sortOrderFromString(strSortOrder: string): SortOrder { + let property: string; + let direction = Direction.ASC; + if (strSortOrder.startsWith('-')) { + direction = Direction.DESC; + property = strSortOrder.substring(1); + } else { + if (strSortOrder.startsWith('+')) { + property = strSortOrder.substring(1); + } else { + property = strSortOrder; + } + } + return {property, direction}; +} diff --git a/ui-ngx/src/app/shared/models/rule-chain.models.ts b/ui-ngx/src/app/shared/models/rule-chain.models.ts index 3f6c205e08..8596bf4ba7 100644 --- a/ui-ngx/src/app/shared/models/rule-chain.models.ts +++ b/ui-ngx/src/app/shared/models/rule-chain.models.ts @@ -33,7 +33,7 @@ export interface RuleChain extends BaseData { export interface RuleChainMetaData { ruleChainId: RuleChainId; - firstNodeIndex: number; + firstNodeIndex?: number; nodes: Array; connections: Array; ruleChainConnections: Array; @@ -45,7 +45,8 @@ export interface ResolvedRuleChainMetaData extends RuleChainMetaData { export interface RuleChainImport { ruleChain: RuleChain; - metadata: ResolvedRuleChainMetaData; + metadata: RuleChainMetaData; + resolvedMetadata?: ResolvedRuleChainMetaData; } export interface NodeConnectionInfo { 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 e80e43a03b..082d82f40b 100644 --- a/ui-ngx/src/app/shared/models/rule-node.models.ts +++ b/ui-ngx/src/app/shared/models/rule-node.models.ts @@ -26,18 +26,12 @@ import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { AbstractControl, FormGroup } from '@angular/forms'; -export enum MsgDataType { - JSON = 'JSON', - TEXT = 'TEXT', - BINARY = 'BINARY' -} - export interface RuleNodeConfiguration { [key: string]: any; } export interface RuleNode extends BaseData { - ruleChainId: RuleChainId; + ruleChainId?: RuleChainId; type: string; name: string; debugMode: boolean; diff --git a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts index d0c837332a..cf2f9d93db 100644 --- a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts +++ b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts @@ -229,7 +229,7 @@ export interface TelemetryService { export class TelemetrySubscriber { - private dataSubject = new ReplaySubject(); + private dataSubject = new ReplaySubject(1); private reconnectSubject = new Subject(); private zone: NgZone; diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index ab42427e58..32ec5316af 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -363,8 +363,6 @@ export interface WidgetConfig { datasources?: Array; targetDeviceAliasIds?: Array; [key: string]: any; - - // TODO: } export interface Widget { diff --git a/ui-ngx/src/styles.scss b/ui-ngx/src/styles.scss index 73180b4eae..e5d7b04f67 100644 --- a/ui-ngx/src/styles.scss +++ b/ui-ngx/src/styles.scss @@ -509,10 +509,10 @@ mat-label { .mat-row { transition: background-color .2s; &:hover:not(.tb-current-entity) { - background-color: #f4f4f4; + background-color: rgba(221, 221, 221, 0.3); } &.tb-current-entity { - background-color: #e9e9e9; + background-color: rgba(221, 221, 221, 0.65); } } @@ -561,6 +561,9 @@ mat-label { text-overflow: ellipsis; white-space: nowrap; } + &.mat-table-sticky { + background: transparent; + } } .mat-cell, .mat-footer-cell { From 68954f8db18fa5fdf4aac20e7c12137fbd0d5bf1 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 22 Jan 2020 20:11:00 +0200 Subject: [PATCH 076/133] Minor improvements --- ui-ngx/src/app/core/services/utils.service.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ui-ngx/src/app/core/services/utils.service.ts b/ui-ngx/src/app/core/services/utils.service.ts index e5c6196412..3ab20bb36d 100644 --- a/ui-ngx/src/app/core/services/utils.service.ts +++ b/ui-ngx/src/app/core/services/utils.service.ts @@ -360,7 +360,10 @@ export class UtilsService { } public createLabelFromDatasource(datasource: Datasource, pattern: string) { - let label = deepClone(pattern); + let label = pattern; + if (!datasource) { + return label; + } let match = varsRegex.exec(pattern); while (match !== null) { const variable = match[0]; From 8f4944fe170ed29b1415f071fdbd52351bcdd95b Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 24 Jan 2020 19:05:41 +0200 Subject: [PATCH 077/133] Material table improvements. Alarm widget implementation. --- .../system/widget_bundles/alarm_widgets.json | 6 +- ui-ngx/src/app/app.component.ts | 25 +- ui-ngx/src/app/core/api/widget-api.models.ts | 3 +- ui-ngx/src/app/core/auth/auth.service.ts | 12 - ui-ngx/src/app/core/utils.ts | 4 + .../components/alarm/alarm-table-config.ts | 8 +- .../audit-log/audit-log-table-config.ts | 10 +- .../entity/entities-table.component.html | 12 +- .../entity/entities-table.component.ts | 8 +- .../components/event/event-table-config.ts | 31 +- .../lib/alarms-table-widget.component.html | 135 +++ .../lib/alarms-table-widget.component.scss | 19 + .../lib/alarms-table-widget.component.ts | 770 ++++++++++++++++++ .../lib/entities-table-widget.component.html | 6 +- .../lib/entities-table-widget.component.scss | 20 - .../lib/entities-table-widget.component.ts | 224 ++--- .../widget/lib/table-widget.models.ts | 206 ++++- .../components/widget/lib/table-widget.scss | 61 ++ .../widget/widget-components.module.ts | 7 +- .../components/widget/widget.component.ts | 1 + .../entity/entities-table-config.models.ts | 17 +- .../home/models/widget-component.models.ts | 17 +- .../asset/assets-table-config.resolver.ts | 6 +- .../customers-table-config.resolver.ts | 8 +- .../dashboards-table-config.resolver.ts | 4 +- .../device/devices-table-config.resolver.ts | 8 +- .../entity-views-table-config.resolver.ts | 6 +- .../tenant/tenants-table-config.resolver.ts | 8 +- .../pages/user/users-table-config.resolver.ts | 6 +- .../widgets-bundles-table-config.resolver.ts | 2 +- .../components/breadcrumb.component.scss | 1 + ui-ngx/src/app/shared/models/alarm.models.ts | 1 + .../src/app/shared/models/page/page-link.ts | 5 +- ui-ngx/src/styles.scss | 90 +- 34 files changed, 1444 insertions(+), 303 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/table-widget.scss diff --git a/application/src/main/data/json/system/widget_bundles/alarm_widgets.json b/application/src/main/data/json/system/widget_bundles/alarm_widgets.json index d811393c4b..e250e744ad 100644 --- a/application/src/main/data/json/system/widget_bundles/alarm_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/alarm_widgets.json @@ -13,10 +13,10 @@ "sizeX": 10.5, "sizeY": 6.5, "resources": [], - "templateHtml": "\n", + "templateHtml": "\n", "templateCss": "", - "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n var id = self.ctx.$scope.$injector.get('utils').guid();\n scope.tableId = \"table-\"+id;\n scope.ctx = self.ctx;\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.$broadcast('alarms-table-data-updated', self.ctx.$scope.tableId);\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n", - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"AlarmTableSettings\",\n \"properties\": {\n \"alarmsTitle\": {\n \"title\": \"Alarms table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSelection\": {\n \"title\": \"Enable alarms selection\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSearch\": {\n \"title\": \"Enable alarms search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayDetails\": {\n \"title\": \"Display alarm details\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"allowAcknowledgment\": {\n \"title\": \"Allow alarms acknowledgment\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"allowClear\": {\n \"title\": \"Allow alarms clear\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"-createdTime\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"alarmsTitle\",\n \"enableSelection\",\n \"enableSearch\",\n \"displayDetails\",\n \"allowAcknowledgment\",\n \"allowClear\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\"\n ]\n}", + "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.alarmsTableWidget.onDataUpdated();\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"AlarmTableSettings\",\n \"properties\": {\n \"alarmsTitle\": {\n \"title\": \"Alarms table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSelection\": {\n \"title\": \"Enable alarms selection\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSearch\": {\n \"title\": \"Enable alarms search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSelectColumnDisplay\": {\n \"title\": \"Enable select columns to display\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStatusFilter\": {\n \"title\": \"Enable alarm status filter\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayDetails\": {\n \"title\": \"Display alarm details\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"allowAcknowledgment\": {\n \"title\": \"Allow alarms acknowledgment\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"allowClear\": {\n \"title\": \"Allow alarms clear\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"-createdTime\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"alarmsTitle\",\n \"enableSelection\",\n \"enableSearch\",\n \"enableSelectColumnDisplay\",\n \"enableStatusFilter\",\n \"displayDetails\",\n \"allowAcknowledgment\",\n \"allowClear\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\"\n ]\n}", "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, alarm, filter)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSelection\":true,\"enableSearch\":true,\"displayDetails\":true,\"allowAcknowledgment\":true,\"allowClear\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"-createdTime\"},\"title\":\"Alarms table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"alarmSource\":{\"type\":\"function\",\"dataKeys\":[{\"name\":\"createdTime\",\"type\":\"alarm\",\"label\":\"Created time\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.021092237451093787},{\"name\":\"originator\",\"type\":\"alarm\",\"label\":\"Originator\",\"color\":\"#4caf50\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.2780007688856758},{\"name\":\"type\",\"type\":\"alarm\",\"label\":\"Type\",\"color\":\"#f44336\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.7323586880398418},{\"name\":\"severity\",\"type\":\"alarm\",\"label\":\"Severity\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":false,\"useCellContentFunction\":false},\"_hash\":0.09927019860088193},{\"name\":\"status\",\"type\":\"alarm\",\"label\":\"Status\",\"color\":\"#607d8b\",\"settings\":{\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6588418951443418}],\"entityAliasId\":null,\"name\":\"alarms\"},\"alarmSearchStatus\":\"ANY\",\"alarmsPollingInterval\":5}" } diff --git a/ui-ngx/src/app/app.component.ts b/ui-ngx/src/app/app.component.ts index ef30d0209f..2a0c78585f 100644 --- a/ui-ngx/src/app/app.component.ts +++ b/ui-ngx/src/app/app.component.ts @@ -19,11 +19,15 @@ import { Component, OnInit } from '@angular/core'; import { environment as env } from '@env/environment'; import { TranslateService } from '@ngx-translate/core'; -import { Store } from '@ngrx/store'; +import { select, Store } from '@ngrx/store'; import { AppState } from './core/core.state'; import { LocalStorageService } from './core/local-storage/local-storage.service'; import { DomSanitizer } from '@angular/platform-browser'; import { MatIconRegistry } from '@angular/material'; +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'; @Component({ selector: 'tb-root', @@ -36,7 +40,8 @@ export class AppComponent implements OnInit { private storageService: LocalStorageService, private translate: TranslateService, private matIconRegistry: MatIconRegistry, - private domSanitizer: DomSanitizer) { + private domSanitizer: DomSanitizer, + private authService: AuthService) { console.log(`ThingsBoard Version: ${env.tbVersion}`); @@ -56,6 +61,7 @@ export class AppComponent implements OnInit { this.storageService.testLocalStorage(); this.setupTranslate(); + this.setupAuth(); } setupTranslate() { @@ -69,6 +75,21 @@ export class AppComponent implements OnInit { this.translate.setDefaultLang(env.defaultLang); } + setupAuth() { + combineLatest([ + this.store.pipe(select(selectIsAuthenticated)), + this.store.pipe(select(selectIsUserLoaded))] + ).pipe( + map(results => ({isAuthenticated: results[0], isUserLoaded: results[1]})), + distinctUntilChanged(), + filter((data) => data.isUserLoaded ), + skip(1), + ).subscribe((data) => { + this.authService.gotoDefaultPlace(data.isAuthenticated); + }); + this.authService.reloadUser(); + } + ngOnInit() { } diff --git a/ui-ngx/src/app/core/api/widget-api.models.ts b/ui-ngx/src/app/core/api/widget-api.models.ts index de0d82f592..d9365a50cc 100644 --- a/ui-ngx/src/app/core/api/widget-api.models.ts +++ b/ui-ngx/src/app/core/api/widget-api.models.ts @@ -30,7 +30,7 @@ import { AlarmService } from '../http/alarm.service'; import { UtilsService } from '@core/services/utils.service'; import { Timewindow, WidgetTimewindow } from '@shared/models/time/time.models'; import { EntityType } from '@shared/models/entity-type.models'; -import { AlarmSearchStatus } from '@shared/models/alarm.models'; +import { AlarmInfo, AlarmSearchStatus } from '@shared/models/alarm.models'; import { HttpErrorResponse } from '@angular/common/http'; import { DatasourceService } from '@core/api/datasource.service'; import { RafService } from '@core/services/raf.service'; @@ -226,6 +226,7 @@ export interface IWidgetSubscription { timeWindowConfig?: Timewindow; timeWindow?: WidgetTimewindow; + alarms?: Array; alarmSource?: Datasource; alarmSearchStatus?: AlarmSearchStatus; alarmsPollingInterval?: number; diff --git a/ui-ngx/src/app/core/auth/auth.service.ts b/ui-ngx/src/app/core/auth/auth.service.ts index 03253918fa..171345a20c 100644 --- a/ui-ngx/src/app/core/auth/auth.service.ts +++ b/ui-ngx/src/app/core/auth/auth.service.ts @@ -62,18 +62,6 @@ export class AuthService { private adminService: AdminService, private translate: TranslateService ) { - combineLatest( - this.store.pipe(select(selectIsAuthenticated)), - this.store.pipe(select(selectIsUserLoaded)) - ).pipe( - map(results => ({isAuthenticated: results[0], isUserLoaded: results[1]})), - distinctUntilChanged(), - filter((data) => data.isUserLoaded ), - skip(1), - ).subscribe((data) => { - this.gotoDefaultPlace(data.isAuthenticated); - }); - this.reloadUser(); } redirectUrl: string; diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index e58e3dd400..0d5af8c8cc 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -398,3 +398,7 @@ export function snakeCase(name: string, separator: string): string { return (pos ? separator : '') + letter.toLowerCase(); }); } + +export function getDescendantProp(obj: any, path: string): any { + return path.split('.').reduce((acc, part) => acc && acc[part], obj) +} diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts index 632cd8ecd3..3a1ad3762b 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts @@ -84,19 +84,19 @@ export class AlarmTableConfig extends EntityTableConfig this.columns.push( new DateEntityTableColumn('createdTime', 'alarm.created-time', this.datePipe, '150px')); this.columns.push( - new EntityTableColumn('originatorName', 'alarm.originator', '100%', + new EntityTableColumn('originatorName', 'alarm.originator', '25%', (entity) => entity.originatorName, entity => ({}), false)); this.columns.push( - new EntityTableColumn('type', 'alarm.type', '100%')); + new EntityTableColumn('type', 'alarm.type', '25%')); this.columns.push( - new EntityTableColumn('severity', 'alarm.severity', '100%', + new EntityTableColumn('severity', 'alarm.severity', '25%', (entity) => this.translate.instant(alarmSeverityTranslations.get(entity.severity)), entity => ({ fontWeight: 'bold', color: alarmSeverityColors.get(entity.severity) }))); this.columns.push( - new EntityTableColumn('status', 'alarm.status', '100%', + new EntityTableColumn('status', 'alarm.status', '25%', (entity) => this.translate.instant(alarmStatusTranslations.get(entity.status)))); this.cellActionDescriptors.push( diff --git a/ui-ngx/src/app/modules/home/components/audit-log/audit-log-table-config.ts b/ui-ngx/src/app/modules/home/components/audit-log/audit-log-table-config.ts index 26ad0f2edd..8f09a3e12c 100644 --- a/ui-ngx/src/app/modules/home/components/audit-log/audit-log-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/audit-log/audit-log-table-config.ts @@ -83,22 +83,22 @@ export class AuditLogTableConfig extends EntityTableConfig('entityType', 'audit-log.entity-type', '100%', + new EntityTableColumn('entityType', 'audit-log.entity-type', '20%', (entity) => translate.instant(entityTypeTranslations.get(entity.entityId.entityType).type)), - new EntityTableColumn('entityName', 'audit-log.entity-name'), + new EntityTableColumn('entityName', 'audit-log.entity-name', '20%'), ); } if (this.auditLogMode !== AuditLogMode.USER) { this.columns.push( - new EntityTableColumn('userName', 'audit-log.user') + new EntityTableColumn('userName', 'audit-log.user', '33%') ); } this.columns.push( - new EntityTableColumn('actionType', 'audit-log.type', '100%', + new EntityTableColumn('actionType', 'audit-log.type', '33%', (entity) => translate.instant(actionTypeTranslations.get(entity.actionType))), - new EntityTableColumn('actionStatus', 'audit-log.status', '100%', + new EntityTableColumn('actionStatus', 'audit-log.status', '33%', (entity) => translate.instant(actionStatusTranslations.get(entity.actionStatus))) ); 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 23eec24073..66b3ed430f 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 @@ -153,8 +153,10 @@ - {{ column.title | translate }} - {{ column.title | translate }} + - + {{ entitiesTableConfig.actionsColumnTitle ? (entitiesTableConfig.actionsColumnTitle | translate) : '' }} - +
-
+
+ +   + + + +
+ + +
+ + {{ translate.get('alarm.selected-alarms', + {count: alarmsDatasource.selection.selected.length}) | async }} + + + + +
+
+
+ + + + + + + + + + + + + {{ column.title }} + + + + + + + +
+ +
+
+ + + + +
+
+
+ + +
+ alarm.no-alarms-prompt +
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.scss new file mode 100644 index 0000000000..cafc50605a --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.scss @@ -0,0 +1,19 @@ +/** + * Copyright © 2016-2019 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 { + width: 100%; + height: 100%; +} 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 new file mode 100644 index 0000000000..5ac66cbee0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts @@ -0,0 +1,770 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + AfterViewInit, + Component, + ElementRef, + EventEmitter, + Input, + NgZone, + OnInit, + ViewChild, + ViewContainerRef +} from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { WidgetAction, WidgetContext } from '@home/models/widget-component.models'; +import { Datasource, WidgetActionDescriptor, WidgetConfig } from '@shared/models/widget.models'; +import { IWidgetSubscription } from '@core/api/widget-api.models'; +import { UtilsService } from '@core/services/utils.service'; +import { TranslateService } from '@ngx-translate/core'; +import { deepClone, isDefined, isNumber } from '@core/utils'; +import cssjs from '@core/css/css'; +import { PageLink } from '@shared/models/page/page-link'; +import { Direction, SortOrder, sortOrderFromString } from '@shared/models/page/sort-order'; +import { DataSource } from '@angular/cdk/typings/collections'; +import { CollectionViewer, SelectionModel } from '@angular/cdk/collections'; +import { BehaviorSubject, fromEvent, merge, Observable, of } from 'rxjs'; +import { emptyPageData, PageData } from '@shared/models/page/page-data'; +import { entityTypeTranslations } from '@shared/models/entity-type.models'; +import { catchError, debounceTime, distinctUntilChanged, map, take, tap } from 'rxjs/operators'; +import { MatPaginator } from '@angular/material/paginator'; +import { MatSort } from '@angular/material/sort'; +import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; +import { + CellContentInfo, + CellStyleInfo, + constructTableCssString, + DisplayColumn, + EntityColumn, + fromAlarmColumnDef, + getAlarmValue, + getCellContentInfo, + getCellStyleInfo, + getColumnWidth, + TableWidgetDataKeySettings, + TableWidgetSettings, + toAlarmColumnDef +} from '@home/components/widget/lib/table-widget.models'; +import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; +import { ComponentPortal, PortalInjector } from '@angular/cdk/portal'; +import { + DISPLAY_COLUMNS_PANEL_DATA, + DisplayColumnsPanelComponent, + DisplayColumnsPanelData +} from '@home/components/widget/lib/display-columns-panel.component'; +import { + alarmFields, + AlarmInfo, + alarmSeverityColors, + alarmSeverityTranslations, + AlarmStatus, + alarmStatusTranslations +} from '@shared/models/alarm.models'; +import { DatePipe } from '@angular/common'; + +interface AlarmsTableWidgetSettings extends TableWidgetSettings { + alarmsTitle: string; + enableSelection: boolean; + enableStatusFilter: boolean; + displayDetails: boolean; + allowAcknowledgment: boolean; + allowClear: boolean; +} + +interface AlarmsTableDataKeySettings extends TableWidgetDataKeySettings { +} + +interface AlarmWidgetActionDescriptor extends WidgetActionDescriptor { + details?: boolean; + acknowledge?: boolean; + clear?: boolean; +} + +@Component({ + selector: 'tb-alarms-table-widget', + templateUrl: './alarms-table-widget.component.html', + styleUrls: ['./alarms-table-widget.component.scss', './table-widget.scss'] +}) +export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, AfterViewInit { + + @Input() + ctx: WidgetContext; + + @ViewChild('searchInput', {static: false}) searchInputField: ElementRef; + @ViewChild(MatPaginator, {static: false}) paginator: MatPaginator; + @ViewChild(MatSort, {static: false}) sort: MatSort; + + public enableSelection = true; + public displayPagination = true; + public pageSizeOptions; + public pageLink: PageLink; + public sortOrderProperty: string; + public textSearchMode = false; + public columns: Array = []; + public displayedColumns: string[] = []; + public actionCellDescriptors: AlarmWidgetActionDescriptor[] = []; + public alarmsDatasource: AlarmsDatasource; + + private settings: AlarmsTableWidgetSettings; + private widgetConfig: WidgetConfig; + private subscription: IWidgetSubscription; + private alarmSource: Datasource; + + private displayDetails = true; + private allowAcknowledgment = true; + private allowClear = true; + + private defaultPageSize = 10; + private defaultSortOrder = '-' + alarmFields.createdTime.value; + + private contentsInfo: {[key: string]: CellContentInfo} = {}; + private stylesInfo: {[key: string]: CellStyleInfo} = {}; + private columnWidth: {[key: string]: string} = {}; + + private searchAction: WidgetAction = { + name: 'action.search', + show: true, + icon: 'search', + onAction: () => { + this.enterFilterMode(); + } + }; + + private columnDisplayAction: WidgetAction = { + name: 'entity.columns-to-display', + show: true, + icon: 'view_column', + onAction: ($event) => { + this.editColumnsToDisplay($event); + } + }; + + private statusFilterAction: WidgetAction = { + name: 'alarm.alarm-status-filter', + show: true, + onAction: ($event) => { + this.editAlarmStatusFilter($event); + }, + icon: 'filter_list' + }; + + constructor(protected store: Store, + private elementRef: ElementRef, + private ngZone: NgZone, + private overlay: Overlay, + private viewContainerRef: ViewContainerRef, + private utils: UtilsService, + public translate: TranslateService, + private domSanitizer: DomSanitizer, + private datePipe: DatePipe) { + super(store); + + const sortOrder: SortOrder = sortOrderFromString(this.defaultSortOrder); + this.pageLink = new PageLink(this.defaultPageSize, 0, null, sortOrder); + } + + ngOnInit(): void { + this.ctx.$scope.alarmsTableWidget = this; + this.settings = this.ctx.settings; + this.widgetConfig = this.ctx.widgetConfig; + this.subscription = this.ctx.defaultSubscription; + this.alarmSource = this.subscription.alarmSource; + this.initializeConfig(); + this.updateAlarmSource(); + this.ctx.updateWidgetParams(); + } + + ngAfterViewInit(): void { + fromEvent(this.searchInputField.nativeElement, 'keyup') + .pipe( + debounceTime(150), + distinctUntilChanged(), + tap(() => { + if (this.displayPagination) { + this.paginator.pageIndex = 0; + } + this.updateData(); + }) + ) + .subscribe(); + + if (this.displayPagination) { + this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0); + } + (this.displayPagination ? merge(this.sort.sortChange, this.paginator.page) : this.sort.sortChange) + .pipe( + tap(() => this.updateData()) + ) + .subscribe(); + this.updateData(); + } + + public onDataUpdated() { + this.ngZone.run(() => { + this.alarmsDatasource.updateAlarms(this.subscription.alarms); + this.ctx.detectChanges(); + }); + } + + private initializeConfig() { + this.ctx.widgetActions = [this.searchAction, this.statusFilterAction, this.columnDisplayAction]; + + this.displayDetails = isDefined(this.settings.displayDetails) ? this.settings.displayDetails : true; + this.allowAcknowledgment = isDefined(this.settings.allowAcknowledgment) ? this.settings.allowAcknowledgment : true; + this.allowClear = isDefined(this.settings.allowClear) ? this.settings.allowClear : true; + + if (this.displayDetails) { + this.actionCellDescriptors.push( + { + displayName: this.translate.instant('alarm.details'), + icon: 'more_horiz', + details: true + } as AlarmWidgetActionDescriptor + ); + } + + if (this.allowAcknowledgment) { + this.actionCellDescriptors.push( + { + displayName: this.translate.instant('alarm.acknowledge'), + icon: 'done', + acknowledge: true + } as AlarmWidgetActionDescriptor + ); + } + + if (this.allowClear) { + this.actionCellDescriptors.push( + { + displayName: this.translate.instant('alarm.clear'), + icon: 'clear', + clear: true + } as AlarmWidgetActionDescriptor + ); + } + + this.actionCellDescriptors = this.actionCellDescriptors.concat(this.ctx.actionsApi.getActionDescriptors('actionCellButton')); + + let alarmsTitle: string; + + if (this.settings.alarmsTitle && this.settings.alarmsTitle.length) { + alarmsTitle = this.utils.customTranslation(this.settings.alarmsTitle, this.settings.alarmsTitle); + } else { + alarmsTitle = this.translate.instant('alarm.alarms'); + } + + this.ctx.widgetTitle = this.utils.createLabelFromDatasource(this.alarmSource, alarmsTitle); + + this.enableSelection = isDefined(this.settings.enableSelection) ? this.settings.enableSelection : true; + if (!this.allowAcknowledgment && !this.allowClear) { + this.enableSelection = false; + } + + this.searchAction.show = isDefined(this.settings.enableSearch) ? this.settings.enableSearch : true; + this.displayPagination = isDefined(this.settings.displayPagination) ? this.settings.displayPagination : true; + this.columnDisplayAction.show = isDefined(this.settings.enableSelectColumnDisplay) ? this.settings.enableSelectColumnDisplay : true; + this.statusFilterAction.show = isDefined(this.settings.enableStatusFilter) ? this.settings.enableStatusFilter : true; + + const pageSize = this.settings.defaultPageSize; + if (isDefined(pageSize) && isNumber(pageSize) && pageSize > 0) { + this.defaultPageSize = pageSize; + } + this.pageSizeOptions = [this.defaultPageSize, this.defaultPageSize*2, this.defaultPageSize*3]; + this.pageLink.pageSize = this.displayPagination ? this.defaultPageSize : Number.POSITIVE_INFINITY; + + const cssString = constructTableCssString(this.widgetConfig); + const cssParser = new cssjs(); + cssParser.testMode = false; + const namespace = 'alarms-table-' + this.utils.hashCode(cssString); + cssParser.cssPreviewNamespace = namespace; + cssParser.createStyleElement(namespace, cssString); + $(this.elementRef.nativeElement).addClass(namespace); + } + + private updateAlarmSource() { + + if (this.enableSelection) { + this.displayedColumns.push('select'); + } + + if (this.alarmSource) { + this.alarmSource.dataKeys.forEach((_dataKey) => { + const dataKey: EntityColumn = deepClone(_dataKey) as EntityColumn; + dataKey.title = this.utils.customTranslation(dataKey.label, dataKey.label); + dataKey.def = 'def' + this.columns.length; + const keySettings: AlarmsTableDataKeySettings = dataKey.settings; + + this.stylesInfo[dataKey.def] = getCellStyleInfo(keySettings); + this.contentsInfo[dataKey.def] = getCellContentInfo(keySettings, 'value, alarm, ctx'); + this.columnWidth[dataKey.def] = getColumnWidth(keySettings); + this.columns.push(dataKey); + }); + this.displayedColumns.push(...this.columns.map(column => column.def)); + } + if (this.settings.defaultSortOrder && this.settings.defaultSortOrder.length) { + this.defaultSortOrder = this.settings.defaultSortOrder; + } + this.pageLink.sortOrder = sortOrderFromString(this.defaultSortOrder); + this.sortOrderProperty = toAlarmColumnDef(this.pageLink.sortOrder.property, this.columns); + + if (this.actionCellDescriptors.length) { + this.displayedColumns.push('actions'); + } + this.alarmsDatasource = new AlarmsDatasource(); + if (this.enableSelection) { + this.alarmsDatasource.selectionModeChanged$.subscribe((selectionMode) => { + const hideTitlePanel = selectionMode || this.textSearchMode; + if (this.ctx.hideTitlePanel !== hideTitlePanel) { + this.ctx.hideTitlePanel = hideTitlePanel; + this.ctx.detectChanges(true); + } else { + this.ctx.detectChanges(); + } + }); + } + } + + private editColumnsToDisplay($event: Event) { + if ($event) { + $event.stopPropagation(); + } + const target = $event.target || $event.srcElement || $event.currentTarget; + const config = new OverlayConfig(); + config.backdropClass = 'cdk-overlay-transparent-backdrop'; + config.hasBackdrop = true; + const connectedPosition: ConnectedPosition = { + originX: 'end', + originY: 'bottom', + overlayX: 'end', + overlayY: 'top' + }; + config.positionStrategy = this.overlay.position().flexibleConnectedTo(target as HTMLElement) + .withPositions([connectedPosition]); + + const overlayRef = this.overlay.create(config); + overlayRef.backdropClick().subscribe(() => { + overlayRef.dispose(); + }); + + const columns: DisplayColumn[] = this.columns.map(column => { + return { + title: column.title, + def: column.def, + display: this.displayedColumns.indexOf(column.def) > -1 + } + }); + + const injectionTokens = new WeakMap([ + [DISPLAY_COLUMNS_PANEL_DATA, { + columns, + columnsUpdated: (newColumns) => { + this.displayedColumns = newColumns.filter(column => column.display).map(column => column.def); + if (this.enableSelection) { + this.displayedColumns.unshift('select'); + } + this.displayedColumns.push('actions'); + } + } as DisplayColumnsPanelData], + [OverlayRef, overlayRef] + ]); + const injector = new PortalInjector(this.viewContainerRef.injector, injectionTokens); + overlayRef.attach(new ComponentPortal(DisplayColumnsPanelComponent, + this.viewContainerRef, injector)); + this.ctx.detectChanges(); + } + + private editAlarmStatusFilter($event: Event) { + // TODO: + } + + private enterFilterMode() { + this.textSearchMode = true; + this.pageLink.textSearch = ''; + this.ctx.hideTitlePanel = true; + this.ctx.detectChanges(true); + setTimeout(() => { + this.searchInputField.nativeElement.focus(); + this.searchInputField.nativeElement.setSelectionRange(0, 0); + }, 10); + } + + exitFilterMode() { + this.textSearchMode = false; + this.pageLink.textSearch = null; + if (this.displayPagination) { + this.paginator.pageIndex = 0; + } + this.updateData(); + this.ctx.hideTitlePanel = false; + this.ctx.detectChanges(true); + } + + private updateData() { + if (this.displayPagination) { + this.pageLink.page = this.paginator.pageIndex; + this.pageLink.pageSize = this.paginator.pageSize; + } else { + this.pageLink.page = 0; + } + this.pageLink.sortOrder.property = fromAlarmColumnDef(this.sort.active, this.columns); + this.pageLink.sortOrder.direction = Direction[this.sort.direction.toUpperCase()]; + this.alarmsDatasource.loadAlarms(this.pageLink); + this.ctx.detectChanges(); + } + + public trackByColumnDef(index, column: EntityColumn) { + return column.def; + } + + public headerStyle(key: EntityColumn): any { + const columnWidth = this.columnWidth[key.def]; + return { + width: columnWidth + } + } + + public cellStyle(alarm: AlarmInfo, key: EntityColumn): any { + let style: any = {}; + if (alarm && key) { + const styleInfo = this.stylesInfo[key.def]; + const value = getAlarmValue(alarm, key); + if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) { + try { + style = styleInfo.cellStyleFunction(value); + } catch (e) { + style = {}; + } + } else { + style = this.defaultStyle(key, value); + } + } + if (!style.width) { + const columnWidth = this.columnWidth[key.def]; + style.width = columnWidth; + } + return style; + } + + public cellContent(alarm: AlarmInfo, key: EntityColumn): SafeHtml { + let strContent = ''; + if (alarm && key) { + const contentInfo = this.contentsInfo[key.def]; + const value = getAlarmValue(alarm, key); + if (contentInfo.useCellContentFunction && contentInfo.cellContentFunction) { + if (isDefined(value)) { + strContent = '' + value; + } + var content = strContent; + try { + content = contentInfo.cellContentFunction(value, alarm, this.ctx); + } catch (e) { + content = strContent; + } + } else { + content = this.defaultContent(key, value); + } + return this.domSanitizer.bypassSecurityTrustHtml(content); + } else { + return strContent; + } + } + + public onRowClick($event: Event, alarm: AlarmInfo) { + if ($event) { + $event.stopPropagation(); + } + this.alarmsDatasource.toggleCurrentAlarm(alarm); + const descriptors = this.ctx.actionsApi.getActionDescriptors('rowClick'); + if (descriptors.length) { + let entityId; + let entityName; + if (alarm && alarm.originator) { + entityId = alarm.originator; + entityName = alarm.originatorName; + } + this.ctx.actionsApi.handleWidgetAction($event, descriptors[0], entityId, entityName, {alarm}); + } + } + + public onActionButtonClick($event: Event, alarm: AlarmInfo, actionDescriptor: AlarmWidgetActionDescriptor) { + if (actionDescriptor.details) { + this.openAlarmDetails($event, alarm); + } else if (actionDescriptor.acknowledge) { + this.ackAlarm($event, alarm); + } else if (actionDescriptor.clear) { + this.clearAlarm($event, alarm); + } else { + if ($event) { + $event.stopPropagation(); + } + let entityId; + let entityName; + if (alarm && alarm.originator) { + entityId = alarm.originator; + entityName = alarm.originatorName; + } + this.ctx.actionsApi.handleWidgetAction($event, actionDescriptor, entityId, entityName, {alarm}); + } + } + + public actionEnabled(alarm: AlarmInfo, actionDescriptor: AlarmWidgetActionDescriptor): boolean { + if (actionDescriptor.acknowledge) { + return (alarm.status === AlarmStatus.ACTIVE_UNACK || + alarm.status === AlarmStatus.CLEARED_UNACK); + } else if (actionDescriptor.clear) { + return (alarm.status === AlarmStatus.ACTIVE_ACK || + alarm.status === AlarmStatus.ACTIVE_UNACK); + } + return true; + } + + private openAlarmDetails($event: Event, alarm: AlarmInfo) { + if ($event) { + $event.stopPropagation(); + } + // TODO: + } + + private ackAlarm($event: Event, alarm: AlarmInfo) { + if ($event) { + $event.stopPropagation(); + } + // TODO: + } + + public ackAlarms($event: Event) { + if ($event) { + $event.stopPropagation(); + } + // TODO: + } + + private clearAlarm($event: Event, alarm: AlarmInfo) { + if ($event) { + $event.stopPropagation(); + } + // TODO: + } + + public clearAlarms($event: Event) { + if ($event) { + $event.stopPropagation(); + } + // TODO: + } + + private defaultContent(key: EntityColumn, value: any): any { + if (isDefined(value)) { + const alarmField = alarmFields[key.name]; + if (alarmField) { + if (alarmField.time) { + return 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 this.translate.instant(alarmStatusTranslations.get(value)); + } else if (alarmField.value === alarmFields.originatorType.value) { + return this.translate.instant(entityTypeTranslations.get(value).type); + } + else { + return value; + } + } else { + return value; + } + } else { + return ''; + } + } + + private defaultStyle(key: EntityColumn, value: any): any { + if (isDefined(value)) { + const alarmField = alarmFields[key.name]; + if (alarmField) { + if (alarmField.value == alarmFields.severity.value) { + return { + fontWeight: 'bold', + color: alarmSeverityColors.get(value) + }; + } else { + return {}; + } + } else { + return {}; + } + } else { + return {}; + } + } + +} + +class AlarmsDatasource implements DataSource { + + private alarmsSubject = new BehaviorSubject([]); + private pageDataSubject = new BehaviorSubject>(emptyPageData()); + + public selection = new SelectionModel(true, [], false); + + private selectionModeChanged = new EventEmitter(); + + public selectionModeChanged$ = this.selectionModeChanged.asObservable(); + + private allAlarms: Array = []; + private allAlarmsSubject = new BehaviorSubject([]); + private allAlarms$: Observable> = this.allAlarmsSubject.asObservable(); + + private currentAlarm: AlarmInfo = null; + + constructor() { + } + + connect(collectionViewer: CollectionViewer): Observable> { + return this.alarmsSubject.asObservable(); + } + + disconnect(collectionViewer: CollectionViewer): void { + this.alarmsSubject.complete(); + this.pageDataSubject.complete(); + } + + loadAlarms(pageLink: PageLink) { + if (this.selection.hasValue()) { + this.selection.clear(); + this.onSelectionModeChanged(false); + } + this.fetchAlarms(pageLink).pipe( + catchError(() => of(emptyPageData())), + ).subscribe( + (pageData) => { + this.alarmsSubject.next(pageData.data); + this.pageDataSubject.next(pageData); + } + ); + } + + updateAlarms(alarms: AlarmInfo[]) { + alarms.forEach((newAlarm) => { + const existingAlarmIndex = this.allAlarms.findIndex(alarm => alarm.id.id === newAlarm.id.id); + if (existingAlarmIndex > -1) { + Object.assign(this.allAlarms[existingAlarmIndex], newAlarm); + } else { + this.allAlarms.push(newAlarm); + } + }); + for (let i = this.allAlarms.length - 1; i >= 0; i--) { + const oldAlarm = this.allAlarms[i]; + const newAlarmIndex = alarms.findIndex(alarm => alarm.id.id === oldAlarm.id.id); + if (newAlarmIndex === -1) { + this.allAlarms.splice(i, 1); + } + } + if (this.selection.hasValue()) { + const toRemove: AlarmInfo[] = []; + this.selection.selected.forEach((selectedAlarm) => { + const existingAlarm = this.allAlarms.find(alarm => alarm.id.id === selectedAlarm.id.id); + if (!existingAlarm) { + toRemove.push(selectedAlarm); + } + }); + this.selection.deselect(...toRemove); + if (this.selection.isEmpty()) { + this.onSelectionModeChanged(false); + } + } + this.allAlarmsSubject.next(this.allAlarms); + } + + isAllSelected(): Observable { + const numSelected = this.selection.selected.length; + return this.alarmsSubject.pipe( + map((alarms) => numSelected === alarms.length) + ); + } + + isEmpty(): Observable { + return this.alarmsSubject.pipe( + map((alarms) => !alarms.length) + ); + } + + total(): Observable { + return this.pageDataSubject.pipe( + map((pageData) => pageData.totalElements) + ); + } + + toggleSelection(alarm: AlarmInfo) { + const hasValue = this.selection.hasValue(); + this.selection.toggle(alarm); + if (hasValue !== this.selection.hasValue()) { + this.onSelectionModeChanged(this.selection.hasValue()); + } + } + + isSelected(alarm: AlarmInfo): boolean { + return this.selection.isSelected(alarm); + } + + masterToggle() { + this.alarmsSubject.pipe( + tap((alarms) => { + const numSelected = this.selection.selected.length; + if (numSelected === alarms.length) { + this.selection.clear(); + if (numSelected > 0) { + this.onSelectionModeChanged(false); + } + } else { + alarms.forEach(row => { + this.selection.select(row); + }); + if (numSelected === 0) { + this.onSelectionModeChanged(true); + } + } + }), + take(1) + ).subscribe(); + } + + public toggleCurrentAlarm(alarm: AlarmInfo): boolean { + if (this.currentAlarm !== alarm) { + this.currentAlarm = alarm; + return true; + } else { + return false; + } + } + + public isCurrentAlarm(alarm: AlarmInfo): boolean { + return (this.currentAlarm && alarm && this.currentAlarm.id && alarm.id) && + (this.currentAlarm.id.id === alarm.id.id); + } + + private onSelectionModeChanged(selectionMode: boolean) { + this.selectionModeChanged.emit(selectionMode); + } + + private fetchAlarms(pageLink: PageLink): Observable> { + return this.allAlarms$.pipe( + map((data) => pageLink.filterData(data)) + ); + } +} 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 ae779f55be..ba0cb90c0e 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 @@ -15,7 +15,7 @@ limitations under the License. --> -
+
@@ -39,8 +39,8 @@
- + matSort [matSortActive]="sortOrderProperty" [matSortDirection]="(pageLink.sortOrder.direction + '').toLowerCase()" matSortDisableClear> + {{ column.title }} = []; public displayedColumns: string[] = []; @@ -138,6 +143,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni constructor(protected store: Store, private elementRef: ElementRef, + private ngZone: NgZone, private overlay: Overlay, private viewContainerRef: ViewContainerRef, private utils: UtilsService, @@ -185,13 +191,17 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni } public onDataUpdated() { - this.entityDatasource.updateEntitiesData(this.subscription.data); - this.ctx.detectChanges(); + this.ngZone.run(() => { + this.entityDatasource.updateEntitiesData(this.subscription.data); + this.ctx.detectChanges(); + }); } private initializeConfig() { this.ctx.widgetActions = [this.searchAction, this.columnDisplayAction]; + this.actionCellDescriptors = this.ctx.actionsApi.getActionDescriptors('actionCellButton'); + let entitiesTitle: string; if (this.settings.entitiesTitle && this.settings.entitiesTitle.length) { @@ -212,78 +222,9 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni this.defaultPageSize = pageSize; } this.pageSizeOptions = [this.defaultPageSize, this.defaultPageSize*2, this.defaultPageSize*3]; - - if (this.settings.defaultSortOrder && this.settings.defaultSortOrder.length) { - this.defaultSortOrder = this.settings.defaultSortOrder; - } - this.pageLink.pageSize = this.displayPagination ? this.defaultPageSize : Number.POSITIVE_INFINITY; - this.pageLink.sortOrder = sortOrderFromString(this.defaultSortOrder); - - const origColor = this.widgetConfig.color || 'rgba(0, 0, 0, 0.87)'; - const origBackgroundColor = this.widgetConfig.backgroundColor || 'rgb(255, 255, 255)'; - const defaultColor = tinycolor(origColor); - const mdDark = defaultColor.setAlpha(0.87).toRgbString(); - const mdDarkSecondary = defaultColor.setAlpha(0.54).toRgbString(); - const mdDarkDisabled = defaultColor.setAlpha(0.26).toRgbString(); - const mdDarkDivider = defaultColor.setAlpha(0.12).toRgbString(); - - const cssString = - '.mat-input-element::placeholder {\n' + - ' color: ' + mdDarkSecondary + ';\n'+ - '}\n' + - '.mat-input-element::-moz-placeholder {\n' + - ' color: ' + mdDarkSecondary + ';\n'+ - '}\n' + - '.mat-input-element::-webkit-input-placeholder {\n' + - ' color: ' + mdDarkSecondary + ';\n'+ - '}\n' + - '.mat-input-element:-ms-input-placeholder {\n' + - ' color: ' + mdDarkSecondary + ';\n'+ - '}\n' + - 'mat-toolbar.mat-table-toolbar {\n'+ - 'color: ' + mdDark + ';\n'+ - '}\n'+ - 'mat-toolbar.mat-table-toolbar button.mat-icon-button mat-icon {\n'+ - 'color: ' + mdDarkSecondary + ';\n'+ - '}\n'+ - '.mat-table .mat-header-row {\n'+ - 'background-color: ' + origBackgroundColor + ';\n'+ - '}\n'+ - '.mat-table .mat-header-cell {\n'+ - 'color: ' + mdDarkSecondary + ';\n'+ - '}\n'+ - '.mat-table .mat-header-cell .mat-sort-header-arrow {\n'+ - 'color: ' + mdDarkDisabled + ';\n'+ - '}\n'+ - '.mat-table .mat-row, .mat-table .mat-header-row {\n'+ - 'border-bottom-color: '+mdDarkDivider+';\n'+ - '}\n'+ - '.mat-table .mat-row:not(.tb-current-entity):not(:hover) .mat-cell.mat-table-sticky, .mat-table .mat-header-cell.mat-table-sticky {\n'+ - 'background-color: ' + origBackgroundColor + ';\n'+ - '}\n'+ - '.mat-table .mat-cell {\n'+ - 'color: ' + mdDark + ';\n'+ - '}\n'+ - '.mat-table .mat-cell button.mat-icon-button mat-icon {\n'+ - 'color: ' + mdDarkSecondary + ';\n'+ - '}\n'+ - '.mat-divider {\n'+ - 'border-top-color: ' + mdDarkDivider + ';\n'+ - '}\n'+ - '.mat-paginator {\n'+ - 'color: ' + mdDarkSecondary + ';\n'+ - '}\n'+ - '.mat-paginator button.mat-icon-button {\n'+ - 'color: ' + mdDarkSecondary + ';\n'+ - '}\n'+ - '.mat-paginator button.mat-icon-button[disabled][disabled] {\n'+ - 'color: ' + mdDarkDisabled + ';\n'+ - '}\n'+ - '.mat-paginator .mat-select-value {\n'+ - 'color: ' + mdDarkSecondary + ';\n'+ - '}'; + const cssString = constructTableCssString(this.widgetConfig); const cssParser = new cssjs(); cssParser.testMode = false; const namespace = 'entities-table-' + this.utils.hashCode(cssString); @@ -294,8 +235,6 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni private updateDatasources() { - this.actionCellDescriptors = this.ctx.actionsApi.getActionDescriptors('actionCellButton'); - const displayEntityName = isDefined(this.settings.displayEntityName) ? this.settings.displayEntityName : true; let entityNameColumnTitle: string; if (this.settings.entityNameColumnTitle && this.settings.entityNameColumnTitle.length) { @@ -306,11 +245,11 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni const displayEntityType = isDefined(this.settings.displayEntityType) ? this.settings.displayEntityType : true; if (displayEntityName) { - this.displayedColumns.push('entityName'); this.columns.push( { name: 'entityName', label: 'entityName', + def: 'entityName', title: entityNameColumnTitle } as EntityColumn ); @@ -320,14 +259,14 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni this.stylesInfo['entityName'] = { useCellStyleFunction: false }; - this.columnWidth['entityName'] = '100px'; + this.columnWidth['entityName'] = '0px'; } if (displayEntityType) { - this.displayedColumns.push('entityType'); this.columns.push( { name: 'entityType', label: 'entityType', + def: 'entityType', title: this.translate.instant('entity.entity-type'), } as EntityColumn ); @@ -337,7 +276,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni this.stylesInfo['entityType'] = { useCellStyleFunction: false }; - this.columnWidth['entityType'] = '100px'; + this.columnWidth['entityType'] = '0px'; } const dataKeys: Array = []; @@ -353,55 +292,24 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni dataKeys.push(dataKey); dataKey.title = this.utils.customTranslation(dataKey.label, dataKey.label); + dataKey.def = 'def' + this.columns.length; const keySettings: EntitiesTableDataKeySettings = dataKey.settings; - let cellStyleFunction: Function = null; - let useCellStyleFunction = false; - - if (keySettings.useCellStyleFunction === true) { - if (isDefined(keySettings.cellStyleFunction) && keySettings.cellStyleFunction.length > 0) { - try { - cellStyleFunction = new Function('value', keySettings.cellStyleFunction); - useCellStyleFunction = true; - } catch (e) { - cellStyleFunction = null; - useCellStyleFunction = false; - } - } - } - this.stylesInfo[dataKey.label] = { - useCellStyleFunction, - cellStyleFunction - }; - - let cellContentFunction: Function = null; - let useCellContentFunction = false; - - if (keySettings.useCellContentFunction === true) { - if (isDefined(keySettings.cellContentFunction) && keySettings.cellContentFunction.length > 0) { - try { - cellContentFunction = new Function('value, entity, ctx', keySettings.cellContentFunction); - useCellContentFunction = true; - } catch (e) { - cellContentFunction = null; - useCellContentFunction = false; - } - } - } - - this.contentsInfo[dataKey.label] = { - useCellContentFunction, - cellContentFunction, - units: dataKey.units, - decimals: dataKey.decimals - }; - - const columnWidth = isDefined(keySettings.columnWidth) ? keySettings.columnWidth : '0px'; - this.columnWidth[dataKey.label] = columnWidth; - this.displayedColumns.push(dataKey.label); + this.stylesInfo[dataKey.def] = getCellStyleInfo(keySettings); + this.contentsInfo[dataKey.def] = getCellContentInfo(keySettings, 'value, entity, ctx'); + this.contentsInfo[dataKey.def].units = dataKey.units; + this.contentsInfo[dataKey.def].decimals = dataKey.decimals; + this.columnWidth[dataKey.def] = getColumnWidth(keySettings); this.columns.push(dataKey); }); + this.displayedColumns.push(...this.columns.map(column => column.def)); + } + + if (this.settings.defaultSortOrder && this.settings.defaultSortOrder.length) { + this.defaultSortOrder = this.settings.defaultSortOrder; } + this.pageLink.sortOrder = sortOrderFromString(this.defaultSortOrder); + this.sortOrderProperty = toEntityColumnDef(this.pageLink.sortOrder.property, this.columns); if (this.actionCellDescriptors.length) { this.displayedColumns.push('actions'); @@ -435,8 +343,8 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni const columns: DisplayColumn[] = this.columns.map(column => { return { title: column.title, - label: column.label, - display: this.displayedColumns.indexOf(column.label) > -1 + def: column.def, + display: this.displayedColumns.indexOf(column.def) > -1 } }); @@ -444,7 +352,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni [DISPLAY_COLUMNS_PANEL_DATA, { columns, columnsUpdated: (newColumns) => { - this.displayedColumns = newColumns.filter(column => column.display).map(column => column.label); + this.displayedColumns = newColumns.filter(column => column.display).map(column => column.def); this.displayedColumns.push('actions'); } } as DisplayColumnsPanelData], @@ -485,24 +393,27 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni } else { this.pageLink.page = 0; } - this.pageLink.sortOrder.property = this.sort.active; + this.pageLink.sortOrder.property = fromEntityColumnDef(this.sort.active, this.columns); this.pageLink.sortOrder.direction = Direction[this.sort.direction.toUpperCase()]; this.entityDatasource.loadEntities(this.pageLink); this.ctx.detectChanges(); } - public trackByColumnLabel(index, column: EntityColumn) { - return column.label; + public trackByColumnDef(index, column: EntityColumn) { + return column.def; } public headerStyle(key: EntityColumn): any { - return this.widthStyle(key); + const columnWidth = this.columnWidth[key.def]; + return { + width: columnWidth + } } public cellStyle(entity: EntityData, key: EntityColumn): any { let style: any = {}; if (entity && key) { - const styleInfo = this.stylesInfo[key.label]; + const styleInfo = this.stylesInfo[key.def]; const value = getEntityValue(entity, key); if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) { try { @@ -514,20 +425,9 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni style = this.defaultStyle(key, value); } } - const widthStyle = this.widthStyle(key); - style = {...style, ...widthStyle}; - return style; - } - - private widthStyle(key: EntityColumn): any { - let style: any = {}; - const columnWidth = this.columnWidth[key.label]; - if (columnWidth !== "0px") { - style.minWidth = columnWidth; + if (!style.width) { + const columnWidth = this.columnWidth[key.def]; style.width = columnWidth; - } else { - style.minWidth = "auto"; - style.width = "auto"; } return style; } @@ -535,7 +435,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni public cellContent(entity: EntityData, key: EntityColumn): SafeHtml { let strContent = ''; if (entity && key) { - const contentInfo = this.contentsInfo[key.label]; + const contentInfo = this.contentsInfo[key.def]; const value = getEntityValue(entity, key); if (contentInfo.useCellContentFunction && contentInfo.cellContentFunction) { if (isDefined(value)) { 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 07ec5671d0..1926bb72ba 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 @@ -15,7 +15,28 @@ /// import { EntityId } from '@shared/models/id/entity-id'; -import { DataKey } from '@shared/models/widget.models'; +import { DataKey, WidgetConfig } from '@shared/models/widget.models'; +import { getDescendantProp, isDefined } from '@core/utils'; +import { alarmFields, AlarmInfo } from '@shared/models/alarm.models'; +import * as tinycolor_ from 'tinycolor2'; + +const tinycolor = tinycolor_; + +export interface TableWidgetSettings { + enableSearch: boolean; + enableSelectColumnDisplay: boolean; + displayPagination: boolean; + defaultPageSize: number; + defaultSortOrder: string; +} + +export interface TableWidgetDataKeySettings { + columnWidth: string; + useCellStyleFunction: boolean; + cellStyleFunction: string; + useCellContentFunction: boolean; + cellContentFunction: string; +} export interface EntityData { id: EntityId; @@ -25,12 +46,13 @@ export interface EntityData { } export interface EntityColumn extends DataKey { + def: string; title: string; } export interface DisplayColumn { title: string; - label: string; + def: string; display: boolean; } @@ -46,10 +68,186 @@ export interface CellStyleInfo { cellStyleFunction?: Function; } +export function findColumnProperty(searchProperty: string, searchValue: string, columnProperty: string, columns: EntityColumn[]): string { + let res = searchValue; + const column = columns.find(column => column[searchProperty] === searchValue); + if (column) { + res = column[columnProperty]; + } + return res; +} + +export function toEntityColumnDef(label: string, columns: EntityColumn[]): string { + return findColumnProperty('label', label, 'def', columns); +} + +export function fromEntityColumnDef(def: string, columns: EntityColumn[]): string { + return findColumnProperty('def', def, 'label', columns); +} + +export function toAlarmColumnDef(name: string, columns: EntityColumn[]): string { + return findColumnProperty('name', name, 'def', columns); +} + +export function fromAlarmColumnDef(def: string, columns: EntityColumn[]): string { + return findColumnProperty('def', def, 'name', columns); +} + export function getEntityValue(entity: any, key: DataKey): any { return getDescendantProp(entity, key.label); } -export function getDescendantProp(obj: any, path: string): any { - return path.split('.').reduce((acc, part) => acc && acc[part], obj) +export function getAlarmValue(alarm: AlarmInfo, key: EntityColumn) { + const alarmField = alarmFields[key.name]; + if (alarmField) { + return getDescendantProp(alarm, alarmField.value); + } else { + return getDescendantProp(alarm, key.name); + } +} + +export function getCellStyleInfo(keySettings: TableWidgetDataKeySettings): CellStyleInfo { + let cellStyleFunction: Function = null; + let useCellStyleFunction = false; + + if (keySettings.useCellStyleFunction === true) { + if (isDefined(keySettings.cellStyleFunction) && keySettings.cellStyleFunction.length > 0) { + try { + cellStyleFunction = new Function('value', keySettings.cellStyleFunction); + useCellStyleFunction = true; + } catch (e) { + cellStyleFunction = null; + useCellStyleFunction = false; + } + } + } + return { + useCellStyleFunction, + cellStyleFunction + }; +} + +export function getCellContentInfo(keySettings: TableWidgetDataKeySettings, ...args: string[]): CellContentInfo { + let cellContentFunction: Function = null; + let useCellContentFunction = false; + + if (keySettings.useCellContentFunction === true) { + if (isDefined(keySettings.cellContentFunction) && keySettings.cellContentFunction.length > 0) { + try { + cellContentFunction = new Function(...args, keySettings.cellContentFunction); + useCellContentFunction = true; + } catch (e) { + cellContentFunction = null; + useCellContentFunction = false; + } + } + } + return { + cellContentFunction, + useCellContentFunction + }; +} + +export function getColumnWidth(keySettings: TableWidgetDataKeySettings): string { + return isDefined(keySettings.columnWidth) ? keySettings.columnWidth : '0px'; +} + +export function constructTableCssString(widgetConfig: WidgetConfig): string { + const origColor = widgetConfig.color || 'rgba(0, 0, 0, 0.87)'; + const origBackgroundColor = widgetConfig.backgroundColor || 'rgb(255, 255, 255)'; + const currentEntityColor = 'rgba(221, 221, 221, 0.65)'; + const currentEntityStickyColor = tinycolor.mix(origBackgroundColor, + tinycolor(currentEntityColor).setAlpha(1), 65).toRgbString(); + const selectedColor = 'rgba(221, 221, 221, 0.5)'; + const selectedStickyColor = tinycolor.mix(origBackgroundColor, + tinycolor(selectedColor).setAlpha(1), 50).toRgbString(); + const hoverColor = 'rgba(221, 221, 221, 0.3)'; + const hoverStickyColor = tinycolor.mix(origBackgroundColor, + tinycolor(hoverColor).setAlpha(1), 30).toRgbString(); + const defaultColor = tinycolor(origColor); + const mdDark = defaultColor.setAlpha(0.87).toRgbString(); + const mdDarkSecondary = defaultColor.setAlpha(0.54).toRgbString(); + const mdDarkDisabled = defaultColor.setAlpha(0.26).toRgbString(); + const mdDarkDivider = defaultColor.setAlpha(0.12).toRgbString(); + + const cssString = + '.mat-input-element::placeholder {\n' + + ' color: ' + mdDarkSecondary + ';\n'+ + '}\n' + + '.mat-input-element::-moz-placeholder {\n' + + ' color: ' + mdDarkSecondary + ';\n'+ + '}\n' + + '.mat-input-element::-webkit-input-placeholder {\n' + + ' color: ' + mdDarkSecondary + ';\n'+ + '}\n' + + '.mat-input-element:-ms-input-placeholder {\n' + + ' color: ' + mdDarkSecondary + ';\n'+ + '}\n' + + 'mat-toolbar.mat-table-toolbar {\n'+ + 'color: ' + mdDark + ';\n'+ + '}\n'+ + 'mat-toolbar.mat-table-toolbar:not([color="primary"]) button.mat-icon-button mat-icon {\n'+ + 'color: ' + mdDarkSecondary + ';\n'+ + '}\n'+ + '.mat-table .mat-header-row {\n'+ + 'background-color: ' + origBackgroundColor + ';\n'+ + '}\n'+ + '.mat-table .mat-header-cell {\n'+ + 'color: ' + mdDarkSecondary + ';\n'+ + '}\n'+ + '.mat-table .mat-header-cell .mat-sort-header-arrow {\n'+ + 'color: ' + mdDarkDisabled + ';\n'+ + '}\n'+ + '.mat-table .mat-cell, .mat-table .mat-header-cell {\n'+ + 'border-bottom-color: '+mdDarkDivider+';\n'+ + '}\n'+ + '.mat-table .mat-cell .mat-checkbox-frame, .mat-table .mat-header-cell .mat-checkbox-frame {\n'+ + 'border-color: '+mdDarkSecondary+';\n'+ + '}\n'+ + '.mat-table .mat-row .mat-cell.mat-table-sticky {\n'+ + 'transition: background-color .2s;\n'+ + '}\n'+ + '.mat-table .mat-row.tb-current-entity {\n'+ + 'background-color: ' + currentEntityColor + ';\n'+ + '}\n'+ + '.mat-table .mat-row.tb-current-entity .mat-cell.mat-table-sticky {\n'+ + 'background-color: ' + currentEntityStickyColor + ';\n'+ + '}\n'+ + '.mat-table .mat-row:hover:not(.tb-current-entity) {\n'+ + 'background-color: ' + hoverColor + ';\n'+ + '}\n'+ + '.mat-table .mat-row:hover:not(.tb-current-entity) .mat-cell.mat-table-sticky {\n'+ + 'background-color: ' + hoverStickyColor + ';\n'+ + '}\n'+ + '.mat-table .mat-row.mat-row-select.mat-selected:not(.tb-current-entity) {\n'+ + 'background-color: ' + selectedColor + ';\n'+ + '}\n'+ + '.mat-table .mat-row.mat-row-select.mat-selected:not(.tb-current-entity) .mat-cell.mat-table-sticky {\n'+ + 'background-color: ' + selectedStickyColor + ';\n'+ + '}\n'+ + '.mat-table .mat-row .mat-cell.mat-table-sticky, .mat-table .mat-header-cell.mat-table-sticky {\n'+ + 'background-color: ' + origBackgroundColor + ';\n'+ + '}\n'+ + '.mat-table .mat-cell {\n'+ + 'color: ' + mdDark + ';\n'+ + '}\n'+ + '.mat-table .mat-cell button.mat-icon-button mat-icon {\n'+ + 'color: ' + mdDarkSecondary + ';\n'+ + '}\n'+ + '.mat-divider {\n'+ + 'border-top-color: ' + mdDarkDivider + ';\n'+ + '}\n'+ + '.mat-paginator {\n'+ + 'color: ' + mdDarkSecondary + ';\n'+ + '}\n'+ + '.mat-paginator button.mat-icon-button {\n'+ + 'color: ' + mdDarkSecondary + ';\n'+ + '}\n'+ + '.mat-paginator button.mat-icon-button[disabled][disabled] {\n'+ + 'color: ' + mdDarkDisabled + ';\n'+ + '}\n'+ + '.mat-paginator .mat-select-value {\n'+ + 'color: ' + mdDarkSecondary + ';\n'+ + '}'; + return cssString; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.scss b/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.scss new file mode 100644 index 0000000000..4bf18cbd44 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.scss @@ -0,0 +1,61 @@ +/** + * Copyright © 2016-2019 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-table-widget { + .mat-table, .mat-paginator, mat-toolbar.mat-table-toolbar:not([color="primary"]) { + background: transparent; + } + mat-toolbar { + height: 39px; + max-height: 39px; + .mat-toolbar-tools { + height: 39px; + max-height: 39px; + } + } + .table-container { + overflow: auto; + } + + .mat-row:not(.mat-row-select), .mat-header-row:not(.mat-row-select) { + mat-cell:nth-child(n+2):nth-last-child(n+2), mat-footer-cell:nth-child(n+2):nth-last-child(n+2), mat-header-cell:nth-child(n+2):nth-last-child(n+2) { + padding: 0px 5px; + } + } + + .mat-row.mat-row-select, .mat-header-row.mat-row-select { + mat-cell:nth-child(2), mat-footer-cell:nth-child(2), mat-header-cell:nth-child(2) { + padding: 0px 5px; + } + mat-cell:nth-child(n+3):nth-last-child(n+2), mat-footer-cell:nth-child(n+3):nth-last-child(n+2), mat-header-cell:nth-child(n+3):nth-last-child(n+2) { + padding: 0px 5px; + } + } + } +} + +:host-context(.tb-has-timewindow) { + .tb-table-widget { + mat-toolbar { + height: 65px; + max-height: 65px; + .mat-toolbar-tools { + height: 65px; + max-height: 65px; + } + } + } +} 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 cce9d797cb..b965add9f2 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 @@ -21,6 +21,7 @@ import { AlarmDetailsDialogComponent } from '@home/components/alarm/alarm-detail import { LegendComponent } from '@home/components/widget/legend.component'; import { EntitiesTableWidgetComponent } from '@home/components/widget/lib/entities-table-widget.component'; import { DisplayColumnsPanelComponent } from '@home/components/widget/lib/display-columns-panel.component'; +import { AlarmsTableWidgetComponent } from '@home/components/widget/lib/alarms-table-widget.component'; @NgModule({ entryComponents: [ @@ -29,14 +30,16 @@ import { DisplayColumnsPanelComponent } from '@home/components/widget/lib/displa declarations: [ DisplayColumnsPanelComponent, - EntitiesTableWidgetComponent + EntitiesTableWidgetComponent, + AlarmsTableWidgetComponent ], imports: [ CommonModule, SharedModule ], exports: [ - EntitiesTableWidgetComponent + EntitiesTableWidgetComponent, + AlarmsTableWidgetComponent ] }) export class WidgetComponentsModule { } 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 5fc43e8d03..d2cc1bd122 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 @@ -365,6 +365,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI this.handleWidgetException(e); } } + this.widgetContext.destroyed = true; this.destroyDynamicWidgetComponent(); } 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 4c4982d357..ed819866ca 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,7 @@ export class BaseEntityTableColumn> { constructor(public type: EntityTableColumnType, public key: string, public title: string, - public maxWidth: string = '100%', + public width: string = '0px', public sortable: boolean = true) { } } @@ -83,13 +83,14 @@ export class BaseEntityTableColumn> { export class EntityTableColumn> extends BaseEntityTableColumn { constructor(public key: string, public title: string, - public maxWidth: string = '100%', + public width: string = '0px', public cellContentFunction: CellContentFunction = (entity, property) => entity[property], public cellStyleFunction: CellStyleFunction = () => ({}), public sortable: boolean = true, public headerCellStyleFunction: HeaderCellStyleFunction = () => ({}), - public cellTooltipFunction: CellTooltipFunction = () => undefined) { - super('content', key, title, maxWidth, sortable); + public cellTooltipFunction: CellTooltipFunction = () => undefined, + public isNumberColumn: boolean = false) { + super('content', key, title, width, sortable); } } @@ -97,8 +98,8 @@ export class EntityActionTableColumn> extends BaseEnti constructor(public key: string, public title: string, public actionDescriptor: CellActionDescriptor, - public maxWidth: string = '100%') { - super('action', key, title, maxWidth, false); + public width: string = '0px') { + super('action', key, title, width, false); } } @@ -106,12 +107,12 @@ export class DateEntityTableColumn> extends EntityTabl constructor(key: string, title: string, datePipe: DatePipe, - maxWidth: string = '100%', + width: string = '0px', dateFormat: string = 'yyyy-MM-dd HH:mm:ss', cellStyleFunction: CellStyleFunction = () => ({})) { super(key, title, - maxWidth, + width, (entity, property) => datePipe.transform(entity[property], dateFormat), cellStyleFunction); } 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 020242e54c..bf214fc660 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 @@ -114,19 +114,24 @@ export class WidgetContext { private _changeDetector: ChangeDetectorRef; detectChanges(updateWidgetParams: boolean = false) { - if (updateWidgetParams) { - this.dashboardWidget.updateWidgetParams(); + if (!this.destroyed) { + if (updateWidgetParams) { + this.dashboardWidget.updateWidgetParams(); + } + this._changeDetector.detectChanges(); } - this._changeDetector.detectChanges(); } updateWidgetParams() { - setTimeout(() => { - this.dashboardWidget.updateWidgetParams(); - }, 0); + if (!this.destroyed) { + setTimeout(() => { + this.dashboardWidget.updateWidgetParams(); + }, 0); + } } inited = false; + destroyed = false; subscriptions: {[id: string]: IWidgetSubscription} = {}; defaultSubscription: IWidgetSubscription = null; diff --git a/ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts index d605dddf78..23e7d3ef98 100644 --- a/ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts @@ -146,12 +146,12 @@ export class AssetsTableConfigResolver implements Resolve> { const columns: Array> = [ new DateEntityTableColumn('createdTime', 'asset.created-time', this.datePipe, '150px'), - new EntityTableColumn('name', 'asset.name'), - new EntityTableColumn('type', 'asset.asset-type'), + new EntityTableColumn('name', 'asset.name', '33%'), + new EntityTableColumn('type', 'asset.asset-type', '33%'), ]; if (assetScope === 'tenant') { columns.push( - new EntityTableColumn('customerTitle', 'customer.customer'), + new EntityTableColumn('customerTitle', 'customer.customer', '33%'), new EntityTableColumn('customerIsPublic', 'asset.public', '60px', entity => { return checkBoxCell(entity.customerIsPublic); diff --git a/ui-ngx/src/app/modules/home/pages/customer/customers-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/customer/customers-table-config.resolver.ts index f1983cf370..0bc921c6f7 100644 --- a/ui-ngx/src/app/modules/home/pages/customer/customers-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/customer/customers-table-config.resolver.ts @@ -55,10 +55,10 @@ export class CustomersTableConfigResolver implements Resolve('createdTime', 'customer.created-time', this.datePipe, '150px'), - new EntityTableColumn('title', 'customer.title'), - new EntityTableColumn('email', 'contact.email'), - new EntityTableColumn('country', 'contact.country'), - new EntityTableColumn('city', 'contact.city') + new EntityTableColumn('title', 'customer.title', '25%'), + new EntityTableColumn('email', 'contact.email', '25%'), + new EntityTableColumn('country', 'contact.country', '25%'), + new EntityTableColumn('city', 'contact.city', '25%') ); this.config.cellActionDescriptors.push( diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts index e7d2070d91..a684829ce2 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts @@ -143,12 +143,12 @@ export class DashboardsTableConfigResolver implements Resolve> { const columns: Array> = [ new DateEntityTableColumn('createdTime', 'dashboard.created-time', this.datePipe, '150px'), - new EntityTableColumn('title', 'dashboard.title') + new EntityTableColumn('title', 'dashboard.title', '50%') ]; if (dashboardScope === 'tenant') { columns.push( new EntityTableColumn('customersTitle', 'dashboard.assignedToCustomers', - '100%', entity => { + '50%', entity => { return getDashboardAssignedCustomersText(entity); }, () => ({}), false), new EntityTableColumn('dashboardIsPublic', 'dashboard.public', '60px', 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 555bbd0499..c60ff5e59f 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 @@ -150,13 +150,13 @@ export class DevicesTableConfigResolver implements Resolve> { const columns: Array> = [ new DateEntityTableColumn('createdTime', 'device.created-time', this.datePipe, '150px'), - new EntityTableColumn('name', 'device.name'), - new EntityTableColumn('type', 'device.device-type'), - new EntityTableColumn('label', 'device.label') + new EntityTableColumn('name', 'device.name', '25%'), + new EntityTableColumn('type', 'device.device-type', '25%'), + new EntityTableColumn('label', 'device.label', '25%') ]; if (deviceScope === 'tenant') { columns.push( - new EntityTableColumn('customerTitle', 'customer.customer'), + new EntityTableColumn('customerTitle', 'customer.customer', '25%'), new EntityTableColumn('customerIsPublic', 'device.public', '60px', entity => { return checkBoxCell(entity.customerIsPublic); diff --git a/ui-ngx/src/app/modules/home/pages/entity-view/entity-views-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/entity-view/entity-views-table-config.resolver.ts index c1e8250dd2..94ea10a623 100644 --- a/ui-ngx/src/app/modules/home/pages/entity-view/entity-views-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/entity-view/entity-views-table-config.resolver.ts @@ -147,12 +147,12 @@ export class EntityViewsTableConfigResolver implements Resolve> { const columns: Array> = [ new DateEntityTableColumn('createdTime', 'entity-view.created-time', this.datePipe, '150px'), - new EntityTableColumn('name', 'entity-view.name'), - new EntityTableColumn('type', 'entity-view.entity-view-type'), + new EntityTableColumn('name', 'entity-view.name', '33%'), + new EntityTableColumn('type', 'entity-view.entity-view-type', '33%'), ]; if (entityViewScope === 'tenant') { columns.push( - new EntityTableColumn('customerTitle', 'customer.customer'), + new EntityTableColumn('customerTitle', 'customer.customer', '33%'), new EntityTableColumn('customerIsPublic', 'entity-view.public', '60px', entity => { return checkBoxCell(entity.customerIsPublic); diff --git a/ui-ngx/src/app/modules/home/pages/tenant/tenants-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/tenant/tenants-table-config.resolver.ts index 0ccc7e20d9..4b9ca75053 100644 --- a/ui-ngx/src/app/modules/home/pages/tenant/tenants-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/tenant/tenants-table-config.resolver.ts @@ -55,10 +55,10 @@ export class TenantsTableConfigResolver implements Resolve('createdTime', 'tenant.created-time', this.datePipe, '150px'), - new EntityTableColumn('title', 'tenant.title'), - new EntityTableColumn('email', 'contact.email'), - new EntityTableColumn('country', 'contact.country'), - new EntityTableColumn('city', 'contact.city') + new EntityTableColumn('title', 'tenant.title', '25%'), + new EntityTableColumn('email', 'contact.email', '25%'), + new EntityTableColumn('country', 'contact.country', '25%'), + new EntityTableColumn('city', 'contact.city', '25%') ); this.config.cellActionDescriptors.push( diff --git a/ui-ngx/src/app/modules/home/pages/user/users-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/user/users-table-config.resolver.ts index e075d93a1a..1361eb2776 100644 --- a/ui-ngx/src/app/modules/home/pages/user/users-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/user/users-table-config.resolver.ts @@ -90,9 +90,9 @@ export class UsersTableConfigResolver implements Resolve this.config.columns.push( new DateEntityTableColumn('createdTime', 'user.created-time', this.datePipe, '150px'), - new EntityTableColumn('firstName', 'user.first-name'), - new EntityTableColumn('lastName', 'user.last-name'), - new EntityTableColumn('email', 'user.email') + new EntityTableColumn('firstName', 'user.first-name', '33%'), + new EntityTableColumn('lastName', 'user.last-name', '33%'), + new EntityTableColumn('email', 'user.email', '33%') ); this.config.deleteEnabled = user => user && user.id && user.id.id !== this.authUser.id.id; diff --git a/ui-ngx/src/app/modules/home/pages/widget/widgets-bundles-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/widget/widgets-bundles-table-config.resolver.ts index 12c9b044fe..86e495d7f7 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widgets-bundles-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/widget/widgets-bundles-table-config.resolver.ts @@ -58,7 +58,7 @@ export class WidgetsBundlesTableConfigResolver implements Resolve('createdTime', 'widgets-bundle.created-time', this.datePipe, '150px'), - new EntityTableColumn('title', 'widgets-bundle.title'), + new EntityTableColumn('title', 'widgets-bundle.title', '100%'), new EntityTableColumn('tenantId', 'widgets-bundle.system', '60px', entity => { return checkBoxCell(entity.tenantId.id === NULL_UUID); diff --git a/ui-ngx/src/app/shared/components/breadcrumb.component.scss b/ui-ngx/src/app/shared/components/breadcrumb.component.scss index 36ad8fcd91..f7e8160f7d 100644 --- a/ui-ngx/src/app/shared/components/breadcrumb.component.scss +++ b/ui-ngx/src/app/shared/components/breadcrumb.component.scss @@ -23,6 +23,7 @@ .tb-breadcrumb { font-size: 18px !important; font-weight: 400 !important; + overflow: hidden; h1, a, diff --git a/ui-ngx/src/app/shared/models/alarm.models.ts b/ui-ngx/src/app/shared/models/alarm.models.ts index f014f865a7..cae70c9863 100644 --- a/ui-ngx/src/app/shared/models/alarm.models.ts +++ b/ui-ngx/src/app/shared/models/alarm.models.ts @@ -106,6 +106,7 @@ export interface AlarmInfo extends Alarm { } export const simulatedAlarm: AlarmInfo = { + id: new AlarmId(NULL_UUID), tenantId: new TenantId(NULL_UUID), createdTime: new Date().getTime(), startTs: new Date().getTime(), diff --git a/ui-ngx/src/app/shared/models/page/page-link.ts b/ui-ngx/src/app/shared/models/page/page-link.ts index 307d1e4e3f..8317cc325b 100644 --- a/ui-ngx/src/app/shared/models/page/page-link.ts +++ b/ui-ngx/src/app/shared/models/page/page-link.ts @@ -16,6 +16,7 @@ import { Direction, SortOrder } from '@shared/models/page/sort-order'; import { emptyPageData, PageData } from '@shared/models/page/page-data'; +import { getDescendantProp } from '@core/utils'; export type PageLinkSearchFunction = (entity: T, textSearch: string) => boolean; @@ -69,8 +70,8 @@ export class PageLink { public sort(item1: any, item2: any): number { if (this.sortOrder) { const property = this.sortOrder.property; - const item1Value = item1[property]; - const item2Value = item2[property]; + const item1Value = getDescendantProp(item1, property); + const item2Value = getDescendantProp(item2, property); let result = 0; if (item1Value !== item2Value) { if (typeof item1Value === 'number' && typeof item2Value === 'number') { diff --git a/ui-ngx/src/styles.scss b/ui-ngx/src/styles.scss index e5d7b04f67..a9f69e6e62 100644 --- a/ui-ngx/src/styles.scss +++ b/ui-ngx/src/styles.scss @@ -472,6 +472,16 @@ mat-label { } } + // Material table + + mat-toolbar.mat-primary { + button.mat-icon-button { + mat-icon { + color: white; + } + } + } + mat-toolbar.mat-table-toolbar { background: #fff; padding: 0 24px; @@ -483,7 +493,7 @@ mat-label { } } - mat-toolbar.mat-table-toolbar, .mat-cell { + mat-toolbar.mat-table-toolbar:not(.mat-primary), .mat-cell { button.mat-icon-button { mat-icon { color: rgba(0, 0, 0, .54); @@ -491,28 +501,40 @@ mat-label { } } - .mat-cell { - mat-icon { - color: rgba(0, 0, 0, .54); - } + .mat-table { + width: 100%; + max-width: 100%; + margin-bottom: 1rem; + display: table; + border-collapse: separate; + margin: 0px; } - mat-toolbar.mat-primary { - button.mat-icon-button { - mat-icon { - color: white; + .mat-row, + .mat-header-row { + display: table-row; + } + + + .mat-header-row.mat-table-sticky { + .mat-header-cell { + position: sticky; + top: 0; + z-index: 10; + background: inherit; + &.mat-table-sticky { + z-index: 11 !important; } } } - .mat-row { transition: background-color .2s; &:hover:not(.tb-current-entity) { - background-color: rgba(221, 221, 221, 0.3); + background-color: #f4f4f4; } &.tb-current-entity { - background-color: rgba(221, 221, 221, 0.65); + background-color: #e9e9e9; } } @@ -541,12 +563,20 @@ mat-label { } } + .mat-cell, .mat-header-cell { - white-space: nowrap; - } - - .mat-cell, .mat-header-cell { min-width: 40px; + word-wrap: initial; + display: table-cell; + line-break: unset; + width: 0px; + overflow: hidden; + vertical-align: middle; + border-width: 0; + border-bottom-width: 1px; + border-bottom-color: rgba(0, 0, 0, 0.12); + border-style: solid; + text-overflow: ellipsis; &:last-child { padding: 0 12px 0 0; } @@ -561,8 +591,28 @@ mat-label { text-overflow: ellipsis; white-space: nowrap; } - &.mat-table-sticky { - background: transparent; + } + + .mat-header-cell { + white-space: nowrap; + button.mat-sort-header-button { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + &.mat-number-cell { + .mat-sort-header-container { + justify-content: flex-end; + } + } + } + + .mat-cell { + &.mat-number-cell { + text-align: end; + } + mat-icon { + color: rgba(0, 0, 0, .54); } } @@ -575,6 +625,10 @@ mat-label { height: 20px; } + .mat-sort-header-sorted .mat-sort-header-arrow { + opacity: 1 !important; + } + .mat-toolbar-tools { font-size: 20px; letter-spacing: .005em; From 85748665d3250e6a60613c461640ce39287055b0 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 24 Jan 2020 19:14:40 +0200 Subject: [PATCH 078/133] Toast improvements --- .../src/app/shared/components/toast.directive.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/ui-ngx/src/app/shared/components/toast.directive.ts b/ui-ngx/src/app/shared/components/toast.directive.ts index de92352874..fca3fada21 100644 --- a/ui-ngx/src/app/shared/components/toast.directive.ts +++ b/ui-ngx/src/app/shared/components/toast.directive.ts @@ -47,6 +47,8 @@ export class ToastDirective implements AfterViewInit, OnDestroy { private notificationSubscription: Subscription = null; private hideNotificationSubscription: Subscription = null; + private snackBarRef: MatSnackBarRef = null; + constructor(public elementRef: ElementRef, public viewContainerRef: ViewContainerRef, private notificationService: NotificationService, @@ -55,8 +57,6 @@ export class ToastDirective implements AfterViewInit, OnDestroy { } ngAfterViewInit(): void { - const toastComponent = this; - this.notificationSubscription = this.notificationService.getNotification().subscribe( (notificationMessage) => { if (notificationMessage && notificationMessage.message) { @@ -70,11 +70,17 @@ export class ToastDirective implements AfterViewInit, OnDestroy { const config: MatSnackBarConfig = { horizontalPosition: notificationMessage.horizontalPosition || 'left', verticalPosition: !isGtSm ? 'bottom' : (notificationMessage.verticalPosition || 'top'), - viewContainerRef: toastComponent.viewContainerRef, + viewContainerRef: this.viewContainerRef, duration: notificationMessage.duration, data }; - this.snackBar.openFromComponent(TbSnackBarComponent, config); + if (this.snackBarRef) { + this.snackBarRef.dismiss(); + } + this.snackBarRef = this.snackBar.openFromComponent(TbSnackBarComponent, config); + this.snackBarRef.afterDismissed().subscribe(() => { + this.snackBarRef = null; + }); } } } From ae3ea3138b78047794f3630d899b17e6116e3905 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 24 Jan 2020 19:35:10 +0200 Subject: [PATCH 079/133] Toast improvements --- .../app/shared/components/toast.directive.ts | 56 +++++++++++++------ 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/ui-ngx/src/app/shared/components/toast.directive.ts b/ui-ngx/src/app/shared/components/toast.directive.ts index fca3fada21..b9c2198f11 100644 --- a/ui-ngx/src/app/shared/components/toast.directive.ts +++ b/ui-ngx/src/app/shared/components/toast.directive.ts @@ -19,7 +19,7 @@ import { Component, Directive, ElementRef, - Inject, Input, + Inject, Input, NgZone, OnDestroy, ViewContainerRef } from '@angular/core'; @@ -48,40 +48,43 @@ export class ToastDirective implements AfterViewInit, OnDestroy { private hideNotificationSubscription: Subscription = null; private snackBarRef: MatSnackBarRef = null; + private currentMessage: NotificationMessage = null; constructor(public elementRef: ElementRef, public viewContainerRef: ViewContainerRef, private notificationService: NotificationService, public snackBar: MatSnackBar, + private ngZone: NgZone, private breakpointObserver: BreakpointObserver) { } ngAfterViewInit(): void { this.notificationSubscription = this.notificationService.getNotification().subscribe( (notificationMessage) => { - if (notificationMessage && notificationMessage.message) { - const target = notificationMessage.target || 'root'; - if (this.toastTarget === target) { - const data = { - parent: this.elementRef, - notification: notificationMessage - }; - const isGtSm = this.breakpointObserver.isMatched(MediaBreakpoints['gt-sm']); - const config: MatSnackBarConfig = { - horizontalPosition: notificationMessage.horizontalPosition || 'left', - verticalPosition: !isGtSm ? 'bottom' : (notificationMessage.verticalPosition || 'top'), - viewContainerRef: this.viewContainerRef, - duration: notificationMessage.duration, - data - }; + if (this.shouldDisplayMessage(notificationMessage)) { + this.currentMessage = notificationMessage; + const data = { + parent: this.elementRef, + notification: notificationMessage + }; + const isGtSm = this.breakpointObserver.isMatched(MediaBreakpoints['gt-sm']); + const config: MatSnackBarConfig = { + horizontalPosition: notificationMessage.horizontalPosition || 'left', + verticalPosition: !isGtSm ? 'bottom' : (notificationMessage.verticalPosition || 'top'), + viewContainerRef: this.viewContainerRef, + duration: notificationMessage.duration, + data + }; + this.ngZone.run(() => { if (this.snackBarRef) { this.snackBarRef.dismiss(); } this.snackBarRef = this.snackBar.openFromComponent(TbSnackBarComponent, config); this.snackBarRef.afterDismissed().subscribe(() => { this.snackBarRef = null; + this.currentMessage = null; }); - } + }); } } ); @@ -91,13 +94,30 @@ export class ToastDirective implements AfterViewInit, OnDestroy { if (hideNotification) { const target = hideNotification.target || 'root'; if (this.toastTarget === target) { - this.snackBar.dismiss(); + this.ngZone.run(() => { + if (this.snackBarRef) { + this.snackBarRef.dismiss(); + } + }); } } } ); } + private shouldDisplayMessage(notificationMessage: NotificationMessage): boolean { + if (notificationMessage && notificationMessage.message) { + const target = notificationMessage.target || 'root'; + if (this.toastTarget === target) { + if (!this.currentMessage || this.currentMessage.message !== notificationMessage.message + || this.currentMessage.type !== notificationMessage.type) { + return true; + } + } + } + return false; + } + ngOnDestroy(): void { if (this.notificationSubscription) { this.notificationSubscription.unsubscribe(); From bd8af1111eecf4ddd0c708278a02ba685985eda0 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 29 Jan 2020 17:20:28 +0200 Subject: [PATCH 080/133] Implement Alarm widget --- ui-ngx/src/app/core/api/widget-api.models.ts | 2 + .../home/components/home-components.module.ts | 8 +- .../shared-home-components.module.ts | 38 ++++ .../alarm-status-filter-panel.component.html | 25 +++ .../alarm-status-filter-panel.component.scss | 36 ++++ .../alarm-status-filter-panel.component.ts | 42 +++++ .../lib/alarms-table-widget.component.ts | 171 +++++++++++++++++- .../widget/lib/table-widget.models.ts | 3 + .../widget/widget-component.service.ts | 25 ++- .../widget/widget-components.module.ts | 11 +- .../src/app/shared/models/page/page-link.ts | 16 +- ui-ngx/src/styles.scss | 5 + 12 files changed, 351 insertions(+), 31 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/shared-home-components.module.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/alarm-status-filter-panel.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/alarm-status-filter-panel.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/alarm-status-filter-panel.component.ts diff --git a/ui-ngx/src/app/core/api/widget-api.models.ts b/ui-ngx/src/app/core/api/widget-api.models.ts index d9365a50cc..00d33cdde1 100644 --- a/ui-ngx/src/app/core/api/widget-api.models.ts +++ b/ui-ngx/src/app/core/api/widget-api.models.ts @@ -259,5 +259,7 @@ export interface IWidgetSubscription { destroy(): void; + update(): void; + [key: string]: any; } diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index 5fce77ab5e..cdbd9d7763 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -30,7 +30,6 @@ import { RelationTableComponent } from '@home/components/relation/relation-table import { RelationDialogComponent } from './relation/relation-dialog.component'; import { AlarmTableHeaderComponent } from '@home/components/alarm/alarm-table-header.component'; import { AlarmTableComponent } from '@home/components/alarm/alarm-table.component'; -import { AlarmDetailsDialogComponent } from '@home/components/alarm/alarm-details-dialog.component'; import { AttributeTableComponent } from '@home/components/attribute/attribute-table.component'; import { AddAttributeDialogComponent } from './attribute/add-attribute-dialog.component'; import { EditAttributeValuePanelComponent } from './attribute/edit-attribute-value-panel.component'; @@ -64,6 +63,7 @@ import { AddWidgetToDashboardDialogComponent } from './attribute/add-widget-to-d import { ImportDialogCsvComponent } from './import-export/import-dialog-csv.component'; import { TableColumnsAssignmentComponent } from './import-export/table-columns-assignment.component'; import { EventContentDialogComponent } from '@home/components/event/event-content-dialog.component'; +import { SharedHomeComponentsModule } from '@home/components/shared-home-components.module'; @NgModule({ entryComponents: [ @@ -73,7 +73,6 @@ import { EventContentDialogComponent } from '@home/components/event/event-conten EventTableHeaderComponent, RelationDialogComponent, AlarmTableHeaderComponent, - AlarmDetailsDialogComponent, AddAttributeDialogComponent, EditAttributeValuePanelComponent, AliasesEntitySelectPanelComponent, @@ -104,7 +103,6 @@ import { EventContentDialogComponent } from '@home/components/event/event-conten RelationFiltersComponent, AlarmTableHeaderComponent, AlarmTableComponent, - AlarmDetailsDialogComponent, AttributeTableComponent, AddAttributeDialogComponent, EditAttributeValuePanelComponent, @@ -136,7 +134,8 @@ import { EventContentDialogComponent } from '@home/components/event/event-conten ], imports: [ CommonModule, - SharedModule + SharedModule, + SharedHomeComponentsModule ], exports: [ EntitiesTableComponent, @@ -149,7 +148,6 @@ import { EventContentDialogComponent } from '@home/components/event/event-conten RelationTableComponent, RelationFiltersComponent, AlarmTableComponent, - AlarmDetailsDialogComponent, AttributeTableComponent, AliasesEntitySelectComponent, EntityAliasesDialogComponent, diff --git a/ui-ngx/src/app/modules/home/components/shared-home-components.module.ts b/ui-ngx/src/app/modules/home/components/shared-home-components.module.ts new file mode 100644 index 0000000000..30616276c5 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/shared-home-components.module.ts @@ -0,0 +1,38 @@ +/// +/// Copyright © 2016-2019 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 { CommonModule } from '@angular/common'; +import { SharedModule } from '@app/shared/shared.module'; +import { AlarmDetailsDialogComponent } from '@home/components/alarm/alarm-details-dialog.component'; + +@NgModule({ + entryComponents: [ + AlarmDetailsDialogComponent + ], + declarations: + [ + AlarmDetailsDialogComponent + ], + imports: [ + CommonModule, + SharedModule + ], + exports: [ + AlarmDetailsDialogComponent + ] +}) +export class SharedHomeComponentsModule { } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/alarm-status-filter-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/alarm-status-filter-panel.component.html new file mode 100644 index 0000000000..7c3b4e95d7 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/alarm-status-filter-panel.component.html @@ -0,0 +1,25 @@ + +
+ + + + {{ alarmSearchStatusTranslationMap.get(searchStatus) | translate }} + + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/alarm-status-filter-panel.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/alarm-status-filter-panel.component.scss new file mode 100644 index 0000000000..0ead8c7992 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/alarm-status-filter-panel.component.scss @@ -0,0 +1,36 @@ +/** + * Copyright © 2016-2019 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 { + width: 100%; + height: 100%; + min-width: 300px; + overflow: hidden; + background: #fff; + border-radius: 4px; + box-shadow: + 0 7px 8px -4px rgba(0, 0, 0, .2), + 0 13px 19px 2px rgba(0, 0, 0, .14), + 0 5px 24px 4px rgba(0, 0, 0, .12); + + .mat-content { + overflow: hidden; + background-color: #fff; + } + + .mat-padding { + padding: 16px; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/alarm-status-filter-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/alarm-status-filter-panel.component.ts new file mode 100644 index 0000000000..a260c22b89 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/alarm-status-filter-panel.component.ts @@ -0,0 +1,42 @@ +/// +/// Copyright © 2016-2019 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, InjectionToken } from '@angular/core'; +import { IWidgetSubscription } from '@core/api/widget-api.models'; +import { AlarmSearchStatus, alarmSearchStatusTranslations } from '@shared/models/alarm.models'; + +export const ALARM_STATUS_FILTER_PANEL_DATA = new InjectionToken('AlarmStatusFilterPanelData'); + +export interface AlarmStatusFilterPanelData { + subscription: IWidgetSubscription; +} + +@Component({ + selector: 'tb-alarm-status-filter-panel', + templateUrl: './alarm-status-filter-panel.component.html', + styleUrls: ['./alarm-status-filter-panel.component.scss'] +}) +export class AlarmStatusFilterPanelComponent { + + subscription: IWidgetSubscription; + + alarmSearchStatuses = Object.keys(AlarmSearchStatus); + alarmSearchStatusTranslationMap = alarmSearchStatusTranslations; + + constructor(@Inject(ALARM_STATUS_FILTER_PANEL_DATA) public data: AlarmStatusFilterPanelData) { + this.subscription = this.data.subscription; + } +} 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 5ac66cbee0..7a143c80ac 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 @@ -39,7 +39,7 @@ import { PageLink } from '@shared/models/page/page-link'; import { Direction, SortOrder, sortOrderFromString } from '@shared/models/page/sort-order'; import { DataSource } from '@angular/cdk/typings/collections'; import { CollectionViewer, SelectionModel } from '@angular/cdk/collections'; -import { BehaviorSubject, fromEvent, merge, Observable, of } from 'rxjs'; +import { BehaviorSubject, forkJoin, fromEvent, merge, Observable, of } from 'rxjs'; import { emptyPageData, PageData } from '@shared/models/page/page-data'; import { entityTypeTranslations } from '@shared/models/entity-type.models'; import { catchError, debounceTime, distinctUntilChanged, map, take, tap } from 'rxjs/operators'; @@ -77,6 +77,19 @@ import { alarmStatusTranslations } from '@shared/models/alarm.models'; import { DatePipe } from '@angular/common'; +import { + ALARM_STATUS_FILTER_PANEL_DATA, + AlarmStatusFilterPanelComponent, + AlarmStatusFilterPanelData +} from '@home/components/widget/lib/alarm-status-filter-panel.component'; +import { + AlarmDetailsDialogComponent, + AlarmDetailsDialogData +} from '@home/components/alarm/alarm-details-dialog.component'; +import { MatDialog } from '@angular/material/dialog'; +import { NULL_UUID } from '@shared/models/id/has-uuid'; +import { DialogService } from '@core/services/dialog.service'; +import { AlarmService } from '@core/http/alarm.service'; interface AlarmsTableWidgetSettings extends TableWidgetSettings { alarmsTitle: string; @@ -172,7 +185,10 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, private utils: UtilsService, public translate: TranslateService, private domSanitizer: DomSanitizer, - private datePipe: DatePipe) { + private datePipe: DatePipe, + private dialog: MatDialog, + private dialogService: DialogService, + private alarmService: AlarmService) { super(store); const sortOrder: SortOrder = sortOrderFromString(this.defaultSortOrder); @@ -390,7 +406,36 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, } private editAlarmStatusFilter($event: Event) { - // TODO: + if ($event) { + $event.stopPropagation(); + } + const target = $event.target || $event.srcElement || $event.currentTarget; + const config = new OverlayConfig(); + config.backdropClass = 'cdk-overlay-transparent-backdrop'; + config.hasBackdrop = true; + const connectedPosition: ConnectedPosition = { + originX: 'end', + originY: 'bottom', + overlayX: 'end', + overlayY: 'top' + }; + config.positionStrategy = this.overlay.position().flexibleConnectedTo(target as HTMLElement) + .withPositions([connectedPosition]); + + const overlayRef = this.overlay.create(config); + overlayRef.backdropClick().subscribe(() => { + overlayRef.dispose(); + }); + const injectionTokens = new WeakMap([ + [ALARM_STATUS_FILTER_PANEL_DATA, { + subscription: this.subscription, + } as AlarmStatusFilterPanelData], + [OverlayRef, overlayRef] + ]); + const injector = new PortalInjector(this.viewContainerRef.injector, injectionTokens); + overlayRef.attach(new ComponentPortal(AlarmStatusFilterPanelComponent, + this.viewContainerRef, injector)); + this.ctx.detectChanges(); } private enterFilterMode() { @@ -538,35 +583,138 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, if ($event) { $event.stopPropagation(); } - // TODO: + if (alarm && alarm.id && alarm.id.id !== NULL_UUID) { + this.dialog.open + (AlarmDetailsDialogComponent, + { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + alarmId: alarm.id.id, + allowAcknowledgment: this.allowAcknowledgment, + allowClear: this.allowClear, + displayDetails: true + } + }).afterClosed().subscribe( + (res) => { + if (res) { + this.subscription.update(); + } + } + ); + } } private ackAlarm($event: Event, alarm: AlarmInfo) { if ($event) { $event.stopPropagation(); } - // TODO: + if (alarm && alarm.id && alarm.id.id !== NULL_UUID) { + this.dialogService.confirm( + this.translate.instant('alarm.aknowledge-alarm-title'), + this.translate.instant('alarm.aknowledge-alarm-text'), + this.translate.instant('action.no'), + this.translate.instant('action.yes') + ).subscribe((res) => { + if (res) { + if (res) { + this.alarmService.ackAlarm(alarm.id.id).subscribe(() => { + this.subscription.update(); + }); + } + } + }); + } } public ackAlarms($event: Event) { if ($event) { $event.stopPropagation(); } - // TODO: + if (this.alarmsDatasource.selection.hasValue()) { + const alarms = this.alarmsDatasource.selection.selected.filter( + (alarm) => { return alarm.id.id !== NULL_UUID } + ); + if (alarms.length) { + const title = this.translate.instant('alarm.aknowledge-alarms-title', {count: alarms.length}); + const content = this.translate.instant('alarm.aknowledge-alarms-text', {count: alarms.length}); + this.dialogService.confirm( + title, + content, + this.translate.instant('action.no'), + this.translate.instant('action.yes') + ).subscribe((res) => { + if (res) { + if (res) { + const tasks: Observable[] = []; + for (const alarm of alarms) { + tasks.push(this.alarmService.ackAlarm(alarm.id.id)); + } + forkJoin(tasks).subscribe(() => { + this.alarmsDatasource.clearSelection(); + this.subscription.update(); + }); + } + } + }); + } + } } private clearAlarm($event: Event, alarm: AlarmInfo) { if ($event) { $event.stopPropagation(); } - // TODO: + if (alarm && alarm.id && alarm.id.id !== NULL_UUID) { + this.dialogService.confirm( + this.translate.instant('alarm.clear-alarm-title'), + this.translate.instant('alarm.clear-alarm-text'), + this.translate.instant('action.no'), + this.translate.instant('action.yes') + ).subscribe((res) => { + if (res) { + if (res) { + this.alarmService.clearAlarm(alarm.id.id).subscribe(() => { + this.subscription.update(); + }); + } + } + }); + } } public clearAlarms($event: Event) { if ($event) { $event.stopPropagation(); } - // TODO: + if (this.alarmsDatasource.selection.hasValue()) { + const alarms = this.alarmsDatasource.selection.selected.filter( + (alarm) => { return alarm.id.id !== NULL_UUID } + ); + if (alarms.length) { + const title = this.translate.instant('alarm.clear-alarms-title', {count: alarms.length}); + const content = this.translate.instant('alarm.clear-alarms-text', {count: alarms.length}); + this.dialogService.confirm( + title, + content, + this.translate.instant('action.no'), + this.translate.instant('action.yes') + ).subscribe((res) => { + if (res) { + if (res) { + const tasks: Observable[] = []; + for (const alarm of alarms) { + tasks.push(this.alarmService.clearAlarm(alarm.id.id)); + } + forkJoin(tasks).subscribe(() => { + this.alarmsDatasource.clearSelection(); + this.subscription.update(); + }); + } + } + }); + } + } } private defaultContent(key: EntityColumn, value: any): any { @@ -722,6 +870,13 @@ class AlarmsDatasource implements DataSource { return this.selection.isSelected(alarm); } + clearSelection() { + if (this.selection.hasValue()) { + this.selection.clear(); + this.onSelectionModeChanged(false); + } + } + masterToggle() { this.alarmsSubject.pipe( tap((alarms) => { 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 1926bb72ba..db864fddff 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 @@ -234,6 +234,9 @@ export function constructTableCssString(widgetConfig: WidgetConfig): string { '.mat-table .mat-cell button.mat-icon-button mat-icon {\n'+ 'color: ' + mdDarkSecondary + ';\n'+ '}\n'+ + '.mat-table .mat-cell button.mat-icon-button[disabled][disabled] mat-icon {\n'+ + 'color: ' + mdDarkDisabled + ';\n'+ + '}\n'+ '.mat-divider {\n'+ 'border-top-color: ' + mdDarkDivider + ';\n'+ '}\n'+ 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 75317f1108..363595badf 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 @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Inject, Injectable } from '@angular/core'; +import { Inject, Injectable, Type } from '@angular/core'; import { DynamicComponentFactoryService } from '@core/services/dynamic-component-factory.service'; import { WidgetService } from '@core/http/widget.service'; import { forkJoin, Observable, of, ReplaySubject, Subject, throwError } from 'rxjs'; @@ -34,7 +34,6 @@ import { catchError, map, mergeMap, switchMap } from 'rxjs/operators'; import { isFunction, isUndefined } from '@core/utils'; import { TranslateService } from '@ngx-translate/core'; import { DynamicWidgetComponent } from '@home/components/widget/dynamic-widget.component'; -import { SharedModule } from '@shared/shared.module'; import { WidgetComponentsModule } from '@home/components/widget/widget-components.module'; import { WINDOW } from '@core/services/window.service'; @@ -43,6 +42,7 @@ import { TbFlot } from './lib/flot-widget'; import { NULL_UUID } from '@shared/models/id/has-uuid'; import { WidgetTypeId } from '@app/shared/models/id/widget-type-id'; import { TenantId } from '@app/shared/models/id/tenant-id'; +import { SharedModule } from '@shared/shared.module'; const tinycolor = tinycolor_; @@ -117,16 +117,23 @@ export class WidgetComponentService { const initSubject = new ReplaySubject(); this.init$ = initSubject.asObservable(); const loadDefaultWidgetInfoTasks = [ - this.loadWidgetResources(this.missingWidgetType, 'global-widget-missing-type'), - this.loadWidgetResources(this.errorWidgetType, 'global-widget-error-type'), + this.loadWidgetResources(this.missingWidgetType, 'global-widget-missing-type', [SharedModule]), + this.loadWidgetResources(this.errorWidgetType, 'global-widget-error-type', [SharedModule]), ]; forkJoin(loadDefaultWidgetInfoTasks).subscribe( () => { initSubject.next(); }, - () => { + (e) => { + let errorMessages = ['Failed to load default widget types!']; + if (e && e.length) { + errorMessages = errorMessages.concat(e); + } console.error('Failed to load default widget types!'); - initSubject.error('Failed to load default widget types!'); + initSubject.error({ + widgetInfo: this.errorWidgetType, + errorMessages + }); } ); return this.init$; @@ -194,7 +201,7 @@ export class WidgetComponentService { } if (widgetControllerDescriptor) { const widgetNamespace = `widget-type-${(isSystem ? 'sys-' : '')}${bundleAlias}-${widgetInfo.alias}`; - this.loadWidgetResources(widgetInfo, widgetNamespace).subscribe( + this.loadWidgetResources(widgetInfo, widgetNamespace, [WidgetComponentsModule]).subscribe( () => { if (widgetControllerDescriptor.settingsSchema) { widgetInfo.typeSettingsSchema = widgetControllerDescriptor.settingsSchema; @@ -219,7 +226,7 @@ export class WidgetComponentService { } } - private loadWidgetResources(widgetInfo: WidgetInfo, widgetNamespace: string): Observable { + private loadWidgetResources(widgetInfo: WidgetInfo, widgetNamespace: string, modules?: Type[]): Observable { this.cssParser.cssPreviewNamespace = widgetNamespace; this.cssParser.createStyleElement(widgetNamespace, widgetInfo.templateCss); const resourceTasks: Observable[] = []; @@ -236,7 +243,7 @@ export class WidgetComponentService { this.dynamicComponentFactoryService.createDynamicComponentFactory( class DynamicWidgetComponentInstance extends DynamicWidgetComponent {}, widgetInfo.templateHtml, - [SharedModule, WidgetComponentsModule] + modules ).pipe( map((factory) => { widgetInfo.componentFactory = factory; 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 b965add9f2..b006b317a6 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 @@ -17,25 +17,28 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { SharedModule } from '@app/shared/shared.module'; -import { AlarmDetailsDialogComponent } from '@home/components/alarm/alarm-details-dialog.component'; -import { LegendComponent } from '@home/components/widget/legend.component'; import { EntitiesTableWidgetComponent } from '@home/components/widget/lib/entities-table-widget.component'; import { DisplayColumnsPanelComponent } from '@home/components/widget/lib/display-columns-panel.component'; import { AlarmsTableWidgetComponent } from '@home/components/widget/lib/alarms-table-widget.component'; +import { AlarmStatusFilterPanelComponent } from '@home/components/widget/lib/alarm-status-filter-panel.component'; +import { SharedHomeComponentsModule } from '@home/components/shared-home-components.module'; @NgModule({ entryComponents: [ - DisplayColumnsPanelComponent + DisplayColumnsPanelComponent, + AlarmStatusFilterPanelComponent ], declarations: [ DisplayColumnsPanelComponent, + AlarmStatusFilterPanelComponent, EntitiesTableWidgetComponent, AlarmsTableWidgetComponent ], imports: [ CommonModule, - SharedModule + SharedModule, + SharedHomeComponentsModule ], exports: [ EntitiesTableWidgetComponent, diff --git a/ui-ngx/src/app/shared/models/page/page-link.ts b/ui-ngx/src/app/shared/models/page/page-link.ts index 8317cc325b..8cd5c7fe56 100644 --- a/ui-ngx/src/app/shared/models/page/page-link.ts +++ b/ui-ngx/src/app/shared/models/page/page-link.ts @@ -16,7 +16,7 @@ import { Direction, SortOrder } from '@shared/models/page/sort-order'; import { emptyPageData, PageData } from '@shared/models/page/page-data'; -import { getDescendantProp } from '@core/utils'; +import { getDescendantProp, isObject } from '@core/utils'; export type PageLinkSearchFunction = (entity: T, textSearch: string) => boolean; @@ -28,10 +28,16 @@ const defaultPageLinkSearchFunction: PageLinkSearchFunction = const expected = ('' + textSearch).toLowerCase(); for (const key of Object.keys(entity)) { const val = entity[key]; - if (val !== null && val !== Object(val)) { - const actual = ('' + val).toLowerCase(); - if (actual.indexOf(expected) !== -1) { - return true; + if (val !== null) { + if (val !== Object(val)) { + const actual = ('' + val).toLowerCase(); + if (actual.indexOf(expected) !== -1) { + return true; + } + } else if (isObject(val)) { + if (defaultPageLinkSearchFunction(val, textSearch)) { + return true; + } } } } diff --git a/ui-ngx/src/styles.scss b/ui-ngx/src/styles.scss index a9f69e6e62..0e490633e6 100644 --- a/ui-ngx/src/styles.scss +++ b/ui-ngx/src/styles.scss @@ -498,6 +498,11 @@ mat-label { mat-icon { color: rgba(0, 0, 0, .54); } + &[disabled][disabled] { + mat-icon { + color: rgba(0, 0, 0, .26); + } + } } } From d47371d8fd253a4459aae231b417070f656e961a Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 30 Jan 2020 13:03:53 +0200 Subject: [PATCH 081/133] Implemented Timeseries table widget. --- .../json/system/widget_bundles/cards.json | 10 +- .../dashboard/dashboard.component.scss | 10 + .../entity/entities-table.component.html | 2 +- .../entity/entities-table.component.ts | 30 +- .../lib/alarms-table-widget.component.ts | 12 +- .../lib/entities-table-widget.component.ts | 12 +- .../widget/lib/table-widget.models.ts | 12 +- .../timeseries-table-widget.component.html | 107 ++++ .../timeseries-table-widget.component.scss | 48 ++ .../lib/timeseries-table-widget.component.ts | 554 ++++++++++++++++++ .../widget/widget-components.module.ts | 7 +- ui-ngx/src/styles.scss | 2 +- 12 files changed, 765 insertions(+), 41 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts 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 0a6b0b0d47..bf40d495df 100644 --- a/application/src/main/data/json/system/widget_bundles/cards.json +++ b/application/src/main/data/json/system/widget_bundles/cards.json @@ -109,12 +109,12 @@ "sizeX": 8, "sizeY": 6.5, "resources": [], - "templateHtml": "\n", + "templateHtml": "\n", "templateCss": "", - "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n var id = self.ctx.$scope.$injector.get('utils').guid();\n scope.tableId = \"table-\"+id;\n scope.ctx = self.ctx;\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.$broadcast('timeseries-table-data-updated', self.ctx.$scope.tableId);\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}", - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"TimeseriesTableSettings\",\n \"properties\": {\n \"showTimestamp\": {\n \"title\": \"Display timestamp column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n }, \n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"hideEmptyLines\": {\n \"title\": \"Hide empty lines\",\n \"type\": \"boolean\",\n \"default\": false\n }\n },\n \"required\": []\n },\n \"form\": [\n \"showTimestamp\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"hideEmptyLines\"\n ]\n}", - "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, rowData, filter)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", - "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature °C\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = (value + 60)/120 * 100;\\n var color = tinycolor.mix('blue', 'red', amount = percent);\\n color.setAlpha(.5);\\n return {\\n paddingLeft: '20px',\\n color: '#ffffff',\\n background: color.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\"},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity, %\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = value;\\n var backgroundColor = tinycolor('blue');\\n backgroundColor.setAlpha(value/100);\\n var color = 'blue';\\n if (value > 50) {\\n color = 'white';\\n }\\n \\n return {\\n paddingLeft: '20px',\\n color: color,\\n background: backgroundColor.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 5) {\\n\\tvalue = 5;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"showTimestamp\":true,\"displayPagination\":true,\"defaultPageSize\":10},\"title\":\"Timeseries table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":false,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{}}" + "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeseriesTableWidget.onDataUpdated();\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"TimeseriesTableSettings\",\n \"properties\": {\n \"showTimestamp\": {\n \"title\": \"Display timestamp column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showMilliseconds\": {\n \"title\": \"Display timestamp milliseconds\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n }, \n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"hideEmptyLines\": {\n \"title\": \"Hide empty lines\",\n \"type\": \"boolean\",\n \"default\": false\n }\n },\n \"required\": []\n },\n \"form\": [\n \"showTimestamp\",\n \"showMilliseconds\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"hideEmptyLines\"\n ]\n}", + "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature °C\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = (value + 60)/120 * 100;\\n var color = tinycolor.mix('blue', 'red', amount = percent);\\n color.setAlpha(.5);\\n return {\\n paddingLeft: '20px',\\n color: '#ffffff',\\n background: color.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\"},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity, %\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = value;\\n var backgroundColor = tinycolor('blue');\\n backgroundColor.setAlpha(value/100);\\n var color = 'blue';\\n if (value > 50) {\\n color = 'white';\\n }\\n \\n return {\\n paddingLeft: '20px',\\n color: color,\\n background: backgroundColor.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 5) {\\n\\tvalue = 5;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"showTimestamp\":true,\"displayPagination\":true,\"defaultPageSize\":10},\"title\":\"Timeseries table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\"}" } }, { diff --git a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.scss b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.scss index 60c0f7c65e..7012daa1f9 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.scss +++ b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.scss @@ -48,6 +48,16 @@ } } +tb-widget.tb-widget { + position: relative; + height: 100%; + margin: 0; + overflow: hidden; + outline: none; + + transition: all .2s ease-in-out; +} + div.tb-widget { position: relative; height: 100%; 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 66b3ed430f..f6d0a108fc 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 @@ -135,7 +135,7 @@
- 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 c09ce4ac5e..b52044a601 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 @@ -16,13 +16,13 @@ import { AfterViewInit, - Component, ComponentFactoryResolver, + ChangeDetectionStrategy, + Component, + ComponentFactoryResolver, ElementRef, Input, OnInit, - Type, - ViewChild, - ChangeDetectionStrategy + ViewChild } from '@angular/core'; import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; @@ -35,28 +35,24 @@ import { Direction, SortOrder } from '@shared/models/page/sort-order'; import { forkJoin, fromEvent, merge, Observable } from 'rxjs'; import { TranslateService } from '@ngx-translate/core'; import { BaseData, HasId } from '@shared/models/base-data'; -import { EntityId } from '@shared/models/id/entity-id'; import { ActivatedRoute } from '@angular/router'; import { CellActionDescriptor, + EntityActionTableColumn, + EntityColumn, EntityTableColumn, EntityTableConfig, GroupActionDescriptor, - HeaderActionDescriptor, - EntityColumn, EntityActionTableColumn + HeaderActionDescriptor } from '@home/models/entity/entities-table-config.models'; import { EntityTypeTranslation } from '@shared/models/entity-type.models'; import { DialogService } from '@core/services/dialog.service'; import { AddEntityDialogComponent } from './add-entity-dialog.component'; -import { - AddEntityDialogData, - EntityAction -} from '@home/models/entity/entity-component.models'; -import { Timewindow, historyInterval } from '@shared/models/time/time.models'; -import {DomSanitizer, SafeHtml} from '@angular/platform-browser'; +import { AddEntityDialogData, EntityAction } from '@home/models/entity/entity-component.models'; +import { historyInterval, Timewindow } from '@shared/models/time/time.models'; +import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; -import { instanceOf } from 'prop-types'; -import { isDefined, isDefinedAndNotNull, isUndefined } from '@core/utils'; +import { isDefined, isUndefined } from '@core/utils'; @Component({ selector: 'tb-entities-table', @@ -458,4 +454,8 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn return column.key; } + trackByEntityId(index: number, entity: BaseData) { + return entity.id.id; + } + } 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 7a143c80ac..d0d8b41337 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 @@ -507,26 +507,22 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, } public cellContent(alarm: AlarmInfo, key: EntityColumn): SafeHtml { - let strContent = ''; if (alarm && key) { const contentInfo = this.contentsInfo[key.def]; const value = getAlarmValue(alarm, key); + let content = ''; if (contentInfo.useCellContentFunction && contentInfo.cellContentFunction) { - if (isDefined(value)) { - strContent = '' + value; - } - var content = strContent; try { content = contentInfo.cellContentFunction(value, alarm, this.ctx); } catch (e) { - content = strContent; + content = '' + value; } } else { content = this.defaultContent(key, value); } - return this.domSanitizer.bypassSecurityTrustHtml(content); + return isDefined(content) ? this.domSanitizer.bypassSecurityTrustHtml(content) : ''; } else { - return strContent; + return ''; } } 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 5e427b0e1f..05ebbdccd0 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 @@ -433,28 +433,24 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni } public cellContent(entity: EntityData, key: EntityColumn): SafeHtml { - let strContent = ''; if (entity && key) { const contentInfo = this.contentsInfo[key.def]; const value = getEntityValue(entity, key); + let content = ''; if (contentInfo.useCellContentFunction && contentInfo.cellContentFunction) { - if (isDefined(value)) { - strContent = '' + value; - } - var content = strContent; try { content = contentInfo.cellContentFunction(value, entity, this.ctx); } catch (e) { - content = strContent; + content = '' + value; } } else { const decimals = (contentInfo.decimals || contentInfo.decimals === 0) ? contentInfo.decimals : this.ctx.widgetConfig.decimals; const units = contentInfo.units || this.ctx.widgetConfig.units; content = this.ctx.utils.formatValue(value, decimals, units, true); } - return this.domSanitizer.bypassSecurityTrustHtml(content); + return isDefined(content) ? this.domSanitizer.bypassSecurityTrustHtml(content) : ''; } else { - return strContent; + return ''; } } 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 db864fddff..f40514a572 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 @@ -31,7 +31,7 @@ export interface TableWidgetSettings { } export interface TableWidgetDataKeySettings { - columnWidth: string; + columnWidth?: string; useCellStyleFunction: boolean; cellStyleFunction: string; useCellContentFunction: boolean; @@ -168,6 +168,7 @@ export function constructTableCssString(widgetConfig: WidgetConfig): string { const mdDark = defaultColor.setAlpha(0.87).toRgbString(); const mdDarkSecondary = defaultColor.setAlpha(0.54).toRgbString(); const mdDarkDisabled = defaultColor.setAlpha(0.26).toRgbString(); + const mdDarkDisabled2 = defaultColor.setAlpha(0.38).toRgbString(); const mdDarkDivider = defaultColor.setAlpha(0.12).toRgbString(); const cssString = @@ -189,6 +190,15 @@ export function constructTableCssString(widgetConfig: WidgetConfig): string { 'mat-toolbar.mat-table-toolbar:not([color="primary"]) button.mat-icon-button mat-icon {\n'+ 'color: ' + mdDarkSecondary + ';\n'+ '}\n'+ + '.mat-tab-label {\n'+ + 'color: ' + mdDark + ';\n'+ + '}\n'+ + '.mat-tab-header-pagination-chevron {\n'+ + 'border-color: ' + mdDark + ';\n'+ + '}\n'+ + '.mat-tab-header-pagination-disabled .mat-tab-header-pagination-chevron {\n'+ + 'border-color: ' + mdDarkDisabled2 + ';\n'+ + '}\n'+ '.mat-table .mat-header-row {\n'+ 'background-color: ' + origBackgroundColor + ';\n'+ '}\n'+ 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 new file mode 100644 index 0000000000..c6820dc351 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html @@ -0,0 +1,107 @@ + +
+
+ +
+ + +   + + + +
+
+ + +
+ + + Timestamp + + + + + {{ h.dataKey.label }} + + + + + + + +
+ +
+
+ + + + +
+
+
+ + +
+ widget.no-data-found +
+ + +
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.scss new file mode 100644 index 0000000000..963ab754d6 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.scss @@ -0,0 +1,48 @@ +/** + * Copyright © 2016-2019 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 { + width: 100%; + height: 100%; + .tb-table-widget { + mat-footer-row, mat-row { + min-height: 38px; + } + mat-header-row { + min-height: 40px; + } + mat-toolbar { + z-index: 10; + } + span.no-data-found { + height: calc(100% - 44px); + } + } +} + +:host ::ng-deep { + .tb-table-widget { + .mat-tab-group { + height: 100%; + } + .mat-tab-body-wrapper { + height: 100%; + } + .mat-tab-body-content { + display: flex; + flex-direction: column; + } + } +} 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 new file mode 100644 index 0000000000..c11a4c5f28 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts @@ -0,0 +1,554 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { + AfterViewInit, + Component, + ElementRef, + Input, + NgZone, + OnInit, + QueryList, + ViewChild, + ViewChildren, + ViewContainerRef +} from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { WidgetAction, WidgetContext } from '@home/models/widget-component.models'; +import { + DataKey, + Datasource, + DatasourceData, + DatasourceType, + WidgetActionDescriptor, + WidgetConfig +} from '@shared/models/widget.models'; +import { UtilsService } from '@core/services/utils.service'; +import { TranslateService } from '@ngx-translate/core'; +import { isDefined, isNumber } from '@core/utils'; +import cssjs from '@core/css/css'; +import { PageLink } from '@shared/models/page/page-link'; +import { Direction, SortOrder, sortOrderFromString } from '@shared/models/page/sort-order'; +import { DataSource } from '@angular/cdk/typings/collections'; +import { CollectionViewer } from '@angular/cdk/collections'; +import { BehaviorSubject, fromEvent, merge, Observable, of } from 'rxjs'; +import { emptyPageData, PageData } from '@shared/models/page/page-data'; +import { catchError, debounceTime, distinctUntilChanged, map, tap } from 'rxjs/operators'; +import { MatPaginator } from '@angular/material/paginator'; +import { MatSort } from '@angular/material/sort'; +import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; +import { + CellContentInfo, + CellStyleInfo, + constructTableCssString, + getCellContentInfo, + getCellStyleInfo, + TableWidgetDataKeySettings +} from '@home/components/widget/lib/table-widget.models'; +import { Overlay } from '@angular/cdk/overlay'; +import { SubscriptionEntityInfo } from '@core/api/widget-api.models'; +import { DatePipe } from '@angular/common'; + +interface TimeseriesTableWidgetSettings { + showTimestamp: boolean; + showMilliseconds: boolean; + displayPagination: boolean; + defaultPageSize: number; + hideEmptyLines: boolean; +} + +interface TimeseriesTableDataKeySettings extends TableWidgetDataKeySettings { +} + +interface TimeseriesRow { + [col: number]: any; + formattedTs: string; +} + +interface TimeseriesHeader { + index: number; + dataKey: DataKey; +} + +interface TimeseriesTableSource { + keyStartIndex: number; + keyEndIndex: number; + datasource: Datasource; + rawData: Array; + data: TimeseriesRow[]; + pageLink: PageLink; + displayedColumns: string[]; + timeseriesDatasource: TimeseriesDatasource; + header: TimeseriesHeader[], + stylesInfo: CellStyleInfo[], + contentsInfo: CellContentInfo[], + rowDataTemplate: {[key: string]: any} +} + +@Component({ + selector: 'tb-timeseries-table-widget', + templateUrl: './timeseries-table-widget.component.html', + styleUrls: ['./timeseries-table-widget.component.scss', './table-widget.scss'] +}) +export class TimeseriesTableWidgetComponent extends PageComponent implements OnInit, AfterViewInit { + + @Input() + ctx: WidgetContext; + + @ViewChild('searchInput', {static: false}) searchInputField: ElementRef; + @ViewChildren(MatPaginator) paginators: QueryList; + @ViewChildren(MatSort) sorts: QueryList; + + public displayPagination = true; + public pageSizeOptions; + public textSearchMode = false; + public textSearch: string = null; + public actionCellDescriptors: WidgetActionDescriptor[]; + public sources: TimeseriesTableSource[]; + public sourceIndex: number; + + private settings: TimeseriesTableWidgetSettings; + private widgetConfig: WidgetConfig; + private data: Array; + private datasources: Array; + + private defaultPageSize = 10; + private defaultSortOrder = '-0'; + private hideEmptyLines = false; + private showTimestamp = true; + private dateFormatFilter: string; + + private searchAction: WidgetAction = { + name: 'action.search', + show: true, + icon: 'search', + onAction: () => { + this.enterFilterMode(); + } + }; + + constructor(protected store: Store, + private elementRef: ElementRef, + private ngZone: NgZone, + private overlay: Overlay, + private viewContainerRef: ViewContainerRef, + private utils: UtilsService, + private translate: TranslateService, + private domSanitizer: DomSanitizer, + private datePipe: DatePipe) { + super(store); + } + + ngOnInit(): void { + this.ctx.$scope.timeseriesTableWidget = this; + this.settings = this.ctx.settings; + this.widgetConfig = this.ctx.widgetConfig; + this.data = this.ctx.data; + this.datasources = this.ctx.datasources; + this.initialize(); + this.ctx.updateWidgetParams(); + } + + ngAfterViewInit(): void { + fromEvent(this.searchInputField.nativeElement, 'keyup') + .pipe( + debounceTime(150), + distinctUntilChanged(), + tap(() => { + if (this.displayPagination) { + this.paginators.forEach((paginator) => { + paginator.pageIndex = 0; + }); + } + this.sources.forEach((source) => { + source.pageLink.textSearch = this.textSearch; + }); + this.updateAllData(); + }) + ) + .subscribe(); + + if (this.displayPagination) { + this.sorts.forEach((sort, index) => { + sort.sortChange.subscribe(() => this.paginators.toArray()[index].pageIndex = 0); + }); + } + this.sorts.forEach((sort, index) => { + const paginator = this.displayPagination ? this.paginators.toArray()[index] : null; + sort.sortChange.subscribe(() => this.paginators.toArray()[index].pageIndex = 0); + (this.displayPagination ? merge(sort.sortChange, paginator.page) : sort.sortChange) + .pipe( + tap(() => this.updateData(sort, paginator, index)) + ) + .subscribe(); + }); + this.updateAllData(); + } + + public onDataUpdated() { + this.ngZone.run(() => { + this.sources.forEach((source) => { + source.timeseriesDatasource.dataUpdated(this.data); + }); + this.ctx.detectChanges(); + }); + } + + private initialize() { + this.ctx.widgetActions = [this.searchAction ]; + + this.actionCellDescriptors = this.ctx.actionsApi.getActionDescriptors('actionCellButton'); + + this.displayPagination = isDefined(this.settings.displayPagination) ? this.settings.displayPagination : true; + this.hideEmptyLines = isDefined(this.settings.hideEmptyLines) ? this.settings.hideEmptyLines : false; + this.showTimestamp = this.settings.showTimestamp !== false; + this.dateFormatFilter = (this.settings.showMilliseconds !== true) ? 'yyyy-MM-dd HH:mm:ss' : 'yyyy-MM-dd HH:mm:ss.sss'; + + const pageSize = this.settings.defaultPageSize; + if (isDefined(pageSize) && isNumber(pageSize) && pageSize > 0) { + this.defaultPageSize = pageSize; + } + this.pageSizeOptions = [this.defaultPageSize, this.defaultPageSize*2, this.defaultPageSize*3]; + + let cssString = constructTableCssString(this.widgetConfig); + + const origBackgroundColor = this.widgetConfig.backgroundColor || 'rgb(255, 255, 255)'; + cssString += '.tb-table-widget mat-toolbar.mat-table-toolbar:not([color=primary]) {\n'+ + 'background-color: ' + origBackgroundColor + ' !important;\n'+ + '}\n'; + + const cssParser = new cssjs(); + cssParser.testMode = false; + const namespace = 'ts-table-' + this.utils.hashCode(cssString); + cssParser.cssPreviewNamespace = namespace; + cssParser.createStyleElement(namespace, cssString); + $(this.elementRef.nativeElement).addClass(namespace); + this.updateDatasources(); + } + + private updateDatasources() { + this.sources = []; + this.sourceIndex = 0; + let keyOffset = 0; + const pageSize = this.displayPagination ? this.defaultPageSize : Number.POSITIVE_INFINITY; + if (this.datasources) { + for (const datasource of this.datasources) { + const sortOrder: SortOrder = sortOrderFromString(this.defaultSortOrder); + const source = {} as TimeseriesTableSource; + source.keyStartIndex = keyOffset; + keyOffset += datasource.dataKeys.length; + source.keyEndIndex = keyOffset; + source.datasource = datasource; + source.data = []; + source.rawData = []; + source.displayedColumns = []; + source.pageLink = new PageLink(pageSize, 0, null, sortOrder); + source.header = []; + source.stylesInfo = []; + source.contentsInfo = []; + source.rowDataTemplate = {}; + source.rowDataTemplate['Timestamp'] = null; + if (this.showTimestamp) { + source.displayedColumns.push('0'); + } + for (let a = 0; a < datasource.dataKeys.length; a++ ) { + const dataKey = datasource.dataKeys[a]; + const keySettings: TimeseriesTableDataKeySettings = dataKey.settings; + const index = a + 1; + source.header.push({ + index, + dataKey + }); + source.displayedColumns.push(index + ''); + source.rowDataTemplate[dataKey.label] = null; + source.stylesInfo.push(getCellStyleInfo(keySettings)); + const cellContentInfo = getCellContentInfo(keySettings, 'value, rowData, ctx'); + cellContentInfo.units = dataKey.units; + cellContentInfo.decimals = dataKey.decimals; + source.contentsInfo.push(cellContentInfo); + } + source.displayedColumns.push('actions'); + const tsDatasource = new TimeseriesDatasource(source, this.hideEmptyLines, this.dateFormatFilter, this.datePipe); + tsDatasource.dataUpdated(this.data); + this.sources.push(source); + } + } + this.updateActiveEntityInfo(); + } + + private updateActiveEntityInfo() { + const source = this.sources[this.sourceIndex]; + let activeEntityInfo: SubscriptionEntityInfo = null; + if (source) { + const datasource = source.datasource; + if (datasource.type === DatasourceType.entity && + datasource.entityType && datasource.entityId) { + activeEntityInfo = { + entityId: { + entityType: datasource.entityType, + id: datasource.entityId + }, + entityName: datasource.entityName + }; + } + } + this.ctx.activeEntityInfo = activeEntityInfo; + } + + onSourceIndexChanged() { + this.updateActiveEntityInfo(); + } + + private enterFilterMode() { + this.textSearchMode = true; + this.textSearch = ''; + this.sources.forEach((source) => { + source.pageLink.textSearch = this.textSearch; + }); + this.ctx.hideTitlePanel = true; + this.ctx.detectChanges(true); + setTimeout(() => { + this.searchInputField.nativeElement.focus(); + this.searchInputField.nativeElement.setSelectionRange(0, 0); + }, 10); + } + + exitFilterMode() { + this.textSearchMode = false; + this.textSearch = null; + this.sources.forEach((source, index) => { + source.pageLink.textSearch = this.textSearch; + const sort = this.sorts.toArray()[index]; + let paginator = null; + if (this.displayPagination) { + paginator = this.paginators.toArray()[index]; + paginator.pageIndex = 0; + } + this.updateData(sort, paginator, index); + }); + this.ctx.hideTitlePanel = false; + this.ctx.detectChanges(true); + } + + private updateAllData() { + this.sources.forEach((source, index) => { + const sort = this.sorts.toArray()[index]; + const paginator = this.displayPagination ? this.paginators.toArray()[index] : null; + this.updateData(sort, paginator, index); + }); + } + + private updateData(sort: MatSort, paginator: MatPaginator, index: number) { + const source = this.sources[index]; + if (this.displayPagination) { + source.pageLink.page = paginator.pageIndex; + source.pageLink.pageSize = paginator.pageSize; + } else { + source.pageLink.page = 0; + } + source.pageLink.sortOrder.property = sort.active; + source.pageLink.sortOrder.direction = Direction[sort.direction.toUpperCase()]; + source.timeseriesDatasource.loadRows(); + this.ctx.detectChanges(); + } + + public trackByColumnIndex(index, header: TimeseriesHeader) { + return header.index; + } + + public trackByRowIndex(index: number, row: TimeseriesRow) { + return index; + } + + public cellStyle(source: TimeseriesTableSource, index: number, value: any): any { + let style: any = {}; + if (index > 0) { + const styleInfo = source.stylesInfo[index-1]; + if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) { + try { + style = styleInfo.cellStyleFunction(value); + } catch (e) { + style = {}; + } + } + } + return style; + } + + public cellContent(source: TimeseriesTableSource, index: number, row: TimeseriesRow, value: any): SafeHtml { + if (index === 0) { + return row.formattedTs; + } else { + let content = ''; + const contentInfo = source.contentsInfo[index-1]; + if (contentInfo.useCellContentFunction && contentInfo.cellContentFunction) { + try { + const rowData = source.rowDataTemplate; + rowData['Timestamp'] = row[0]; + for (let h=0; h < source.header.length; h++) { + const headerInfo = source.header[h]; + rowData[headerInfo.dataKey.name] = row[headerInfo.index]; + } + content = contentInfo.cellContentFunction(value, rowData, this.ctx); + } catch (e) { + content = '' + value; + } + } else { + const decimals = (contentInfo.decimals || contentInfo.decimals === 0) ? contentInfo.decimals : this.ctx.widgetConfig.decimals; + const units = contentInfo.units || this.ctx.widgetConfig.units; + content = this.ctx.utils.formatValue(value, decimals, units, true); + } + return isDefined(content) ? this.domSanitizer.bypassSecurityTrustHtml(content) : ''; + } + } + + public onRowClick($event: Event, row: TimeseriesRow) { + const descriptors = this.ctx.actionsApi.getActionDescriptors('rowClick'); + if (descriptors.length) { + if ($event) { + $event.stopPropagation(); + } + let entityId; + let entityName; + if (this.ctx.activeEntityInfo) { + entityId = this.ctx.activeEntityInfo.entityId; + entityName = this.ctx.activeEntityInfo.entityName; + } + this.ctx.actionsApi.handleWidgetAction($event, descriptors[0], entityId, entityName, row); + } + } + + public onActionButtonClick($event: Event, row: TimeseriesRow, actionDescriptor: WidgetActionDescriptor) { + if ($event) { + $event.stopPropagation(); + } + let entityId; + let entityName; + if (this.ctx.activeEntityInfo) { + entityId = this.ctx.activeEntityInfo.entityId; + entityName = this.ctx.activeEntityInfo.entityName; + } + this.ctx.actionsApi.handleWidgetAction($event, actionDescriptor, entityId, entityName, row); + } +} + +class TimeseriesDatasource implements DataSource { + + private rowsSubject = new BehaviorSubject([]); + private pageDataSubject = new BehaviorSubject>(emptyPageData()); + + private allRowsSubject = new BehaviorSubject([]); + private allRows$: Observable> = this.allRowsSubject.asObservable(); + + constructor( + private source: TimeseriesTableSource, + private hideEmptyLines: boolean, + private dateFormatFilter: string, + private datePipe: DatePipe + ) { + this.source.timeseriesDatasource = this; + } + + connect(collectionViewer: CollectionViewer): Observable> { + return this.rowsSubject.asObservable(); + } + + disconnect(collectionViewer: CollectionViewer): void { + this.rowsSubject.complete(); + this.pageDataSubject.complete(); + } + + loadRows() { + this.fetchRows(this.source.pageLink).pipe( + catchError(() => of(emptyPageData())), + ).subscribe( + (pageData) => { + this.rowsSubject.next(pageData.data); + this.pageDataSubject.next(pageData); + } + ); + } + + dataUpdated(data: DatasourceData[]) { + this.source.rawData = data.slice(this.source.keyStartIndex, this.source.keyEndIndex); + this.updateSourceData(); + } + + private updateSourceData() { + this.source.data = this.convertData(this.source.rawData); + this.allRowsSubject.next(this.source.data); + } + + private convertData(data: DatasourceData[]): TimeseriesRow[] { + const rowsMap: {[timestamp: number]: TimeseriesRow} = {}; + for (let d = 0; d < data.length; d++) { + const columnData = data[d].data; + for (let i = 0; i < columnData.length; i++) { + const cellData = columnData[i]; + const timestamp = cellData[0]; + let row = rowsMap[timestamp]; + if (!row) { + row = { + formattedTs: this.datePipe.transform(timestamp, this.dateFormatFilter) + }; + row[0] = timestamp; + for (let c = 0; c < data.length; c++) { + row[c+1] = undefined; + } + rowsMap[timestamp] = row; + } + row[d+1] = cellData[1]; + } + } + const rows: TimeseriesRow[] = []; + for (const t of Object.keys(rowsMap)) { + if (this.hideEmptyLines) { + let hideLine = true; + for (let _c = 0; (_c < data.length) && hideLine; _c++) { + if (rowsMap[t][_c+1]) + hideLine = false; + } + if (!hideLine) { + rows.push(rowsMap[t]); + } + } else { + rows.push(rowsMap[t]); + } + } + return rows; + } + + + isEmpty(): Observable { + return this.rowsSubject.pipe( + map((rows) => !rows.length) + ); + } + + total(): Observable { + return this.pageDataSubject.pipe( + map((pageData) => pageData.totalElements) + ); + } + + private fetchRows(pageLink: PageLink): Observable> { + return this.allRows$.pipe( + map((data) => pageLink.filterData(data)) + ); + } +} 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 b006b317a6..369d2546b0 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 @@ -22,6 +22,7 @@ import { DisplayColumnsPanelComponent } from '@home/components/widget/lib/displa import { AlarmsTableWidgetComponent } from '@home/components/widget/lib/alarms-table-widget.component'; import { AlarmStatusFilterPanelComponent } from '@home/components/widget/lib/alarm-status-filter-panel.component'; import { SharedHomeComponentsModule } from '@home/components/shared-home-components.module'; +import { TimeseriesTableWidgetComponent } from '@home/components/widget/lib/timeseries-table-widget.component'; @NgModule({ entryComponents: [ @@ -33,7 +34,8 @@ import { SharedHomeComponentsModule } from '@home/components/shared-home-compone DisplayColumnsPanelComponent, AlarmStatusFilterPanelComponent, EntitiesTableWidgetComponent, - AlarmsTableWidgetComponent + AlarmsTableWidgetComponent, + TimeseriesTableWidgetComponent ], imports: [ CommonModule, @@ -42,7 +44,8 @@ import { SharedHomeComponentsModule } from '@home/components/shared-home-compone ], exports: [ EntitiesTableWidgetComponent, - AlarmsTableWidgetComponent + AlarmsTableWidgetComponent, + TimeseriesTableWidgetComponent ] }) export class WidgetComponentsModule { } diff --git a/ui-ngx/src/styles.scss b/ui-ngx/src/styles.scss index 0e490633e6..d81548ac10 100644 --- a/ui-ngx/src/styles.scss +++ b/ui-ngx/src/styles.scss @@ -857,7 +857,7 @@ mat-label { span.no-data-found { position: relative; display: flex; - height: calc(100% - 57px); + height: calc(100% - 60px); text-transform: uppercase; } From 7261c75c611ba288ded318648765d01e4d803e0e Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 31 Jan 2020 20:26:08 +0200 Subject: [PATCH 082/133] Implement entities hierarchy widget --- .../json/system/widget_bundles/cards.json | 4 +- ui-ngx/angular.json | 6 +- ui-ngx/package-lock.json | 25 + ui-ngx/package.json | 3 + ui-ngx/src/app/app.component.ts | 2 +- .../interceptors/global-http-interceptor.ts | 2 +- .../attribute/attribute-table.component.scss | 6 + .../entity-details-panel.component.scss | 26 +- .../entities-hierarchy-widget.component.html | 51 ++ .../entities-hierarchy-widget.component.scss | 122 +++++ .../entities-hierarchy-widget.component.ts | 490 ++++++++++++++++++ .../lib/entities-hierarchy-widget.models.ts | 160 ++++++ .../widget/widget-components.module.ts | 7 +- .../app/modules/login/login-routing.module.ts | 10 + .../login/pages/login/login.component.ts | 17 +- .../pages/login/reset-password.component.html | 3 + .../pages/login/reset-password.component.ts | 3 + .../shared/components/nav-tree.component.html | 18 + .../shared/components/nav-tree.component.scss | 347 +++++++++++++ .../shared/components/nav-tree.component.ts | 272 ++++++++++ ui-ngx/src/app/shared/models/constants.ts | 1 + ui-ngx/src/app/shared/shared.module.ts | 3 + ui-ngx/src/scss/mixins.scss | 10 +- ui-ngx/src/tsconfig.app.json | 2 +- ui-ngx/src/typings/jquery.jstree.typings.d.ts | 29 ++ ui-ngx/tsconfig.json | 1 + 26 files changed, 1594 insertions(+), 26 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.models.ts create mode 100644 ui-ngx/src/app/shared/components/nav-tree.component.html create mode 100644 ui-ngx/src/app/shared/components/nav-tree.component.scss create mode 100644 ui-ngx/src/app/shared/components/nav-tree.component.ts create mode 100644 ui-ngx/src/typings/jquery.jstree.typings.d.ts 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 bf40d495df..eaf129c1c0 100644 --- a/application/src/main/data/json/system/widget_bundles/cards.json +++ b/application/src/main/data/json/system/widget_bundles/cards.json @@ -125,9 +125,9 @@ "sizeX": 7.5, "sizeY": 3.5, "resources": [], - "templateHtml": "\n", + "templateHtml": "\n", "templateCss": "", - "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n var id = self.ctx.$scope.$injector.get('utils').guid();\n scope.hierarchyId = \"hierarchy-\"+id;\n scope.ctx = self.ctx;\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.$broadcast('entities-hierarchy-data-updated', self.ctx.$scope.hierarchyId);\n}\n\nself.typeParameters = function() {\n return {\n dataKeysOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'nodeSelected': {\n name: 'widget-action.node-selected',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n", + "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.entitiesHierarchyWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n dataKeysOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'nodeSelected': {\n name: 'widget-action.node-selected',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesHierarchySettings\",\n \"properties\": {\n \"nodeRelationQueryFunction\": {\n \"title\": \"Node relations query function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeHasChildrenFunction\": {\n \"title\": \"Node has children function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeOpenedFunction\": {\n \"title\": \"Default node opened function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeDisabledFunction\": {\n \"title\": \"Node disabled function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeIconFunction\": {\n \"title\": \"Node icon function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeTextFunction\": {\n \"title\": \"Node text function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodesSortFunction\": {\n \"title\": \"Nodes sort function: f(nodeCtx1, nodeCtx2)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n {\n \"key\": \"nodeRelationQueryFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodeHasChildrenFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodeOpenedFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodeDisabledFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodeIconFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodeTextFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodesSortFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {},\n \"required\": []\n },\n \"form\": []\n}", "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"nodeRelationQueryFunction\":\"/**\\n\\n// Function should return relations query object for current node used to fetch entity children.\\n// Function can return 'default' string value. In this case default relations query will be used.\\n\\n// The following example code will construct simple relations query that will fetch relations of type 'Contains'\\n// from the current entity.\\n\\nvar entity = nodeCtx.entity;\\nvar query = {\\n parameters: {\\n rootId: entity.id.id,\\n rootType: entity.id.entityType,\\n direction: types.entitySearchDirection.from,\\n relationTypeGroup: \\\"COMMON\\\",\\n maxLevel: 1\\n },\\n filters: [{\\n relationType: \\\"Contains\\\",\\n entityTypes: []\\n }]\\n};\\nreturn query;\\n\\n**/\\n\",\"nodeHasChildrenFunction\":\"/**\\n\\n// Function should return boolean value indicating whether current node has children (whether it can be expanded).\\n\\n// The following example code will restrict entities hierarchy expansion up to third level.\\n\\nreturn nodeCtx.level <= 2;\\n\\n// The next example code will restrict entities expansion according to the value of example 'nodeHasChildren' attribute.\\n\\nvar data = nodeCtx.data;\\nif (data.hasOwnProperty('nodeHasChildren') && data['nodeHasChildren'] !== null) {\\n return data['nodeHasChildren'] === 'true';\\n} else {\\n return true;\\n}\\n \\n**/\\n \",\"nodeTextFunction\":\"/**\\n\\n// Function should return text (can be HTML code) for the current node.\\n\\n// The following example code will generate node text consisting of entity name and temperature if temperature value is present in entity attributes/timeseries.\\n\\nvar data = nodeCtx.data;\\nvar entity = nodeCtx.entity;\\nvar text = entity.name;\\nif (data.hasOwnProperty('temperature') && data['temperature'] !== null) {\\n text += \\\" \\\"+ data['temperature'] +\\\" °C\\\";\\n}\\nreturn text;\\n\\n**/\",\"nodeIconFunction\":\"/** \\n\\n// Function should return node icon info object.\\n// Resulting object should contain either 'materialIcon' or 'iconUrl' property. \\n// Where:\\n - 'materialIcon' - name of the material icon to be used from the Material Icons Library (https://material.io/tools/icons);\\n - 'iconUrl' - url of the external image to be used as node icon.\\n// Function can return 'default' string value. In this case default icons according to entity type will be used.\\n\\n// The following example code shows how to use external image for devices which name starts with 'Test' and use \\n// default icons for the rest of entities.\\n\\nvar entity = nodeCtx.entity;\\nif (entity.id.entityType === 'DEVICE' && entity.name.startsWith('Test')) {\\n return {iconUrl: 'https://avatars1.githubusercontent.com/u/14793288?v=4&s=117'};\\n} else {\\n return 'default';\\n}\\n \\n**/\",\"nodeDisabledFunction\":\"/**\\n\\n// Function should return boolean value indicating whether current node should be disabled (not selectable).\\n\\n// The following example code will disable current node according to the value of example 'nodeDisabled' attribute.\\n\\nvar data = nodeCtx.data;\\nif (data.hasOwnProperty('nodeDisabled') && data['nodeDisabled'] !== null) {\\n return data['nodeDisabled'] === 'true';\\n} else {\\n return false;\\n}\\n \\n**/\\n\",\"nodesSortFunction\":\"/**\\n\\n// This function is used to sort nodes of the same level. Function should compare two nodes and return \\n// integer value: \\n// - less than 0 - sort nodeCtx1 to an index lower than nodeCtx2\\n// - 0 - leave nodeCtx1 and nodeCtx2 unchanged with respect to each other\\n// - greater than 0 - sort nodeCtx2 to an index lower than nodeCtx1\\n\\n// The following example code will sort entities first by entity type in alphabetical order then\\n// by entity name in alphabetical order.\\n\\nvar result = nodeCtx1.entity.id.entityType.localeCompare(nodeCtx2.entity.id.entityType);\\nif (result === 0) {\\n result = nodeCtx1.entity.name.localeCompare(nodeCtx2.entity.name);\\n}\\nreturn result;\\n \\n**/\",\"nodeOpenedFunction\":\"/**\\n\\n// Function should return boolean value indicating whether current node should be opened (expanded) when it first loaded.\\n\\n// The following example code will open by default nodes up to third level.\\n\\nreturn nodeCtx.level <= 2;\\n\\n**/\\n \"},\"title\":\"Entities hierarchy\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#2196f3\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.472295003170325,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Cos\",\"color\":\"#4caf50\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.8926244886945558,\"funcBody\":\"return Math.round(1000*Math.cos(time/5000));\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"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;\"}]}],\"widgetStyle\":{},\"actions\":{}}" diff --git a/ui-ngx/angular.json b/ui-ngx/angular.json index 3a6fb4fdb6..e1106a7c7e 100644 --- a/ui-ngx/angular.json +++ b/ui-ngx/angular.json @@ -36,7 +36,8 @@ "node_modules/tooltipster/dist/css/tooltipster.bundle.min.css", "node_modules/tooltipster/dist/css/plugins/tooltipster/sideTip/themes/tooltipster-sideTip-shadow.min.css", "src/app/shared/components/json-form/react/json-form.scss", - "node_modules/rc-select/assets/index.css" + "node_modules/rc-select/assets/index.css", + "node_modules/jstree-bootstrap-theme/dist/themes/proton/style.min.css" ], "stylePreprocessorOptions": { "includePaths": [ @@ -79,7 +80,8 @@ "node_modules/ace-builds/src-min/snippets/json.js", "node_modules/ace-builds/src-min/snippets/java.js", "node_modules/ace-builds/src-min/snippets/javascript.js", - "node_modules/systemjs/dist/system.js" + "node_modules/systemjs/dist/system.js", + "node_modules/jstree/dist/jstree.min.js" ], "es5BrowserSupport": true, "customWebpackConfig": { diff --git a/ui-ngx/package-lock.json b/ui-ngx/package-lock.json index 1922bf20ed..95a439ed51 100644 --- a/ui-ngx/package-lock.json +++ b/ui-ngx/package-lock.json @@ -3603,6 +3603,15 @@ "integrity": "sha512-B1Br8yE27obcYvFx5ECZswT/947aAFNb9lHqnkUOhtOfvJqaa6Axibo4T+5G6iQlUfjgSd8am9R/9j9UBfRlrw==", "dev": true }, + "@types/jstree": { + "version": "3.3.39", + "resolved": "https://registry.npmjs.org/@types/jstree/-/jstree-3.3.39.tgz", + "integrity": "sha512-lUUl9NCRqziIXYTC0n6NUykQS0oII5tUhXq6/pXVZSbrbcxB0jr3i7Bpn/8v6f8BVA9su1/NF/DoJ8IrwKuzKw==", + "dev": true, + "requires": { + "@types/jquery": "*" + } + }, "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", @@ -8685,6 +8694,22 @@ "jss": "10.0.0" } }, + "jstree": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/jstree/-/jstree-3.3.8.tgz", + "integrity": "sha512-0/nhGxVLSGfGQyVg+q59ocqSEKWRDKHoA8wNrcOIvlzCCw19tzvcMNGJ19hf+U0b7fycABowkny7fQPcLgUwwA==", + "requires": { + "jquery": ">=1.9.1" + } + }, + "jstree-bootstrap-theme": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/jstree-bootstrap-theme/-/jstree-bootstrap-theme-1.0.1.tgz", + "integrity": "sha1-fV7cc6hG6Np/lPV6HMXd7p2eq0s=", + "requires": { + "jquery": ">=1.9.1" + } + }, "jszip": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.2.2.tgz", diff --git a/ui-ngx/package.json b/ui-ngx/package.json index 07d0d21c81..dced2e17a4 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -54,6 +54,8 @@ "jquery.terminal": "^2.9.0", "js-beautify": "^1.10.2", "json-schema-defaults": "^0.4.0", + "jstree": "^3.3.8", + "jstree-bootstrap-theme": "^1.0.1", "material-design-icons": "^3.0.1", "messageformat": "^2.3.0", "moment": "^2.24.0", @@ -93,6 +95,7 @@ "@types/jasminewd2": "~2.0.8", "@types/jquery": "^3.3.31", "@types/js-beautify": "^1.8.1", + "@types/jstree": "^3.3.39", "@types/node": "~12.12.17", "@types/react": "^16.9.16", "@types/react-dom": "^16.9.4", diff --git a/ui-ngx/src/app/app.component.ts b/ui-ngx/src/app/app.component.ts index 2a0c78585f..1d30454510 100644 --- a/ui-ngx/src/app/app.component.ts +++ b/ui-ngx/src/app/app.component.ts @@ -51,7 +51,7 @@ export class AppComponent implements OnInit { this.matIconRegistry.addSvgIconLiteral( 'alpha-a-circle-outline', this.domSanitizer.bypassSecurityTrustHtml( - '' diff --git a/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts b/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts index fe11d8461d..ded6a2ec5a 100644 --- a/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts +++ b/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts @@ -128,7 +128,7 @@ export class GlobalHttpInterceptor implements HttpInterceptor { if (errorResponse.error.refreshTokenPending || errorResponse.status === 401) { if (errorResponse.error.refreshTokenPending || errorCode && errorCode === Constants.serverErrorCode.jwtTokenExpired) { return this.refreshTokenAndRetry(req, next); - } else { + } else if (errorCode !== Constants.serverErrorCode.credentialsExpired) { unhandled = true; } } else if (errorResponse.status === 429) { 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 3b525bae39..e5cbdb8404 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 @@ -91,5 +91,11 @@ opacity: 1; } } + ul.indicators { + pointer-events: none; + li { + pointer-events: all; + } + } } } diff --git a/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.scss b/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.scss index 1197999f23..b10ad081f1 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.scss +++ b/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.scss @@ -21,14 +21,22 @@ } :host ::ng-deep { - .mat-tab-body-wrapper { - position: absolute; - top: 49px; - left: 0; - right: 0; - bottom: 0; - } - .mat-tab-label { - min-width: 40px; + tb-details-panel { + > .mat-content { + > .mat-tab-group { + > .mat-tab-body-wrapper { + position: absolute; + top: 49px; + left: 0; + right: 0; + bottom: 0; + } + > .mat-tab-header { + .mat-tab-label { + min-width: 40px; + } + } + } + } } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.component.html new file mode 100644 index 0000000000..1e3f6da1a3 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.component.html @@ -0,0 +1,51 @@ + +
+
+ +
+ + +   + + + +
+
+
+ +
+
+
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 new file mode 100644 index 0000000000..9f296c4e60 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.component.scss @@ -0,0 +1,122 @@ +/** + * Copyright © 2016-2019 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-context(.tb-has-timewindow) { + .tb-entities-hierarchy { + mat-toolbar { + height: 60px; + max-height: 60px; + .mat-toolbar-tools { + height: 60px; + max-height: 60px; + } + } + } +} + +:host { + .tb-entities-hierarchy { + mat-toolbar.mat-table-toolbar:not([color="primary"]) { + background: transparent; + } + mat-toolbar { + min-height: 39px; + max-height: 39px; + .mat-toolbar-tools { + min-height: 39px; + max-height: 39px; + } + } + + .tb-entities-nav-tree-panel { + overflow-x: auto; + overflow-y: auto; + } + } +} + +:host ::ng-deep { + .tb-nav-tree-container { + &.jstree-proton { + .jstree-anchor { + div.node-icon { + display: inline-block; + width: 22px; + height: 22px; + margin-right: 2px; + margin-bottom: 2px; + background-color: transparent; + background-repeat: no-repeat; + background-attachment: scroll; + background-position: center center; + background-size: 18px 18px; + } + + mat-icon.node-icon { + width: 22px; + min-width: 22px; + height: 22px; + min-height: 22px; + margin-right: 2px; + margin-bottom: 2px; + color: inherit; + vertical-align: middle; + + &.material-icons { + font-size: 18px; + line-height: 22px; + text-align: center; + } + } + + &.jstree-hovered:not(.jstree-clicked), + &.jstree-disabled { + div.node-icon { + opacity: .5; + } + } + } + } + } + + @media (max-width: 768px) { + .tb-nav-tree-container { + &.jstree-proton-responsive { + .jstree-anchor { + div.node-icon { + width: 40px; + height: 40px; + margin: 0; + background-size: 24px 24px; + } + + mat-icon.node-icon { + width: 40px; + min-width: 40px; + height: 40px; + min-height: 40px; + margin: 0; + + &.material-icons { + font-size: 24px; + line-height: 40px; + } + } + } + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.component.ts new file mode 100644 index 0000000000..bde8f744ff --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.component.ts @@ -0,0 +1,490 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { AfterViewInit, Component, ElementRef, Input, OnInit, ViewChild, ViewContainerRef } from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { WidgetAction, WidgetContext } from '@home/models/widget-component.models'; +import { DatasourceData, DatasourceType, WidgetConfig, widgetType } from '@shared/models/widget.models'; +import { IWidgetSubscription, WidgetSubscriptionOptions } from '@core/api/widget-api.models'; +import { UtilsService } from '@core/services/utils.service'; +import cssjs from '@core/css/css'; +import { forkJoin, fromEvent, Observable, of } from 'rxjs'; +import { catchError, debounceTime, distinctUntilChanged, map, mergeMap, tap } from 'rxjs/operators'; +import { constructTableCssString } from '@home/components/widget/lib/table-widget.models'; +import { Overlay } from '@angular/cdk/overlay'; +import { + LoadNodesCallback, + NavTreeEditCallbacks, + NodeSearchCallback, + NodeSelectedCallback, + NodesInsertedCallback +} from '@shared/components/nav-tree.component'; +import { BaseData } from '@shared/models/base-data'; +import { EntityId } from '@shared/models/id/entity-id'; +import { EntityType } from '@shared/models/entity-type.models'; +import { deepClone } from '@core/utils'; +import { + defaultNodeIconFunction, + defaultNodeOpenedFunction, + defaultNodeRelationQueryFunction, + defaultNodesSortFunction, + EntitiesHierarchyWidgetSettings, + HierarchyNavTreeNode, + HierarchyNodeContext, + HierarchyNodeDatasource, + iconUrlHtml, + loadNodeCtxFunction, + materialIconHtml, + NodeDisabledFunction, + NodeHasChildrenFunction, + NodeIconFunction, + NodeOpenedFunction, + NodeRelationQueryFunction, + NodesSortFunction, + NodeTextFunction +} from '@home/components/widget/lib/entities-hierarchy-widget.models'; +import { EntityService } from '@core/http/entity.service'; +import { EntityRelationsQuery, EntitySearchDirection } from '@shared/models/relation.models'; +import { EntityRelationService } from '@core/http/entity-relation.service'; +import { ActionNotificationShow } from '@core/notification/notification.actions'; + +@Component({ + selector: 'tb-entities-hierarchy-widget', + templateUrl: './entities-hierarchy-widget.component.html', + styleUrls: ['./entities-hierarchy-widget.component.scss'] +}) +export class EntitiesHierarchyWidgetComponent extends PageComponent implements OnInit, AfterViewInit { + + @Input() + ctx: WidgetContext; + + @ViewChild('searchInput', {static: false}) searchInputField: ElementRef; + + public toastTargetId = 'entities-hierarchy-' + this.utils.guid(); + + public textSearchMode = false; + public textSearch = null; + + public nodeEditCallbacks: NavTreeEditCallbacks = {}; + + private settings: EntitiesHierarchyWidgetSettings; + private widgetConfig: WidgetConfig; + private subscription: IWidgetSubscription; + private datasources: Array; + + private nodesMap: {[nodeId: string]: HierarchyNavTreeNode} = {}; + private pendingUpdateNodeTasks: {[nodeId: string]: () => void} = {}; + private nodeIdCounter = 0; + + private nodeRelationQueryFunction: NodeRelationQueryFunction; + private nodeIconFunction: NodeIconFunction; + private nodeTextFunction: NodeTextFunction; + private nodeDisabledFunction: NodeDisabledFunction; + private nodeOpenedFunction: NodeOpenedFunction; + private nodeHasChildrenFunction: NodeHasChildrenFunction; + private nodesSortFunction: NodesSortFunction; + + private searchAction: WidgetAction = { + name: 'action.search', + show: true, + icon: 'search', + onAction: () => { + this.enterFilterMode(); + } + }; + + + constructor(protected store: Store, + private elementRef: ElementRef, + private overlay: Overlay, + private viewContainerRef: ViewContainerRef, + private utils: UtilsService, + private entityService: EntityService, + private entityRelationService: EntityRelationService) { + super(store); + } + + ngOnInit(): void { + this.ctx.$scope.entitiesHierarchyWidget = this; + this.settings = this.ctx.settings; + this.widgetConfig = this.ctx.widgetConfig; + this.subscription = this.ctx.defaultSubscription; + this.datasources = this.subscription.datasources as Array; + this.initializeConfig(); + this.ctx.updateWidgetParams(); + } + + ngAfterViewInit(): void { + fromEvent(this.searchInputField.nativeElement, 'keyup') + .pipe( + debounceTime(150), + distinctUntilChanged(), + tap(() => { + this.updateSearchNodes(); + }) + ) + .subscribe(); + } + + public onDataUpdated() { + this.updateNodeData(this.subscription.data); + } + + private initializeConfig() { + this.ctx.widgetActions = [this.searchAction]; + + const testNodeCtx: HierarchyNodeContext = { + entity: { + id: { + entityType: EntityType.DEVICE, + id: '123' + }, + name: 'TEST DEV1' + }, + data: {}, + level: 2 + }; + const parentNodeCtx = deepClone(testNodeCtx); + parentNodeCtx.level = 1; + testNodeCtx.parentNodeCtx = parentNodeCtx; + + this.nodeRelationQueryFunction = loadNodeCtxFunction(this.settings.nodeRelationQueryFunction, 'nodeCtx', testNodeCtx); + this.nodeIconFunction = loadNodeCtxFunction(this.settings.nodeIconFunction, 'nodeCtx', testNodeCtx); + this.nodeTextFunction = loadNodeCtxFunction(this.settings.nodeTextFunction, 'nodeCtx', testNodeCtx); + this.nodeDisabledFunction = loadNodeCtxFunction(this.settings.nodeDisabledFunction, 'nodeCtx', testNodeCtx); + this.nodeOpenedFunction = loadNodeCtxFunction(this.settings.nodeOpenedFunction, 'nodeCtx', testNodeCtx); + this.nodeHasChildrenFunction = loadNodeCtxFunction(this.settings.nodeHasChildrenFunction, 'nodeCtx', testNodeCtx); + + const testNodeCtx2 = deepClone(testNodeCtx); + testNodeCtx2.entity.name = 'TEST DEV2'; + + this.nodesSortFunction = loadNodeCtxFunction(this.settings.nodesSortFunction, 'nodeCtx1,nodeCtx2', testNodeCtx, testNodeCtx2); + + this.nodeRelationQueryFunction = this.nodeRelationQueryFunction || defaultNodeRelationQueryFunction; + this.nodeIconFunction = this.nodeIconFunction || defaultNodeIconFunction; + this.nodeTextFunction = this.nodeTextFunction || ((nodeCtx) => nodeCtx.entity.name); + this.nodeDisabledFunction = this.nodeDisabledFunction || (() => false); + this.nodeOpenedFunction = this.nodeOpenedFunction || defaultNodeOpenedFunction; + this.nodeHasChildrenFunction = this.nodeHasChildrenFunction || (() => true); + this.nodesSortFunction = this.nodesSortFunction || defaultNodesSortFunction; + + const cssString = constructTableCssString(this.widgetConfig); + const cssParser = new cssjs(); + cssParser.testMode = false; + const namespace = 'entities-hierarchy-' + this.utils.hashCode(cssString); + cssParser.cssPreviewNamespace = namespace; + cssParser.createStyleElement(namespace, cssString); + $(this.elementRef.nativeElement).addClass(namespace); + } + + private enterFilterMode() { + this.textSearchMode = true; + this.textSearch = ''; + this.ctx.hideTitlePanel = true; + this.ctx.detectChanges(true); + setTimeout(() => { + this.searchInputField.nativeElement.focus(); + this.searchInputField.nativeElement.setSelectionRange(0, 0); + }, 10); + } + + exitFilterMode() { + this.textSearchMode = false; + this.textSearch = null; + this.updateSearchNodes(); + this.ctx.hideTitlePanel = false; + this.ctx.detectChanges(true); + } + + private updateSearchNodes() { + if (this.textSearch != null) { + this.nodeEditCallbacks.search(this.textSearch); + } else { + this.nodeEditCallbacks.clearSearch(); + } + } + + private updateNodeData(subscriptionData: Array) { + const affectedNodes: string[] = []; + if (subscriptionData) { + subscriptionData.forEach((datasourceData) => { + const datasource = datasourceData.datasource as HierarchyNodeDatasource; + if (datasource.nodeId) { + const node = this.nodesMap[datasource.nodeId]; + const key = datasourceData.dataKey.label; + let value = undefined; + if (datasourceData.data && datasourceData.data.length) { + value = datasourceData.data[0][1]; + } + if (node.data.nodeCtx.data[key] !== value) { + if (affectedNodes.indexOf(datasource.nodeId) === -1) { + affectedNodes.push(datasource.nodeId); + } + node.data.nodeCtx.data[key] = value; + } + } + }); + } + affectedNodes.forEach((nodeId) => { + const node: HierarchyNavTreeNode = this.nodeEditCallbacks.getNode(nodeId); + if (node) { + this.updateNodeStyle(this.nodesMap[nodeId]); + } else { + this.pendingUpdateNodeTasks[nodeId] = () => { + this.updateNodeStyle(this.nodesMap[nodeId]); + }; + } + }); + } + + public loadNodes: LoadNodesCallback = (node, cb) => { + if (node.id === '#') { + const tasks: Observable[] = []; + this.datasources.forEach((datasource) => { + tasks.push(this.datasourceToNode(datasource)); + }); + forkJoin(tasks).subscribe((nodes) => { + cb(this.prepareNodes(nodes)); + this.updateNodeData(this.subscription.data); + }); + } else { + if (node.data && node.data.nodeCtx.entity && node.data.nodeCtx.entity.id && node.data.nodeCtx.entity.id.entityType !== 'function') { + const relationQuery = this.prepareNodeRelationQuery(node.data.nodeCtx); + this.entityRelationService.findByQuery(relationQuery, {ignoreErrors: true, ignoreLoading: true}).subscribe( + (entityRelations) => { + if (entityRelations.length) { + const tasks: Observable[] = []; + entityRelations.forEach((relation) => { + const targetId = relationQuery.parameters.direction === EntitySearchDirection.FROM ? relation.to : relation.from; + tasks.push(this.entityIdToNode(targetId.entityType as EntityType, targetId.id, node.data.datasource, node.data.nodeCtx)); + }); + forkJoin(tasks).subscribe((nodes) => { + cb(this.prepareNodes(nodes)); + }); + } else { + cb([]); + } + }, + (error) => { + let errorText = 'Failed to get relations!'; + if (error && error.status === 400) { + errorText = 'Invalid relations query returned by \'Node relations query function\'! Please check widget configuration!'; + } + this.showError(errorText); + } + ); + } else { + cb([]); + } + } + }; + + public onNodeSelected: NodeSelectedCallback = (node, event) => { + let nodeId; + if (!node) { + nodeId = -1; + } else { + nodeId = node.id; + } + if (nodeId !== -1) { + const selectedNode = this.nodesMap[nodeId]; + if (selectedNode) { + const descriptors = this.ctx.actionsApi.getActionDescriptors('nodeSelected'); + if (descriptors.length) { + const entity = selectedNode.data.nodeCtx.entity; + this.ctx.actionsApi.handleWidgetAction(event, descriptors[0], entity.id, entity.name, { nodeCtx: selectedNode.data.nodeCtx }); + } + } + } + }; + + public onNodesInserted: NodesInsertedCallback = (nodes, parent) => { + if (nodes) { + nodes.forEach((nodeId) => { + const task = this.pendingUpdateNodeTasks[nodeId]; + if (task) { + task(); + delete this.pendingUpdateNodeTasks[nodeId]; + } + }); + } + }; + + public searchCallback: NodeSearchCallback = (searchText, node) => { + const theNode = this.nodesMap[node.id]; + if (theNode && theNode.data.searchText) { + return theNode.data.searchText.includes(searchText.toLowerCase()); + } + return false; + }; + + private updateNodeStyle(node: HierarchyNavTreeNode) { + const newText = this.prepareNodeText(node); + if (node.text !== newText) { + node.text = newText; + this.nodeEditCallbacks.updateNode(node.id, node.text); + } + const newDisabled = this.nodeDisabledFunction(node.data.nodeCtx); + if (node.state.disabled !== newDisabled) { + node.state.disabled = newDisabled; + if (node.state.disabled) { + this.nodeEditCallbacks.disableNode(node.id); + } else { + this.nodeEditCallbacks.enableNode(node.id); + } + } + const newHasChildren = this.nodeHasChildrenFunction(node.data.nodeCtx); + if (node.children !== newHasChildren) { + node.children = newHasChildren; + this.nodeEditCallbacks.setNodeHasChildren(node.id, node.children); + } + } + + private showError(errorText: string) { + this.store.dispatch(new ActionNotificationShow( + { + message: errorText, + type: 'error', + target: this.toastTargetId, + verticalPosition: 'bottom', + horizontalPosition: 'left' + })); + } + + private prepareNodes(nodes: HierarchyNavTreeNode[]): HierarchyNavTreeNode[] { + nodes = nodes.filter((node) => node !== null); + nodes.sort((node1, node2) => this.nodesSortFunction(node1.data.nodeCtx, node2.data.nodeCtx)); + return nodes; + } + + private prepareNodeText(node: HierarchyNavTreeNode): string { + const nodeIcon = this.prepareNodeIcon(node.data.nodeCtx); + const nodeText = this.nodeTextFunction(node.data.nodeCtx); + node.data.searchText = nodeText ? nodeText.replace(/<[^>]+>/g, '').toLowerCase() : ''; + return nodeIcon + nodeText; + } + + private prepareNodeIcon(nodeCtx: HierarchyNodeContext): string { + let iconInfo = this.nodeIconFunction(nodeCtx); + if (iconInfo) { + if (iconInfo === 'default') { + iconInfo = defaultNodeIconFunction(nodeCtx); + } + if (iconInfo && iconInfo !== 'default' && (iconInfo.iconUrl || iconInfo.materialIcon)) { + if (iconInfo.materialIcon) { + return materialIconHtml(iconInfo.materialIcon); + } else { + return iconUrlHtml(iconInfo.iconUrl); + } + } else { + return ''; + } + } else { + return ''; + } + } + + private datasourceToNode(datasource: HierarchyNodeDatasource, parentNodeCtx?: HierarchyNodeContext): Observable { + return this.resolveEntity(datasource).pipe( + map(entity => { + if (entity !== null) { + const node: HierarchyNavTreeNode = { + id: (++this.nodeIdCounter)+'' + }; + this.nodesMap[node.id] = node; + datasource.nodeId = node.id; + node.icon = false; + const nodeCtx: HierarchyNodeContext = { + parentNodeCtx, + entity, + data: {} + }; + nodeCtx.level = parentNodeCtx ? parentNodeCtx.level + 1 : 1; + node.data = { + datasource, + nodeCtx + }; + node.state = { + disabled: this.nodeDisabledFunction(node.data.nodeCtx), + opened: this.nodeOpenedFunction(node.data.nodeCtx) + }; + node.text = this.prepareNodeText(node); + node.children = this.nodeHasChildrenFunction(node.data.nodeCtx); + return node; + } else { + return null; + } + }) + ); + } + + private entityIdToNode(entityType: EntityType, entityId: string, + parentDatasource: HierarchyNodeDatasource, + parentNodeCtx: HierarchyNodeContext): Observable { + const datasource = { + dataKeys: parentDatasource.dataKeys, + type: DatasourceType.entity, + entityType, + entityId + } as HierarchyNodeDatasource; + return this.datasourceToNode(datasource, parentNodeCtx).pipe( + mergeMap((node) => { + if (node != null) { + const subscriptionOptions: WidgetSubscriptionOptions = { + type: widgetType.latest, + datasources: [datasource], + callbacks: { + onDataUpdated: subscription => { + this.updateNodeData(subscription.data); + } + } + }; + return this.ctx.subscriptionApi. + createSubscription(subscriptionOptions, true).pipe( + map(() => node)); + } else { + return of(node); + } + }) + ); + } + + private resolveEntity(datasource: HierarchyNodeDatasource): Observable> { + if (datasource.type === DatasourceType.function) { + const entity = { + id: { + entityType: 'function' + }, + name: datasource.name + }; + return of(entity as BaseData); + } else { + return this.entityService.getEntity(datasource.entityType, datasource.entityId, {ignoreLoading: true}).pipe( + catchError(err => of(null)) + ); + } + } + + private prepareNodeRelationQuery(nodeCtx: HierarchyNodeContext): EntityRelationsQuery { + let relationQuery = this.nodeRelationQueryFunction(nodeCtx); + if (relationQuery && relationQuery === 'default') { + relationQuery = defaultNodeRelationQueryFunction(nodeCtx); + } + return relationQuery as EntityRelationsQuery; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.models.ts new file mode 100644 index 0000000000..585d40e20c --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.models.ts @@ -0,0 +1,160 @@ +/// +/// Copyright © 2016-2019 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 { BaseData } from '@shared/models/base-data'; +import { EntityId } from '@shared/models/id/entity-id'; +import { NavTreeNode } from '@shared/components/nav-tree.component'; +import { Datasource } from '@shared/models/widget.models'; +import { isDefined, isUndefined } from '@core/utils'; +import { EntityRelationsQuery, EntitySearchDirection, RelationTypeGroup } from '@shared/models/relation.models'; +import { EntityType } from '@shared/models/entity-type.models'; + +export interface EntitiesHierarchyWidgetSettings { + nodeRelationQueryFunction: string; + nodeHasChildrenFunction: string; + nodeOpenedFunction: string; + nodeDisabledFunction: string; + nodeIconFunction: string; + nodeTextFunction: string; + nodesSortFunction: string; +} + +export interface HierarchyNodeContext { + parentNodeCtx?: HierarchyNodeContext; + entity: BaseData; + level?: number; + data: {[key: string]: any}; +} + +export interface HierarchyNavTreeNode extends NavTreeNode { + data?: { + datasource: HierarchyNodeDatasource; + nodeCtx: HierarchyNodeContext; + searchText?: string; + } +} + +export interface HierarchyNodeDatasource extends Datasource { + nodeId: string; +} + +export interface HierarchyNodeIconInfo { + iconUrl?: string; + materialIcon?: string; +} + +export type NodeRelationQueryFunction = (nodeCtx: HierarchyNodeContext) => EntityRelationsQuery | 'default'; +export type NodeTextFunction = (nodeCtx: HierarchyNodeContext) => string; +export type NodeDisabledFunction = (nodeCtx: HierarchyNodeContext) => boolean; +export type NodeIconFunction = (nodeCtx: HierarchyNodeContext) => HierarchyNodeIconInfo | 'default'; +export type NodeOpenedFunction = (nodeCtx: HierarchyNodeContext) => boolean; +export type NodeHasChildrenFunction = (nodeCtx: HierarchyNodeContext) => boolean; +export type NodesSortFunction = (nodeCtx1: HierarchyNodeContext, nodeCtx2: HierarchyNodeContext) => number; + +export function loadNodeCtxFunction(functionBody: string, argNames: string, ...args: any[]): F { + let nodeCtxFunction: F = null; + if (isDefined(functionBody) && functionBody.length) { + try { + nodeCtxFunction = new Function(argNames, functionBody) as F; + const res = nodeCtxFunction.apply(null, args); + if (isUndefined(res)) { + nodeCtxFunction = null; + } + } catch (e) { + nodeCtxFunction = null; + } + } + return nodeCtxFunction; +} + +export function materialIconHtml(materialIcon: string): string { + return ''+materialIcon+''; +} + +export function iconUrlHtml(iconUrl: string): string { + return '
 
'; +} + +export const defaultNodeRelationQueryFunction: NodeRelationQueryFunction = nodeCtx => { + const entity = nodeCtx.entity; + const query: EntityRelationsQuery = { + parameters: { + rootId: entity.id.id, + rootType: entity.id.entityType as EntityType, + direction: EntitySearchDirection.FROM, + relationTypeGroup: RelationTypeGroup.COMMON, + maxLevel: 1 + }, + filters: [ + { + relationType: "Contains", + entityTypes: [] + } + ] + }; + return query; +}; + +export const defaultNodeIconFunction: NodeIconFunction = nodeCtx => { + let materialIcon = 'insert_drive_file'; + const entity = nodeCtx.entity; + if (entity && entity.id && entity.id.entityType) { + switch (entity.id.entityType as EntityType | string) { + case 'function': + materialIcon = 'functions'; + break; + case EntityType.DEVICE: + materialIcon = 'devices_other'; + break; + case EntityType.ASSET: + materialIcon = 'domain'; + break; + case EntityType.TENANT: + materialIcon = 'supervisor_account'; + break; + case EntityType.CUSTOMER: + materialIcon = 'supervisor_account'; + break; + case EntityType.USER: + materialIcon = 'account_circle'; + break; + case EntityType.DASHBOARD: + materialIcon = 'dashboards'; + break; + case EntityType.ALARM: + materialIcon = 'notifications_active'; + break; + case EntityType.ENTITY_VIEW: + materialIcon = 'view_quilt'; + break; + } + } + return { + materialIcon + }; +}; + +export const defaultNodeOpenedFunction: NodeOpenedFunction = nodeCtx => { + return nodeCtx.level <= 4; +}; + +export const defaultNodesSortFunction: NodesSortFunction = (nodeCtx1, nodeCtx2) => { + let result = nodeCtx1.entity.id.entityType.localeCompare(nodeCtx2.entity.id.entityType); + if (result === 0) { + result = nodeCtx1.entity.name.localeCompare(nodeCtx2.entity.name); + } + return result; +}; 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 369d2546b0..27a4a5487f 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 @@ -23,6 +23,7 @@ import { AlarmsTableWidgetComponent } from '@home/components/widget/lib/alarms-t import { AlarmStatusFilterPanelComponent } from '@home/components/widget/lib/alarm-status-filter-panel.component'; import { SharedHomeComponentsModule } from '@home/components/shared-home-components.module'; import { TimeseriesTableWidgetComponent } from '@home/components/widget/lib/timeseries-table-widget.component'; +import { EntitiesHierarchyWidgetComponent } from '@home/components/widget/lib/entities-hierarchy-widget.component'; @NgModule({ entryComponents: [ @@ -35,7 +36,8 @@ import { TimeseriesTableWidgetComponent } from '@home/components/widget/lib/time AlarmStatusFilterPanelComponent, EntitiesTableWidgetComponent, AlarmsTableWidgetComponent, - TimeseriesTableWidgetComponent + TimeseriesTableWidgetComponent, + EntitiesHierarchyWidgetComponent ], imports: [ CommonModule, @@ -45,7 +47,8 @@ import { TimeseriesTableWidgetComponent } from '@home/components/widget/lib/time exports: [ EntitiesTableWidgetComponent, AlarmsTableWidgetComponent, - TimeseriesTableWidgetComponent + TimeseriesTableWidgetComponent, + EntitiesHierarchyWidgetComponent ] }) export class WidgetComponentsModule { } diff --git a/ui-ngx/src/app/modules/login/login-routing.module.ts b/ui-ngx/src/app/modules/login/login-routing.module.ts index 3af7f4dd1d..bfc37edba2 100644 --- a/ui-ngx/src/app/modules/login/login-routing.module.ts +++ b/ui-ngx/src/app/modules/login/login-routing.module.ts @@ -51,6 +51,16 @@ const routes: Routes = [ }, canActivate: [AuthGuard] }, + { + path: 'login/resetExpiredPassword', + component: ResetPasswordComponent, + data: { + title: 'login.reset-password', + module: 'public', + expiredPassword: true + }, + canActivate: [AuthGuard] + }, { path: 'login/createPassword', component: CreatePasswordComponent, diff --git a/ui-ngx/src/app/modules/login/pages/login/login.component.ts b/ui-ngx/src/app/modules/login/pages/login/login.component.ts index b893f729f3..c048276a57 100644 --- a/ui-ngx/src/app/modules/login/pages/login/login.component.ts +++ b/ui-ngx/src/app/modules/login/pages/login/login.component.ts @@ -21,6 +21,9 @@ import { Store } from '@ngrx/store'; import { AppState } from '../../../../core/core.state'; import { PageComponent } from '../../../../shared/components/page.component'; import { FormBuilder } from '@angular/forms'; +import { HttpErrorResponse } from '@angular/common/http'; +import { Constants } from '@shared/models/constants'; +import { Router } from '@angular/router'; @Component({ selector: 'tb-login', @@ -36,7 +39,8 @@ export class LoginComponent extends PageComponent implements OnInit { constructor(protected store: Store, private authService: AuthService, - public fb: FormBuilder) { + public fb: FormBuilder, + private router: Router) { super(store); } @@ -45,7 +49,16 @@ export class LoginComponent extends PageComponent implements OnInit { login(): void { if (this.loginFormGroup.valid) { - this.authService.login(this.loginFormGroup.value).subscribe(); + this.authService.login(this.loginFormGroup.value).subscribe( + () => {}, + (error: HttpErrorResponse) => { + if (error && error.error && error.error.errorCode) { + if (error.error.errorCode === Constants.serverErrorCode.credentialsExpired) { + this.router.navigateByUrl(`login/resetExpiredPassword?resetToken=${error.error.resetToken}`); + } + } + } + ); } else { Object.keys(this.loginFormGroup.controls).forEach(field => { const control = this.loginFormGroup.get(field); diff --git a/ui-ngx/src/app/modules/login/pages/login/reset-password.component.html b/ui-ngx/src/app/modules/login/pages/login/reset-password.component.html index 6e7ddcee49..828da1e21f 100644 --- a/ui-ngx/src/app/modules/login/pages/login/reset-password.component.html +++ b/ui-ngx/src/app/modules/login/pages/login/reset-password.component.html @@ -20,6 +20,9 @@ login.password-reset + + login.expired-password-reset-message + diff --git a/ui-ngx/src/app/modules/login/pages/login/reset-password.component.ts b/ui-ngx/src/app/modules/login/pages/login/reset-password.component.ts index 6d97bbe2dd..7ad658fcba 100644 --- a/ui-ngx/src/app/modules/login/pages/login/reset-password.component.ts +++ b/ui-ngx/src/app/modules/login/pages/login/reset-password.component.ts @@ -34,6 +34,8 @@ import { map } from 'rxjs/operators'; }) export class ResetPasswordComponent extends PageComponent implements OnInit, OnDestroy { + isExpiredPassword: boolean; + resetToken = ''; sub: Subscription; @@ -51,6 +53,7 @@ export class ResetPasswordComponent extends PageComponent implements OnInit, OnD } ngOnInit() { + this.isExpiredPassword = this.route.snapshot.data.expiredPassword; this.sub = this.route .queryParams .subscribe(params => { diff --git a/ui-ngx/src/app/shared/components/nav-tree.component.html b/ui-ngx/src/app/shared/components/nav-tree.component.html new file mode 100644 index 0000000000..88612965ff --- /dev/null +++ b/ui-ngx/src/app/shared/components/nav-tree.component.html @@ -0,0 +1,18 @@ + +
diff --git a/ui-ngx/src/app/shared/components/nav-tree.component.scss b/ui-ngx/src/app/shared/components/nav-tree.component.scss new file mode 100644 index 0000000000..eef75026b0 --- /dev/null +++ b/ui-ngx/src/app/shared/components/nav-tree.component.scss @@ -0,0 +1,347 @@ +/** + * Copyright © 2016-2019 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-nav-tree-container { + padding: 15px; + font-family: Roboto, "Helvetica Neue", sans-serif; + + &.jstree-proton { + .jstree-node, + .jstree-icon { + background-image: url("../../../assets/jstree/32px.png"); + } + + .jstree-last { + background: transparent; + } + + .jstree-themeicon-custom { + background-image: none; + + &.material-icons { + font-size: 18px; + } + } + + .jstree-anchor { + font-size: 16px; + } + } + + &.jstree-proton-small { + .jstree-node, + .jstree-icon { + background-image: url("../../../assets/jstree/32px.png"); + } + + .jstree-last { + background: transparent; + } + + .jstree-themeicon-custom { + background-image: none; + + &.material-icons { + font-size: 14px; + } + } + + .jstree-anchor { + font-size: 14px; + } + } + + &.jstree-proton-large { + .jstree-node, + .jstree-icon { + background-image: url("../../../assets/jstree/32px.png"); + } + + .jstree-last { + background: transparent; + } + + .jstree-themeicon-custom { + background-image: none; + + &.material-icons { + font-size: 24px; + } + } + + .jstree-anchor { + font-size: 20px; + } + } + + a { + border-bottom: none; + + i.jstree-themeicon-custom { + &.tb-user-group { + &::before { + content: "account_circle"; + } + } + + &.tb-customer-group { + &::before { + content: "supervisor_account"; + } + } + + &.tb-asset-group { + &::before { + content: "domain"; + } + } + + &.tb-device-group { + &::before { + content: "devices_other"; + } + } + + &.tb-entity-view-group { + &::before { + content: "view_quilt"; + } + } + + &.tb-dashboard-group { + &::before { + content: "dashboard"; + } + } + + &.tb-customer { + &::before { + content: "supervisor_account"; + } + } + } + } +} + +@media (max-width: 768px) { + .tb-nav-tree-container { + &.jstree-proton-responsive { + .jstree-node, + .jstree-icon, + .jstree-node > .jstree-ocl, + .jstree-themeicon, + .jstree-checkbox { + background-image: url("../../../assets/jstree/40px.png"); + background-size: 120px 240px; + } + + .jstree-container-ul { + overflow: visible; + } + + .jstree-themeicon-custom { + background-color: transparent; + background-image: none; + background-position: 0 0; + + &.material-icons { + margin: 0; + font-size: 24px; + } + } + + .jstree-node, + .jstree-leaf > .jstree-ocl { + background: 0 0; + } + + .jstree-node { + min-width: 40px; + min-height: 40px; + margin-left: 40px; + line-height: 40px; + white-space: nowrap; + background-repeat: repeat-y; + background-position: -80px 0; + } + + .jstree-last { + background: 0 0; + } + + .jstree-anchor { + height: 40px; + font-size: 1.1em; + font-weight: 700; + line-height: 40px; + text-shadow: 1px 1px #fff; + } + + .jstree-icon, + .jstree-icon:empty { + width: 40px; + height: 40px; + line-height: 40px; + } + + > { + .jstree-container-ul > .jstree-node { + margin-right: 0; + margin-left: 0; + } + } + + .jstree-ocl, + .jstree-themeicon, + .jstree-checkbox { + background-size: 120px 240px; + } + + .jstree-leaf > .jstree-ocl { + background: 0 0; + background-position: -40px -120px; + } + + .jstree-last > .jstree-ocl { + background-position: -40px -160px; + } + + .jstree-open > .jstree-ocl { + background-position: 0 0 !important; + } + + .jstree-closed > .jstree-ocl { + background-position: 0 -40px !important; + } + + .jstree-themeicon { + background-position: -40px -40px; + } + + .jstree-checkbox, + .jstree-checkbox:hover { + background-position: -40px -80px; + } + + &.jstree-checkbox-selection { + .jstree-clicked > .jstree-checkbox, + .jstree-clicked > .jstree-checkbox:hover { + background-position: 0 -80px; + } + } + + .jstree-checked > .jstree-checkbox, + .jstree-checked > .jstree-checkbox:hover { + background-position: 0 -80px; + } + + .jstree-anchor > .jstree-undetermined, + .jstree-anchor > .jstree-undetermined:hover { + background-position: 0 -120px; + } + + .jstree-striped { + background: 0 0; + } + + .jstree-wholerow { + height: 40px; + background: #ebebeb; + border-top: 1px solid rgba(255, 255, 255, .7); + border-bottom: 1px solid rgba(64, 64, 64, .2); + } + + .jstree-wholerow-hovered { + background: #e7f4f9; + } + + .jstree-wholerow-clicked { + background: #beebff; + } + + .jstree-children { + .jstree-last > .jstree-wholerow { + box-shadow: inset 0 -6px 3px -5px #666; + } + + .jstree-open > .jstree-wholerow { + border-top: 0; + box-shadow: inset 0 6px 3px -5px #666; + } + + .jstree-open + .jstree-open { + box-shadow: none; + } + } + + &.jstree-rtl { + .jstree-node { + margin-right: 40px; + margin-left: 0; + } + + .jstree-container-ul > .jstree-node { + margin-right: 0; + } + + .jstree-closed > .jstree-ocl { + background-position: -40px 0 !important; + } + } + } + } +} + +.tb-nav-tree .mat-button.tb-active { + font-weight: 500; + background-color: rgba(255, 255, 255, .15); +} + +.tb-nav-tree, +.tb-nav-tree ul { + margin-top: 0; + list-style: none; + + &:first-child { + padding: 0; + } + + li { + .mat-button { + width: 100%; + max-height: 40px; + padding: 0 16px; + margin: 0; + overflow: hidden; + line-height: 40px; + color: inherit; + text-align: left; + text-decoration: none; + text-overflow: ellipsis; + text-transform: none; + text-rendering: optimizeLegibility; + white-space: nowrap; + cursor: pointer; + border-radius: 0; + + span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } + } +} + diff --git a/ui-ngx/src/app/shared/components/nav-tree.component.ts b/ui-ngx/src/app/shared/components/nav-tree.component.ts new file mode 100644 index 0000000000..30f124731c --- /dev/null +++ b/ui-ngx/src/app/shared/components/nav-tree.component.ts @@ -0,0 +1,272 @@ +/// +/// Copyright © 2016-2019 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, Input, NgZone, OnInit, ViewEncapsulation } from '@angular/core'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { deepClone } from '@core/utils'; + +export interface NavTreeNodeState { + disabled?: boolean; + opened?: boolean; + loaded?: boolean; +} + +export interface NavTreeNode { + id: string; + icon?: boolean; + text?: string; + state?: NavTreeNodeState; + children?: NavTreeNode[] | boolean; + data?: any; +} + +export interface NavTreeEditCallbacks { + selectNode?: (id: string) => void; + deselectAll?: () => void; + getNode?: (id: string) => NavTreeNode; + getParentNodeId?: (id: string) => string; + openNode?: (id: string, cb?: () => void) => void; + nodeIsOpen?: (id: string) => boolean; + nodeIsLoaded?: (id: string) => boolean; + refreshNode?: (id: string) => void; + updateNode?: (id: string, newName: string) => void; + createNode?: (parentId: string, node: NavTreeNode, pos: number) => void; + deleteNode?: (id: string) => void; + disableNode?: (id: string) => void; + enableNode?: (id: string) => void; + setNodeHasChildren?: (id: string, hasChildren: boolean) => void; + search?: (searchText: string) => void; + clearSearch?: () => void; +} + +export type NodesCallback = (nodes: NavTreeNode[]) => void; +export type LoadNodesCallback = (node: NavTreeNode, cb: NodesCallback) => void; +export type NodeSearchCallback = (searchText: string, node: NavTreeNode) => boolean; +export type NodeSelectedCallback = (node: NavTreeNode, event: Event) => void; +export type NodesInsertedCallback = (nodes: string[], parent: string) => void; + +@Component({ + selector: 'tb-nav-tree', + templateUrl: './nav-tree.component.html', + styleUrls: ['./nav-tree.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class NavTreeComponent implements OnInit { + + private enableSearchValue: boolean; + get enableSearch(): boolean { + return this.enableSearchValue; + } + @Input() + set enableSearch(value: boolean) { + this.enableSearchValue = coerceBooleanProperty(value); + } + + @Input() + private loadNodes: LoadNodesCallback; + + @Input() + private searchCallback: NodeSearchCallback; + + @Input() + private onNodeSelected: NodeSelectedCallback; + + @Input() + private onNodesInserted: NodesInsertedCallback; + + @Input() + private editCallbacks: NavTreeEditCallbacks; + + private treeElement: JSTree; + + constructor(private elementRef: ElementRef, + private ngZone: NgZone) { + } + + ngOnInit(): void { + this.initTree(); + } + + private initTree() { + + const loadNodes: LoadNodesCallback = (node, cb) => { + const outCb = (_nodes: NavTreeNode[]) => { + const copied: NavTreeNode[] = []; + if (_nodes) { + _nodes.forEach((n) => { + copied.push(deepClone(n, ['data'])); + }); + } + cb(copied); + }; + this.ngZone.runOutsideAngular(() => { + this.loadNodes(node, outCb); + }); + }; + + const config: JSTreeStaticDefaults = { + core: { + worker: false, + multiple: false, + check_callback: true, + themes: { name: 'proton', responsive: true }, + data: loadNodes, + error: () => { + console.error('Unexpected jstree error!'); + } + }, + plugins: [] + }; + + if (this.enableSearch) { + config.plugins.push('search'); + config.search = { + ajax: false, + fuzzy: false, + close_opened_onclear: true, + case_sensitive: false, + show_only_matches: true, + show_only_matches_children: false, + search_leaves_only: false, + search_callback: this.searchCallback + }; + } + + this.treeElement = $('.tb-nav-tree-container', this.elementRef.nativeElement).jstree(config); + + this.treeElement.on('changed.jstree', (e, data) => { + const node: NavTreeNode = data.instance.get_selected(true)[0]; + if (this.onNodeSelected) { + this.onNodeSelected(node, e); + } + }); + + this.treeElement.on('model.jstree', (e, data) => { + if (this.onNodesInserted) { + this.onNodesInserted(data.nodes, data.parent); + } + }); + + if (this.editCallbacks) { + this.editCallbacks.selectNode = id => { + const node: NavTreeNode = this.treeElement.jstree('get_node', id); + if (node) { + this.treeElement.jstree('deselect_all', true); + this.treeElement.jstree('select_node', node); + } + }; + this.editCallbacks.deselectAll = () => { + this.treeElement.jstree('deselect_all'); + }; + this.editCallbacks.getNode = (id) => { + const node: NavTreeNode = this.treeElement.jstree('get_node', id); + return node; + }; + this.editCallbacks.getParentNodeId = (id) => { + const node: NavTreeNode = this.treeElement.jstree('get_node', id); + if (node) { + return this.treeElement.jstree('get_parent', node); + } + }; + this.editCallbacks.openNode = (id, cb) => { + const node: NavTreeNode = this.treeElement.jstree('get_node', id); + if (node) { + this.treeElement.jstree('open_node', node, cb); + } + }; + this.editCallbacks.nodeIsOpen = (id) => { + const node: NavTreeNode = this.treeElement.jstree('get_node', id); + if (node) { + return this.treeElement.jstree('is_open', node); + } else { + return true; + } + }; + this.editCallbacks.nodeIsLoaded = (id) => { + const node: NavTreeNode = this.treeElement.jstree('get_node', id); + if (node) { + return this.treeElement.jstree('is_loaded', node); + } else { + return true; + } + }; + this.editCallbacks.refreshNode = (id) => { + if (id === '#') { + this.treeElement.jstree('refresh'); + this.treeElement.jstree('redraw'); + } else { + const node: NavTreeNode = this.treeElement.jstree('get_node', id); + if (node) { + const opened = this.treeElement.jstree('is_open', node); + this.treeElement.jstree('refresh_node', node); + this.treeElement.jstree('redraw'); + if (node.children && opened/* && !node.children.length*/) { + this.treeElement.jstree('open_node', node); + } + } + } + }; + this.editCallbacks.updateNode = (id, newName) => { + const node: NavTreeNode = this.treeElement.jstree('get_node', id); + if (node) { + this.treeElement.jstree('rename_node', node, newName); + } + }; + this.editCallbacks.createNode = (parentId, node, pos) => { + const parentNode: NavTreeNode = this.treeElement.jstree('get_node', parentId); + if (parentNode) { + this.treeElement.jstree('create_node', parentNode, node, pos); + } + }; + this.editCallbacks.deleteNode = (id) => { + const node: NavTreeNode = this.treeElement.jstree('get_node', id); + if (node) { + this.treeElement.jstree('delete_node', node); + } + }; + this.editCallbacks.disableNode = (id) => { + const node: NavTreeNode = this.treeElement.jstree('get_node', id); + if (node) { + this.treeElement.jstree('disable_node', node); + } + }; + this.editCallbacks.enableNode = (id) => { + const node: NavTreeNode = this.treeElement.jstree('get_node', id); + if (node) { + this.treeElement.jstree('enable_node', node); + } + }; + this.editCallbacks.setNodeHasChildren = (id, hasChildren) => { + const node: NavTreeNode = this.treeElement.jstree('get_node', id); + if (node) { + if (!node.children || (Array.isArray(node.children) && !node.children.length)) { + node.children = hasChildren; + node.state.loaded = !hasChildren; + node.state.opened = false; + this.treeElement.jstree('_node_changed', node.id); + this.treeElement.jstree('redraw'); + } + } + }; + this.editCallbacks.search = (searchText) => { + this.treeElement.jstree('search', searchText); + }; + this.editCallbacks.clearSearch = () => { + this.treeElement.jstree('clear_search'); + }; + } + } +} diff --git a/ui-ngx/src/app/shared/models/constants.ts b/ui-ngx/src/app/shared/models/constants.ts index 0bbb06ce2b..22d4557f79 100644 --- a/ui-ngx/src/app/shared/models/constants.ts +++ b/ui-ngx/src/app/shared/models/constants.ts @@ -20,6 +20,7 @@ export const Constants = { authentication: 10, jwtTokenExpired: 11, tenantTrialExpired: 12, + credentialsExpired: 15, permissionDenied: 20, invalidArguments: 30, badRequestParams: 31, diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index 1334836c67..b42298df15 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -120,6 +120,7 @@ import { JsonContentComponent } from './components/json-content.component'; import { KeyValMapComponent } from './components/kv-map.component'; import { TbCheatSheetComponent } from '@shared/components/cheatsheet.component'; import { TbHotkeysDirective } from '@shared/components/hotkeys.directive'; +import { NavTreeComponent } from '@shared/components/nav-tree.component'; @NgModule({ providers: [ @@ -198,6 +199,7 @@ import { TbHotkeysDirective } from '@shared/components/hotkeys.directive'; FileInputComponent, MessageTypeAutocompleteComponent, KeyValMapComponent, + NavTreeComponent, NospacePipe, MillisecondsToTimeStringPipe, EnumToArrayPipe, @@ -346,6 +348,7 @@ import { TbHotkeysDirective } from '@shared/components/hotkeys.directive'; FileInputComponent, MessageTypeAutocompleteComponent, KeyValMapComponent, + NavTreeComponent, NospacePipe, MillisecondsToTimeStringPipe, EnumToArrayPipe, diff --git a/ui-ngx/src/scss/mixins.scss b/ui-ngx/src/scss/mixins.scss index 3b31b0e10c..07208636f9 100644 --- a/ui-ngx/src/scss/mixins.scss +++ b/ui-ngx/src/scss/mixins.scss @@ -21,12 +21,10 @@ min-height: #{$size}px; font-size: #{$size}px; line-height: #{$size}px; -/* svg { - width: 24px; - height: 24px; - transform: scale($size/24); - transform-origin: top; - }*/ + svg { + width: #{$size}px; + height: #{$size}px; + } } @mixin tb-mat-icon-button-size($size) { diff --git a/ui-ngx/src/tsconfig.app.json b/ui-ngx/src/tsconfig.app.json index d32504308a..1332922f5b 100644 --- a/ui-ngx/src/tsconfig.app.json +++ b/ui-ngx/src/tsconfig.app.json @@ -2,7 +2,7 @@ "extends": "../tsconfig.json", "compilerOptions": { "outDir": "../out-tsc/app", - "types": ["node", "jquery", "flot", "tooltipster", "tinycolor2", "js-beautify", "react", "react-dom"] + "types": ["node", "jquery", "flot", "tooltipster", "tinycolor2", "js-beautify", "react", "react-dom", "jstree"] }, "exclude": [ "test.ts", diff --git a/ui-ngx/src/typings/jquery.jstree.typings.d.ts b/ui-ngx/src/typings/jquery.jstree.typings.d.ts new file mode 100644 index 0000000000..a19c9bc3af --- /dev/null +++ b/ui-ngx/src/typings/jquery.jstree.typings.d.ts @@ -0,0 +1,29 @@ +/// +/// Copyright © 2016-2019 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. +/// + +interface JSTreeEventData { + instance: JSTree; +} + +interface JSTreeModelEventData extends JSTreeEventData { + nodes: string[]; + parent: string; +} + +interface JQuery { + on(events: 'changed.jstree', handler: (e: Event, data: JSTreeEventData) => void): this; + on(events: 'model.jstree', handler: (e: Event, data: JSTreeModelEventData) => void): this; +} diff --git a/ui-ngx/tsconfig.json b/ui-ngx/tsconfig.json index 02b121d540..911c6a31dc 100644 --- a/ui-ngx/tsconfig.json +++ b/ui-ngx/tsconfig.json @@ -18,6 +18,7 @@ "src/typings/rawloader.typings.d.ts", "src/typings/jquery.typings.d.ts", "src/typings/jquery.flot.typings.d.ts", + "src/typings/jquery.jstree.typings.d.ts", "src/typings/split.js.typings.d.ts" ], "paths": { From 2807c497f00dfce52521e0285c8237b2409eaa1e Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 3 Feb 2020 17:29:01 +0200 Subject: [PATCH 083/133] GPIO widgets --- .../system/widget_bundles/gpio_widgets.json | 20 +-- ui-ngx/package-lock.json | 19 +++ ui-ngx/package.json | 2 + .../src/app/core/api/widget-subscription.ts | 5 +- .../interceptors/global-http-interceptor.ts | 7 +- .../widget/widget-component.service.ts | 2 +- .../components/widget/widget.component.ts | 23 +++- .../home/models/widget-component.models.ts | 2 + .../default-state-controller.component.ts | 22 +--- .../entity-state-controller.component.ts | 22 +--- .../states/state-controller.component.ts | 17 ++- .../json-form/react/json-form-array.tsx | 1 + .../json-form/react/json-form-utils.ts | 4 +- .../components/led-light.component.html | 18 +++ .../shared/components/led-light.component.ts | 124 ++++++++++++++++++ ui-ngx/src/app/shared/shared.module.ts | 3 + ui-ngx/src/tsconfig.app.json | 2 +- 17 files changed, 227 insertions(+), 66 deletions(-) create mode 100644 ui-ngx/src/app/shared/components/led-light.component.html create mode 100644 ui-ngx/src/app/shared/components/led-light.component.ts diff --git a/application/src/main/data/json/system/widget_bundles/gpio_widgets.json b/application/src/main/data/json/system/widget_bundles/gpio_widgets.json index fd8d0538b6..1cc9a1635d 100644 --- a/application/src/main/data/json/system/widget_bundles/gpio_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/gpio_widgets.json @@ -13,9 +13,9 @@ "sizeX": 4, "sizeY": 2, "resources": [], - "templateHtml": "
\n
\n
\n
\n {{ cell.label }}\n
\n {{cell.pin}}\n \n \n \n \n {{cell.pin}}\n
\n {{ cell.label }}\n
\n
\n \n \n \n
\n
\n
\n {{rpcErrorText}}\n \n
", - "templateCss": ".error {\n font-size: 14px !important;\n color: maroon;/*rgb(250,250,250);*/\n background-color: transparent;\n padding: 6px;\n}\n\n.error span {\n margin: auto;\n}\n\n.gpio-panel {\n padding-top: 10px;\n white-space: nowrap;\n}\n\n.switch-panel {\n margin: 0;\n height: 32px;\n width: 66px;\n min-width: 66px;\n}\n\n.switch-panel md-switch {\n margin: 0;\n width: 36px;\n min-width: 36px;\n}\n\n.switch-panel md-switch > div.md-container {\n margin: 0;\n}\n\n.switch-panel.col-0 md-switch {\n padding-left: 8px;\n padding-right: 4px;\n}\n\n.switch-panel.col-1 md-switch {\n padding-left: 4px;\n padding-right: 8px;\n}\n\n.gpio-row {\n height: 32px;\n}\n\n.pin {\n margin-top: auto;\n margin-bottom: auto;\n color: white;\n font-size: 12px;\n width: 16px;\n min-width: 16px;\n}\n\n.switch-panel.col-0 .pin {\n margin-left: auto;\n padding-left: 2px;\n text-align: right;\n}\n\n.switch-panel.col-1 .pin {\n margin-right: auto;\n \n text-align: left;\n}\n\n.gpio-left-label {\n margin-right: 8px;\n}\n\n.gpio-right-label {\n margin-left: 8px;\n}", - "controllerScript": "self.onInit = function() {\n \n var i, gpio;\n var scope = self.ctx.$scope;\n var settings = self.ctx.settings;\n scope.gpioList = [];\n for (var g = 0; g < settings.gpioList.length; g++) {\n gpio = settings.gpioList[g];\n scope.gpioList.push(\n {\n row: gpio.row,\n col: gpio.col,\n pin: gpio.pin,\n label: gpio.label,\n enabled: false\n }\n );\n }\n\n scope.requestTimeout = settings.requestTimeout || 1000;\n\n scope.switchPanelBackgroundColor = settings.switchPanelBackgroundColor || tinycolor('green').lighten(2).toRgbString();\n\n scope.gpioStatusRequest = {\n method: \"getGpioStatus\",\n paramsBody: \"{}\"\n };\n \n if (settings.gpioStatusRequest) {\n scope.gpioStatusRequest.method = settings.gpioStatusRequest.method || scope.gpioStatusRequest.method;\n scope.gpioStatusRequest.paramsBody = settings.gpioStatusRequest.paramsBody || scope.gpioStatusRequest.paramsBody;\n }\n \n scope.gpioStatusChangeRequest = {\n method: \"setGpioStatus\",\n paramsBody: \"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"\n };\n \n if (settings.gpioStatusChangeRequest) {\n scope.gpioStatusChangeRequest.method = settings.gpioStatusChangeRequest.method || scope.gpioStatusChangeRequest.method;\n scope.gpioStatusChangeRequest.paramsBody = settings.gpioStatusChangeRequest.paramsBody || scope.gpioStatusChangeRequest.paramsBody;\n }\n \n scope.parseGpioStatusFunction = \"return body[pin] === true;\";\n \n if (settings.parseGpioStatusFunction && settings.parseGpioStatusFunction.length > 0) {\n scope.parseGpioStatusFunction = settings.parseGpioStatusFunction;\n }\n \n scope.parseGpioStatusFunction = new Function(\"body, pin\", scope.parseGpioStatusFunction);\n \n function requestGpioStatus() {\n self.ctx.controlApi.sendTwoWayCommand(scope.gpioStatusRequest.method, \n scope.gpioStatusRequest.paramsBody, \n scope.requestTimeout)\n .then(\n function success(responseBody) {\n for (var g = 0; g < scope.gpioList.length; g++) {\n var gpio = scope.gpioList[g];\n var enabled = scope.parseGpioStatusFunction.apply(this, [responseBody, gpio.pin]);\n gpio.enabled = enabled; \n }\n }\n );\n }\n \n function changeGpioStatus(gpio) {\n var pin = gpio.pin + '';\n var enabled = !gpio.enabled;\n enabled = enabled === true ? 'true' : 'false';\n var paramsBody = scope.gpioStatusChangeRequest.paramsBody;\n var requestBody = JSON.parse(paramsBody.replace(\"\\\"{$pin}\\\"\", pin).replace(\"\\\"{$enabled}\\\"\", enabled));\n self.ctx.controlApi.sendTwoWayCommand(scope.gpioStatusChangeRequest.method, \n requestBody, scope.requestTimeout)\n .then(\n function success(responseBody) {\n var enabled = scope.parseGpioStatusFunction.apply(this, [responseBody, gpio.pin]);\n gpio.enabled = enabled;\n }\n );\n }\n \n scope.gpioCells = {};\n var rowCount = 0;\n for (i = 0; i < scope.gpioList.length; i++) {\n gpio = scope.gpioList[i];\n scope.gpioCells[gpio.row+'_'+gpio.col] = gpio;\n rowCount = Math.max(rowCount, gpio.row+1);\n }\n \n scope.prefferedRowHeight = 32;\n scope.rows = [];\n for (i = 0; i < rowCount; i++) {\n var row = [];\n for (var c =0; c<2;c++) {\n if (scope.gpioCells[i+'_'+c]) {\n row[c] = scope.gpioCells[i+'_'+c];\n } else {\n row[c] = null;\n }\n }\n scope.rows.push(row);\n }\n\n scope.gpioClick = function($event, gpio) {\n changeGpioStatus(gpio);\n };\n\n requestGpioStatus(); \n \n self.onResize();\n}\n\nself.onResize = function() {\n var scope = self.ctx.$scope;\n var rowCount = scope.rows.length;\n var prefferedRowHeight = (self.ctx.height - 35)/rowCount;\n prefferedRowHeight = Math.min(32, prefferedRowHeight);\n prefferedRowHeight = Math.max(12, prefferedRowHeight);\n scope.prefferedRowHeight = prefferedRowHeight;\n var ratio = prefferedRowHeight/32;\n var switches = $('md-switch', self.ctx.$container);\n switches.css('height', 30*ratio+'px');\n switches.css('width', 36*ratio+'px');\n switches.css('min-width', 36*ratio+'px');\n $('.md-container', switches).css('height', 24*ratio+'px');\n $('.md-container', switches).css('width', 36*ratio+'px');\n var bars = $('.md-bar', self.ctx.$container);\n bars.css('height', 14*ratio+'px');\n bars.css('width', 34*ratio+'px');\n var thumbs = $('.md-thumb', self.ctx.$container);\n thumbs.css('height', 20*ratio+'px');\n thumbs.css('width', 20*ratio+'px');\n \n var leftLabels = $('.gpio-left-label', self.ctx.$container);\n leftLabels.css('font-size', 16*ratio+'px');\n var rightLabels = $('.gpio-right-label', self.ctx.$container);\n rightLabels.css('font-size', 16*ratio+'px');\n var pins = $('.pin', self.ctx.$container);\n var pinsFontSize = Math.max(9, 12*ratio);\n pins.css('font-size', pinsFontSize+'px'); \n}\n\nself.onDestroy = function() {\n}\n", + "templateHtml": "
\n
\n
\n
\n {{ cell.label }}\n
\n {{cell.pin}}\n \n \n \n \n {{cell.pin}}\n
\n {{ cell.label }}\n
\n
\n \n \n \n
\n
\n
\n {{rpcErrorText}}\n \n
", + "templateCss": ".error {\n font-size: 14px !important;\n color: maroon;/*rgb(250,250,250);*/\n background-color: transparent;\n padding: 6px;\n}\n\n.error span {\n margin: auto;\n}\n\n.gpio-panel {\n padding-top: 10px;\n white-space: nowrap;\n}\n\n.switch-panel {\n margin: 0;\n height: 32px;\n width: 66px;\n min-width: 66px;\n}\n\n.switch-panel mat-slide-toggle {\n margin: 0;\n width: 36px;\n min-width: 36px;\n}\n\n.switch-panel.col-0 mat-slide-toggle {\n margin-left: 8px;\n margin-right: 4px;\n}\n\n.switch-panel.col-1 mat-slide-toggle {\n margin-left: 4px;\n margin-right: 8px;\n}\n\n.gpio-row {\n height: 32px;\n}\n\n.pin {\n margin-top: auto;\n margin-bottom: auto;\n color: white;\n font-size: 12px;\n width: 16px;\n min-width: 16px;\n}\n\n.switch-panel.col-0 .pin {\n margin-left: auto;\n padding-left: 2px;\n text-align: right;\n}\n\n.switch-panel.col-1 .pin {\n margin-right: auto;\n \n text-align: left;\n}\n\n.gpio-left-label {\n margin-right: 8px;\n}\n\n.gpio-right-label {\n margin-left: 8px;\n}", + "controllerScript": "var namespace;\nvar cssParser = new cssjs();\n\nself.onInit = function() {\n var utils = self.ctx.$injector.get(self.ctx.servicesMap.get('utils'));\n namespace = 'gpio-control-' + utils.guid();\n cssParser.testMode = false;\n cssParser.cssPreviewNamespace = namespace;\n self.ctx.$container.addClass(namespace);\n self.ctx.ngZone.run(function() {\n init(); \n });\n}\n\nfunction init() {\n \n var i, gpio;\n var scope = self.ctx.$scope;\n var settings = self.ctx.settings;\n scope.gpioList = [];\n for (var g = 0; g < settings.gpioList.length; g++) {\n gpio = settings.gpioList[g];\n scope.gpioList.push(\n {\n row: gpio.row,\n col: gpio.col,\n pin: gpio.pin,\n label: gpio.label,\n enabled: false\n }\n );\n }\n\n scope.requestTimeout = settings.requestTimeout || 1000;\n\n scope.switchPanelBackgroundColor = settings.switchPanelBackgroundColor || tinycolor('green').lighten(2).toRgbString();\n\n scope.gpioStatusRequest = {\n method: \"getGpioStatus\",\n paramsBody: \"{}\"\n };\n \n if (settings.gpioStatusRequest) {\n scope.gpioStatusRequest.method = settings.gpioStatusRequest.method || scope.gpioStatusRequest.method;\n scope.gpioStatusRequest.paramsBody = settings.gpioStatusRequest.paramsBody || scope.gpioStatusRequest.paramsBody;\n }\n \n scope.gpioStatusChangeRequest = {\n method: \"setGpioStatus\",\n paramsBody: \"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"\n };\n \n if (settings.gpioStatusChangeRequest) {\n scope.gpioStatusChangeRequest.method = settings.gpioStatusChangeRequest.method || scope.gpioStatusChangeRequest.method;\n scope.gpioStatusChangeRequest.paramsBody = settings.gpioStatusChangeRequest.paramsBody || scope.gpioStatusChangeRequest.paramsBody;\n }\n \n scope.parseGpioStatusFunction = \"return body[pin] === true;\";\n \n if (settings.parseGpioStatusFunction && settings.parseGpioStatusFunction.length > 0) {\n scope.parseGpioStatusFunction = settings.parseGpioStatusFunction;\n }\n \n scope.parseGpioStatusFunction = new Function(\"body, pin\", scope.parseGpioStatusFunction);\n \n function requestGpioStatus() {\n self.ctx.controlApi.sendTwoWayCommand(scope.gpioStatusRequest.method, \n scope.gpioStatusRequest.paramsBody, \n scope.requestTimeout)\n .subscribe(\n function success(responseBody) {\n for (var g = 0; g < scope.gpioList.length; g++) {\n var gpio = scope.gpioList[g];\n var enabled = scope.parseGpioStatusFunction.apply(this, [responseBody, gpio.pin]);\n gpio.enabled = enabled; \n self.ctx.detectChanges();\n }\n }\n );\n }\n \n function changeGpioStatus(gpio) {\n var pin = gpio.pin + '';\n var enabled = !gpio.enabled;\n enabled = enabled === true ? 'true' : 'false';\n var paramsBody = scope.gpioStatusChangeRequest.paramsBody;\n var requestBody = JSON.parse(paramsBody.replace(\"\\\"{$pin}\\\"\", pin).replace(\"\\\"{$enabled}\\\"\", enabled));\n self.ctx.controlApi.sendTwoWayCommand(scope.gpioStatusChangeRequest.method, \n requestBody, scope.requestTimeout)\n .subscribe(\n function success(responseBody) {\n var enabled = scope.parseGpioStatusFunction.apply(this, [responseBody, gpio.pin]);\n gpio.enabled = enabled;\n self.ctx.detectChanges();\n }\n );\n }\n \n scope.gpioCells = {};\n var rowCount = 0;\n for (i = 0; i < scope.gpioList.length; i++) {\n gpio = scope.gpioList[i];\n scope.gpioCells[gpio.row+'_'+gpio.col] = gpio;\n rowCount = Math.max(rowCount, gpio.row+1);\n }\n \n scope.prefferedRowHeight = 32;\n scope.rows = [];\n for (i = 0; i < rowCount; i++) {\n var row = [];\n for (var c =0; c<2;c++) {\n if (scope.gpioCells[i+'_'+c]) {\n row[c] = scope.gpioCells[i+'_'+c];\n } else {\n row[c] = null;\n }\n }\n scope.rows.push(row);\n }\n\n scope.gpioClick = function($event, gpio) {\n if (scope.rpcEnabled && !scope.executingRpcRequest) {\n changeGpioStatus(gpio);\n }\n };\n \n scope.gpioToggleChange = function($event, gpio) {\n gpio.enabled = !$event.checked;\n $event.source.toggle();\n self.ctx.detectChanges();\n }\n \n if (scope.rpcEnabled) {\n requestGpioStatus(); \n }\n \n self.onResize();\n}\n\nself.onResize = function() {\n var scope = self.ctx.$scope;\n var rowCount = scope.rows.length;\n var prefferedRowHeight = (self.ctx.height - 35)/rowCount;\n prefferedRowHeight = Math.min(32, prefferedRowHeight);\n prefferedRowHeight = Math.max(12, prefferedRowHeight);\n scope.prefferedRowHeight = prefferedRowHeight;\n var ratio = prefferedRowHeight/32;\n \n var css = '.mat-slide-toggle .mat-slide-toggle-bar {\\n' +\n ' height: ' + 14*ratio+'px;\\n'+\n ' width: ' + 36*ratio+'px;\\n'+\n '}\\n';\n css += '.mat-slide-toggle .mat-slide-toggle-thumb-container {\\n' +\n ' height: ' + 20*ratio+'px;\\n'+\n ' width: ' + 20*ratio+'px;\\n'+\n '}\\n';\n css += '.mat-slide-toggle .mat-slide-toggle-thumb {\\n' +\n ' height: ' + 20*ratio+'px;\\n'+\n ' width: ' + 20*ratio+'px;\\n'+\n '}\\n';\n css += '.mat-slide-toggle .mat-slide-toggle-ripple {\\n' +\n ' height: ' + 40*ratio+'px;\\n'+\n ' width: ' + 40*ratio+'px;\\n'+\n ' top: calc(50% - '+20*ratio+'px);\\n'+\n ' left: calc(50% - '+20*ratio+'px);\\n'+\n '}\\n';\n css += '.gpio-left-label, .gpio-right-label {\\n' +\n ' font-size: ' + 16*ratio+'px;\\n'+\n '}\\n';\n var pinsFontSize = Math.max(9, 12*ratio);\n css += '.pin {\\n' +\n ' font-size: ' + pinsFontSize+'px;\\n'+\n '}\\n';\n\n cssParser.createStyleElement(namespace, css);\n \n self.ctx.detectChanges();\n}\n\nself.onDestroy = function() {\n}\n", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"gpioList\": {\n \"title\": \"Gpio switches\",\n \"type\": \"array\",\n \"minItems\" : 1,\n \"items\": {\n \"title\": \"Gpio switch\",\n \"type\": \"object\",\n \"properties\": {\n \"pin\": {\n \"title\": \"Pin\",\n \"type\": \"number\"\n },\n \"label\": {\n \"title\": \"Label\",\n \"type\": \"string\"\n },\n \"row\": {\n \"title\": \"Row\",\n \"type\": \"number\"\n },\n \"col\": {\n \"title\": \"Column\",\n \"type\": \"number\"\n }\n },\n \"required\": [\"pin\", \"label\", \"row\", \"col\"]\n }\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"switchPanelBackgroundColor\": {\n \"title\": \"Switches panel background color\",\n \"type\": \"string\",\n \"default\": \"#008a00\"\n },\n \"gpioStatusRequest\": {\n \"title\": \"GPIO status request\",\n \"type\": \"object\",\n \"properties\": {\n \"method\": {\n \"title\": \"Method name\",\n \"type\": \"string\",\n \"default\": \"getGpioStatus\"\n },\n \"paramsBody\": {\n \"title\": \"Method body\",\n \"type\": \"string\",\n \"default\": \"{}\"\n }\n },\n \"required\": [\"method\", \"paramsBody\"]\n },\n \"gpioStatusChangeRequest\": {\n \"title\": \"GPIO status change request\",\n \"type\": \"object\",\n \"properties\": {\n \"method\": {\n \"title\": \"Method name\",\n \"type\": \"string\",\n \"default\": \"setGpioStatus\"\n },\n \"paramsBody\": {\n \"title\": \"Method body\",\n \"type\": \"string\",\n \"default\": \"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"\n }\n },\n \"required\": [\"method\", \"paramsBody\"]\n },\n \"parseGpioStatusFunction\": {\n \"title\": \"Parse gpio status function\",\n \"type\": \"string\",\n \"default\": \"return body[pin] === true;\"\n } \n },\n \"required\": [\"gpioList\", \n \"requestTimeout\",\n \"switchPanelBackgroundColor\",\n \"gpioStatusRequest\",\n \"gpioStatusChangeRequest\",\n \"parseGpioStatusFunction\"]\n },\n \"form\": [\n \"gpioList\",\n \"requestTimeout\",\n {\n \"key\": \"switchPanelBackgroundColor\",\n \"type\": \"color\"\n },\n {\n \"key\": \"gpioStatusRequest\",\n \"items\": [\n \"gpioStatusRequest.method\",\n {\n \"key\": \"gpioStatusRequest.paramsBody\",\n \"type\": \"json\"\n }\n ]\n },\n {\n \"key\": \"gpioStatusChangeRequest\",\n \"items\": [\n \"gpioStatusChangeRequest.method\",\n {\n \"key\": \"gpioStatusChangeRequest.paramsBody\",\n \"type\": \"json\"\n }\n ]\n },\n {\n \"key\": \"parseGpioStatusFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"parseGpioStatusFunction\":\"return body[pin] === true;\",\"gpioStatusChangeRequest\":{\"method\":\"setGpioStatus\",\"paramsBody\":\"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"},\"requestTimeout\":500,\"switchPanelBackgroundColor\":\"#b71c1c\",\"gpioStatusRequest\":{\"method\":\"getGpioStatus\",\"paramsBody\":\"{}\"},\"gpioList\":[{\"pin\":1,\"label\":\"GPIO 1\",\"row\":0,\"col\":0,\"_uniqueKey\":0},{\"pin\":2,\"label\":\"GPIO 2\",\"row\":0,\"col\":1,\"_uniqueKey\":1},{\"pin\":3,\"label\":\"GPIO 3\",\"row\":1,\"col\":0,\"_uniqueKey\":2}]},\"title\":\"Basic GPIO Control\"}" @@ -29,9 +29,9 @@ "sizeX": 5, "sizeY": 2, "resources": [], - "templateHtml": "
\n
\n
\n
\n {{ cell.label }}\n
\n {{cell.pin}}\n \n \n \n \n {{cell.pin}}\n
\n {{ cell.label }}\n
\n
\n \n \n \n
\n
\n
\n
", + "templateHtml": "
\n
\n
\n
\n {{ cell.label }}\n
\n {{cell.pin}}\n \n \n \n \n {{cell.pin}}\n
\n {{ cell.label }}\n
\n
\n \n \n \n
\n
\n
\n
", "templateCss": ".error {\n font-size: 14px !important;\n color: maroon;/*rgb(250,250,250);*/\n background-color: transparent;\n padding: 6px;\n}\n\n.error span {\n margin: auto;\n}\n\n.gpio-panel {\n padding-top: 10px;\n white-space: nowrap;\n}\n\n.gpio-panel tb-led-light > div {\n margin: auto;\n}\n\n.led-panel {\n margin: 0;\n width: 66px;\n min-width: 66px;\n}\n\n.led-container {\n width: 48px;\n min-width: 48px;\n}\n\n.pin {\n margin-top: auto;\n margin-bottom: auto;\n color: white;\n font-size: 12px;\n width: 16px;\n min-width: 16px;\n}\n\n.led-panel.col-0 .pin {\n margin-left: auto;\n padding-left: 2px;\n text-align: right;\n}\n\n.led-panel.col-1 .pin {\n margin-right: auto;\n \n text-align: left;\n}\n\n.gpio-left-label {\n margin-right: 8px;\n}\n\n.gpio-right-label {\n margin-left: 8px;\n}", - "controllerScript": "self.onInit = function() {\n var i, gpio;\n \n var scope = self.ctx.$scope;\n var settings = self.ctx.settings;\n \n scope.gpioList = [];\n scope.gpioByPin = {};\n for (var g = 0; g < settings.gpioList.length; g++) {\n gpio = settings.gpioList[g];\n scope.gpioList.push(\n {\n row: gpio.row,\n col: gpio.col,\n pin: gpio.pin,\n label: gpio.label,\n enabled: false,\n colorOn: tinycolor(gpio.color).lighten(20).toHexString(),\n colorOff: tinycolor(gpio.color).darken().toHexString()\n }\n );\n scope.gpioByPin[gpio.pin] = scope.gpioList[scope.gpioList.length-1];\n }\n\n scope.ledPanelBackgroundColor = settings.ledPanelBackgroundColor || tinycolor('green').lighten(2).toRgbString();\n\n scope.gpioCells = {};\n var rowCount = 0;\n for (i = 0; i < scope.gpioList.length; i++) {\n gpio = scope.gpioList[i];\n scope.gpioCells[gpio.row+'_'+gpio.col] = gpio;\n rowCount = Math.max(rowCount, gpio.row+1);\n }\n \n scope.prefferedRowHeight = 32;\n scope.rows = [];\n for (i = 0; i < rowCount; i++) {\n var row = [];\n for (var c =0; c<2;c++) {\n if (scope.gpioCells[i+'_'+c]) {\n row[c] = scope.gpioCells[i+'_'+c];\n } else {\n row[c] = null;\n }\n }\n scope.rows.push(row);\n } \n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n var changed = false;\n for (var d = 0; d < self.ctx.data.length; d++) {\n var cellData = self.ctx.data[d];\n var dataKey = cellData.dataKey;\n var gpio = self.ctx.$scope.gpioByPin[dataKey.label];\n if (gpio) {\n var enabled = false;\n if (cellData.data.length > 0) {\n var tvPair = cellData.data[cellData.data.length - 1];\n enabled = (tvPair[1] === true || tvPair[1] === 'true');\n }\n if (gpio.enabled != enabled) {\n changed = true;\n gpio.enabled = enabled;\n }\n }\n }\n if (changed) {\n self.ctx.$scope.$digest();\n } \n}\n\nself.onResize = function() {\n var rowCount = self.ctx.$scope.rows.length;\n var prefferedRowHeight = (self.ctx.height - 35)/rowCount;\n prefferedRowHeight = Math.min(32, prefferedRowHeight);\n prefferedRowHeight = Math.max(12, prefferedRowHeight);\n self.ctx.$scope.prefferedRowHeight = prefferedRowHeight;\n \n var ratio = prefferedRowHeight/32;\n \n var leftLabels = $('.gpio-left-label', self.ctx.$container);\n leftLabels.css('font-size', 16*ratio+'px');\n var rightLabels = $('.gpio-right-label', self.ctx.$container);\n rightLabels.css('font-size', 16*ratio+'px');\n var pins = $('.pin', self.ctx.$container);\n var pinsFontSize = Math.max(9, 12*ratio);\n pins.css('font-size', pinsFontSize+'px'); \n}\n\nself.onDestroy = function() {\n}\n", + "controllerScript": "var namespace;\nvar cssParser = new cssjs();\n\nself.onInit = function() {\n var utils = self.ctx.$injector.get(self.ctx.servicesMap.get('utils'));\n namespace = 'gpio-panel-' + utils.guid();\n cssParser.testMode = false;\n cssParser.cssPreviewNamespace = namespace;\n self.ctx.$container.addClass(namespace);\n self.ctx.ngZone.run(function() {\n init(); \n });\n}\n\nfunction init() {\n var i, gpio;\n \n var scope = self.ctx.$scope;\n var settings = self.ctx.settings;\n \n scope.gpioList = [];\n scope.gpioByPin = {};\n for (var g = 0; g < settings.gpioList.length; g++) {\n gpio = settings.gpioList[g];\n scope.gpioList.push(\n {\n row: gpio.row,\n col: gpio.col,\n pin: gpio.pin,\n label: gpio.label,\n enabled: false,\n colorOn: tinycolor(gpio.color).lighten(20).toHexString(),\n colorOff: tinycolor(gpio.color).darken().toHexString()\n }\n );\n scope.gpioByPin[gpio.pin] = scope.gpioList[scope.gpioList.length-1];\n }\n\n scope.ledPanelBackgroundColor = settings.ledPanelBackgroundColor || tinycolor('green').lighten(2).toRgbString();\n\n scope.gpioCells = {};\n var rowCount = 0;\n for (i = 0; i < scope.gpioList.length; i++) {\n gpio = scope.gpioList[i];\n scope.gpioCells[gpio.row+'_'+gpio.col] = gpio;\n rowCount = Math.max(rowCount, gpio.row+1);\n }\n \n scope.prefferedRowHeight = 32;\n scope.rows = [];\n for (i = 0; i < rowCount; i++) {\n var row = [];\n for (var c =0; c<2;c++) {\n if (scope.gpioCells[i+'_'+c]) {\n row[c] = scope.gpioCells[i+'_'+c];\n } else {\n row[c] = null;\n }\n }\n scope.rows.push(row);\n } \n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n var changed = false;\n for (var d = 0; d < self.ctx.data.length; d++) {\n var cellData = self.ctx.data[d];\n var dataKey = cellData.dataKey;\n var gpio = self.ctx.$scope.gpioByPin[dataKey.label];\n if (gpio) {\n var enabled = false;\n if (cellData.data.length > 0) {\n var tvPair = cellData.data[cellData.data.length - 1];\n enabled = (tvPair[1] === true || tvPair[1] === 'true');\n }\n if (gpio.enabled != enabled) {\n changed = true;\n gpio.enabled = enabled;\n }\n }\n }\n if (changed) {\n self.ctx.detectChanges();\n } \n}\n\nself.onResize = function() {\n var rowCount = self.ctx.$scope.rows.length;\n var prefferedRowHeight = (self.ctx.height - 35)/rowCount;\n prefferedRowHeight = Math.min(32, prefferedRowHeight);\n prefferedRowHeight = Math.max(12, prefferedRowHeight);\n self.ctx.$scope.prefferedRowHeight = prefferedRowHeight;\n \n var ratio = prefferedRowHeight/32;\n \n var css = '.gpio-left-label, .gpio-right-label {\\n' +\n ' font-size: ' + 16*ratio+'px;\\n'+\n '}\\n';\n var pinsFontSize = Math.max(9, 12*ratio);\n css += '.pin {\\n' +\n ' font-size: ' + pinsFontSize+'px;\\n'+\n '}\\n';\n \n cssParser.createStyleElement(namespace, css); \n \n self.ctx.detectChanges();\n}\n\nself.onDestroy = function() {\n}\n", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"gpioList\": {\n \"title\": \"Gpio leds\",\n \"type\": \"array\",\n \"minItems\" : 1,\n \"items\": {\n \"title\": \"Gpio led\",\n \"type\": \"object\",\n \"properties\": {\n \"pin\": {\n \"title\": \"Pin\",\n \"type\": \"number\"\n },\n \"label\": {\n \"title\": \"Label\",\n \"type\": \"string\"\n },\n \"row\": {\n \"title\": \"Row\",\n \"type\": \"number\"\n },\n \"col\": {\n \"title\": \"Column\",\n \"type\": \"number\"\n },\n \"color\": {\n \"title\": \"Color\",\n \"type\": \"string\",\n \"default\": \"red\"\n }\n },\n \"required\": [\"pin\", \"label\", \"row\", \"col\", \"color\"]\n }\n },\n \"ledPanelBackgroundColor\": {\n \"title\": \"LED panel background color\",\n \"type\": \"string\",\n \"default\": \"#008a00\"\n } \n },\n \"required\": [\"gpioList\", \n \"ledPanelBackgroundColor\"]\n },\n \"form\": [\n {\n \"key\": \"gpioList\",\n \"items\": [\n \"gpioList[].pin\",\n \"gpioList[].label\",\n \"gpioList[].row\",\n \"gpioList[].col\",\n {\n \"key\": \"gpioList[].color\",\n \"type\": \"color\"\n }\n ]\n },\n {\n \"key\": \"ledPanelBackgroundColor\",\n \"type\": \"color\"\n }\n ]\n}", "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"gpioList\":[{\"pin\":1,\"label\":\"GPIO 1\",\"row\":0,\"col\":0,\"color\":\"#008000\",\"_uniqueKey\":0},{\"pin\":2,\"label\":\"GPIO 2\",\"row\":0,\"col\":1,\"color\":\"#ffff00\",\"_uniqueKey\":1},{\"pin\":3,\"label\":\"GPIO 3\",\"row\":1,\"col\":0,\"color\":\"#cf006f\",\"_uniqueKey\":2}],\"ledPanelBackgroundColor\":\"#b71c1c\"},\"title\":\"Basic GPIO Panel\",\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"1\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.22518255793320163,\"funcBody\":\"var period = time % 1500;\\nreturn period < 500;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"2\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.7008206860666621,\"funcBody\":\"var period = time % 1500;\\nreturn period >= 500 && period < 1000;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"3\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.42600325102193426,\"funcBody\":\"var period = time % 1500;\\nreturn period >= 1000;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}}}" @@ -45,9 +45,9 @@ "sizeX": 6, "sizeY": 10.5, "resources": [], - "templateHtml": "
\n
\n
\n
\n {{ cell.label }}\n
\n {{cell.pin}}\n \n \n \n \n {{cell.pin}}\n
\n {{ cell.label }}\n
\n
\n \n \n \n
\n
\n
\n {{rpcErrorText}}\n \n
", - "templateCss": ".error {\n font-size: 14px !important;\n color: maroon;/*rgb(250,250,250);*/\n background-color: transparent;\n padding: 6px;\n}\n\n.error span {\n margin: auto;\n}\n\n.gpio-panel {\n padding-top: 10px;\n white-space: nowrap;\n}\n\n.switch-panel {\n margin: 0;\n height: 32px;\n width: 66px;\n min-width: 66px;\n}\n\n.switch-panel md-switch {\n margin: 0;\n width: 36px;\n min-width: 36px;\n}\n\n.switch-panel md-switch > div.md-container {\n margin: 0;\n}\n\n.switch-panel.col-0 md-switch {\n margin-left: 8px;\n margin-right: 4px;\n}\n\n.switch-panel.col-1 md-switch {\n margin-left: 4px;\n margin-right: 8px;\n}\n\n.gpio-row {\n height: 32px;\n}\n\n.pin {\n margin-top: auto;\n margin-bottom: auto;\n color: white;\n font-size: 12px;\n width: 16px;\n min-width: 16px;\n}\n\n.switch-panel.col-0 .pin {\n margin-left: auto;\n padding-left: 2px;\n text-align: right;\n}\n\n.switch-panel.col-1 .pin {\n margin-right: auto;\n \n text-align: left;\n}\n\n.gpio-left-label {\n margin-right: 8px;\n}\n\n.gpio-right-label {\n margin-left: 8px;\n}", - "controllerScript": "self.onInit = function() {\n \n var i, gpio;\n var scope = self.ctx.$scope;\n var settings = self.ctx.settings;\n scope.gpioList = [];\n for (var g = 0; g < settings.gpioList.length; g++) {\n gpio = settings.gpioList[g];\n scope.gpioList.push(\n {\n row: gpio.row,\n col: gpio.col,\n pin: gpio.pin,\n label: gpio.label,\n enabled: false\n }\n );\n }\n\n scope.requestTimeout = settings.requestTimeout || 1000;\n\n scope.switchPanelBackgroundColor = settings.switchPanelBackgroundColor || tinycolor('green').lighten(2).toRgbString();\n\n scope.gpioStatusRequest = {\n method: \"getGpioStatus\",\n paramsBody: \"{}\"\n };\n \n if (settings.gpioStatusRequest) {\n scope.gpioStatusRequest.method = settings.gpioStatusRequest.method || scope.gpioStatusRequest.method;\n scope.gpioStatusRequest.paramsBody = settings.gpioStatusRequest.paramsBody || scope.gpioStatusRequest.paramsBody;\n }\n \n scope.gpioStatusChangeRequest = {\n method: \"setGpioStatus\",\n paramsBody: \"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"\n };\n \n if (settings.gpioStatusChangeRequest) {\n scope.gpioStatusChangeRequest.method = settings.gpioStatusChangeRequest.method || scope.gpioStatusChangeRequest.method;\n scope.gpioStatusChangeRequest.paramsBody = settings.gpioStatusChangeRequest.paramsBody || scope.gpioStatusChangeRequest.paramsBody;\n }\n \n scope.parseGpioStatusFunction = \"return body[pin] === true;\";\n \n if (settings.parseGpioStatusFunction && settings.parseGpioStatusFunction.length > 0) {\n scope.parseGpioStatusFunction = settings.parseGpioStatusFunction;\n }\n \n scope.parseGpioStatusFunction = new Function(\"body, pin\", scope.parseGpioStatusFunction);\n \n function requestGpioStatus() {\n self.ctx.controlApi.sendTwoWayCommand(scope.gpioStatusRequest.method, \n scope.gpioStatusRequest.paramsBody, \n scope.requestTimeout)\n .then(\n function success(responseBody) {\n for (var g = 0; g < scope.gpioList.length; g++) {\n var gpio = scope.gpioList[g];\n var enabled = scope.parseGpioStatusFunction.apply(this, [responseBody, gpio.pin]);\n gpio.enabled = enabled; \n }\n }\n );\n }\n \n function changeGpioStatus(gpio) {\n var pin = gpio.pin + '';\n var enabled = !gpio.enabled;\n enabled = enabled === true ? 'true' : 'false';\n var paramsBody = scope.gpioStatusChangeRequest.paramsBody;\n var requestBody = JSON.parse(paramsBody.replace(\"\\\"{$pin}\\\"\", pin).replace(\"\\\"{$enabled}\\\"\", enabled));\n self.ctx.controlApi.sendTwoWayCommand(scope.gpioStatusChangeRequest.method, \n requestBody, scope.requestTimeout)\n .then(\n function success(responseBody) {\n var enabled = scope.parseGpioStatusFunction.apply(this, [responseBody, gpio.pin]);\n gpio.enabled = enabled;\n }\n );\n }\n \n scope.gpioCells = {};\n var rowCount = 0;\n for (i = 0; i < scope.gpioList.length; i++) {\n gpio = scope.gpioList[i];\n scope.gpioCells[gpio.row+'_'+gpio.col] = gpio;\n rowCount = Math.max(rowCount, gpio.row+1);\n }\n \n scope.prefferedRowHeight = 32;\n scope.rows = [];\n for (i = 0; i < rowCount; i++) {\n var row = [];\n for (var c =0; c<2;c++) {\n if (scope.gpioCells[i+'_'+c]) {\n row[c] = scope.gpioCells[i+'_'+c];\n } else {\n row[c] = null;\n }\n }\n scope.rows.push(row);\n }\n\n scope.gpioClick = function($event, gpio) {\n changeGpioStatus(gpio);\n };\n\n requestGpioStatus(); \n \n self.onResize();\n}\n\nself.onResize = function() {\n var scope = self.ctx.$scope;\n var rowCount = scope.rows.length;\n var prefferedRowHeight = (self.ctx.height - 35)/rowCount;\n prefferedRowHeight = Math.min(32, prefferedRowHeight);\n prefferedRowHeight = Math.max(12, prefferedRowHeight);\n scope.prefferedRowHeight = prefferedRowHeight;\n var ratio = prefferedRowHeight/32;\n var switches = $('md-switch', self.ctx.$container);\n switches.css('height', 30*ratio+'px');\n switches.css('width', 36*ratio+'px');\n switches.css('min-width', 36*ratio+'px');\n $('.md-container', switches).css('height', 24*ratio+'px');\n $('.md-container', switches).css('width', 36*ratio+'px');\n var bars = $('.md-bar', self.ctx.$container);\n bars.css('height', 14*ratio+'px');\n bars.css('width', 34*ratio+'px');\n var thumbs = $('.md-thumb', self.ctx.$container);\n thumbs.css('height', 20*ratio+'px');\n thumbs.css('width', 20*ratio+'px');\n \n var leftLabels = $('.gpio-left-label', self.ctx.$container);\n leftLabels.css('font-size', 16*ratio+'px');\n var rightLabels = $('.gpio-right-label', self.ctx.$container);\n rightLabels.css('font-size', 16*ratio+'px');\n var pins = $('.pin', self.ctx.$container);\n var pinsFontSize = Math.max(9, 12*ratio);\n pins.css('font-size', pinsFontSize+'px'); \n}\n\nself.onDestroy = function() {\n}\n", + "templateHtml": "
\n
\n
\n
\n {{ cell.label }}\n
\n {{cell.pin}}\n \n \n \n \n {{cell.pin}}\n
\n {{ cell.label }}\n
\n
\n \n \n \n
\n
\n
\n {{rpcErrorText}}\n \n
", + "templateCss": ".error {\n font-size: 14px !important;\n color: maroon;/*rgb(250,250,250);*/\n background-color: transparent;\n padding: 6px;\n}\n\n.error span {\n margin: auto;\n}\n\n.gpio-panel {\n padding-top: 10px;\n white-space: nowrap;\n}\n\n.switch-panel {\n margin: 0;\n height: 32px;\n width: 66px;\n min-width: 66px;\n}\n\n.switch-panel mat-slide-toggle {\n margin: 0;\n width: 36px;\n min-width: 36px;\n}\n\n.switch-panel.col-0 mat-slide-toggle {\n margin-left: 8px;\n margin-right: 4px;\n}\n\n.switch-panel.col-1 mat-slide-toggle {\n margin-left: 4px;\n margin-right: 8px;\n}\n\n.gpio-row {\n height: 32px;\n}\n\n.pin {\n margin-top: auto;\n margin-bottom: auto;\n color: white;\n font-size: 12px;\n width: 16px;\n min-width: 16px;\n}\n\n.switch-panel.col-0 .pin {\n margin-left: auto;\n padding-left: 2px;\n text-align: right;\n}\n\n.switch-panel.col-1 .pin {\n margin-right: auto;\n \n text-align: left;\n}\n\n.gpio-left-label {\n margin-right: 8px;\n}\n\n.gpio-right-label {\n margin-left: 8px;\n}", + "controllerScript": "var namespace;\nvar cssParser = new cssjs();\n\nself.onInit = function() {\n var utils = self.ctx.$injector.get(self.ctx.servicesMap.get('utils'));\n namespace = 'gpio-control-' + utils.guid();\n cssParser.testMode = false;\n cssParser.cssPreviewNamespace = namespace;\n self.ctx.$container.addClass(namespace);\n self.ctx.ngZone.run(function() {\n init(); \n });\n}\n\nfunction init() {\n \n var i, gpio;\n var scope = self.ctx.$scope;\n var settings = self.ctx.settings;\n scope.gpioList = [];\n for (var g = 0; g < settings.gpioList.length; g++) {\n gpio = settings.gpioList[g];\n scope.gpioList.push(\n {\n row: gpio.row,\n col: gpio.col,\n pin: gpio.pin,\n label: gpio.label,\n enabled: false\n }\n );\n }\n\n scope.requestTimeout = settings.requestTimeout || 1000;\n\n scope.switchPanelBackgroundColor = settings.switchPanelBackgroundColor || tinycolor('green').lighten(2).toRgbString();\n\n scope.gpioStatusRequest = {\n method: \"getGpioStatus\",\n paramsBody: \"{}\"\n };\n \n if (settings.gpioStatusRequest) {\n scope.gpioStatusRequest.method = settings.gpioStatusRequest.method || scope.gpioStatusRequest.method;\n scope.gpioStatusRequest.paramsBody = settings.gpioStatusRequest.paramsBody || scope.gpioStatusRequest.paramsBody;\n }\n \n scope.gpioStatusChangeRequest = {\n method: \"setGpioStatus\",\n paramsBody: \"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"\n };\n \n if (settings.gpioStatusChangeRequest) {\n scope.gpioStatusChangeRequest.method = settings.gpioStatusChangeRequest.method || scope.gpioStatusChangeRequest.method;\n scope.gpioStatusChangeRequest.paramsBody = settings.gpioStatusChangeRequest.paramsBody || scope.gpioStatusChangeRequest.paramsBody;\n }\n \n scope.parseGpioStatusFunction = \"return body[pin] === true;\";\n \n if (settings.parseGpioStatusFunction && settings.parseGpioStatusFunction.length > 0) {\n scope.parseGpioStatusFunction = settings.parseGpioStatusFunction;\n }\n \n scope.parseGpioStatusFunction = new Function(\"body, pin\", scope.parseGpioStatusFunction);\n \n function requestGpioStatus() {\n self.ctx.controlApi.sendTwoWayCommand(scope.gpioStatusRequest.method, \n scope.gpioStatusRequest.paramsBody, \n scope.requestTimeout)\n .subscribe(\n function success(responseBody) {\n for (var g = 0; g < scope.gpioList.length; g++) {\n var gpio = scope.gpioList[g];\n var enabled = scope.parseGpioStatusFunction.apply(this, [responseBody, gpio.pin]);\n gpio.enabled = enabled; \n self.ctx.detectChanges();\n }\n }\n );\n }\n \n function changeGpioStatus(gpio) {\n var pin = gpio.pin + '';\n var enabled = !gpio.enabled;\n enabled = enabled === true ? 'true' : 'false';\n var paramsBody = scope.gpioStatusChangeRequest.paramsBody;\n var requestBody = JSON.parse(paramsBody.replace(\"\\\"{$pin}\\\"\", pin).replace(\"\\\"{$enabled}\\\"\", enabled));\n self.ctx.controlApi.sendTwoWayCommand(scope.gpioStatusChangeRequest.method, \n requestBody, scope.requestTimeout)\n .subscribe(\n function success(responseBody) {\n var enabled = scope.parseGpioStatusFunction.apply(this, [responseBody, gpio.pin]);\n gpio.enabled = enabled;\n self.ctx.detectChanges();\n }\n );\n }\n \n scope.gpioCells = {};\n var rowCount = 0;\n for (i = 0; i < scope.gpioList.length; i++) {\n gpio = scope.gpioList[i];\n scope.gpioCells[gpio.row+'_'+gpio.col] = gpio;\n rowCount = Math.max(rowCount, gpio.row+1);\n }\n \n scope.prefferedRowHeight = 32;\n scope.rows = [];\n for (i = 0; i < rowCount; i++) {\n var row = [];\n for (var c =0; c<2;c++) {\n if (scope.gpioCells[i+'_'+c]) {\n row[c] = scope.gpioCells[i+'_'+c];\n } else {\n row[c] = null;\n }\n }\n scope.rows.push(row);\n }\n\n scope.gpioClick = function($event, gpio) {\n if (scope.rpcEnabled && !scope.executingRpcRequest) {\n changeGpioStatus(gpio);\n }\n };\n \n scope.gpioToggleChange = function($event, gpio) {\n gpio.enabled = !$event.checked;\n $event.source.toggle();\n self.ctx.detectChanges();\n }\n \n if (scope.rpcEnabled) {\n requestGpioStatus(); \n }\n \n self.onResize();\n}\n\nself.onResize = function() {\n var scope = self.ctx.$scope;\n var rowCount = scope.rows.length;\n var prefferedRowHeight = (self.ctx.height - 35)/rowCount;\n prefferedRowHeight = Math.min(32, prefferedRowHeight);\n prefferedRowHeight = Math.max(12, prefferedRowHeight);\n scope.prefferedRowHeight = prefferedRowHeight;\n var ratio = prefferedRowHeight/32;\n \n var css = '.mat-slide-toggle .mat-slide-toggle-bar {\\n' +\n ' height: ' + 14*ratio+'px;\\n'+\n ' width: ' + 36*ratio+'px;\\n'+\n '}\\n';\n css += '.mat-slide-toggle .mat-slide-toggle-thumb-container {\\n' +\n ' height: ' + 20*ratio+'px;\\n'+\n ' width: ' + 20*ratio+'px;\\n'+\n '}\\n';\n css += '.mat-slide-toggle .mat-slide-toggle-thumb {\\n' +\n ' height: ' + 20*ratio+'px;\\n'+\n ' width: ' + 20*ratio+'px;\\n'+\n '}\\n';\n css += '.mat-slide-toggle .mat-slide-toggle-ripple {\\n' +\n ' height: ' + 40*ratio+'px;\\n'+\n ' width: ' + 40*ratio+'px;\\n'+\n ' top: calc(50% - '+20*ratio+'px);\\n'+\n ' left: calc(50% - '+20*ratio+'px);\\n'+\n '}\\n';\n css += '.gpio-left-label, .gpio-right-label {\\n' +\n ' font-size: ' + 16*ratio+'px;\\n'+\n '}\\n';\n var pinsFontSize = Math.max(9, 12*ratio);\n css += '.pin {\\n' +\n ' font-size: ' + pinsFontSize+'px;\\n'+\n '}\\n';\n\n cssParser.createStyleElement(namespace, css);\n \n self.ctx.detectChanges();\n}\n\nself.onDestroy = function() {\n}\n", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"gpioList\": {\n \"title\": \"Gpio switches\",\n \"type\": \"array\",\n \"minItems\" : 1,\n \"items\": {\n \"title\": \"Gpio switch\",\n \"type\": \"object\",\n \"properties\": {\n \"pin\": {\n \"title\": \"Pin\",\n \"type\": \"number\"\n },\n \"label\": {\n \"title\": \"Label\",\n \"type\": \"string\"\n },\n \"row\": {\n \"title\": \"Row\",\n \"type\": \"number\"\n },\n \"col\": {\n \"title\": \"Column\",\n \"type\": \"number\"\n }\n },\n \"required\": [\"pin\", \"label\", \"row\", \"col\"]\n }\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"switchPanelBackgroundColor\": {\n \"title\": \"Switches panel background color\",\n \"type\": \"string\",\n \"default\": \"#008a00\"\n },\n \"gpioStatusRequest\": {\n \"title\": \"GPIO status request\",\n \"type\": \"object\",\n \"properties\": {\n \"method\": {\n \"title\": \"Method name\",\n \"type\": \"string\",\n \"default\": \"getGpioStatus\"\n },\n \"paramsBody\": {\n \"title\": \"Method body\",\n \"type\": \"string\",\n \"default\": \"{}\"\n }\n },\n \"required\": [\"method\", \"paramsBody\"]\n },\n \"gpioStatusChangeRequest\": {\n \"title\": \"GPIO status change request\",\n \"type\": \"object\",\n \"properties\": {\n \"method\": {\n \"title\": \"Method name\",\n \"type\": \"string\",\n \"default\": \"setGpioStatus\"\n },\n \"paramsBody\": {\n \"title\": \"Method body\",\n \"type\": \"string\",\n \"default\": \"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"\n }\n },\n \"required\": [\"method\", \"paramsBody\"]\n },\n \"parseGpioStatusFunction\": {\n \"title\": \"Parse gpio status function\",\n \"type\": \"string\",\n \"default\": \"return body[pin] === true;\"\n } \n },\n \"required\": [\"gpioList\", \n \"requestTimeout\",\n \"switchPanelBackgroundColor\",\n \"gpioStatusRequest\",\n \"gpioStatusChangeRequest\",\n \"parseGpioStatusFunction\"]\n },\n \"form\": [\n \"gpioList\",\n \"requestTimeout\",\n {\n \"key\": \"switchPanelBackgroundColor\",\n \"type\": \"color\"\n },\n {\n \"key\": \"gpioStatusRequest\",\n \"items\": [\n \"gpioStatusRequest.method\",\n {\n \"key\": \"gpioStatusRequest.paramsBody\",\n \"type\": \"json\"\n }\n ]\n },\n {\n \"key\": \"gpioStatusChangeRequest\",\n \"items\": [\n \"gpioStatusChangeRequest.method\",\n {\n \"key\": \"gpioStatusChangeRequest.paramsBody\",\n \"type\": \"json\"\n }\n ]\n },\n {\n \"key\": \"parseGpioStatusFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"parseGpioStatusFunction\":\"return body[pin] === true;\",\"gpioStatusChangeRequest\":{\"method\":\"setGpioStatus\",\"paramsBody\":\"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"},\"requestTimeout\":500,\"switchPanelBackgroundColor\":\"#008a00\",\"gpioStatusRequest\":{\"method\":\"getGpioStatus\",\"paramsBody\":\"{}\"},\"gpioList\":[{\"pin\":7,\"label\":\"GPIO 4 (GPCLK0)\",\"row\":3,\"col\":0,\"_uniqueKey\":0},{\"pin\":11,\"label\":\"GPIO 17\",\"row\":5,\"col\":0,\"_uniqueKey\":1},{\"pin\":12,\"label\":\"GPIO 18\",\"row\":5,\"col\":1,\"_uniqueKey\":2},{\"_uniqueKey\":3,\"pin\":13,\"label\":\"GPIO 27\",\"row\":6,\"col\":0},{\"_uniqueKey\":4,\"pin\":15,\"label\":\"GPIO 22\",\"row\":7,\"col\":0},{\"_uniqueKey\":5,\"pin\":16,\"label\":\"GPIO 23\",\"row\":7,\"col\":1},{\"_uniqueKey\":6,\"pin\":18,\"label\":\"GPIO 24\",\"row\":8,\"col\":1},{\"_uniqueKey\":7,\"pin\":22,\"label\":\"GPIO 25\",\"row\":10,\"col\":1},{\"_uniqueKey\":8,\"pin\":29,\"label\":\"GPIO 5\",\"row\":14,\"col\":0},{\"_uniqueKey\":9,\"pin\":31,\"label\":\"GPIO 6\",\"row\":15,\"col\":0},{\"_uniqueKey\":10,\"pin\":32,\"label\":\"GPIO 12\",\"row\":15,\"col\":1},{\"_uniqueKey\":11,\"pin\":33,\"label\":\"GPIO 13\",\"row\":16,\"col\":0},{\"_uniqueKey\":12,\"pin\":35,\"label\":\"GPIO 19\",\"row\":17,\"col\":0},{\"_uniqueKey\":13,\"pin\":36,\"label\":\"GPIO 16\",\"row\":17,\"col\":1},{\"_uniqueKey\":14,\"pin\":37,\"label\":\"GPIO 26\",\"row\":18,\"col\":0},{\"_uniqueKey\":15,\"pin\":38,\"label\":\"GPIO 20\",\"row\":18,\"col\":1},{\"_uniqueKey\":16,\"pin\":40,\"label\":\"GPIO 21\",\"row\":19,\"col\":1}]},\"title\":\"Raspberry Pi GPIO Control\"}" @@ -61,9 +61,9 @@ "sizeX": 7, "sizeY": 10.5, "resources": [], - "templateHtml": "
\n
\n
\n
\n {{ cell.label }}\n
\n {{cell.pin}}\n \n \n \n \n {{cell.pin}}\n
\n {{ cell.label }}\n
\n
\n \n \n \n
\n
\n
\n
", + "templateHtml": "
\n
\n
\n
\n {{ cell.label }}\n
\n {{cell.pin}}\n \n \n \n \n {{cell.pin}}\n
\n {{ cell.label }}\n
\n
\n \n \n \n
\n
\n
\n
", "templateCss": ".error {\n font-size: 14px !important;\n color: maroon;/*rgb(250,250,250);*/\n background-color: transparent;\n padding: 6px;\n}\n\n.error span {\n margin: auto;\n}\n\n.gpio-panel {\n padding-top: 10px;\n white-space: nowrap;\n}\n\n.gpio-panel tb-led-light > div {\n margin: auto;\n}\n\n.led-panel {\n margin: 0;\n width: 66px;\n min-width: 66px;\n}\n\n.led-container {\n width: 48px;\n min-width: 48px;\n}\n\n.pin {\n margin-top: auto;\n margin-bottom: auto;\n color: white;\n font-size: 12px;\n width: 16px;\n min-width: 16px;\n}\n\n.led-panel.col-0 .pin {\n margin-left: auto;\n padding-left: 2px;\n text-align: right;\n}\n\n.led-panel.col-1 .pin {\n margin-right: auto;\n \n text-align: left;\n}\n\n.gpio-left-label {\n margin-right: 8px;\n}\n\n.gpio-right-label {\n margin-left: 8px;\n}", - "controllerScript": "self.onInit = function() {\n var i, gpio;\n \n var scope = self.ctx.$scope;\n var settings = self.ctx.settings;\n \n scope.gpioList = [];\n scope.gpioByPin = {};\n for (var g = 0; g < settings.gpioList.length; g++) {\n gpio = settings.gpioList[g];\n scope.gpioList.push(\n {\n row: gpio.row,\n col: gpio.col,\n pin: gpio.pin,\n label: gpio.label,\n enabled: false,\n colorOn: tinycolor(gpio.color).lighten(20).toHexString(),\n colorOff: tinycolor(gpio.color).darken().toHexString()\n }\n );\n scope.gpioByPin[gpio.pin] = scope.gpioList[scope.gpioList.length-1];\n }\n\n scope.ledPanelBackgroundColor = settings.ledPanelBackgroundColor || tinycolor('green').lighten(2).toRgbString();\n\n scope.gpioCells = {};\n var rowCount = 0;\n for (i = 0; i < scope.gpioList.length; i++) {\n gpio = scope.gpioList[i];\n scope.gpioCells[gpio.row+'_'+gpio.col] = gpio;\n rowCount = Math.max(rowCount, gpio.row+1);\n }\n \n scope.prefferedRowHeight = 32;\n scope.rows = [];\n for (i = 0; i < rowCount; i++) {\n var row = [];\n for (var c =0; c<2;c++) {\n if (scope.gpioCells[i+'_'+c]) {\n row[c] = scope.gpioCells[i+'_'+c];\n } else {\n row[c] = null;\n }\n }\n scope.rows.push(row);\n } \n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n var changed = false;\n for (var d = 0; d < self.ctx.data.length; d++) {\n var cellData = self.ctx.data[d];\n var dataKey = cellData.dataKey;\n var gpio = self.ctx.$scope.gpioByPin[dataKey.label];\n if (gpio) {\n var enabled = false;\n if (cellData.data.length > 0) {\n var tvPair = cellData.data[cellData.data.length - 1];\n enabled = (tvPair[1] === true || tvPair[1] === 'true');\n }\n if (gpio.enabled != enabled) {\n changed = true;\n gpio.enabled = enabled;\n }\n }\n }\n if (changed) {\n self.ctx.$scope.$digest();\n } \n}\n\nself.onResize = function() {\n var rowCount = self.ctx.$scope.rows.length;\n var prefferedRowHeight = (self.ctx.height - 35)/rowCount;\n prefferedRowHeight = Math.min(32, prefferedRowHeight);\n prefferedRowHeight = Math.max(12, prefferedRowHeight);\n self.ctx.$scope.prefferedRowHeight = prefferedRowHeight;\n \n var ratio = prefferedRowHeight/32;\n \n var leftLabels = $('.gpio-left-label', self.ctx.$container);\n leftLabels.css('font-size', 16*ratio+'px');\n var rightLabels = $('.gpio-right-label', self.ctx.$container);\n rightLabels.css('font-size', 16*ratio+'px');\n var pins = $('.pin', self.ctx.$container);\n var pinsFontSize = Math.max(9, 12*ratio);\n pins.css('font-size', pinsFontSize+'px'); \n}\n\nself.onDestroy = function() {\n}\n", + "controllerScript": "var namespace;\nvar cssParser = new cssjs();\n\nself.onInit = function() {\n var utils = self.ctx.$injector.get(self.ctx.servicesMap.get('utils'));\n namespace = 'gpio-panel-' + utils.guid();\n cssParser.testMode = false;\n cssParser.cssPreviewNamespace = namespace;\n self.ctx.$container.addClass(namespace);\n self.ctx.ngZone.run(function() {\n init(); \n });\n}\n\nfunction init() {\n var i, gpio;\n \n var scope = self.ctx.$scope;\n var settings = self.ctx.settings;\n \n scope.gpioList = [];\n scope.gpioByPin = {};\n for (var g = 0; g < settings.gpioList.length; g++) {\n gpio = settings.gpioList[g];\n scope.gpioList.push(\n {\n row: gpio.row,\n col: gpio.col,\n pin: gpio.pin,\n label: gpio.label,\n enabled: false,\n colorOn: tinycolor(gpio.color).lighten(20).toHexString(),\n colorOff: tinycolor(gpio.color).darken().toHexString()\n }\n );\n scope.gpioByPin[gpio.pin] = scope.gpioList[scope.gpioList.length-1];\n }\n\n scope.ledPanelBackgroundColor = settings.ledPanelBackgroundColor || tinycolor('green').lighten(2).toRgbString();\n\n scope.gpioCells = {};\n var rowCount = 0;\n for (i = 0; i < scope.gpioList.length; i++) {\n gpio = scope.gpioList[i];\n scope.gpioCells[gpio.row+'_'+gpio.col] = gpio;\n rowCount = Math.max(rowCount, gpio.row+1);\n }\n \n scope.prefferedRowHeight = 32;\n scope.rows = [];\n for (i = 0; i < rowCount; i++) {\n var row = [];\n for (var c =0; c<2;c++) {\n if (scope.gpioCells[i+'_'+c]) {\n row[c] = scope.gpioCells[i+'_'+c];\n } else {\n row[c] = null;\n }\n }\n scope.rows.push(row);\n } \n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n var changed = false;\n for (var d = 0; d < self.ctx.data.length; d++) {\n var cellData = self.ctx.data[d];\n var dataKey = cellData.dataKey;\n var gpio = self.ctx.$scope.gpioByPin[dataKey.label];\n if (gpio) {\n var enabled = false;\n if (cellData.data.length > 0) {\n var tvPair = cellData.data[cellData.data.length - 1];\n enabled = (tvPair[1] === true || tvPair[1] === 'true');\n }\n if (gpio.enabled != enabled) {\n changed = true;\n gpio.enabled = enabled;\n }\n }\n }\n if (changed) {\n self.ctx.detectChanges();\n } \n}\n\nself.onResize = function() {\n var rowCount = self.ctx.$scope.rows.length;\n var prefferedRowHeight = (self.ctx.height - 35)/rowCount;\n prefferedRowHeight = Math.min(32, prefferedRowHeight);\n prefferedRowHeight = Math.max(12, prefferedRowHeight);\n self.ctx.$scope.prefferedRowHeight = prefferedRowHeight;\n \n var ratio = prefferedRowHeight/32;\n \n var css = '.gpio-left-label, .gpio-right-label {\\n' +\n ' font-size: ' + 16*ratio+'px;\\n'+\n '}\\n';\n var pinsFontSize = Math.max(9, 12*ratio);\n css += '.pin {\\n' +\n ' font-size: ' + pinsFontSize+'px;\\n'+\n '}\\n';\n \n cssParser.createStyleElement(namespace, css); \n \n self.ctx.detectChanges();\n}\n\nself.onDestroy = function() {\n}\n", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"gpioList\": {\n \"title\": \"Gpio leds\",\n \"type\": \"array\",\n \"minItems\" : 1,\n \"items\": {\n \"title\": \"Gpio led\",\n \"type\": \"object\",\n \"properties\": {\n \"pin\": {\n \"title\": \"Pin\",\n \"type\": \"number\"\n },\n \"label\": {\n \"title\": \"Label\",\n \"type\": \"string\"\n },\n \"row\": {\n \"title\": \"Row\",\n \"type\": \"number\"\n },\n \"col\": {\n \"title\": \"Column\",\n \"type\": \"number\"\n },\n \"color\": {\n \"title\": \"Color\",\n \"type\": \"string\",\n \"default\": \"red\"\n }\n },\n \"required\": [\"pin\", \"label\", \"row\", \"col\", \"color\"]\n }\n },\n \"ledPanelBackgroundColor\": {\n \"title\": \"LED panel background color\",\n \"type\": \"string\",\n \"default\": \"#008a00\"\n } \n },\n \"required\": [\"gpioList\", \n \"ledPanelBackgroundColor\"]\n },\n \"form\": [\n {\n \"key\": \"gpioList\",\n \"items\": [\n \"gpioList[].pin\",\n \"gpioList[].label\",\n \"gpioList[].row\",\n \"gpioList[].col\",\n {\n \"key\": \"gpioList[].color\",\n \"type\": \"color\"\n }\n ]\n },\n {\n \"key\": \"ledPanelBackgroundColor\",\n \"type\": \"color\"\n }\n ]\n}", "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"gpioList\":[{\"pin\":1,\"label\":\"3.3V\",\"row\":0,\"col\":0,\"color\":\"#fc9700\",\"_uniqueKey\":0},{\"pin\":2,\"label\":\"5V\",\"row\":0,\"col\":1,\"color\":\"#fb0000\",\"_uniqueKey\":1},{\"pin\":3,\"label\":\"GPIO 2 (I2C1_SDA)\",\"row\":1,\"col\":0,\"color\":\"#02fefb\",\"_uniqueKey\":2},{\"color\":\"#fb0000\",\"pin\":4,\"label\":\"5V\",\"row\":1,\"col\":1},{\"color\":\"#02fefb\",\"pin\":5,\"label\":\"GPIO 3 (I2C1_SCL)\",\"row\":2,\"col\":0},{\"color\":\"#000000\",\"pin\":6,\"label\":\"GND\",\"row\":2,\"col\":1},{\"color\":\"#00fd00\",\"pin\":7,\"label\":\"GPIO 4 (GPCLK0)\",\"row\":3,\"col\":0},{\"color\":\"#fdfb00\",\"pin\":8,\"label\":\"GPIO 14 (UART_TXD)\",\"row\":3,\"col\":1},{\"color\":\"#000000\",\"pin\":9,\"label\":\"GND\",\"row\":4,\"col\":0},{\"color\":\"#fdfb00\",\"pin\":10,\"label\":\"GPIO 15 (UART_RXD)\",\"row\":4,\"col\":1},{\"color\":\"#00fd00\",\"pin\":11,\"label\":\"GPIO 17\",\"row\":5,\"col\":0},{\"color\":\"#00fd00\",\"pin\":12,\"label\":\"GPIO 18\",\"row\":5,\"col\":1},{\"color\":\"#00fd00\",\"pin\":13,\"label\":\"GPIO 27\",\"row\":6,\"col\":0},{\"color\":\"#000000\",\"pin\":14,\"label\":\"GND\",\"row\":6,\"col\":1},{\"color\":\"#00fd00\",\"pin\":15,\"label\":\"GPIO 22\",\"row\":7,\"col\":0},{\"color\":\"#00fd00\",\"pin\":16,\"label\":\"GPIO 23\",\"row\":7,\"col\":1},{\"color\":\"#fc9700\",\"pin\":17,\"label\":\"3.3V\",\"row\":8,\"col\":0},{\"color\":\"#00fd00\",\"pin\":18,\"label\":\"GPIO 24\",\"row\":8,\"col\":1},{\"color\":\"#fd01fd\",\"pin\":19,\"label\":\"GPIO 10 (SPI_MOSI)\",\"row\":9,\"col\":0},{\"color\":\"#000000\",\"pin\":20,\"label\":\"GND\",\"row\":9,\"col\":1},{\"color\":\"#fd01fd\",\"pin\":21,\"label\":\"GPIO 9 (SPI_MISO)\",\"row\":10,\"col\":0},{\"color\":\"#00fd00\",\"pin\":22,\"label\":\"GPIO 25\",\"row\":10,\"col\":1},{\"color\":\"#fd01fd\",\"pin\":23,\"label\":\"GPIO 11 (SPI_SCLK)\",\"row\":11,\"col\":0},{\"color\":\"#fd01fd\",\"pin\":24,\"label\":\"GPIO 8 (SPI_CE0)\",\"row\":11,\"col\":1},{\"color\":\"#000000\",\"pin\":25,\"label\":\"GND\",\"row\":12,\"col\":0},{\"color\":\"#fd01fd\",\"pin\":26,\"label\":\"GPIO 7 (SPI_CE1)\",\"row\":12,\"col\":1},{\"color\":\"#ffffff\",\"pin\":27,\"label\":\"ID_SD\",\"row\":13,\"col\":0},{\"color\":\"#ffffff\",\"pin\":28,\"label\":\"ID_SC\",\"row\":13,\"col\":1},{\"color\":\"#00fd00\",\"pin\":29,\"label\":\"GPIO 5\",\"row\":14,\"col\":0},{\"color\":\"#000000\",\"pin\":30,\"label\":\"GND\",\"row\":14,\"col\":1},{\"color\":\"#00fd00\",\"pin\":31,\"label\":\"GPIO 6\",\"row\":15,\"col\":0},{\"color\":\"#00fd00\",\"pin\":32,\"label\":\"GPIO 12\",\"row\":15,\"col\":1},{\"color\":\"#00fd00\",\"pin\":33,\"label\":\"GPIO 13\",\"row\":16,\"col\":0},{\"color\":\"#000000\",\"pin\":34,\"label\":\"GND\",\"row\":16,\"col\":1},{\"color\":\"#00fd00\",\"pin\":35,\"label\":\"GPIO 19\",\"row\":17,\"col\":0},{\"color\":\"#00fd00\",\"pin\":36,\"label\":\"GPIO 16\",\"row\":17,\"col\":1},{\"color\":\"#00fd00\",\"pin\":37,\"label\":\"GPIO 26\",\"row\":18,\"col\":0},{\"color\":\"#00fd00\",\"pin\":38,\"label\":\"GPIO 20\",\"row\":18,\"col\":1},{\"color\":\"#000000\",\"pin\":39,\"label\":\"GND\",\"row\":19,\"col\":0},{\"color\":\"#00fd00\",\"pin\":40,\"label\":\"GPIO 21\",\"row\":19,\"col\":1}],\"ledPanelBackgroundColor\":\"#008a00\"},\"title\":\"Raspberry Pi GPIO Panel\",\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"7\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.22518255793320163,\"funcBody\":\"var period = time % 1500;\\nreturn period < 500;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"11\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.7008206860666621,\"funcBody\":\"var period = time % 1500;\\nreturn period >= 500 && period < 1000;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"12\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.42600325102193426,\"funcBody\":\"var period = time % 1500;\\nreturn period >= 1000;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"13\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.48362241571415243,\"funcBody\":\"var period = time % 1500;\\nreturn period < 500;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"29\",\"color\":\"#607d8b\",\"settings\":{},\"_hash\":0.7217670147518815,\"funcBody\":\"var period = time % 1500;\\nreturn period >= 500 && period < 1000;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}}}" diff --git a/ui-ngx/package-lock.json b/ui-ngx/package-lock.json index 95a439ed51..ddbac6c5bb 100644 --- a/ui-ngx/package-lock.json +++ b/ui-ngx/package-lock.json @@ -3640,6 +3640,12 @@ "integrity": "sha1-vShOV8hPEyXacCur/IKlMoGQwMU=", "dev": true }, + "@types/raphael": { + "version": "2.1.30", + "resolved": "https://registry.npmjs.org/@types/raphael/-/raphael-2.1.30.tgz", + "integrity": "sha1-dsvqSlVrol6xxtf6XnGsSOcvgc8=", + "dev": true + }, "@types/react": { "version": "16.9.16", "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.16.tgz", @@ -6620,6 +6626,11 @@ "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", "dev": true }, + "eve-raphael": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/eve-raphael/-/eve-raphael-0.5.0.tgz", + "integrity": "sha1-F8dUt5K+7z+maE15z1pHxjxM2jA=" + }, "eventemitter3": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.0.tgz", @@ -10955,6 +10966,14 @@ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", "dev": true }, + "raphael": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/raphael/-/raphael-2.3.0.tgz", + "integrity": "sha512-w2yIenZAQnp257XUWGni4bLMVxpUpcIl7qgxEgDIXtmSypYtlNxfXWpOBxs7LBTps5sDwhRnrToJrMUrivqNTQ==", + "requires": { + "eve-raphael": "0.5.0" + } + }, "raw-body": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", diff --git a/ui-ngx/package.json b/ui-ngx/package.json index dced2e17a4..0c83064414 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -66,6 +66,7 @@ "ngx-translate-messageformat-compiler": "^4.5.0", "objectpath": "^1.2.2", "prop-types": "^15.7.2", + "raphael": "^2.3.0", "rc-select": "^9.2.1", "react": "^16.12.0", "react-ace": "^8.0.0", @@ -97,6 +98,7 @@ "@types/js-beautify": "^1.8.1", "@types/jstree": "^3.3.39", "@types/node": "~12.12.17", + "@types/raphael": "^2.1.30", "@types/react": "^16.9.16", "@types/react-dom": "^16.9.4", "@types/tinycolor2": "^1.4.2", diff --git a/ui-ngx/src/app/core/api/widget-subscription.ts b/ui-ngx/src/app/core/api/widget-subscription.ts index 693ecc22c0..eae24201ae 100644 --- a/ui-ngx/src/app/core/api/widget-subscription.ts +++ b/ui-ngx/src/app/core/api/widget-subscription.ts @@ -552,8 +552,9 @@ export class WidgetSubscription implements IWidgetSubscription { }, 500); } else { this.executingSubjects.push(rpcSubject); - const targetSendFunction = oneWayElseTwoWay ? this.ctx.deviceService.sendOneWayRpcCommand : this.ctx.deviceService.sendTwoWayRpcCommand; - targetSendFunction(this.targetDeviceId, requestBody).subscribe((responseBody) => { + (oneWayElseTwoWay ? this.ctx.deviceService.sendOneWayRpcCommand(this.targetDeviceId, requestBody) : + this.ctx.deviceService.sendTwoWayRpcCommand(this.targetDeviceId, requestBody)) + .subscribe((responseBody) => { this.rpcRejection = null; this.rpcErrorText = null; const index = this.executingSubjects.indexOf(rpcSubject); diff --git a/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts b/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts index ded6a2ec5a..5205256ea2 100644 --- a/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts +++ b/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts @@ -47,7 +47,8 @@ export class GlobalHttpInterceptor implements HttpInterceptor { private AUTH_HEADER_NAME = 'X-Authorization'; private internalUrlPrefixes = [ - '/api/auth/token' + '/api/auth/token', + '/api/plugins/rpc' ]; private activeRequests = 0; @@ -125,8 +126,8 @@ export class GlobalHttpInterceptor implements HttpInterceptor { const ignoreErrors = config.ignoreErrors; const resendRequest = config.resendRequest; const errorCode = errorResponse.error ? errorResponse.error.errorCode : null; - if (errorResponse.error.refreshTokenPending || errorResponse.status === 401) { - if (errorResponse.error.refreshTokenPending || errorCode && errorCode === Constants.serverErrorCode.jwtTokenExpired) { + if (errorResponse.error && errorResponse.error.refreshTokenPending || errorResponse.status === 401) { + if (errorResponse.error && errorResponse.error.refreshTokenPending || errorCode && errorCode === Constants.serverErrorCode.jwtTokenExpired) { return this.refreshTokenAndRetry(req, next); } else if (errorCode !== Constants.serverErrorCode.credentialsExpired) { unhandled = true; 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 363595badf..e0321d0b86 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 @@ -201,7 +201,7 @@ export class WidgetComponentService { } if (widgetControllerDescriptor) { const widgetNamespace = `widget-type-${(isSystem ? 'sys-' : '')}${bundleAlias}-${widgetInfo.alias}`; - this.loadWidgetResources(widgetInfo, widgetNamespace, [WidgetComponentsModule]).subscribe( + this.loadWidgetResources(widgetInfo, widgetNamespace, [SharedModule, WidgetComponentsModule]).subscribe( () => { if (widgetControllerDescriptor.settingsSchema) { widgetInfo.typeSettingsSchema = widgetControllerDescriptor.settingsSchema; 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 d2cc1bd122..8104727f5f 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 @@ -27,18 +27,19 @@ import { NgZone, OnChanges, OnDestroy, - OnInit, ReflectiveInjector, - SimpleChanges, Type, + OnInit, + SimpleChanges, + Type, ViewChild, ViewContainerRef, ViewEncapsulation } from '@angular/core'; -import { DashboardWidget, IDashboardComponent } from '@home/models/dashboard-component.models'; +import { DashboardWidget } from '@home/models/dashboard-component.models'; import { Datasource, + defaultLegendConfig, LegendConfig, LegendData, - LegendDirection, LegendPosition, Widget, WidgetActionDescriptor, @@ -46,8 +47,7 @@ import { WidgetActionType, WidgetResource, widgetType, - WidgetTypeParameters, - defaultLegendConfig + WidgetTypeParameters } from '@shared/models/widget.models'; import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; @@ -101,6 +101,7 @@ ServicesMap.set('assetService', AssetService); ServicesMap.set('dialogs', DialogService); ServicesMap.set('customDialog', CustomDialogService); ServicesMap.set('date', DatePipe); +ServicesMap.set('utils', UtilsService); @Component({ selector: 'tb-widget', @@ -252,6 +253,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI this.widgetContext = this.dashboardWidget.widgetContext; this.widgetContext.changeDetector = this.cd; + this.widgetContext.ngZone = this.ngZone; this.widgetContext.servicesMap = ServicesMap; this.widgetContext.isEdit = this.isEdit; this.widgetContext.isMobile = this.isMobile; @@ -533,7 +535,6 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI private reInitImpl() { this.onDestroy(); - this.configureDynamicWidgetComponent(); if (!this.typeParameters.useCustomDatasources) { this.createDefaultSubscription().subscribe( () => { @@ -541,6 +542,8 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI this.onDestroy(); } else { this.subscriptionInited = true; + this.configureDynamicWidgetComponent(); + this.cd.detectChanges(); this.onInit(); } }, @@ -555,6 +558,8 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI ); } else { this.subscriptionInited = true; + this.configureDynamicWidgetComponent(); + this.cd.detectChanges(); this.onInit(); } } @@ -826,6 +831,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI if (this.dynamicWidgetComponent) { this.dynamicWidgetComponent.rpcEnabled = subscription.rpcEnabled; this.dynamicWidgetComponent.executingRpcRequest = subscription.executingRpcRequest; + this.cd.detectChanges(); } }, onRpcSuccess: (subscription) => { @@ -833,6 +839,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI this.dynamicWidgetComponent.executingRpcRequest = subscription.executingRpcRequest; this.dynamicWidgetComponent.rpcErrorText = subscription.rpcErrorText; this.dynamicWidgetComponent.rpcRejection = subscription.rpcRejection; + this.cd.detectChanges(); } }, onRpcFailed: (subscription) => { @@ -840,12 +847,14 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI this.dynamicWidgetComponent.executingRpcRequest = subscription.executingRpcRequest; this.dynamicWidgetComponent.rpcErrorText = subscription.rpcErrorText; this.dynamicWidgetComponent.rpcRejection = subscription.rpcRejection; + this.cd.detectChanges(); } }, onRpcErrorCleared: (subscription) => { if (this.dynamicWidgetComponent) { this.dynamicWidgetComponent.rpcErrorText = null; this.dynamicWidgetComponent.rpcRejection = null; + this.cd.detectChanges(); } } }; 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 bf214fc660..b71f5198b4 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 @@ -198,6 +198,8 @@ export class WidgetContext { servicesMap?: Map>; $injector?: Injector; + + ngZone?: NgZone; } export interface IDynamicWidgetComponent { diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/default-state-controller.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/states/default-state-controller.component.ts index acf7f851f8..365b40d3ab 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/states/default-state-controller.component.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/default-state-controller.component.ts @@ -14,22 +14,11 @@ /// limitations under the License. /// -import { - Component, - OnInit, - ViewEncapsulation, - Input, - OnDestroy, - OnChanges, - SimpleChanges, - NgZone -} from '@angular/core'; -import { IStateController, StateParams, StateObject } from '@core/api/widget-api.models'; +import { Component, NgZone, OnDestroy, OnInit } from '@angular/core'; +import { StateObject, StateParams } from '@core/api/widget-api.models'; import { ActivatedRoute, Router } from '@angular/router'; -import { Observable, Subscription, of } from 'rxjs'; -import { IDashboardController } from '@home/pages/dashboard/dashboard-page.models'; import { DashboardState } from '@shared/models/dashboard.models'; -import { IStateControllerComponent, StateControllerState } from './state-controller.models'; +import { StateControllerState } from './state-controller.models'; import { StateControllerComponent } from './state-controller.component'; import { StatesControllerService } from '@home/pages/dashboard/states/states-controller.service'; import { EntityId } from '@app/shared/models/id/entity-id'; @@ -37,8 +26,6 @@ import { UtilsService } from '@core/services/utils.service'; import { base64toObj, objToBase64 } from '@app/core/utils'; import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; import { EntityService } from '@core/http/entity.service'; -import { EntityType } from '@shared/models/entity-type.models'; -import { map } from 'rxjs/operators'; @Component({ selector: 'tb-default-state-controller', @@ -49,11 +36,12 @@ export class DefaultStateControllerComponent extends StateControllerComponent im constructor(protected router: Router, protected route: ActivatedRoute, + protected ngZone: NgZone, protected statesControllerService: StatesControllerService, private utils: UtilsService, private entityService: EntityService, private dashboardUtils: DashboardUtilsService) { - super(router, route, statesControllerService); + super(router, route, ngZone, statesControllerService); } ngOnInit(): void { diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/entity-state-controller.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/states/entity-state-controller.component.ts index c9d50a3270..e8d9a6b6d1 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/states/entity-state-controller.component.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/entity-state-controller.component.ts @@ -14,22 +14,11 @@ /// limitations under the License. /// -import { - Component, - OnInit, - ViewEncapsulation, - Input, - OnDestroy, - OnChanges, - SimpleChanges, - NgZone -} from '@angular/core'; -import { IStateController, StateParams, StateObject } from '@core/api/widget-api.models'; +import { Component, NgZone, OnDestroy, OnInit } from '@angular/core'; +import { StateObject, StateParams } from '@core/api/widget-api.models'; import { ActivatedRoute, Router } from '@angular/router'; -import { Observable, Subscription, of } from 'rxjs'; -import { IDashboardController } from '@home/pages/dashboard/dashboard-page.models'; -import { DashboardState } from '@shared/models/dashboard.models'; -import { IStateControllerComponent, StateControllerState } from './state-controller.models'; +import { Observable, of } from 'rxjs'; +import { StateControllerState } from './state-controller.models'; import { StateControllerComponent } from './state-controller.component'; import { StatesControllerService } from '@home/pages/dashboard/states/states-controller.service'; import { EntityId } from '@app/shared/models/id/entity-id'; @@ -51,11 +40,12 @@ export class EntityStateControllerComponent extends StateControllerComponent imp constructor(protected router: Router, protected route: ActivatedRoute, + protected ngZone: NgZone, protected statesControllerService: StatesControllerService, private utils: UtilsService, private entityService: EntityService, private dashboardUtils: DashboardUtilsService) { - super(router, route, statesControllerService); + super(router, route, ngZone, statesControllerService); } ngOnInit(): void { diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/state-controller.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/states/state-controller.component.ts index 3c21b014e5..0a9eafa09d 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/states/state-controller.component.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/state-controller.component.ts @@ -18,7 +18,7 @@ import { IStateControllerComponent, StateControllerState } from '@home/pages/das import { IDashboardController } from '../dashboard-page.models'; import { DashboardState } from '@app/shared/models/dashboard.models'; import { Subscription } from 'rxjs'; -import { OnDestroy, OnInit } from '@angular/core'; +import { NgZone, OnDestroy, OnInit } from '@angular/core'; import { ActivatedRoute, Router, Params } from '@angular/router'; import { StatesControllerService } from '@home/pages/dashboard/states/states-controller.service'; import { EntityId } from '@app/shared/models/id/entity-id'; @@ -91,6 +91,7 @@ export abstract class StateControllerComponent implements IStateControllerCompon constructor(protected router: Router, protected route: ActivatedRoute, + protected ngZone: NgZone, protected statesControllerService: StatesControllerService) { } @@ -121,12 +122,14 @@ export abstract class StateControllerComponent implements IStateControllerCompon protected updateStateParam(newState: string) { this.currentState = newState; const queryParams: Params = { state: this.currentState }; - this.router.navigate( - [], - { - relativeTo: this.route, - queryParams, - queryParamsHandling: 'merge', + this.ngZone.run(() => { + this.router.navigate( + [], + { + relativeTo: this.route, + queryParams, + queryParamsHandling: 'merge', + }); }); } diff --git a/ui-ngx/src/app/shared/components/json-form/react/json-form-array.tsx b/ui-ngx/src/app/shared/components/json-form/react/json-form-array.tsx index 60ff467d68..5aba36cc98 100644 --- a/ui-ngx/src/app/shared/components/json-form/react/json-form-array.tsx +++ b/ui-ngx/src/app/shared/components/json-form/react/json-form-array.tsx @@ -157,6 +157,7 @@ class ThingsboardArray extends React.Component} + style={{marginBottom: '8px'}} onClick={this.onAppend}>{this.props.form.add || 'New'}; } diff --git a/ui-ngx/src/app/shared/components/json-form/react/json-form-utils.ts b/ui-ngx/src/app/shared/components/json-form/react/json-form-utils.ts index d849cd8860..b8922a8bf6 100644 --- a/ui-ngx/src/app/shared/components/json-form/react/json-form-utils.ts +++ b/ui-ngx/src/app/shared/components/json-form/react/json-form-utils.ts @@ -314,7 +314,7 @@ function defaultFormDefinition(name: string, schema: any, options: DefaultsFormO const rules = defaults[stripNullType(schema.type)]; if (rules) { let def; - rules.forEach((rule) => { + for (const rule of rules) { def = rule(name, schema, options); if (def) { @@ -324,7 +324,7 @@ function defaultFormDefinition(name: string, schema: any, options: DefaultsFormO } return def; } - }); + } } } diff --git a/ui-ngx/src/app/shared/components/led-light.component.html b/ui-ngx/src/app/shared/components/led-light.component.html new file mode 100644 index 0000000000..11a7238db1 --- /dev/null +++ b/ui-ngx/src/app/shared/components/led-light.component.html @@ -0,0 +1,18 @@ + +
diff --git a/ui-ngx/src/app/shared/components/led-light.component.ts b/ui-ngx/src/app/shared/components/led-light.component.ts new file mode 100644 index 0000000000..89eb6392c3 --- /dev/null +++ b/ui-ngx/src/app/shared/components/led-light.component.ts @@ -0,0 +1,124 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { AfterViewInit, Component, ElementRef, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import Raphael from 'raphael'; +import * as tinycolor_ from 'tinycolor2'; + +const tinycolor = tinycolor_; + +interface CircleElement extends RaphaelElement { + theGlow?: RaphaelSet; +} + +@Component({ + selector: 'tb-led-light', + templateUrl: './led-light.component.html', + styleUrls: [] +}) +export class LedLightComponent implements OnInit, AfterViewInit, OnChanges { + + @Input() size: number; + + @Input() colorOn: string; + + @Input() colorOff: string; + + @Input() offOpacity: string; + + private enabledValue: boolean; + get enabled(): boolean { + return this.enabledValue; + } + @Input() + set enabled(value: boolean) { + this.enabledValue = coerceBooleanProperty(value); + } + + private canvasSize: number; + private radius: number; + private glowSize: number; + private glowColor: string; + + private paper: RaphaelPaper; + private circleElement: CircleElement; + + constructor(private elementRef: ElementRef) { + } + + ngOnInit(): void { + this.offOpacity = this.offOpacity || '0.4'; + this.glowColor = tinycolor(this.colorOn).lighten().toHexString(); + } + + ngAfterViewInit(): void { + this.update(); + } + + ngOnChanges(changes: SimpleChanges): void { + for (const propName of Object.keys(changes)) { + const change = changes[propName]; + if (!change.firstChange && change.currentValue !== change.previousValue) { + if (propName === 'enabled') { + this.draw(); + } else if (propName === 'size') { + this.update(); + } + } + } + } + + private update() { + this.size = this.size || 50; + this.canvasSize = this.size; + this.radius = this.canvasSize / 4; + this.glowSize = this.radius / 5; + if (this.paper) { + this.paper.remove(); + } + this.paper = Raphael($('#canvas_container', this.elementRef.nativeElement)[0], this.canvasSize, this.canvasSize); + const center = this.canvasSize / 2; + this.circleElement = this.paper.circle(center, center, this.radius); + this.draw(); + } + + private draw() { + if (this.enabled) { + this.circleElement.attr('fill', this.colorOn); + this.circleElement.attr('stroke', this.colorOn); + this.circleElement.attr('opacity', '1'); + if (this.circleElement.theGlow) { + this.circleElement.theGlow.remove(); + } + this.circleElement.theGlow = this.circleElement.glow( + { + color: this.glowColor, + width: this.radius + this.glowSize, + opacity: 0.8, + fill: true + }); + } else { + if (this.circleElement.theGlow) { + this.circleElement.theGlow.remove(); + } + this.circleElement.attr('fill', this.colorOff); + this.circleElement.attr('stroke', this.colorOff); + this.circleElement.attr('opacity', this.offOpacity); + } + } + +} diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index b42298df15..84ee7e7631 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -121,6 +121,7 @@ import { KeyValMapComponent } from './components/kv-map.component'; import { TbCheatSheetComponent } from '@shared/components/cheatsheet.component'; import { TbHotkeysDirective } from '@shared/components/hotkeys.directive'; import { NavTreeComponent } from '@shared/components/nav-tree.component'; +import { LedLightComponent } from '@shared/components/led-light.component'; @NgModule({ providers: [ @@ -200,6 +201,7 @@ import { NavTreeComponent } from '@shared/components/nav-tree.component'; MessageTypeAutocompleteComponent, KeyValMapComponent, NavTreeComponent, + LedLightComponent, NospacePipe, MillisecondsToTimeStringPipe, EnumToArrayPipe, @@ -349,6 +351,7 @@ import { NavTreeComponent } from '@shared/components/nav-tree.component'; MessageTypeAutocompleteComponent, KeyValMapComponent, NavTreeComponent, + LedLightComponent, NospacePipe, MillisecondsToTimeStringPipe, EnumToArrayPipe, diff --git a/ui-ngx/src/tsconfig.app.json b/ui-ngx/src/tsconfig.app.json index 1332922f5b..9e3288087b 100644 --- a/ui-ngx/src/tsconfig.app.json +++ b/ui-ngx/src/tsconfig.app.json @@ -2,7 +2,7 @@ "extends": "../tsconfig.json", "compilerOptions": { "outDir": "../out-tsc/app", - "types": ["node", "jquery", "flot", "tooltipster", "tinycolor2", "js-beautify", "react", "react-dom", "jstree"] + "types": ["node", "jquery", "flot", "tooltipster", "tinycolor2", "js-beautify", "react", "react-dom", "jstree", "raphael"] }, "exclude": [ "test.ts", From d99c2a9f35bb88528311f7858a77a882a17a5fff Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 3 Feb 2020 17:51:22 +0200 Subject: [PATCH 084/133] Minor changes --- ui-ngx/src/app/shared/components/nav-tree.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/shared/components/nav-tree.component.ts b/ui-ngx/src/app/shared/components/nav-tree.component.ts index 30f124731c..5b6e92d0e8 100644 --- a/ui-ngx/src/app/shared/components/nav-tree.component.ts +++ b/ui-ngx/src/app/shared/components/nav-tree.component.ts @@ -150,7 +150,7 @@ export class NavTreeComponent implements OnInit { this.treeElement.on('changed.jstree', (e, data) => { const node: NavTreeNode = data.instance.get_selected(true)[0]; if (this.onNodeSelected) { - this.onNodeSelected(node, e); + this.onNodeSelected(node, e as Event); } }); From ac8a2cea40e74a58b55896911d524af798973852 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 3 Feb 2020 17:52:43 +0200 Subject: [PATCH 085/133] Minor changes --- ui-ngx/src/app/shared/components/nav-tree.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/shared/components/nav-tree.component.ts b/ui-ngx/src/app/shared/components/nav-tree.component.ts index 5b6e92d0e8..81d3a303fd 100644 --- a/ui-ngx/src/app/shared/components/nav-tree.component.ts +++ b/ui-ngx/src/app/shared/components/nav-tree.component.ts @@ -147,14 +147,14 @@ export class NavTreeComponent implements OnInit { this.treeElement = $('.tb-nav-tree-container', this.elementRef.nativeElement).jstree(config); - this.treeElement.on('changed.jstree', (e, data) => { + this.treeElement.on('changed.jstree', (e: any, data) => { const node: NavTreeNode = data.instance.get_selected(true)[0]; if (this.onNodeSelected) { this.onNodeSelected(node, e as Event); } }); - this.treeElement.on('model.jstree', (e, data) => { + this.treeElement.on('model.jstree', (e: any, data) => { if (this.onNodesInserted) { this.onNodesInserted(data.nodes, data.parent); } From 8b4ded7d803d4104501cd930a0f87bc98ed746aa Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 3 Feb 2020 19:38:28 +0200 Subject: [PATCH 086/133] Improve layout handling --- .../dashboard/dashboard.component.ts | 8 +++-- .../components/widget/widget.component.ts | 7 +++- .../home/models/dashboard-component.models.ts | 34 ++++++++++++++----- .../home/models/widget-component.models.ts | 9 +++++ 4 files changed, 46 insertions(+), 12 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts index 9e38e75ce6..9e16757b66 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts @@ -19,7 +19,7 @@ import { Component, DoCheck, Input, - IterableDiffers, + IterableDiffers, KeyValueDiffers, NgZone, OnChanges, OnDestroy, @@ -42,7 +42,7 @@ import { IDashboardComponent } from '../../models/dashboard-component.models'; import { ReplaySubject, Subject, Subscription } from 'rxjs'; -import { WidgetLayouts } from '@shared/models/dashboard.models'; +import { WidgetLayout, WidgetLayouts } from '@shared/models/dashboard.models'; import { DialogService } from '@core/services/dialog.service'; import { animatedScroll, deepClone, isDefined } from '@app/core/utils'; import { BreakpointObserver } from '@angular/cdk/layout'; @@ -154,7 +154,8 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo dashboardWidgets = new DashboardWidgets(this, this.differs.find([]).create((index, item) => { return item; - }) + }), + this.kvDiffers.find([]).create() ); breakpointObserverSubscription: Subscription; @@ -168,6 +169,7 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo private dialogService: DialogService, private breakpointObserver: BreakpointObserver, private differs: IterableDiffers, + private kvDiffers: KeyValueDiffers, private ngZone: NgZone) { super(store); this.authUser = getCurrentAuthUser(store); 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 8104727f5f..80ddf9ceb0 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 @@ -529,7 +529,9 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI this.cafs.reinit = null; } this.cafs.reinit = this.raf.raf(() => { - this.reInitImpl(); + this.ngZone.run(() => { + this.reInitImpl(); + }); }); } @@ -541,6 +543,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI if (this.destroyed) { this.onDestroy(); } else { + this.widgetContext.reset(); this.subscriptionInited = true; this.configureDynamicWidgetComponent(); this.cd.detectChanges(); @@ -551,12 +554,14 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI if (this.destroyed) { this.onDestroy(); } else { + this.widgetContext.reset(); this.subscriptionInited = true; this.onInit(); } } ); } else { + this.widgetContext.reset(); this.subscriptionInited = true; this.configureDynamicWidgetComponent(); this.cd.detectChanges(); 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 6a44e5bafb..d3395043e2 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 @@ -101,15 +101,16 @@ export class DashboardWidgets implements Iterable { } constructor(private dashboard: IDashboardComponent, - private widgetsDiffer: IterableDiffer) { + private widgetsDiffer: IterableDiffer, + private widgetLayoutsDiffer: KeyValueDiffer) { } doCheck() { const widgetChange = this.widgetsDiffer.diff(this.widgets); - if (widgetChange !== null) { - - const updateRecords: Array = []; + const widgetLayoutChange = this.widgetLayoutsDiffer.diff(this.widgetLayouts); + const updateRecords: Array = []; + if (widgetChange !== null) { widgetChange.forEachAddedItem((added) => { updateRecords.push({ widget: added.item, @@ -130,6 +131,25 @@ export class DashboardWidgets implements Iterable { updateRecords.push(operation); } }); + } + if (widgetLayoutChange !== null) { + widgetLayoutChange.forEachChangedItem((changed) => { + let operation = updateRecords.find((record) => record.widgetId === changed.key); + if (!operation) { + let index = this.dashboardWidgets.findIndex((dashboardWidget) => dashboardWidget.widgetId === changed.key); + if (index > -1) { + const widget = this.dashboardWidgets[index]; + updateRecords.push({ + widget: widget.widget, + widgetId: changed.key, + widgetLayout: changed.currentValue, + operation: 'update' + }); + } + } + }); + } + if (updateRecords.length) { updateRecords.forEach((record) => { switch (record.operation) { case 'add': @@ -147,7 +167,7 @@ export class DashboardWidgets implements Iterable { index = this.dashboardWidgets.findIndex((dashboardWidget) => dashboardWidget.widgetId === record.widgetId); if (index > -1) { const prevDashboardWidget = this.dashboardWidgets[index]; - if (!deepEqual(prevDashboardWidget.widget, record.widget)) { + if (!deepEqual(prevDashboardWidget.widget, record.widget) || !deepEqual(prevDashboardWidget.widgetLayout, record.widgetLayout)) { this.dashboardWidgets[index] = new DashboardWidget(this.dashboard, record.widget, record.widgetLayout); this.dashboardWidgets[index].highlighted = prevDashboardWidget.highlighted; this.dashboardWidgets[index].selected = prevDashboardWidget.selected; @@ -159,9 +179,7 @@ export class DashboardWidgets implements Iterable { break; } }); - if (updateRecords.length) { - this.updateRowsAndSort(); - } + this.updateRowsAndSort(); } } 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 b71f5198b4..818f94391e 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 @@ -130,6 +130,15 @@ export class WidgetContext { } } + reset() { + this.destroyed = false; + this.hideTitlePanel = false; + this.widgetTitleTemplate = undefined; + this.widgetTitle = undefined; + this.customHeaderActions = undefined; + this.widgetActions = undefined; + } + inited = false; destroyed = false; From ccfebce870ccde187f5680bced413aba72a46658 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 4 Feb 2020 15:14:17 +0200 Subject: [PATCH 087/133] Fix TS warnings. Update RPC shell widgets. --- .../widget_bundles/control_widgets.json | 4 +- ui-ngx/e2e/protractor.conf.js | 20 +- ui-ngx/extra-webpack.config.js | 20 +- ui-ngx/package-lock.json | 3754 +++++++++-------- ui-ngx/package.json | 52 +- ui-ngx/proxy.conf.js | 24 +- ui-ngx/src/app/app.component.ts | 4 +- ui-ngx/src/app/app.module.ts | 8 +- ui-ngx/src/app/core/api/data-aggregator.ts | 4 +- ui-ngx/src/app/core/auth/auth.actions.ts | 2 +- ui-ngx/src/app/core/auth/auth.models.ts | 2 +- ui-ngx/src/app/core/auth/auth.service.ts | 12 +- ui-ngx/src/app/core/css/css.js | 208 +- ui-ngx/src/app/core/http/admin.service.ts | 4 +- ui-ngx/src/app/core/http/asset.service.ts | 2 +- ui-ngx/src/app/core/http/attribute.service.ts | 4 +- ui-ngx/src/app/core/http/audit-log.service.ts | 6 +- ui-ngx/src/app/core/http/customer.service.ts | 4 +- ui-ngx/src/app/core/http/dashboard.service.ts | 16 +- ui-ngx/src/app/core/http/device.service.ts | 13 +- .../app/core/http/entity-relation.service.ts | 4 +- .../src/app/core/http/entity-view.service.ts | 15 +- ui-ngx/src/app/core/http/entity.service.ts | 8 +- ui-ngx/src/app/core/http/event.service.ts | 4 +- .../src/app/core/http/rule-chain.service.ts | 12 +- ui-ngx/src/app/core/http/tenant.service.ts | 4 +- ui-ngx/src/app/core/http/user.service.ts | 9 +- ui-ngx/src/app/core/http/widget.service.ts | 10 +- .../interceptors/global-http-interceptor.ts | 11 +- .../app/core/services/item-buffer.service.ts | 13 +- ui-ngx/src/app/core/services/raf.service.ts | 4 +- ui-ngx/src/app/core/utils.ts | 2 +- .../alarm/alarm-table-header.component.html | 2 +- .../alarm/alarm-table-header.component.ts | 1 + .../add-attribute-dialog.component.html | 2 +- .../attribute/attribute-table.component.html | 4 +- .../attribute/attribute-table.component.ts | 3 +- .../entity/add-entity-dialog.component.ts | 3 +- .../entity/contact-based.component.ts | 4 +- .../entity/entities-table.component.html | 2 +- .../entity/entity-filter.component.html | 4 +- .../entity/entity-filter.component.ts | 1 + .../components/event/event-table-config.ts | 2 +- .../import-export/import-dialog.component.ts | 4 +- .../import-export/import-export.service.ts | 2 +- .../relation/relation-table.component.html | 4 +- .../manage-widget-actions.component.html | 2 +- .../widget-action-dialog.component.html | 2 +- .../custom-dialog-container.component.ts | 6 +- .../widget/legend-config-panel.component.html | 4 +- .../alarm-status-filter-panel.component.html | 2 +- .../alarm-status-filter-panel.component.ts | 1 + .../lib/alarms-table-widget.component.html | 2 +- .../lib/alarms-table-widget.component.ts | 24 +- .../entities-hierarchy-widget.component.ts | 12 +- .../lib/entities-hierarchy-widget.models.ts | 10 +- .../lib/entities-table-widget.component.html | 2 +- .../lib/entities-table-widget.component.ts | 27 +- .../widget/lib/flot-widget.models.ts | 3 +- .../home/components/widget/lib/flot-widget.ts | 22 +- .../widget/lib/table-widget.models.ts | 180 +- .../timeseries-table-widget.component.html | 2 +- .../lib/timeseries-table-widget.component.ts | 50 +- .../widget/widget-config.component.ts | 4 +- .../src/app/modules/home/home.component.html | 6 +- ui-ngx/src/app/modules/home/home.component.ts | 2 +- .../home/models/dashboard-component.models.ts | 10 +- .../home/models/widget-component.models.ts | 56 +- .../home/pages/admin/admin-routing.module.ts | 4 +- ...age-dashboard-states-dialog.component.html | 2 +- .../states/state-controller.component.ts | 8 +- .../states/states-component.directive.ts | 1 + .../device-credentials-dialog.component.html | 2 +- .../rulechain/rulechain-page.component.ts | 30 +- .../rulechain/rulechain-routing.module.ts | 3 +- .../pages/rulechain/rulenode.component.ts | 1 + .../pages/user/add-user-dialog.component.html | 2 +- .../pages/user/add-user-dialog.component.ts | 1 + .../select-widget-type-dialog.component.html | 10 +- .../pages/widget/widget-editor.component.html | 2 +- .../pages/widget/widget-library.component.ts | 3 +- .../app/modules/login/login-routing.module.ts | 4 +- .../pages/login/create-password.component.ts | 12 +- .../login/pages/login/login.component.ts | 7 +- .../login/reset-password-request.component.ts | 7 +- .../pages/login/reset-password.component.ts | 10 +- .../shared/components/breadcrumb.component.ts | 2 +- .../shared/components/cheatsheet.component.ts | 6 +- .../entity/entity-list-select.component.ts | 2 +- .../entity/entity-select.component.ts | 2 +- .../entity/entity-type-select.component.ts | 2 +- .../components/fab-toolbar.component.ts | 3 + .../app/shared/components/help.component.ts | 1 + .../shared/components/hotkeys.directive.ts | 33 +- .../shared/components/js-func.component.ts | 2 +- .../json-form/react/json-form-schema-form.tsx | 2 +- .../mat-chip-draggable.directive.ts | 2 +- .../shared/components/nav-tree.component.ts | 6 +- .../app/shared/components/page.component.ts | 4 +- .../time/timewindow-panel.component.html | 2 +- .../components/value-input.component.html | 4 +- ui-ngx/src/app/shared/models/alarm.models.ts | 4 +- .../src/app/shared/models/page/page-link.ts | 9 + .../models/telemetry/telemetry.models.ts | 8 + ui-ngx/src/karma.conf.js | 30 +- ui-ngx/src/main.ts | 4 +- ui-ngx/src/tslint.json | 2 +- 107 files changed, 2554 insertions(+), 2411 deletions(-) diff --git a/application/src/main/data/json/system/widget_bundles/control_widgets.json b/application/src/main/data/json/system/widget_bundles/control_widgets.json index 5dfe4d0f7f..2890ede377 100644 --- a/application/src/main/data/json/system/widget_bundles/control_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/control_widgets.json @@ -15,7 +15,7 @@ "resources": [], "templateHtml": "
", "templateCss": ".cmd .cursor.blink {\n -webkit-animation-name: terminal-underline;\n -moz-animation-name: terminal-underline;\n -ms-animation-name: terminal-underline;\n animation-name: terminal-underline;\n}\n.terminal .inverted, .cmd .inverted {\n border-bottom-color: #aaa;\n}\n", - "controllerScript": "var requestTimeout = 500;\n\nself.onInit = function() {\n var subscription = self.ctx.defaultSubscription;\n var rpcEnabled = subscription.rpcEnabled;\n var deviceName = 'Simulated';\n var prompt;\n if (subscription.targetDeviceName && subscription.targetDeviceName.length) {\n deviceName = subscription.targetDeviceName;\n }\n if (self.ctx.settings.requestTimeout) {\n requestTimeout = self.ctx.settings.requestTimeout;\n }\n var greetings = 'Welcome to ThingsBoard RPC debug terminal.\\n\\n';\n if (!rpcEnabled) {\n greetings += 'Target device is not set!\\n\\n';\n prompt = '';\n } else {\n greetings += 'Current target device for RPC commands: [[b;#fff;]' + deviceName + ']\\n\\n';\n greetings += 'Please type [[b;#fff;]\\'help\\'] to see usage.\\n';\n prompt = '[[b;#8bc34a;]' + deviceName +']> ';\n }\n \n var terminal = $('#device-terminal', self.ctx.$container).terminal(\n function(command) {\n if (command !== '') {\n try {\n var localCommand = angular.copy(command).trim();\n if (localCommand == 'help') {\n printUsage(this);\n } else {\n var cmdObj = $.terminal.parse_command(localCommand);\n if (cmdObj.args.length > 1) {\n this.error(\"Wrong number of arguments!\");\n this.echo(' ');\n } else {\n var params;\n if (cmdObj.args.length && cmdObj.args[0]) {\n try {\n params = JSON.parse(cmdObj.args[0]);\n } catch (e) {\n params = cmdObj.args[0];\n }\n }\n performRpc(this, cmdObj.name, params);\n }\n }\n } catch(e) {\n this.error(new String(e));\n }\n } else {\n this.echo('');\n }\n }, {\n greetings: greetings,\n prompt: prompt\n });\n \n if (!rpcEnabled) {\n terminal.error('No RPC target detected!').pause();\n }\n}\n\n\nfunction printUsage(terminal) {\n var commandsListText = '\\n[[b;#fff;]Usage:]\\n';\n commandsListText += ' [params body]\\n\\n';\n commandsListText += '[[b;#fff;]Example 1:]\\n'; \n commandsListText += ' myRemoteMethod1 myText\\n\\n'; \n commandsListText += '[[b;#fff;]Example 2:]\\n'; \n commandsListText += ' myOtherRemoteMethod \"{\\\\\"key1\\\\\": 2, \\\\\"key2\\\\\": \\\\\"myVal\\\\\"}\"\\n'; \n terminal.echo(new String(commandsListText));\n}\n\nfunction performRpc(terminal, method, params) {\n terminal.pause();\n self.ctx.controlApi.sendTwoWayCommand(method, params, requestTimeout).then(\n function success(responseBody) {\n terminal.echo(JSON.stringify(responseBody));\n terminal.echo(' ');\n terminal.resume();\n },\n function fail() {\n var errorText = self.ctx.defaultSubscription.rpcErrorText;\n terminal.error(errorText);\n terminal.echo(' ');\n terminal.resume();\n }\n );\n}\n\n \nself.onDestroy = function() {\n}\n", + "controllerScript": "var requestTimeout = 500;\n\nself.onInit = function() {\n var subscription = self.ctx.defaultSubscription;\n var rpcEnabled = subscription.rpcEnabled;\n var deviceName = 'Simulated';\n var prompt;\n if (subscription.targetDeviceName && subscription.targetDeviceName.length) {\n deviceName = subscription.targetDeviceName;\n }\n if (self.ctx.settings.requestTimeout) {\n requestTimeout = self.ctx.settings.requestTimeout;\n }\n var greetings = 'Welcome to ThingsBoard RPC debug terminal.\\n\\n';\n if (!rpcEnabled) {\n greetings += 'Target device is not set!\\n\\n';\n prompt = '';\n } else {\n greetings += 'Current target device for RPC commands: [[b;#fff;]' + deviceName + ']\\n\\n';\n greetings += 'Please type [[b;#fff;]\\'help\\'] to see usage.\\n';\n prompt = '[[b;#8bc34a;]' + deviceName +']> ';\n }\n \n var terminal = $('#device-terminal', self.ctx.$container).terminal(\n function(command) {\n if (command !== '') {\n try {\n var localCommand = command.trim();\n if (localCommand === 'help') {\n printUsage(this);\n } else {\n var cmdObj = $.terminal.parse_command(localCommand);\n if (cmdObj.args.length > 1) {\n this.error(\"Wrong number of arguments!\");\n this.echo(' ');\n } else {\n var params;\n if (cmdObj.args.length && cmdObj.args[0]) {\n try {\n params = JSON.parse(cmdObj.args[0]);\n } catch (e) {\n params = cmdObj.args[0];\n }\n }\n performRpc(this, cmdObj.name, params);\n }\n }\n } catch(e) {\n this.error(new String(e));\n }\n } else {\n this.echo('');\n }\n }, {\n greetings: greetings,\n prompt: prompt,\n enabled: rpcEnabled\n });\n \n \n \n if (!rpcEnabled) {\n terminal.error('No RPC target detected!').pause();\n }\n}\n\n\nfunction printUsage(terminal) {\n var commandsListText = '\\n[[b;#fff;]Usage:]\\n';\n commandsListText += ' [params body]]\\n\\n';\n commandsListText += '[[b;#fff;]Example 1:]\\n'; \n commandsListText += ' myRemoteMethod1 myText\\n\\n'; \n commandsListText += '[[b;#fff;]Example 2:]\\n'; \n commandsListText += ' myOtherRemoteMethod \"{\\\\\"key1\\\\\": 2, \\\\\"key2\\\\\": \\\\\"myVal\\\\\"}\"\\n'; \n terminal.echo(new String(commandsListText));\n}\n\nfunction performRpc(terminal, method, params) {\n terminal.pause();\n self.ctx.controlApi.sendTwoWayCommand(method, params, requestTimeout).subscribe(\n function success(responseBody) {\n terminal.echo(JSON.stringify(responseBody));\n terminal.echo(' ');\n terminal.resume();\n },\n function fail() {\n var errorText = self.ctx.defaultSubscription.rpcErrorText;\n terminal.error(errorText);\n terminal.echo(' ');\n terminal.resume();\n }\n );\n}\n\n \nself.onDestroy = function() {\n}\n", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"requestTimeout\": {\n \"title\": \"RPC request timeout (ms)\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"requestTimeout\"\n ]\n}", "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":true,\"backgroundColor\":\"#010101\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"parseGpioStatusFunction\":\"return body[pin] === true;\",\"gpioStatusChangeRequest\":{\"method\":\"setGpioStatus\",\"paramsBody\":\"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"},\"requestTimeout\":500,\"switchPanelBackgroundColor\":\"#b71c1c\",\"gpioStatusRequest\":{\"method\":\"getGpioStatus\",\"paramsBody\":\"{}\"},\"gpioList\":[{\"pin\":1,\"label\":\"GPIO 1\",\"row\":0,\"col\":0,\"_uniqueKey\":0},{\"pin\":2,\"label\":\"GPIO 2\",\"row\":0,\"col\":1,\"_uniqueKey\":1},{\"pin\":3,\"label\":\"GPIO 3\",\"row\":1,\"col\":0,\"_uniqueKey\":2}]},\"title\":\"RPC debug terminal\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" @@ -31,7 +31,7 @@ "resources": [], "templateHtml": "
", "templateCss": ".cmd .cursor.blink {\n -webkit-animation-name: terminal-underline;\n -moz-animation-name: terminal-underline;\n -ms-animation-name: terminal-underline;\n animation-name: terminal-underline;\n}\n.terminal .inverted, .cmd .inverted {\n border-bottom-color: #aaa;\n}\n", - "controllerScript": "var requestTimeout = 500;\nconst commandStatusPollingInterval = 200;\n\nconst welcome = 'Welcome to ThingsBoard RPC remote shell.\\n';\n\nvar terminal, rpcEnabled, simulated, deviceName, cwd;\nvar commandExecuting = false;\n\nself.onInit = function() {\n var subscription = self.ctx.defaultSubscription;\n rpcEnabled = subscription.rpcEnabled;\n if (subscription.targetDeviceName && subscription.targetDeviceName.length) {\n deviceName = subscription.targetDeviceName;\n } else {\n deviceName = 'Simulated';\n simulated = true;\n }\n if (self.ctx.settings.requestTimeout) {\n requestTimeout = self.ctx.settings.requestTimeout;\n }\n \n terminal = $('#device-terminal', self.ctx.$container).terminal(\n function (command) {\n if (command && command.trim().length) {\n try {\n if (simulated) {\n this.echo(command);\n } else {\n sendCommand(this, command);\n }\n } catch(e) {\n this.error(new String(e));\n }\n } else {\n this.echo('');\n }\n }, {\n greetings: false,\n prompt: rpcEnabled ? currentPrompt : '',\n name: 'shell',\n pauseEvents: false,\n keydown: (e, term) => {\n if ((e.which == 67 || e.which == 68) && e.ctrlKey) { // CTRL+C || CTRL+D\n if (commandExecuting) {\n terminateCommand(term);\n return false;\n }\n }\n },\n onInit: initTerm\n }\n );\n}\n\nfunction initTerm(terminal) {\n terminal.echo(welcome);\n if (!rpcEnabled) {\n terminal.error('Target device is not set!\\n');\n } else {\n terminal.echo(`Current target device for RPC terminal: [[b;#fff;]${deviceName}]\\n`);\n if (!simulated) {\n terminal.pause();\n getTermInfo(terminal,\n (remoteTermInfo) => {\n if (remoteTermInfo) {\n terminal.echo(`Remote platform info:`);\n terminal.echo(`OS: [[b;#fff;]${remoteTermInfo.platform}]`);\n if (remoteTermInfo.release) {\n terminal.echo(`OS release: [[b;#fff;]${remoteTermInfo.release}]`);\n }\n terminal.echo('\\r');\n } else {\n terminal.echo('[[;#f00;]Unable to get remote platform info.\\nDevice is not responding.]\\n');\n }\n terminal.resume();\n });\n }\n }\n}\n\nfunction currentPrompt(callback) {\n if (cwd) {\n callback('[[b;#2196f3;]' + deviceName +']: [[b;#8bc34a;]' + cwd +']> ');\n } else {\n callback('[[b;#8bc34a;]' + deviceName +']> ');\n }\n}\n\nfunction getTermInfo(terminal, callback) {\n self.ctx.controlApi.sendTwoWayCommand('getTermInfo', null, requestTimeout).then(\n (termInfo) => {\n cwd = termInfo.cwd;\n if (callback) {\n callback(termInfo);\n } \n },\n () => {\n if (callback) {\n callback(null);\n }\n }\n );\n}\n\nfunction sendCommand(terminal, command) {\n terminal.pause();\n var sendCommandRequest = {\n command: command,\n cwd: cwd\n };\n self.ctx.controlApi.sendTwoWayCommand('sendCommand', sendCommandRequest, requestTimeout).then(\n (responseBody) => {\n if (responseBody && responseBody.ok) {\n commandExecuting = true;\n setTimeout( pollCommandStatus.bind(null,terminal), commandStatusPollingInterval );\n } else {\n var error = responseBody ? responseBody.error : 'Unhandled error.';\n terminal.error(error);\n terminal.resume();\n }\n },\n () => {\n onRpcError(terminal);\n }\n );\n}\n\nfunction terminateCommand(terminal) {\n self.ctx.controlApi.sendTwoWayCommand('terminateCommand', null, requestTimeout).then(\n (responseBody) => {\n if (!responseBody.ok) {\n commandExecuting = false;\n terminal.error(responseBody.error);\n terminal.resume();\n } \n },\n () => {\n onRpcError(terminal);\n }\n ); \n}\n\nfunction onRpcError(terminal) {\n var errorText = self.ctx.defaultSubscription.rpcErrorText;\n terminal.error(errorText);\n terminal.resume();\n}\n\nfunction pollCommandStatus(terminal) {\n self.ctx.controlApi.sendTwoWayCommand('getCommandStatus', null, requestTimeout).then(\n (commandStatusResponse) => {\n commandStatusResponse.data.forEach((dataElement) => {\n if (dataElement.stdout) {\n terminal.echo(dataElement.stdout);\n }\n if (dataElement.stderr) {\n terminal.error(dataElement.stderr);\n }\n }); \n if (commandStatusResponse.done) {\n commandExecuting = false;\n cwd = commandStatusResponse.cwd;\n terminal.resume();\n } else {\n var interval = commandStatusPollingInterval;\n if (!commandStatusResponse.data.length) {\n interval *=5;\n }\n setTimeout( pollCommandStatus.bind(null,terminal), interval );\n }\n },\n () => {\n commandExecuting = false;\n onRpcError(terminal);\n }\n );\n}\n\nself.onResize = function () {\n if (terminal) {\n terminal.resize(self.ctx.width, self.ctx.height);\n }\n}\n\nself.onDestroy = function() {\n}\n", + "controllerScript": "var requestTimeout = 500;\nconst commandStatusPollingInterval = 200;\n\nconst welcome = 'Welcome to ThingsBoard RPC remote shell.\\n';\n\nvar terminal, rpcEnabled, simulated, deviceName, cwd;\nvar commandExecuting = false;\n\nself.onInit = function() {\n var subscription = self.ctx.defaultSubscription;\n rpcEnabled = subscription.rpcEnabled;\n if (subscription.targetDeviceName && subscription.targetDeviceName.length) {\n deviceName = subscription.targetDeviceName;\n } else {\n deviceName = 'Simulated';\n simulated = true;\n }\n if (self.ctx.settings.requestTimeout) {\n requestTimeout = self.ctx.settings.requestTimeout;\n }\n \n terminal = $('#device-terminal', self.ctx.$container).terminal(\n function (command) {\n if (command && command.trim().length) {\n try {\n if (simulated) {\n this.echo(command);\n } else {\n sendCommand(this, command);\n }\n } catch(e) {\n this.error(new String(e));\n }\n } else {\n this.echo('');\n }\n }, {\n greetings: false,\n enabled: rpcEnabled,\n prompt: rpcEnabled ? currentPrompt : '',\n name: 'shell',\n pauseEvents: false,\n keydown: (e, term) => {\n if ((e.which == 67 || e.which == 68) && e.ctrlKey) { // CTRL+C || CTRL+D\n if (commandExecuting) {\n terminateCommand(term);\n return false;\n }\n }\n },\n onInit: initTerm\n }\n );\n \n}\n\nfunction initTerm(terminal) {\n terminal.echo(welcome);\n if (!rpcEnabled) {\n terminal.error('Target device is not set!\\n');\n } else {\n terminal.echo(`Current target device for RPC terminal: [[b;#fff;]${deviceName}]\\n`);\n if (!simulated) {\n terminal.pause();\n getTermInfo(terminal,\n (remoteTermInfo) => {\n if (remoteTermInfo) {\n terminal.echo(`Remote platform info:`);\n terminal.echo(`OS: [[b;#fff;]${remoteTermInfo.platform}]`);\n if (remoteTermInfo.release) {\n terminal.echo(`OS release: [[b;#fff;]${remoteTermInfo.release}]`);\n }\n terminal.echo('\\r');\n } else {\n terminal.echo('[[;#f00;]Unable to get remote platform info.\\nDevice is not responding.]\\n');\n }\n terminal.resume();\n });\n }\n }\n}\n\nfunction currentPrompt(callback) {\n if (cwd) {\n callback('[[b;#2196f3;]' + deviceName +']: [[b;#8bc34a;]' + cwd +']> ');\n } else {\n callback('[[b;#8bc34a;]' + deviceName +']> ');\n }\n}\n\nfunction getTermInfo(terminal, callback) {\n self.ctx.controlApi.sendTwoWayCommand('getTermInfo', null, requestTimeout).subscribe(\n (termInfo) => {\n cwd = termInfo.cwd;\n if (callback) {\n callback(termInfo);\n } \n },\n () => {\n if (callback) {\n callback(null);\n }\n }\n );\n}\n\nfunction sendCommand(terminal, command) {\n terminal.pause();\n var sendCommandRequest = {\n command: command,\n cwd: cwd\n };\n self.ctx.controlApi.sendTwoWayCommand('sendCommand', sendCommandRequest, requestTimeout).subscribe(\n (responseBody) => {\n if (responseBody && responseBody.ok) {\n commandExecuting = true;\n setTimeout( pollCommandStatus.bind(null,terminal), commandStatusPollingInterval );\n } else {\n var error = responseBody ? responseBody.error : 'Unhandled error.';\n terminal.error(error);\n terminal.resume();\n }\n },\n () => {\n onRpcError(terminal);\n }\n );\n}\n\nfunction terminateCommand(terminal) {\n self.ctx.controlApi.sendTwoWayCommand('terminateCommand', null, requestTimeout).subscribe(\n (responseBody) => {\n if (!responseBody.ok) {\n commandExecuting = false;\n terminal.error(responseBody.error);\n terminal.resume();\n } \n },\n () => {\n onRpcError(terminal);\n }\n ); \n}\n\nfunction onRpcError(terminal) {\n var errorText = self.ctx.defaultSubscription.rpcErrorText;\n terminal.error(errorText);\n terminal.resume();\n}\n\nfunction pollCommandStatus(terminal) {\n self.ctx.controlApi.sendTwoWayCommand('getCommandStatus', null, requestTimeout).subscribe(\n (commandStatusResponse) => {\n commandStatusResponse.data.forEach((dataElement) => {\n if (dataElement.stdout) {\n terminal.echo(dataElement.stdout);\n }\n if (dataElement.stderr) {\n terminal.error(dataElement.stderr);\n }\n }); \n if (commandStatusResponse.done) {\n commandExecuting = false;\n cwd = commandStatusResponse.cwd;\n terminal.resume();\n } else {\n var interval = commandStatusPollingInterval;\n if (!commandStatusResponse.data.length) {\n interval *=5;\n }\n setTimeout( pollCommandStatus.bind(null,terminal), interval );\n }\n },\n () => {\n commandExecuting = false;\n onRpcError(terminal);\n }\n );\n}\n\nself.onResize = function () {\n if (terminal) {\n terminal.resize(self.ctx.width, self.ctx.height);\n }\n}\n\nself.onDestroy = function() {\n}\n", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"requestTimeout\": {\n \"title\": \"RPC request timeout (ms)\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"requestTimeout\"\n ]\n}", "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":true,\"backgroundColor\":\"#010101\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"parseGpioStatusFunction\":\"return body[pin] === true;\",\"gpioStatusChangeRequest\":{\"method\":\"setGpioStatus\",\"paramsBody\":\"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"},\"requestTimeout\":500,\"switchPanelBackgroundColor\":\"#b71c1c\",\"gpioStatusRequest\":{\"method\":\"getGpioStatus\",\"paramsBody\":\"{}\"},\"gpioList\":[{\"pin\":1,\"label\":\"GPIO 1\",\"row\":0,\"col\":0,\"_uniqueKey\":0},{\"pin\":2,\"label\":\"GPIO 2\",\"row\":0,\"col\":1,\"_uniqueKey\":1},{\"pin\":3,\"label\":\"GPIO 3\",\"row\":1,\"col\":0,\"_uniqueKey\":2}]},\"title\":\"RPC remote shell\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" diff --git a/ui-ngx/e2e/protractor.conf.js b/ui-ngx/e2e/protractor.conf.js index ad71bd0447..afa6a38b62 100644 --- a/ui-ngx/e2e/protractor.conf.js +++ b/ui-ngx/e2e/protractor.conf.js @@ -16,28 +16,28 @@ // Protractor configuration file, see link for more information // https://github.com/angular/protractor/blob/master/lib/config.ts -const { SpecReporter } = require('jasmine-spec-reporter'); +const { SpecReporter } = require("jasmine-spec-reporter"); exports.config = { allScriptsTimeout: 11000, specs: [ - './src/**/*.e2e-spec.ts' + "./src/**/*.e2e-spec.ts", ], capabilities: { - 'browserName': 'chrome' + "browserName": "chrome", }, directConnect: true, - baseUrl: 'http://localhost:4200/', - framework: 'jasmine', + baseUrl: "http://localhost:4200/", + framework: "jasmine", jasmineNodeOpts: { showColors: true, defaultTimeoutInterval: 30000, - print: function() {} + print: function() {}, }, onPrepare() { - require('ts-node').register({ - project: require('path').join(__dirname, './tsconfig.e2e.json') + require("ts-node").register({ + project: require("path").join(__dirname, "./tsconfig.e2e.json"), }); jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); - } -}; \ No newline at end of file + }, +}; diff --git a/ui-ngx/extra-webpack.config.js b/ui-ngx/extra-webpack.config.js index 527470d162..b3a2fbe6e6 100644 --- a/ui-ngx/extra-webpack.config.js +++ b/ui-ngx/extra-webpack.config.js @@ -13,23 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -const CompressionPlugin = require('compression-webpack-plugin'); -const webpack = require('webpack'); -const dirTree = require('directory-tree'); +const CompressionPlugin = require("compression-webpack-plugin"); +const webpack = require("webpack"); +const dirTree = require("directory-tree"); var langs = []; -dirTree('./src/assets/locale/', {extensions:/\.json$/}, (item) => { +dirTree("./src/assets/locale/", {extensions: /\.json$/}, (item) => { /* It is expected what the name of a locale file has the following format: */ /* 'locale.constant-LANG_CODE[_REGION_CODE].json', e.g. locale.constant-es.json or locale.constant-zh_CN.json*/ - langs.push(item.name.slice(item.name.lastIndexOf('-') + 1, -5)); + langs.push(item.name.slice(item.name.lastIndexOf("-") + 1, -5)); }); module.exports = { plugins: [ new webpack.DefinePlugin({ - TB_VERSION: JSON.stringify(require('./package.json').version), - SUPPORTED_LANGS: JSON.stringify(langs) + TB_VERSION: JSON.stringify(require("./package.json").version), + SUPPORTED_LANGS: JSON.stringify(langs), }), new CompressionPlugin({ filename: "[path].gz[query]", @@ -37,7 +37,7 @@ module.exports = { test: /\.js$|\.css$|\.html$|\.svg?.+$|\.jpg$|\.ttf?.+$|\.woff?.+$|\.eot?.+$|\.json$/, threshold: 10240, minRatio: 0.8, - deleteOriginalAssets: false - }) - ] + deleteOriginalAssets: false, + }), + ], }; diff --git a/ui-ngx/package-lock.json b/ui-ngx/package-lock.json index ddbac6c5bb..6fa83746a7 100644 --- a/ui-ngx/package-lock.json +++ b/ui-ngx/package-lock.json @@ -16,12 +16,12 @@ } }, "@angular-devkit/architect": { - "version": "0.803.20", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.803.20.tgz", - "integrity": "sha512-NjyDJ61i9kh8J+qXt0E2j+P5Xsmi2mPasBzwcQyrZZGiho4zC0IFxcdxyzcsXFEupmilJKkjdt2g4QQRC5rUDQ==", + "version": "0.803.24", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.803.24.tgz", + "integrity": "sha512-ONY/Ppzyvtb0tqgwnzQvlGlexb5nTyy58ljgL1aQLTO3cNTkpl4IQYUCTdvn61gGA+FWPAXMCCbNqOPZMsOZCQ==", "dev": true, "requires": { - "@angular-devkit/core": "8.3.20", + "@angular-devkit/core": "8.3.24", "rxjs": "6.4.0" }, "dependencies": { @@ -37,31 +37,31 @@ } }, "@angular-devkit/build-angular": { - "version": "0.803.20", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.803.20.tgz", - "integrity": "sha512-JDZkZjOqPbOtCMsSKxQf9C+uSTZ7fQGlKGsCpJMzfa4iQ0WrmrhZvnRKQeEpMTTZTpuou/HQeQjyDV+Sx3yumw==", - "dev": true, - "requires": { - "@angular-devkit/architect": "0.803.20", - "@angular-devkit/build-optimizer": "0.803.20", - "@angular-devkit/build-webpack": "0.803.20", - "@angular-devkit/core": "8.3.20", - "@babel/core": "7.5.5", - "@babel/preset-env": "7.5.5", - "@ngtools/webpack": "8.3.20", + "version": "0.803.24", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.803.24.tgz", + "integrity": "sha512-uA789spMVghXehwAhl5zK0loY/wfxblUiL+y21T24LMCJc15a9QX5dwbXH72ioHz7qdzb/agXk7AK+foc2/0Hw==", + "dev": true, + "requires": { + "@angular-devkit/architect": "0.803.24", + "@angular-devkit/build-optimizer": "0.803.24", + "@angular-devkit/build-webpack": "0.803.24", + "@angular-devkit/core": "8.3.24", + "@babel/core": "7.8.3", + "@babel/preset-env": "7.8.3", + "@ngtools/webpack": "8.3.24", "ajv": "6.10.2", "autoprefixer": "9.6.1", - "browserslist": "4.6.6", + "browserslist": "4.8.3", "cacache": "12.0.2", - "caniuse-lite": "1.0.30000989", + "caniuse-lite": "1.0.30001019", "circular-dependency-plugin": "5.2.0", "clean-css": "4.2.1", - "copy-webpack-plugin": "5.0.4", - "core-js": "3.2.1", + "copy-webpack-plugin": "5.1.1", + "core-js": "3.6.4", + "coverage-istanbul-loader": "2.0.3", "file-loader": "4.2.0", "find-cache-dir": "3.0.0", "glob": "7.1.4", - "istanbul-instrumenter-loader": "3.0.1", "jest-worker": "24.9.0", "karma-source-map-support": "1.4.0", "less": "3.9.0", @@ -88,9 +88,9 @@ "style-loader": "1.0.0", "stylus": "0.54.5", "stylus-loader": "3.0.2", - "terser": "4.3.9", - "terser-webpack-plugin": "1.4.1", - "tree-kill": "1.2.1", + "terser": "4.6.3", + "terser-webpack-plugin": "1.4.3", + "tree-kill": "1.2.2", "webpack": "4.39.2", "webpack-dev-middleware": "3.7.2", "webpack-dev-server": "3.9.0", @@ -100,35 +100,6 @@ "worker-plugin": "3.2.0" }, "dependencies": { - "@angular-devkit/architect": { - "version": "0.803.20", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.803.20.tgz", - "integrity": "sha512-NjyDJ61i9kh8J+qXt0E2j+P5Xsmi2mPasBzwcQyrZZGiho4zC0IFxcdxyzcsXFEupmilJKkjdt2g4QQRC5rUDQ==", - "dev": true, - "requires": { - "@angular-devkit/core": "8.3.20", - "rxjs": "6.4.0" - } - }, - "@angular-devkit/core": { - "version": "8.3.20", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-8.3.20.tgz", - "integrity": "sha512-UCfW/BJBJnioJU34QennQhA4o+rLoCXWiSrI2LM7yw8/MEM9I8KbqRETP1My3HjHkQnvP+Qh3noedpcu3Nnt8A==", - "dev": true, - "requires": { - "ajv": "6.10.2", - "fast-json-stable-stringify": "2.0.0", - "magic-string": "0.25.3", - "rxjs": "6.4.0", - "source-map": "0.7.3" - } - }, - "core-js": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.2.1.tgz", - "integrity": "sha512-Qa5XSVefSVPRxy2XfUC13WbvqkxhkwB3ve+pgCQveNgYzbM/UxZeu1dcOX/xr4UmfUd+muuvsaxilQzCyUurMw==", - "dev": true - }, "glob": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", @@ -149,28 +120,6 @@ "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", "dev": true }, - "raw-loader": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-3.1.0.tgz", - "integrity": "sha512-lzUVMuJ06HF4rYveaz9Tv0WRlUMxJ0Y1hgSkkgg+50iEdaI0TthyEDe08KIHb0XsF6rn8WYTqPCaGTZg3sX+qA==", - "dev": true, - "requires": { - "loader-utils": "^1.1.0", - "schema-utils": "^2.0.1" - }, - "dependencies": { - "schema-utils": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.1.tgz", - "integrity": "sha512-0WXHDs1VDJyo+Zqs9TKLKyD/h7yDpHUhEFsM2CzkICFdoX1av+GBq/J2xRTFfsQO5kBfhZzANf2VcIm84jqDbg==", - "dev": true, - "requires": { - "ajv": "^6.10.2", - "ajv-keywords": "^3.4.1" - } - } - } - }, "rxjs": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", @@ -210,42 +159,6 @@ } } }, - "terser-webpack-plugin": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.1.tgz", - "integrity": "sha512-ZXmmfiwtCLfz8WKZyYUuuHf3dMYEjg8NrjHMb0JqHVHVOSkzp3cW2/XG1fP3tRhqEqSzMwzzRQGtAPbs4Cncxg==", - "dev": true, - "requires": { - "cacache": "^12.0.2", - "find-cache-dir": "^2.1.0", - "is-wsl": "^1.1.0", - "schema-utils": "^1.0.0", - "serialize-javascript": "^1.7.0", - "source-map": "^0.6.1", - "terser": "^4.1.2", - "webpack-sources": "^1.4.0", - "worker-farm": "^1.7.0" - }, - "dependencies": { - "find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, "webpack-merge": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.1.tgz", @@ -254,31 +167,13 @@ "requires": { "lodash": "^4.17.5" } - }, - "webpack-sources": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", - "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", - "dev": true, - "requires": { - "source-list-map": "^2.0.0", - "source-map": "~0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } } } }, "@angular-devkit/build-optimizer": { - "version": "0.803.20", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.803.20.tgz", - "integrity": "sha512-Vzxf1g1EuzaPBoScDYUhyxemi5chlgnpWmObNo5dzVAVzjxo5gJeDIGpiyDqHvr6LBkprqb6XHcZhMWqIcdIHg==", + "version": "0.803.24", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.803.24.tgz", + "integrity": "sha512-Z+d7M+WpBq7AWWRwbxzb1l9O9qkylxnDRKxXvq3Tzjn43g+2WyspE91dMyrg1ISc+p8jgX6xKSblRLvtWqpA8w==", "dev": true, "requires": { "loader-utils": "1.2.3", @@ -293,67 +188,20 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", "dev": true - }, - "typescript": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.3.tgz", - "integrity": "sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==", - "dev": true - }, - "webpack-sources": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", - "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", - "dev": true, - "requires": { - "source-list-map": "^2.0.0", - "source-map": "~0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } } } }, "@angular-devkit/build-webpack": { - "version": "0.803.20", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.803.20.tgz", - "integrity": "sha512-35af8kD3KG/cIv7AB09YNER5HIPlx55ipBxdVk8D+X3MuUcTmD6fFvqXcV0EPlD1vQephthfzSgtNpvuPv4xuA==", + "version": "0.803.24", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.803.24.tgz", + "integrity": "sha512-Bbd5KUGaE+edN0sp8K3azuqS/JTBmeWXIumdBEtqWyL6VsohX7fL+toJlSvRkj8lg02LVyozAFetXKnyaBkfCQ==", "dev": true, "requires": { - "@angular-devkit/architect": "0.803.20", - "@angular-devkit/core": "8.3.20", + "@angular-devkit/architect": "0.803.24", + "@angular-devkit/core": "8.3.24", "rxjs": "6.4.0" }, "dependencies": { - "@angular-devkit/architect": { - "version": "0.803.20", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.803.20.tgz", - "integrity": "sha512-NjyDJ61i9kh8J+qXt0E2j+P5Xsmi2mPasBzwcQyrZZGiho4zC0IFxcdxyzcsXFEupmilJKkjdt2g4QQRC5rUDQ==", - "dev": true, - "requires": { - "@angular-devkit/core": "8.3.20", - "rxjs": "6.4.0" - } - }, - "@angular-devkit/core": { - "version": "8.3.20", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-8.3.20.tgz", - "integrity": "sha512-UCfW/BJBJnioJU34QennQhA4o+rLoCXWiSrI2LM7yw8/MEM9I8KbqRETP1My3HjHkQnvP+Qh3noedpcu3Nnt8A==", - "dev": true, - "requires": { - "ajv": "6.10.2", - "fast-json-stable-stringify": "2.0.0", - "magic-string": "0.25.3", - "rxjs": "6.4.0", - "source-map": "0.7.3" - } - }, "rxjs": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", @@ -362,19 +210,13 @@ "requires": { "tslib": "^1.9.0" } - }, - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true } } }, "@angular-devkit/core": { - "version": "8.3.20", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-8.3.20.tgz", - "integrity": "sha512-UCfW/BJBJnioJU34QennQhA4o+rLoCXWiSrI2LM7yw8/MEM9I8KbqRETP1My3HjHkQnvP+Qh3noedpcu3Nnt8A==", + "version": "8.3.24", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-8.3.24.tgz", + "integrity": "sha512-xpT5yg+ddGDnifryBv2sRSYtq5F3iZIS+lN/K2AhhEa50B7Z+QaCVlEzoV/IfrGd6sLArdnKYwjLHFZ0LElUuw==", "dev": true, "requires": { "ajv": "6.10.2", @@ -402,12 +244,12 @@ } }, "@angular-devkit/schematics": { - "version": "8.3.20", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-8.3.20.tgz", - "integrity": "sha512-sDHZakh4e3A5WenR9zr1x6Va9GNRqQlRhqT3xcbkG88v2M0YqEt7dHB7YwnOhm7zSxiWQM8PdWEQHiQ4iu9NyQ==", + "version": "8.3.24", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-8.3.24.tgz", + "integrity": "sha512-HrwDCgw7i3GrNns0Ce5zStWkxBqlcLuDkMcLY6981jpvVzgXMIQ+YqDrJ2kD46xHh979ev7hhw1d6jwPXh85Xw==", "dev": true, "requires": { - "@angular-devkit/core": "8.3.20", + "@angular-devkit/core": "8.3.24", "rxjs": "6.4.0" }, "dependencies": { @@ -440,16 +282,16 @@ } }, "@angular/cli": { - "version": "8.3.20", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-8.3.20.tgz", - "integrity": "sha512-bCo8zVFZ6iPc1EnHmVCmKvIcV7YkvalBKGNU7LtVHq6qZBI+ZmFtuyL5obKvFg1vJcminjKcY/UcMr9uGcAQrQ==", + "version": "8.3.24", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-8.3.24.tgz", + "integrity": "sha512-cUB6H+BAISMdaFsstcyvC+17hOV3ET4MaVgcmgT2cL7A4vMBRBxJ0cW4r3D9c6e7m4wipyJzOUESYoIHu0cp4A==", "dev": true, "requires": { - "@angular-devkit/architect": "0.803.20", - "@angular-devkit/core": "8.3.20", - "@angular-devkit/schematics": "8.3.20", - "@schematics/angular": "8.3.20", - "@schematics/update": "0.803.20", + "@angular-devkit/architect": "0.803.24", + "@angular-devkit/core": "8.3.24", + "@angular-devkit/schematics": "8.3.24", + "@schematics/angular": "8.3.24", + "@schematics/update": "0.803.24", "@yarnpkg/lockfile": "1.1.0", "ansi-colors": "4.1.1", "debug": "^4.1.1", @@ -1375,21 +1217,52 @@ "@babel/highlight": "^7.0.0" } }, - "@babel/core": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.5.5.tgz", - "integrity": "sha512-i4qoSr2KTtce0DmkuuQBV4AuQgGPUcPXMr9L5MyYAtk06z068lQ10a4O009fe5OB/DfNV+h+qqT7ddNV8UnRjg==", + "@babel/compat-data": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.8.5.tgz", + "integrity": "sha512-jWYUqQX/ObOhG1UiEkbH5SANsE/8oKXiQWjj7p7xgj9Zmnt//aUvyz4dBkK0HNsS8/cbyC5NmmH87VekW+mXFg==", "dev": true, "requires": { - "@babel/code-frame": "^7.5.5", - "@babel/generator": "^7.5.5", - "@babel/helpers": "^7.5.5", - "@babel/parser": "^7.5.5", - "@babel/template": "^7.4.4", - "@babel/traverse": "^7.5.5", - "@babel/types": "^7.5.5", - "convert-source-map": "^1.1.0", + "browserslist": "^4.8.5", + "invariant": "^2.2.4", + "semver": "^5.5.0" + }, + "dependencies": { + "browserslist": { + "version": "4.8.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.6.tgz", + "integrity": "sha512-ZHao85gf0eZ0ESxLfCp73GG9O/VTytYDIkIiZDlURppLTI9wErSM/5yAKEq6rcUdxBLjMELmrYUJGg5sxGKMHg==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001023", + "electron-to-chromium": "^1.3.341", + "node-releases": "^1.1.47" + } + }, + "caniuse-lite": { + "version": "1.0.30001025", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001025.tgz", + "integrity": "sha512-SKyFdHYfXUZf5V85+PJgLYyit27q4wgvZuf8QTOk1osbypcROihMBlx9GRar2/pIcKH2r4OehdlBr9x6PXetAQ==", + "dev": true + } + } + }, + "@babel/core": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.8.3.tgz", + "integrity": "sha512-4XFkf8AwyrEG7Ziu3L2L0Cv+WyY47Tcsp70JFmpftbAA1K7YL/sgE9jh9HyNj08Y/U50ItUchpN0w6HxAoX1rA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.8.3", + "@babel/helpers": "^7.8.3", + "@babel/parser": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/traverse": "^7.8.3", + "@babel/types": "^7.8.3", + "convert-source-map": "^1.7.0", "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", "json5": "^2.1.0", "lodash": "^4.17.13", "resolve": "^1.3.2", @@ -1397,6 +1270,112 @@ "source-map": "^0.5.0" }, "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.8.3" + } + }, + "@babel/generator": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", + "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", + "dev": true, + "requires": { + "@babel/types": "^7.8.3", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", + "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", + "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", + "dev": true, + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", + "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", + "dev": true, + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/highlight": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", + "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", + "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", + "dev": true + }, + "@babel/template": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", + "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/parser": "^7.8.3", + "@babel/types": "^7.8.3" + } + }, + "@babel/traverse": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", + "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.8.4", + "@babel/helper-function-name": "^7.8.3", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/parser": "^7.8.4", + "@babel/types": "^7.8.3", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + } + }, + "@babel/types": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", + "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -1462,18 +1441,18 @@ } }, "@babel/helper-annotate-as-pure": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.7.4.tgz", - "integrity": "sha512-2BQmQgECKzYKFPpiycoF9tlb5HA4lrVyAmLLVK177EcQAqjVLciUb2/R+n1boQ9y5ENV3uz2ZqiNw7QMBBw1Og==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.8.3.tgz", + "integrity": "sha512-6o+mJrZBxOoEX77Ezv9zwW7WV8DdluouRKNY/IR5u/YTMuKHgugHOzYWlYvYLpLA9nPsQCAAASpCIbjI9Mv+Uw==", "dev": true, "requires": { - "@babel/types": "^7.7.4" + "@babel/types": "^7.8.3" }, "dependencies": { "@babel/types": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", - "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", + "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -1484,19 +1463,19 @@ } }, "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.7.4.tgz", - "integrity": "sha512-Biq/d/WtvfftWZ9Uf39hbPBYDUo986m5Bb4zhkeYDGUllF43D+nUe5M6Vuo6/8JDK/0YX/uBdeoQpyaNhNugZQ==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.8.3.tgz", + "integrity": "sha512-5eFOm2SyFPK4Rh3XMMRDjN7lBH0orh3ss0g3rTYZnBQ+r6YPj7lgDyCvPphynHvUrobJmeMignBr6Acw9mAPlw==", "dev": true, "requires": { - "@babel/helper-explode-assignable-expression": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/helper-explode-assignable-expression": "^7.8.3", + "@babel/types": "^7.8.3" }, "dependencies": { "@babel/types": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", - "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", + "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -1507,95 +1486,115 @@ } }, "@babel/helper-call-delegate": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.7.4.tgz", - "integrity": "sha512-8JH9/B7J7tCYJ2PpWVpw9JhPuEVHztagNVuQAFBVFYluRMlpG7F1CgKEgGeL6KFqcsIa92ZYVj6DSc0XwmN1ZA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.8.3.tgz", + "integrity": "sha512-6Q05px0Eb+N4/GTyKPPvnkig7Lylw+QzihMpws9iiZQv7ZImf84ZsZpQH7QoWN4n4tm81SnSzPgHw2qtO0Zf3A==", "dev": true, "requires": { - "@babel/helper-hoist-variables": "^7.7.4", - "@babel/traverse": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/helper-hoist-variables": "^7.8.3", + "@babel/traverse": "^7.8.3", + "@babel/types": "^7.8.3" }, "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.8.3" + } + }, "@babel/generator": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.7.4.tgz", - "integrity": "sha512-m5qo2WgdOJeyYngKImbkyQrnUN1mPceaG5BV+G0E3gWsa4l/jCSryWJdM2x8OuGAOyh+3d5pVYfZWCiNFtynxg==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", + "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", "dev": true, "requires": { - "@babel/types": "^7.7.4", + "@babel/types": "^7.8.3", "jsesc": "^2.5.1", "lodash": "^4.17.13", "source-map": "^0.5.0" } }, "@babel/helper-function-name": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.7.4.tgz", - "integrity": "sha512-AnkGIdiBhEuiwdoMnKm7jfPfqItZhgRaZfMg1XX3bS25INOnLPjPG1Ppnajh8eqgt5kPJnfqrRHqFqmjKDZLzQ==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", + "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.7.4", - "@babel/template": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/helper-get-function-arity": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/types": "^7.8.3" } }, "@babel/helper-get-function-arity": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.4.tgz", - "integrity": "sha512-QTGKEdCkjgzgfJ3bAyRwF4yyT3pg+vDgan8DSivq1eS0gwi+KGKE5x8kRcbeFTb/673mkO5SN1IZfmCfA5o+EA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", + "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", "dev": true, "requires": { - "@babel/types": "^7.7.4" + "@babel/types": "^7.8.3" } }, "@babel/helper-split-export-declaration": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.4.tgz", - "integrity": "sha512-guAg1SXFcVr04Guk9eq0S4/rWS++sbmyqosJzVs8+1fH5NI+ZcmkaSkc7dmtAFbHFva6yRJnjW3yAcGxjueDug==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", + "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", + "dev": true, + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/highlight": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", + "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", "dev": true, "requires": { - "@babel/types": "^7.7.4" + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.7.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.5.tgz", - "integrity": "sha512-KNlOe9+/nk4i29g0VXgl8PEXIRms5xKLJeuZ6UptN0fHv+jDiriG+y94X6qAgWTR0h3KaoM1wK5G5h7MHFRSig==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", + "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", "dev": true }, "@babel/template": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.4.tgz", - "integrity": "sha512-qUzihgVPguAzXCK7WXw8pqs6cEwi54s3E+HrejlkuWO6ivMKx9hZl3Y2fSXp9i5HgyWmj7RKP+ulaYnKM4yYxw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", + "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/code-frame": "^7.8.3", + "@babel/parser": "^7.8.3", + "@babel/types": "^7.8.3" } }, "@babel/traverse": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.7.4.tgz", - "integrity": "sha512-P1L58hQyupn8+ezVA2z5KBm4/Zr4lCC8dwKCMYzsa5jFMDMQAzaBNy9W5VjB+KAmBjb40U7a/H6ao+Xo+9saIw==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", + "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", "dev": true, "requires": { - "@babel/code-frame": "^7.5.5", - "@babel/generator": "^7.7.4", - "@babel/helper-function-name": "^7.7.4", - "@babel/helper-split-export-declaration": "^7.7.4", - "@babel/parser": "^7.7.4", - "@babel/types": "^7.7.4", + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.8.4", + "@babel/helper-function-name": "^7.8.3", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/parser": "^7.8.4", + "@babel/types": "^7.8.3", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.13" } }, "@babel/types": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", - "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", + "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -1626,105 +1625,120 @@ } } }, - "@babel/helper-create-regexp-features-plugin": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.7.4.tgz", - "integrity": "sha512-Mt+jBKaxL0zfOIWrfQpnfYCN7/rS6GKx6CCCfuoqVVd+17R8zNDlzVYmIi9qyb2wOk002NsmSTDymkIygDUH7A==", + "@babel/helper-compilation-targets": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.4.tgz", + "integrity": "sha512-3k3BsKMvPp5bjxgMdrFyq0UaEO48HciVrOVF0+lon8pp95cyJ2ujAh0TrBHNMnJGT2rr0iKOJPFFbSqjDyf/Pg==", "dev": true, "requires": { - "@babel/helper-regex": "^7.4.4", - "regexpu-core": "^4.6.0" + "@babel/compat-data": "^7.8.4", + "browserslist": "^4.8.5", + "invariant": "^2.2.4", + "levenary": "^1.1.1", + "semver": "^5.5.0" }, "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", - "dev": true - }, - "regexpu-core": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.6.0.tgz", - "integrity": "sha512-YlVaefl8P5BnFYOITTNzDvan1ulLOiXJzCNZxduTIosN17b87h3bvG9yHMoHaRuo88H4mQ06Aodj5VtYGGGiTg==", + "browserslist": { + "version": "4.8.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.6.tgz", + "integrity": "sha512-ZHao85gf0eZ0ESxLfCp73GG9O/VTytYDIkIiZDlURppLTI9wErSM/5yAKEq6rcUdxBLjMELmrYUJGg5sxGKMHg==", "dev": true, "requires": { - "regenerate": "^1.4.0", - "regenerate-unicode-properties": "^8.1.0", - "regjsgen": "^0.5.0", - "regjsparser": "^0.6.0", - "unicode-match-property-ecmascript": "^1.0.4", - "unicode-match-property-value-ecmascript": "^1.1.0" + "caniuse-lite": "^1.0.30001023", + "electron-to-chromium": "^1.3.341", + "node-releases": "^1.1.47" } }, - "regjsgen": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.1.tgz", - "integrity": "sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg==", + "caniuse-lite": { + "version": "1.0.30001025", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001025.tgz", + "integrity": "sha512-SKyFdHYfXUZf5V85+PJgLYyit27q4wgvZuf8QTOk1osbypcROihMBlx9GRar2/pIcKH2r4OehdlBr9x6PXetAQ==", "dev": true - }, - "regjsparser": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.0.tgz", - "integrity": "sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ==", - "dev": true, - "requires": { - "jsesc": "~0.5.0" - } } } }, + "@babel/helper-create-regexp-features-plugin": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.3.tgz", + "integrity": "sha512-Gcsm1OHCUr9o9TcJln57xhWHtdXbA2pgQ58S0Lxlks0WMGNXuki4+GLfX0p+L2ZkINUGZvfkz8rzoqJQSthI+Q==", + "dev": true, + "requires": { + "@babel/helper-regex": "^7.8.3", + "regexpu-core": "^4.6.0" + } + }, "@babel/helper-define-map": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.7.4.tgz", - "integrity": "sha512-v5LorqOa0nVQUvAUTUF3KPastvUt/HzByXNamKQ6RdJRTV7j8rLL+WB5C/MzzWAwOomxDhYFb1wLLxHqox86lg==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.8.3.tgz", + "integrity": "sha512-PoeBYtxoZGtct3md6xZOCWPcKuMuk3IHhgxsRRNtnNShebf4C8YonTSblsK4tvDbm+eJAw2HAPOfCr+Q/YRG/g==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.7.4", - "@babel/types": "^7.7.4", + "@babel/helper-function-name": "^7.8.3", + "@babel/types": "^7.8.3", "lodash": "^4.17.13" }, "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.8.3" + } + }, "@babel/helper-function-name": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.7.4.tgz", - "integrity": "sha512-AnkGIdiBhEuiwdoMnKm7jfPfqItZhgRaZfMg1XX3bS25INOnLPjPG1Ppnajh8eqgt5kPJnfqrRHqFqmjKDZLzQ==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", + "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.7.4", - "@babel/template": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/helper-get-function-arity": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/types": "^7.8.3" } }, "@babel/helper-get-function-arity": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.4.tgz", - "integrity": "sha512-QTGKEdCkjgzgfJ3bAyRwF4yyT3pg+vDgan8DSivq1eS0gwi+KGKE5x8kRcbeFTb/673mkO5SN1IZfmCfA5o+EA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", + "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", "dev": true, "requires": { - "@babel/types": "^7.7.4" + "@babel/types": "^7.8.3" + } + }, + "@babel/highlight": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", + "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.7.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.5.tgz", - "integrity": "sha512-KNlOe9+/nk4i29g0VXgl8PEXIRms5xKLJeuZ6UptN0fHv+jDiriG+y94X6qAgWTR0h3KaoM1wK5G5h7MHFRSig==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", + "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", "dev": true }, "@babel/template": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.4.tgz", - "integrity": "sha512-qUzihgVPguAzXCK7WXw8pqs6cEwi54s3E+HrejlkuWO6ivMKx9hZl3Y2fSXp9i5HgyWmj7RKP+ulaYnKM4yYxw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", + "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/code-frame": "^7.8.3", + "@babel/parser": "^7.8.3", + "@babel/types": "^7.8.3" } }, "@babel/types": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", - "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", + "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -1735,94 +1749,114 @@ } }, "@babel/helper-explode-assignable-expression": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.7.4.tgz", - "integrity": "sha512-2/SicuFrNSXsZNBxe5UGdLr+HZg+raWBLE9vC98bdYOKX/U6PY0mdGlYUJdtTDPSU0Lw0PNbKKDpwYHJLn2jLg==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.8.3.tgz", + "integrity": "sha512-N+8eW86/Kj147bO9G2uclsg5pwfs/fqqY5rwgIL7eTBklgXjcOJ3btzS5iM6AitJcftnY7pm2lGsrJVYLGjzIw==", "dev": true, "requires": { - "@babel/traverse": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/traverse": "^7.8.3", + "@babel/types": "^7.8.3" }, "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.8.3" + } + }, "@babel/generator": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.7.4.tgz", - "integrity": "sha512-m5qo2WgdOJeyYngKImbkyQrnUN1mPceaG5BV+G0E3gWsa4l/jCSryWJdM2x8OuGAOyh+3d5pVYfZWCiNFtynxg==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", + "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", "dev": true, "requires": { - "@babel/types": "^7.7.4", + "@babel/types": "^7.8.3", "jsesc": "^2.5.1", "lodash": "^4.17.13", "source-map": "^0.5.0" } }, "@babel/helper-function-name": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.7.4.tgz", - "integrity": "sha512-AnkGIdiBhEuiwdoMnKm7jfPfqItZhgRaZfMg1XX3bS25INOnLPjPG1Ppnajh8eqgt5kPJnfqrRHqFqmjKDZLzQ==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", + "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.7.4", - "@babel/template": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/helper-get-function-arity": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/types": "^7.8.3" } }, "@babel/helper-get-function-arity": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.4.tgz", - "integrity": "sha512-QTGKEdCkjgzgfJ3bAyRwF4yyT3pg+vDgan8DSivq1eS0gwi+KGKE5x8kRcbeFTb/673mkO5SN1IZfmCfA5o+EA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", + "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", "dev": true, "requires": { - "@babel/types": "^7.7.4" + "@babel/types": "^7.8.3" } }, "@babel/helper-split-export-declaration": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.4.tgz", - "integrity": "sha512-guAg1SXFcVr04Guk9eq0S4/rWS++sbmyqosJzVs8+1fH5NI+ZcmkaSkc7dmtAFbHFva6yRJnjW3yAcGxjueDug==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", + "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", + "dev": true, + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/highlight": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", + "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", "dev": true, "requires": { - "@babel/types": "^7.7.4" + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.7.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.5.tgz", - "integrity": "sha512-KNlOe9+/nk4i29g0VXgl8PEXIRms5xKLJeuZ6UptN0fHv+jDiriG+y94X6qAgWTR0h3KaoM1wK5G5h7MHFRSig==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", + "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", "dev": true }, "@babel/template": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.4.tgz", - "integrity": "sha512-qUzihgVPguAzXCK7WXw8pqs6cEwi54s3E+HrejlkuWO6ivMKx9hZl3Y2fSXp9i5HgyWmj7RKP+ulaYnKM4yYxw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", + "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/code-frame": "^7.8.3", + "@babel/parser": "^7.8.3", + "@babel/types": "^7.8.3" } }, "@babel/traverse": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.7.4.tgz", - "integrity": "sha512-P1L58hQyupn8+ezVA2z5KBm4/Zr4lCC8dwKCMYzsa5jFMDMQAzaBNy9W5VjB+KAmBjb40U7a/H6ao+Xo+9saIw==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", + "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", "dev": true, "requires": { - "@babel/code-frame": "^7.5.5", - "@babel/generator": "^7.7.4", - "@babel/helper-function-name": "^7.7.4", - "@babel/helper-split-export-declaration": "^7.7.4", - "@babel/parser": "^7.7.4", - "@babel/types": "^7.7.4", + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.8.4", + "@babel/helper-function-name": "^7.8.3", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/parser": "^7.8.4", + "@babel/types": "^7.8.3", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.13" } }, "@babel/types": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", - "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", + "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -1874,18 +1908,18 @@ } }, "@babel/helper-hoist-variables": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.7.4.tgz", - "integrity": "sha512-wQC4xyvc1Jo/FnLirL6CEgPgPCa8M74tOdjWpRhQYapz5JC7u3NYU1zCVoVAGCE3EaIP9T1A3iW0WLJ+reZlpQ==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.8.3.tgz", + "integrity": "sha512-ky1JLOjcDUtSc+xkt0xhYff7Z6ILTAHKmZLHPxAhOP0Nd77O+3nCsd6uSVYur6nJnCI029CrNbYlc0LoPfAPQg==", "dev": true, "requires": { - "@babel/types": "^7.7.4" + "@babel/types": "^7.8.3" }, "dependencies": { "@babel/types": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", - "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", + "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -1896,18 +1930,18 @@ } }, "@babel/helper-member-expression-to-functions": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.7.4.tgz", - "integrity": "sha512-9KcA1X2E3OjXl/ykfMMInBK+uVdfIVakVe7W7Lg3wfXUNyS3Q1HWLFRwZIjhqiCGbslummPDnmb7vIekS0C1vw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz", + "integrity": "sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA==", "dev": true, "requires": { - "@babel/types": "^7.7.4" + "@babel/types": "^7.8.3" }, "dependencies": { "@babel/types": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", - "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", + "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -1918,18 +1952,18 @@ } }, "@babel/helper-module-imports": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.7.4.tgz", - "integrity": "sha512-dGcrX6K9l8258WFjyDLJwuVKxR4XZfU0/vTUgOQYWEnRD8mgr+p4d6fCUMq/ys0h4CCt/S5JhbvtyErjWouAUQ==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz", + "integrity": "sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg==", "dev": true, "requires": { - "@babel/types": "^7.7.4" + "@babel/types": "^7.8.3" }, "dependencies": { "@babel/types": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", - "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", + "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -1940,49 +1974,69 @@ } }, "@babel/helper-module-transforms": { - "version": "7.7.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.7.5.tgz", - "integrity": "sha512-A7pSxyJf1gN5qXVcidwLWydjftUN878VkalhXX5iQDuGyiGK3sOrrKKHF4/A4fwHtnsotv/NipwAeLzY4KQPvw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.8.3.tgz", + "integrity": "sha512-C7NG6B7vfBa/pwCOshpMbOYUmrYQDfCpVL/JCRu0ek8B5p8kue1+BCXpg2vOYs7w5ACB9GTOBYQ5U6NwrMg+3Q==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.7.4", - "@babel/helper-simple-access": "^7.7.4", - "@babel/helper-split-export-declaration": "^7.7.4", - "@babel/template": "^7.7.4", - "@babel/types": "^7.7.4", + "@babel/helper-module-imports": "^7.8.3", + "@babel/helper-simple-access": "^7.8.3", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/types": "^7.8.3", "lodash": "^4.17.13" }, "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.8.3" + } + }, "@babel/helper-split-export-declaration": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.4.tgz", - "integrity": "sha512-guAg1SXFcVr04Guk9eq0S4/rWS++sbmyqosJzVs8+1fH5NI+ZcmkaSkc7dmtAFbHFva6yRJnjW3yAcGxjueDug==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", + "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", + "dev": true, + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/highlight": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", + "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", "dev": true, "requires": { - "@babel/types": "^7.7.4" + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.7.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.5.tgz", - "integrity": "sha512-KNlOe9+/nk4i29g0VXgl8PEXIRms5xKLJeuZ6UptN0fHv+jDiriG+y94X6qAgWTR0h3KaoM1wK5G5h7MHFRSig==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", + "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", "dev": true }, "@babel/template": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.4.tgz", - "integrity": "sha512-qUzihgVPguAzXCK7WXw8pqs6cEwi54s3E+HrejlkuWO6ivMKx9hZl3Y2fSXp9i5HgyWmj7RKP+ulaYnKM4yYxw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", + "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/code-frame": "^7.8.3", + "@babel/parser": "^7.8.3", + "@babel/types": "^7.8.3" } }, "@babel/types": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", - "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", + "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -1993,18 +2047,18 @@ } }, "@babel/helper-optimise-call-expression": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.7.4.tgz", - "integrity": "sha512-VB7gWZ2fDkSuqW6b1AKXkJWO5NyNI3bFL/kK79/30moK57blr6NbH8xcl2XcKCwOmJosftWunZqfO84IGq3ZZg==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz", + "integrity": "sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ==", "dev": true, "requires": { - "@babel/types": "^7.7.4" + "@babel/types": "^7.8.3" }, "dependencies": { "@babel/types": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", - "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", + "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -2015,112 +2069,132 @@ } }, "@babel/helper-plugin-utils": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz", - "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", + "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==", "dev": true }, "@babel/helper-regex": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.5.5.tgz", - "integrity": "sha512-CkCYQLkfkiugbRDO8eZn6lRuR8kzZoGXCg3149iTk5se7g6qykSpy3+hELSwquhu+TgHn8nkLiBwHvNX8Hofcw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.8.3.tgz", + "integrity": "sha512-BWt0QtYv/cg/NecOAZMdcn/waj/5P26DR4mVLXfFtDokSR6fyuG0Pj+e2FqtSME+MqED1khnSMulkmGl8qWiUQ==", "dev": true, "requires": { "lodash": "^4.17.13" } }, "@babel/helper-remap-async-to-generator": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.7.4.tgz", - "integrity": "sha512-Sk4xmtVdM9sA/jCI80f+KS+Md+ZHIpjuqmYPk1M7F/upHou5e4ReYmExAiu6PVe65BhJPZA2CY9x9k4BqE5klw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.8.3.tgz", + "integrity": "sha512-kgwDmw4fCg7AVgS4DukQR/roGp+jP+XluJE5hsRZwxCYGg+Rv9wSGErDWhlI90FODdYfd4xG4AQRiMDjjN0GzA==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.7.4", - "@babel/helper-wrap-function": "^7.7.4", - "@babel/template": "^7.7.4", - "@babel/traverse": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/helper-annotate-as-pure": "^7.8.3", + "@babel/helper-wrap-function": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/traverse": "^7.8.3", + "@babel/types": "^7.8.3" }, "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.8.3" + } + }, "@babel/generator": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.7.4.tgz", - "integrity": "sha512-m5qo2WgdOJeyYngKImbkyQrnUN1mPceaG5BV+G0E3gWsa4l/jCSryWJdM2x8OuGAOyh+3d5pVYfZWCiNFtynxg==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", + "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", "dev": true, "requires": { - "@babel/types": "^7.7.4", + "@babel/types": "^7.8.3", "jsesc": "^2.5.1", "lodash": "^4.17.13", "source-map": "^0.5.0" } }, "@babel/helper-function-name": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.7.4.tgz", - "integrity": "sha512-AnkGIdiBhEuiwdoMnKm7jfPfqItZhgRaZfMg1XX3bS25INOnLPjPG1Ppnajh8eqgt5kPJnfqrRHqFqmjKDZLzQ==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", + "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.7.4", - "@babel/template": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/helper-get-function-arity": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/types": "^7.8.3" } }, "@babel/helper-get-function-arity": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.4.tgz", - "integrity": "sha512-QTGKEdCkjgzgfJ3bAyRwF4yyT3pg+vDgan8DSivq1eS0gwi+KGKE5x8kRcbeFTb/673mkO5SN1IZfmCfA5o+EA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", + "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", "dev": true, "requires": { - "@babel/types": "^7.7.4" + "@babel/types": "^7.8.3" } }, "@babel/helper-split-export-declaration": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.4.tgz", - "integrity": "sha512-guAg1SXFcVr04Guk9eq0S4/rWS++sbmyqosJzVs8+1fH5NI+ZcmkaSkc7dmtAFbHFva6yRJnjW3yAcGxjueDug==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", + "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", "dev": true, "requires": { - "@babel/types": "^7.7.4" + "@babel/types": "^7.8.3" + } + }, + "@babel/highlight": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", + "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.7.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.5.tgz", - "integrity": "sha512-KNlOe9+/nk4i29g0VXgl8PEXIRms5xKLJeuZ6UptN0fHv+jDiriG+y94X6qAgWTR0h3KaoM1wK5G5h7MHFRSig==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", + "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", "dev": true }, "@babel/template": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.4.tgz", - "integrity": "sha512-qUzihgVPguAzXCK7WXw8pqs6cEwi54s3E+HrejlkuWO6ivMKx9hZl3Y2fSXp9i5HgyWmj7RKP+ulaYnKM4yYxw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", + "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/code-frame": "^7.8.3", + "@babel/parser": "^7.8.3", + "@babel/types": "^7.8.3" } }, "@babel/traverse": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.7.4.tgz", - "integrity": "sha512-P1L58hQyupn8+ezVA2z5KBm4/Zr4lCC8dwKCMYzsa5jFMDMQAzaBNy9W5VjB+KAmBjb40U7a/H6ao+Xo+9saIw==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", + "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", "dev": true, "requires": { - "@babel/code-frame": "^7.5.5", - "@babel/generator": "^7.7.4", - "@babel/helper-function-name": "^7.7.4", - "@babel/helper-split-export-declaration": "^7.7.4", - "@babel/parser": "^7.7.4", - "@babel/types": "^7.7.4", + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.8.4", + "@babel/helper-function-name": "^7.8.3", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/parser": "^7.8.4", + "@babel/types": "^7.8.3", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.13" } }, "@babel/types": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", - "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", + "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -2152,96 +2226,116 @@ } }, "@babel/helper-replace-supers": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.7.4.tgz", - "integrity": "sha512-pP0tfgg9hsZWo5ZboYGuBn/bbYT/hdLPVSS4NMmiRJdwWhP0IznPwN9AE1JwyGsjSPLC364I0Qh5p+EPkGPNpg==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.8.3.tgz", + "integrity": "sha512-xOUssL6ho41U81etpLoT2RTdvdus4VfHamCuAm4AHxGr+0it5fnwoVdwUJ7GFEqCsQYzJUhcbsN9wB9apcYKFA==", "dev": true, "requires": { - "@babel/helper-member-expression-to-functions": "^7.7.4", - "@babel/helper-optimise-call-expression": "^7.7.4", - "@babel/traverse": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/helper-member-expression-to-functions": "^7.8.3", + "@babel/helper-optimise-call-expression": "^7.8.3", + "@babel/traverse": "^7.8.3", + "@babel/types": "^7.8.3" }, "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.8.3" + } + }, "@babel/generator": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.7.4.tgz", - "integrity": "sha512-m5qo2WgdOJeyYngKImbkyQrnUN1mPceaG5BV+G0E3gWsa4l/jCSryWJdM2x8OuGAOyh+3d5pVYfZWCiNFtynxg==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", + "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", "dev": true, "requires": { - "@babel/types": "^7.7.4", + "@babel/types": "^7.8.3", "jsesc": "^2.5.1", "lodash": "^4.17.13", "source-map": "^0.5.0" } }, "@babel/helper-function-name": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.7.4.tgz", - "integrity": "sha512-AnkGIdiBhEuiwdoMnKm7jfPfqItZhgRaZfMg1XX3bS25INOnLPjPG1Ppnajh8eqgt5kPJnfqrRHqFqmjKDZLzQ==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", + "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.7.4", - "@babel/template": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/helper-get-function-arity": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/types": "^7.8.3" } }, "@babel/helper-get-function-arity": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.4.tgz", - "integrity": "sha512-QTGKEdCkjgzgfJ3bAyRwF4yyT3pg+vDgan8DSivq1eS0gwi+KGKE5x8kRcbeFTb/673mkO5SN1IZfmCfA5o+EA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", + "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", "dev": true, "requires": { - "@babel/types": "^7.7.4" + "@babel/types": "^7.8.3" } }, "@babel/helper-split-export-declaration": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.4.tgz", - "integrity": "sha512-guAg1SXFcVr04Guk9eq0S4/rWS++sbmyqosJzVs8+1fH5NI+ZcmkaSkc7dmtAFbHFva6yRJnjW3yAcGxjueDug==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", + "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", "dev": true, "requires": { - "@babel/types": "^7.7.4" + "@babel/types": "^7.8.3" + } + }, + "@babel/highlight": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", + "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.7.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.5.tgz", - "integrity": "sha512-KNlOe9+/nk4i29g0VXgl8PEXIRms5xKLJeuZ6UptN0fHv+jDiriG+y94X6qAgWTR0h3KaoM1wK5G5h7MHFRSig==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", + "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", "dev": true }, "@babel/template": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.4.tgz", - "integrity": "sha512-qUzihgVPguAzXCK7WXw8pqs6cEwi54s3E+HrejlkuWO6ivMKx9hZl3Y2fSXp9i5HgyWmj7RKP+ulaYnKM4yYxw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", + "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/code-frame": "^7.8.3", + "@babel/parser": "^7.8.3", + "@babel/types": "^7.8.3" } }, "@babel/traverse": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.7.4.tgz", - "integrity": "sha512-P1L58hQyupn8+ezVA2z5KBm4/Zr4lCC8dwKCMYzsa5jFMDMQAzaBNy9W5VjB+KAmBjb40U7a/H6ao+Xo+9saIw==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", + "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", "dev": true, "requires": { - "@babel/code-frame": "^7.5.5", - "@babel/generator": "^7.7.4", - "@babel/helper-function-name": "^7.7.4", - "@babel/helper-split-export-declaration": "^7.7.4", - "@babel/parser": "^7.7.4", - "@babel/types": "^7.7.4", + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.8.4", + "@babel/helper-function-name": "^7.8.3", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/parser": "^7.8.4", + "@babel/types": "^7.8.3", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.13" } }, "@babel/types": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", - "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", + "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -2273,36 +2367,56 @@ } }, "@babel/helper-simple-access": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.7.4.tgz", - "integrity": "sha512-zK7THeEXfan7UlWsG2A6CI/L9jVnI5+xxKZOdej39Y0YtDYKx9raHk5F2EtK9K8DHRTihYwg20ADt9S36GR78A==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz", + "integrity": "sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw==", "dev": true, "requires": { - "@babel/template": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/template": "^7.8.3", + "@babel/types": "^7.8.3" }, "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.8.3" + } + }, + "@babel/highlight": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", + "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, "@babel/parser": { - "version": "7.7.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.5.tgz", - "integrity": "sha512-KNlOe9+/nk4i29g0VXgl8PEXIRms5xKLJeuZ6UptN0fHv+jDiriG+y94X6qAgWTR0h3KaoM1wK5G5h7MHFRSig==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", + "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", "dev": true }, "@babel/template": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.4.tgz", - "integrity": "sha512-qUzihgVPguAzXCK7WXw8pqs6cEwi54s3E+HrejlkuWO6ivMKx9hZl3Y2fSXp9i5HgyWmj7RKP+ulaYnKM4yYxw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", + "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/code-frame": "^7.8.3", + "@babel/parser": "^7.8.3", + "@babel/types": "^7.8.3" } }, "@babel/types": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", - "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", + "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -2322,96 +2436,116 @@ } }, "@babel/helper-wrap-function": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.7.4.tgz", - "integrity": "sha512-VsfzZt6wmsocOaVU0OokwrIytHND55yvyT4BPB9AIIgwr8+x7617hetdJTsuGwygN5RC6mxA9EJztTjuwm2ofg==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.8.3.tgz", + "integrity": "sha512-LACJrbUET9cQDzb6kG7EeD7+7doC3JNvUgTEQOx2qaO1fKlzE/Bf05qs9w1oXQMmXlPO65lC3Tq9S6gZpTErEQ==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.7.4", - "@babel/template": "^7.7.4", - "@babel/traverse": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/helper-function-name": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/traverse": "^7.8.3", + "@babel/types": "^7.8.3" }, "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.8.3" + } + }, "@babel/generator": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.7.4.tgz", - "integrity": "sha512-m5qo2WgdOJeyYngKImbkyQrnUN1mPceaG5BV+G0E3gWsa4l/jCSryWJdM2x8OuGAOyh+3d5pVYfZWCiNFtynxg==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", + "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", "dev": true, "requires": { - "@babel/types": "^7.7.4", + "@babel/types": "^7.8.3", "jsesc": "^2.5.1", "lodash": "^4.17.13", "source-map": "^0.5.0" } }, "@babel/helper-function-name": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.7.4.tgz", - "integrity": "sha512-AnkGIdiBhEuiwdoMnKm7jfPfqItZhgRaZfMg1XX3bS25INOnLPjPG1Ppnajh8eqgt5kPJnfqrRHqFqmjKDZLzQ==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", + "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.7.4", - "@babel/template": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/helper-get-function-arity": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/types": "^7.8.3" } }, "@babel/helper-get-function-arity": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.4.tgz", - "integrity": "sha512-QTGKEdCkjgzgfJ3bAyRwF4yyT3pg+vDgan8DSivq1eS0gwi+KGKE5x8kRcbeFTb/673mkO5SN1IZfmCfA5o+EA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", + "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", "dev": true, "requires": { - "@babel/types": "^7.7.4" + "@babel/types": "^7.8.3" } }, "@babel/helper-split-export-declaration": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.4.tgz", - "integrity": "sha512-guAg1SXFcVr04Guk9eq0S4/rWS++sbmyqosJzVs8+1fH5NI+ZcmkaSkc7dmtAFbHFva6yRJnjW3yAcGxjueDug==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", + "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", + "dev": true, + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/highlight": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", + "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", "dev": true, "requires": { - "@babel/types": "^7.7.4" + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.7.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.5.tgz", - "integrity": "sha512-KNlOe9+/nk4i29g0VXgl8PEXIRms5xKLJeuZ6UptN0fHv+jDiriG+y94X6qAgWTR0h3KaoM1wK5G5h7MHFRSig==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", + "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", "dev": true }, "@babel/template": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.4.tgz", - "integrity": "sha512-qUzihgVPguAzXCK7WXw8pqs6cEwi54s3E+HrejlkuWO6ivMKx9hZl3Y2fSXp9i5HgyWmj7RKP+ulaYnKM4yYxw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", + "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/code-frame": "^7.8.3", + "@babel/parser": "^7.8.3", + "@babel/types": "^7.8.3" } }, "@babel/traverse": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.7.4.tgz", - "integrity": "sha512-P1L58hQyupn8+ezVA2z5KBm4/Zr4lCC8dwKCMYzsa5jFMDMQAzaBNy9W5VjB+KAmBjb40U7a/H6ao+Xo+9saIw==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", + "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", "dev": true, "requires": { - "@babel/code-frame": "^7.5.5", - "@babel/generator": "^7.7.4", - "@babel/helper-function-name": "^7.7.4", - "@babel/helper-split-export-declaration": "^7.7.4", - "@babel/parser": "^7.7.4", - "@babel/types": "^7.7.4", + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.8.4", + "@babel/helper-function-name": "^7.8.3", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/parser": "^7.8.4", + "@babel/types": "^7.8.3", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.13" } }, "@babel/types": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", - "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", + "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -2443,95 +2577,115 @@ } }, "@babel/helpers": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.7.4.tgz", - "integrity": "sha512-ak5NGZGJ6LV85Q1Zc9gn2n+ayXOizryhjSUBTdu5ih1tlVCJeuQENzc4ItyCVhINVXvIT/ZQ4mheGIsfBkpskg==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.8.4.tgz", + "integrity": "sha512-VPbe7wcQ4chu4TDQjimHv/5tj73qz88o12EPkO2ValS2QiQS/1F2SsjyIGNnAD0vF/nZS6Cf9i+vW6HIlnaR8w==", "dev": true, "requires": { - "@babel/template": "^7.7.4", - "@babel/traverse": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/template": "^7.8.3", + "@babel/traverse": "^7.8.4", + "@babel/types": "^7.8.3" }, "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.8.3" + } + }, "@babel/generator": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.7.4.tgz", - "integrity": "sha512-m5qo2WgdOJeyYngKImbkyQrnUN1mPceaG5BV+G0E3gWsa4l/jCSryWJdM2x8OuGAOyh+3d5pVYfZWCiNFtynxg==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", + "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", "dev": true, "requires": { - "@babel/types": "^7.7.4", + "@babel/types": "^7.8.3", "jsesc": "^2.5.1", "lodash": "^4.17.13", "source-map": "^0.5.0" } }, "@babel/helper-function-name": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.7.4.tgz", - "integrity": "sha512-AnkGIdiBhEuiwdoMnKm7jfPfqItZhgRaZfMg1XX3bS25INOnLPjPG1Ppnajh8eqgt5kPJnfqrRHqFqmjKDZLzQ==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", + "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.7.4", - "@babel/template": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/helper-get-function-arity": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/types": "^7.8.3" } }, "@babel/helper-get-function-arity": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.4.tgz", - "integrity": "sha512-QTGKEdCkjgzgfJ3bAyRwF4yyT3pg+vDgan8DSivq1eS0gwi+KGKE5x8kRcbeFTb/673mkO5SN1IZfmCfA5o+EA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", + "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", "dev": true, "requires": { - "@babel/types": "^7.7.4" + "@babel/types": "^7.8.3" } }, "@babel/helper-split-export-declaration": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.4.tgz", - "integrity": "sha512-guAg1SXFcVr04Guk9eq0S4/rWS++sbmyqosJzVs8+1fH5NI+ZcmkaSkc7dmtAFbHFva6yRJnjW3yAcGxjueDug==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", + "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", "dev": true, "requires": { - "@babel/types": "^7.7.4" + "@babel/types": "^7.8.3" + } + }, + "@babel/highlight": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", + "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.7.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.5.tgz", - "integrity": "sha512-KNlOe9+/nk4i29g0VXgl8PEXIRms5xKLJeuZ6UptN0fHv+jDiriG+y94X6qAgWTR0h3KaoM1wK5G5h7MHFRSig==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", + "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", "dev": true }, "@babel/template": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.4.tgz", - "integrity": "sha512-qUzihgVPguAzXCK7WXw8pqs6cEwi54s3E+HrejlkuWO6ivMKx9hZl3Y2fSXp9i5HgyWmj7RKP+ulaYnKM4yYxw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", + "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/code-frame": "^7.8.3", + "@babel/parser": "^7.8.3", + "@babel/types": "^7.8.3" } }, "@babel/traverse": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.7.4.tgz", - "integrity": "sha512-P1L58hQyupn8+ezVA2z5KBm4/Zr4lCC8dwKCMYzsa5jFMDMQAzaBNy9W5VjB+KAmBjb40U7a/H6ao+Xo+9saIw==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", + "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", "dev": true, "requires": { - "@babel/code-frame": "^7.5.5", - "@babel/generator": "^7.7.4", - "@babel/helper-function-name": "^7.7.4", - "@babel/helper-split-export-declaration": "^7.7.4", - "@babel/parser": "^7.7.4", - "@babel/types": "^7.7.4", + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.8.4", + "@babel/helper-function-name": "^7.8.3", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/parser": "^7.8.4", + "@babel/types": "^7.8.3", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.13" } }, "@babel/types": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", - "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", + "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -2580,216 +2734,283 @@ "dev": true }, "@babel/plugin-proposal-async-generator-functions": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.7.4.tgz", - "integrity": "sha512-1ypyZvGRXriY/QP668+s8sFr2mqinhkRDMPSQLNghCQE+GAkFtp+wkHVvg2+Hdki8gwP+NFzJBJ/N1BfzCCDEw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.8.3.tgz", + "integrity": "sha512-NZ9zLv848JsV3hs8ryEh7Uaz/0KsmPLqv0+PdkDJL1cJy0K4kOCFa8zc1E3mp+RHPQcpdfb/6GovEsW4VDrOMw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-remap-async-to-generator": "^7.7.4", - "@babel/plugin-syntax-async-generators": "^7.7.4" + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-remap-async-to-generator": "^7.8.3", + "@babel/plugin-syntax-async-generators": "^7.8.0" } }, "@babel/plugin-proposal-dynamic-import": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.7.4.tgz", - "integrity": "sha512-StH+nGAdO6qDB1l8sZ5UBV8AC3F2VW2I8Vfld73TMKyptMU9DY5YsJAS8U81+vEtxcH3Y/La0wG0btDrhpnhjQ==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.8.3.tgz", + "integrity": "sha512-NyaBbyLFXFLT9FP+zk0kYlUlA8XtCUbehs67F0nnEg7KICgMc2mNkIeu9TYhKzyXMkrapZFwAhXLdnt4IYHy1w==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-dynamic-import": "^7.7.4" + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-dynamic-import": "^7.8.0" } }, "@babel/plugin-proposal-json-strings": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.7.4.tgz", - "integrity": "sha512-wQvt3akcBTfLU/wYoqm/ws7YOAQKu8EVJEvHip/mzkNtjaclQoCCIqKXFP5/eyfnfbQCDV3OLRIK3mIVyXuZlw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.8.3.tgz", + "integrity": "sha512-KGhQNZ3TVCQG/MjRbAUwuH+14y9q0tpxs1nWWs3pbSleRdDro9SAMMDyye8HhY1gqZ7/NqIc8SKhya0wRDgP1Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.0" + } + }, + "@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-TS9MlfzXpXKt6YYomudb/KU7nQI6/xnapG6in1uZxoxDghuSMZsPb6D2fyUwNYSAp4l1iR7QtFOjkqcRYcUsfw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-json-strings": "^7.7.4" + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.7.4.tgz", - "integrity": "sha512-rnpnZR3/iWKmiQyJ3LKJpSwLDcX/nSXhdLk4Aq/tXOApIvyu7qoabrige0ylsAJffaUC51WiBu209Q0U+86OWQ==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-8qvuPwU/xxUCt78HocNlv0mXXo0wdh9VT1R04WU8HGOfaOob26pF+9P5/lYjN/q7DHOX1bvX60hnhOvuQUJdbA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-object-rest-spread": "^7.7.4" + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.0" } }, "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.7.4.tgz", - "integrity": "sha512-DyM7U2bnsQerCQ+sejcTNZh8KQEUuC3ufzdnVnSiUv/qoGJp2Z3hanKL18KDhsBT5Wj6a7CMT5mdyCNJsEaA9w==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-0gkX7J7E+AtAw9fcwlVQj8peP61qhdg/89D5swOkjYbkboA2CVckn3kiyum1DE0wskGb7KJJxBdyEBApDLLVdw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-optional-catch-binding": "^7.7.4" + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" + } + }, + "@babel/plugin-proposal-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.8.3.tgz", + "integrity": "sha512-QIoIR9abkVn+seDE3OjA08jWcs3eZ9+wJCKSRgo3WdEU2csFYgdScb+8qHB3+WXsGJD55u+5hWCISI7ejXS+kg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.0" } }, "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.7.4.tgz", - "integrity": "sha512-cHgqHgYvffluZk85dJ02vloErm3Y6xtH+2noOBOJ2kXOJH3aVCDnj5eR/lVNlTnYu4hndAPJD3rTFjW3qee0PA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.8.3.tgz", + "integrity": "sha512-1/1/rEZv2XGweRwwSkLpY+s60za9OZ1hJs4YDqFHCw0kYWYwL5IFljVY1MYBL+weT1l9pokDO2uhSTLVxzoHkQ==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.7.4", - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-create-regexp-features-plugin": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-syntax-async-generators": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.7.4.tgz", - "integrity": "sha512-Li4+EjSpBgxcsmeEF8IFcfV/+yJGxHXDirDkEoyFjumuwbmfCVHUt0HuowD/iGM7OhIRyXJH9YXxqiH6N815+g==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.0" } }, "@babel/plugin-syntax-dynamic-import": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.7.4.tgz", - "integrity": "sha512-jHQW0vbRGvwQNgyVxwDh4yuXu4bH1f5/EICJLAhl1SblLs2CDhrsmCk+v5XLdE9wxtAFRyxx+P//Iw+a5L/tTg==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.0" } }, "@babel/plugin-syntax-json-strings": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.7.4.tgz", - "integrity": "sha512-QpGupahTQW1mHRXddMG5srgpHWqRLwJnJZKXTigB9RPFCCGbDGCgBeM/iC82ICXp414WeYx/tD54w7M2qRqTMg==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.0" } }, "@babel/plugin-syntax-object-rest-spread": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.7.4.tgz", - "integrity": "sha512-mObR+r+KZq0XhRVS2BrBKBpr5jqrqzlPvS9C9vuOf5ilSwzloAl7RPWLrgKdWS6IreaVrjHxTjtyqFiOisaCwg==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.0" } }, "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.7.4.tgz", - "integrity": "sha512-4ZSuzWgFxqHRE31Glu+fEr/MirNZOMYmD/0BhBWyLyOOQz/gTAl7QmWm2hX1QxEIXsr2vkdlwxIzTyiYRC4xcQ==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.8.3.tgz", + "integrity": "sha512-kwj1j9lL/6Wd0hROD3b/OZZ7MSrZLqqn9RAZ5+cYYsflQ9HZBIKCUkr3+uL1MEJ1NePiUbf98jjiMQSv0NMR9g==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-transform-arrow-functions": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.7.4.tgz", - "integrity": "sha512-zUXy3e8jBNPiffmqkHRNDdZM2r8DWhCB7HhcoyZjiK1TxYEluLHAvQuYnTT+ARqRpabWqy/NHkO6e3MsYB5YfA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.8.3.tgz", + "integrity": "sha512-0MRF+KC8EqH4dbuITCWwPSzsyO3HIWWlm30v8BbbpOrS1B++isGxPnnuq/IZvOX5J2D/p7DQalQm+/2PnlKGxg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-transform-async-to-generator": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.7.4.tgz", - "integrity": "sha512-zpUTZphp5nHokuy8yLlyafxCJ0rSlFoSHypTUWgpdwoDXWQcseaect7cJ8Ppk6nunOM6+5rPMkod4OYKPR5MUg==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.8.3.tgz", + "integrity": "sha512-imt9tFLD9ogt56Dd5CI/6XgpukMwd/fLGSrix2httihVe7LOGVPhyhMh1BU5kDM7iHD08i8uUtmV2sWaBFlHVQ==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.7.4", - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-remap-async-to-generator": "^7.7.4" + "@babel/helper-module-imports": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-remap-async-to-generator": "^7.8.3" } }, "@babel/plugin-transform-block-scoped-functions": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.7.4.tgz", - "integrity": "sha512-kqtQzwtKcpPclHYjLK//3lH8OFsCDuDJBaFhVwf8kqdnF6MN4l618UDlcA7TfRs3FayrHj+svYnSX8MC9zmUyQ==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.8.3.tgz", + "integrity": "sha512-vo4F2OewqjbB1+yaJ7k2EJFHlTP3jR634Z9Cj9itpqNjuLXvhlVxgnjsHsdRgASR8xYDrx6onw4vW5H6We0Jmg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-transform-block-scoping": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.7.4.tgz", - "integrity": "sha512-2VBe9u0G+fDt9B5OV5DQH4KBf5DoiNkwFKOz0TCvBWvdAN2rOykCTkrL+jTLxfCAm76l9Qo5OqL7HBOx2dWggg==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.8.3.tgz", + "integrity": "sha512-pGnYfm7RNRgYRi7bids5bHluENHqJhrV4bCZRwc5GamaWIIs07N4rZECcmJL6ZClwjDz1GbdMZFtPs27hTB06w==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-plugin-utils": "^7.8.3", "lodash": "^4.17.13" } }, "@babel/plugin-transform-classes": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.7.4.tgz", - "integrity": "sha512-sK1mjWat7K+buWRuImEzjNf68qrKcrddtpQo3swi9j7dUcG6y6R6+Di039QN2bD1dykeswlagupEmpOatFHHUg==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.7.4", - "@babel/helper-define-map": "^7.7.4", - "@babel/helper-function-name": "^7.7.4", - "@babel/helper-optimise-call-expression": "^7.7.4", - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-replace-supers": "^7.7.4", - "@babel/helper-split-export-declaration": "^7.7.4", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.8.3.tgz", + "integrity": "sha512-SjT0cwFJ+7Rbr1vQsvphAHwUHvSUPmMjMU/0P59G8U2HLFqSa082JO7zkbDNWs9kH/IUqpHI6xWNesGf8haF1w==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.8.3", + "@babel/helper-define-map": "^7.8.3", + "@babel/helper-function-name": "^7.8.3", + "@babel/helper-optimise-call-expression": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-replace-supers": "^7.8.3", + "@babel/helper-split-export-declaration": "^7.8.3", "globals": "^11.1.0" }, "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.8.3" + } + }, "@babel/helper-function-name": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.7.4.tgz", - "integrity": "sha512-AnkGIdiBhEuiwdoMnKm7jfPfqItZhgRaZfMg1XX3bS25INOnLPjPG1Ppnajh8eqgt5kPJnfqrRHqFqmjKDZLzQ==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", + "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.7.4", - "@babel/template": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/helper-get-function-arity": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/types": "^7.8.3" } }, "@babel/helper-get-function-arity": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.4.tgz", - "integrity": "sha512-QTGKEdCkjgzgfJ3bAyRwF4yyT3pg+vDgan8DSivq1eS0gwi+KGKE5x8kRcbeFTb/673mkO5SN1IZfmCfA5o+EA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", + "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", "dev": true, "requires": { - "@babel/types": "^7.7.4" + "@babel/types": "^7.8.3" } }, "@babel/helper-split-export-declaration": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.4.tgz", - "integrity": "sha512-guAg1SXFcVr04Guk9eq0S4/rWS++sbmyqosJzVs8+1fH5NI+ZcmkaSkc7dmtAFbHFva6yRJnjW3yAcGxjueDug==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", + "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", "dev": true, "requires": { - "@babel/types": "^7.7.4" + "@babel/types": "^7.8.3" + } + }, + "@babel/highlight": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", + "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.7.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.5.tgz", - "integrity": "sha512-KNlOe9+/nk4i29g0VXgl8PEXIRms5xKLJeuZ6UptN0fHv+jDiriG+y94X6qAgWTR0h3KaoM1wK5G5h7MHFRSig==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", + "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", "dev": true }, "@babel/template": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.4.tgz", - "integrity": "sha512-qUzihgVPguAzXCK7WXw8pqs6cEwi54s3E+HrejlkuWO6ivMKx9hZl3Y2fSXp9i5HgyWmj7RKP+ulaYnKM4yYxw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", + "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/code-frame": "^7.8.3", + "@babel/parser": "^7.8.3", + "@babel/types": "^7.8.3" } }, "@babel/types": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", - "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", + "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -2800,112 +3021,132 @@ } }, "@babel/plugin-transform-computed-properties": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.7.4.tgz", - "integrity": "sha512-bSNsOsZnlpLLyQew35rl4Fma3yKWqK3ImWMSC/Nc+6nGjC9s5NFWAer1YQ899/6s9HxO2zQC1WoFNfkOqRkqRQ==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.8.3.tgz", + "integrity": "sha512-O5hiIpSyOGdrQZRQ2ccwtTVkgUDBBiCuK//4RJ6UfePllUTCENOzKxfh6ulckXKc0DixTFLCfb2HVkNA7aDpzA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-transform-destructuring": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.7.4.tgz", - "integrity": "sha512-4jFMXI1Cu2aXbcXXl8Lr6YubCn6Oc7k9lLsu8v61TZh+1jny2BWmdtvY9zSUlLdGUvcy9DMAWyZEOqjsbeg/wA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.8.3.tgz", + "integrity": "sha512-H4X646nCkiEcHZUZaRkhE2XVsoz0J/1x3VVujnn96pSoGCtKPA99ZZA+va+gK+92Zycd6OBKCD8tDb/731bhgQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-transform-dotall-regex": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.7.4.tgz", - "integrity": "sha512-mk0cH1zyMa/XHeb6LOTXTbG7uIJ8Rrjlzu91pUx/KS3JpcgaTDwMS8kM+ar8SLOvlL2Lofi4CGBAjCo3a2x+lw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.8.3.tgz", + "integrity": "sha512-kLs1j9Nn4MQoBYdRXH6AeaXMbEJFaFu/v1nQkvib6QzTj8MZI5OQzqmD83/2jEM1z0DLilra5aWO5YpyC0ALIw==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.7.4", - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-create-regexp-features-plugin": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-transform-duplicate-keys": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.7.4.tgz", - "integrity": "sha512-g1y4/G6xGWMD85Tlft5XedGaZBCIVN+/P0bs6eabmcPP9egFleMAo65OOjlhcz1njpwagyY3t0nsQC9oTFegJA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.8.3.tgz", + "integrity": "sha512-s8dHiBUbcbSgipS4SMFuWGqCvyge5V2ZeAWzR6INTVC3Ltjig/Vw1G2Gztv0vU/hRG9X8IvKvYdoksnUfgXOEQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-transform-exponentiation-operator": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.7.4.tgz", - "integrity": "sha512-MCqiLfCKm6KEA1dglf6Uqq1ElDIZwFuzz1WH5mTf8k2uQSxEJMbOIEh7IZv7uichr7PMfi5YVSrr1vz+ipp7AQ==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.8.3.tgz", + "integrity": "sha512-zwIpuIymb3ACcInbksHaNcR12S++0MDLKkiqXHl3AzpgdKlFNhog+z/K0+TGW+b0w5pgTq4H6IwV/WhxbGYSjQ==", "dev": true, "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.7.4", - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-transform-for-of": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.7.4.tgz", - "integrity": "sha512-zZ1fD1B8keYtEcKF+M1TROfeHTKnijcVQm0yO/Yu1f7qoDoxEIc/+GX6Go430Bg84eM/xwPFp0+h4EbZg7epAA==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.8.4.tgz", + "integrity": "sha512-iAXNlOWvcYUYoV8YIxwS7TxGRJcxyl8eQCfT+A5j8sKUzRFvJdcyjp97jL2IghWSRDaL2PU2O2tX8Cu9dTBq5A==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-transform-function-name": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.7.4.tgz", - "integrity": "sha512-E/x09TvjHNhsULs2IusN+aJNRV5zKwxu1cpirZyRPw+FyyIKEHPXTsadj48bVpc1R5Qq1B5ZkzumuFLytnbT6g==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.8.3.tgz", + "integrity": "sha512-rO/OnDS78Eifbjn5Py9v8y0aR+aSYhDhqAwVfsTl0ERuMZyr05L1aFSCJnbv2mmsLkit/4ReeQ9N2BgLnOcPCQ==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.7.4", - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-function-name": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3" }, "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.8.3" + } + }, "@babel/helper-function-name": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.7.4.tgz", - "integrity": "sha512-AnkGIdiBhEuiwdoMnKm7jfPfqItZhgRaZfMg1XX3bS25INOnLPjPG1Ppnajh8eqgt5kPJnfqrRHqFqmjKDZLzQ==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", + "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.7.4", - "@babel/template": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/helper-get-function-arity": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/types": "^7.8.3" } }, "@babel/helper-get-function-arity": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.4.tgz", - "integrity": "sha512-QTGKEdCkjgzgfJ3bAyRwF4yyT3pg+vDgan8DSivq1eS0gwi+KGKE5x8kRcbeFTb/673mkO5SN1IZfmCfA5o+EA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", + "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", + "dev": true, + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/highlight": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", + "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", "dev": true, "requires": { - "@babel/types": "^7.7.4" + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.7.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.5.tgz", - "integrity": "sha512-KNlOe9+/nk4i29g0VXgl8PEXIRms5xKLJeuZ6UptN0fHv+jDiriG+y94X6qAgWTR0h3KaoM1wK5G5h7MHFRSig==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", + "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", "dev": true }, "@babel/template": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.4.tgz", - "integrity": "sha512-qUzihgVPguAzXCK7WXw8pqs6cEwi54s3E+HrejlkuWO6ivMKx9hZl3Y2fSXp9i5HgyWmj7RKP+ulaYnKM4yYxw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", + "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/code-frame": "^7.8.3", + "@babel/parser": "^7.8.3", + "@babel/types": "^7.8.3" } }, "@babel/types": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", - "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", + "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -2916,119 +3157,120 @@ } }, "@babel/plugin-transform-literals": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.7.4.tgz", - "integrity": "sha512-X2MSV7LfJFm4aZfxd0yLVFrEXAgPqYoDG53Br/tCKiKYfX0MjVjQeWPIhPHHsCqzwQANq+FLN786fF5rgLS+gw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.8.3.tgz", + "integrity": "sha512-3Tqf8JJ/qB7TeldGl+TT55+uQei9JfYaregDcEAyBZ7akutriFrt6C/wLYIer6OYhleVQvH/ntEhjE/xMmy10A==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-transform-member-expression-literals": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.7.4.tgz", - "integrity": "sha512-9VMwMO7i69LHTesL0RdGy93JU6a+qOPuvB4F4d0kR0zyVjJRVJRaoaGjhtki6SzQUu8yen/vxPKN6CWnCUw6bA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.8.3.tgz", + "integrity": "sha512-3Wk2EXhnw+rP+IDkK6BdtPKsUE5IeZ6QOGrPYvw52NwBStw9V1ZVzxgK6fSKSxqUvH9eQPR3tm3cOq79HlsKYA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-transform-modules-amd": { - "version": "7.7.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.7.5.tgz", - "integrity": "sha512-CT57FG4A2ZUNU1v+HdvDSDrjNWBrtCmSH6YbbgN3Lrf0Di/q/lWRxZrE72p3+HCCz9UjfZOEBdphgC0nzOS6DQ==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.8.3.tgz", + "integrity": "sha512-MadJiU3rLKclzT5kBH4yxdry96odTUwuqrZM+GllFI/VhxfPz+k9MshJM+MwhfkCdxxclSbSBbUGciBngR+kEQ==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.7.5", - "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-module-transforms": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3", "babel-plugin-dynamic-import-node": "^2.3.0" } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.7.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.7.5.tgz", - "integrity": "sha512-9Cq4zTFExwFhQI6MT1aFxgqhIsMWQWDVwOgLzl7PTWJHsNaqFvklAU+Oz6AQLAS0dJKTwZSOCo20INwktxpi3Q==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.8.3.tgz", + "integrity": "sha512-JpdMEfA15HZ/1gNuB9XEDlZM1h/gF/YOH7zaZzQu2xCFRfwc01NXBMHHSTT6hRjlXJJs5x/bfODM3LiCk94Sxg==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.7.5", - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-simple-access": "^7.7.4", + "@babel/helper-module-transforms": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-simple-access": "^7.8.3", "babel-plugin-dynamic-import-node": "^2.3.0" } }, "@babel/plugin-transform-modules-systemjs": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.7.4.tgz", - "integrity": "sha512-y2c96hmcsUi6LrMqvmNDPBBiGCiQu0aYqpHatVVu6kD4mFEXKjyNxd/drc18XXAf9dv7UXjrZwBVmTTGaGP8iw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.8.3.tgz", + "integrity": "sha512-8cESMCJjmArMYqa9AO5YuMEkE4ds28tMpZcGZB/jl3n0ZzlsxOAi3mC+SKypTfT8gjMupCnd3YiXCkMjj2jfOg==", "dev": true, "requires": { - "@babel/helper-hoist-variables": "^7.7.4", - "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-hoist-variables": "^7.8.3", + "@babel/helper-module-transforms": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3", "babel-plugin-dynamic-import-node": "^2.3.0" } }, "@babel/plugin-transform-modules-umd": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.7.4.tgz", - "integrity": "sha512-u2B8TIi0qZI4j8q4C51ktfO7E3cQ0qnaXFI1/OXITordD40tt17g/sXqgNNCcMTcBFKrUPcGDx+TBJuZxLx7tw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.8.3.tgz", + "integrity": "sha512-evhTyWhbwbI3/U6dZAnx/ePoV7H6OUG+OjiJFHmhr9FPn0VShjwC2kdxqIuQ/+1P50TMrneGzMeyMTFOjKSnAw==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.7.4", - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-module-transforms": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.7.4.tgz", - "integrity": "sha512-jBUkiqLKvUWpv9GLSuHUFYdmHg0ujC1JEYoZUfeOOfNydZXp1sXObgyPatpcwjWgsdBGsagWW0cdJpX/DO2jMw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz", + "integrity": "sha512-f+tF/8UVPU86TrCb06JoPWIdDpTNSGGcAtaD9mLP0aYGA0OS0j7j7DHJR0GTFrUZPUU6loZhbsVZgTh0N+Qdnw==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.7.4" + "@babel/helper-create-regexp-features-plugin": "^7.8.3" } }, "@babel/plugin-transform-new-target": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.7.4.tgz", - "integrity": "sha512-CnPRiNtOG1vRodnsyGX37bHQleHE14B9dnnlgSeEs3ek3fHN1A1SScglTCg1sfbe7sRQ2BUcpgpTpWSfMKz3gg==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.8.3.tgz", + "integrity": "sha512-QuSGysibQpyxexRyui2vca+Cmbljo8bcRckgzYV4kRIsHpVeyeC3JDO63pY+xFZ6bWOBn7pfKZTqV4o/ix9sFw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-transform-object-super": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.7.4.tgz", - "integrity": "sha512-ho+dAEhC2aRnff2JCA0SAK7V2R62zJd/7dmtoe7MHcso4C2mS+vZjn1Pb1pCVZvJs1mgsvv5+7sT+m3Bysb6eg==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.8.3.tgz", + "integrity": "sha512-57FXk+gItG/GejofIyLIgBKTas4+pEU47IXKDBWFTxdPd7F80H8zybyAY7UoblVfBhBGs2EKM+bJUu2+iUYPDQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-replace-supers": "^7.7.4" + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-replace-supers": "^7.8.3" } }, "@babel/plugin-transform-parameters": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.7.4.tgz", - "integrity": "sha512-VJwhVePWPa0DqE9vcfptaJSzNDKrWU/4FbYCjZERtmqEs05g3UMXnYMZoXja7JAJ7Y7sPZipwm/pGApZt7wHlw==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.8.4.tgz", + "integrity": "sha512-IsS3oTxeTsZlE5KqzTbcC2sV0P9pXdec53SU+Yxv7o/6dvGM5AkTotQKhoSffhNgZ/dftsSiOoxy7evCYJXzVA==", "dev": true, "requires": { - "@babel/helper-call-delegate": "^7.7.4", - "@babel/helper-get-function-arity": "^7.7.4", - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-call-delegate": "^7.8.3", + "@babel/helper-get-function-arity": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3" }, "dependencies": { "@babel/helper-get-function-arity": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.4.tgz", - "integrity": "sha512-QTGKEdCkjgzgfJ3bAyRwF4yyT3pg+vDgan8DSivq1eS0gwi+KGKE5x8kRcbeFTb/673mkO5SN1IZfmCfA5o+EA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", + "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", "dev": true, "requires": { - "@babel/types": "^7.7.4" + "@babel/types": "^7.8.3" } }, "@babel/types": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", - "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", + "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -3039,162 +3281,172 @@ } }, "@babel/plugin-transform-property-literals": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.7.4.tgz", - "integrity": "sha512-MatJhlC4iHsIskWYyawl53KuHrt+kALSADLQQ/HkhTjX954fkxIEh4q5slL4oRAnsm/eDoZ4q0CIZpcqBuxhJQ==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.8.3.tgz", + "integrity": "sha512-uGiiXAZMqEoQhRWMK17VospMZh5sXWg+dlh2soffpkAl96KAm+WZuJfa6lcELotSRmooLqg0MWdH6UUq85nmmg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-transform-regenerator": { - "version": "7.7.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.7.5.tgz", - "integrity": "sha512-/8I8tPvX2FkuEyWbjRCt4qTAgZK0DVy8QRguhA524UH48RfGJy94On2ri+dCuwOpcerPRl9O4ebQkRcVzIaGBw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.8.3.tgz", + "integrity": "sha512-qt/kcur/FxrQrzFR432FGZznkVAjiyFtCOANjkAKwCbt465L6ZCiUQh2oMYGU3Wo8LRFJxNDFwWn106S5wVUNA==", "dev": true, "requires": { "regenerator-transform": "^0.14.0" } }, "@babel/plugin-transform-reserved-words": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.7.4.tgz", - "integrity": "sha512-OrPiUB5s5XvkCO1lS7D8ZtHcswIC57j62acAnJZKqGGnHP+TIc/ljQSrgdX/QyOTdEK5COAhuc820Hi1q2UgLQ==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.8.3.tgz", + "integrity": "sha512-mwMxcycN3omKFDjDQUl+8zyMsBfjRFr0Zn/64I41pmjv4NJuqcYlEtezwYtw9TFd9WR1vN5kiM+O0gMZzO6L0A==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-transform-shorthand-properties": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.7.4.tgz", - "integrity": "sha512-q+suddWRfIcnyG5YiDP58sT65AJDZSUhXQDZE3r04AuqD6d/XLaQPPXSBzP2zGerkgBivqtQm9XKGLuHqBID6Q==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.8.3.tgz", + "integrity": "sha512-I9DI6Odg0JJwxCHzbzW08ggMdCezoWcuQRz3ptdudgwaHxTjxw5HgdFJmZIkIMlRymL6YiZcped4TTCB0JcC8w==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-transform-spread": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.7.4.tgz", - "integrity": "sha512-8OSs0FLe5/80cndziPlg4R0K6HcWSM0zyNhHhLsmw/Nc5MaA49cAsnoJ/t/YZf8qkG7fD+UjTRaApVDB526d7Q==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.8.3.tgz", + "integrity": "sha512-CkuTU9mbmAoFOI1tklFWYYbzX5qCIZVXPVy0jpXgGwkplCndQAa58s2jr66fTeQnA64bDox0HL4U56CFYoyC7g==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-transform-sticky-regex": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.7.4.tgz", - "integrity": "sha512-Ls2NASyL6qtVe1H1hXts9yuEeONV2TJZmplLONkMPUG158CtmnrzW5Q5teibM5UVOFjG0D3IC5mzXR6pPpUY7A==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.8.3.tgz", + "integrity": "sha512-9Spq0vGCD5Bb4Z/ZXXSK5wbbLFMG085qd2vhL1JYu1WcQ5bXqZBAYRzU1d+p79GcHs2szYv5pVQCX13QgldaWw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-regex": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-regex": "^7.8.3" } }, "@babel/plugin-transform-template-literals": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.7.4.tgz", - "integrity": "sha512-sA+KxLwF3QwGj5abMHkHgshp9+rRz+oY9uoRil4CyLtgEuE/88dpkeWgNk5qKVsJE9iSfly3nvHapdRiIS2wnQ==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.8.3.tgz", + "integrity": "sha512-820QBtykIQOLFT8NZOcTRJ1UNuztIELe4p9DCgvj4NK+PwluSJ49we7s9FB1HIGNIYT7wFUJ0ar2QpCDj0escQ==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.7.4", - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-annotate-as-pure": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-transform-typeof-symbol": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.7.4.tgz", - "integrity": "sha512-KQPUQ/7mqe2m0B8VecdyaW5XcQYaePyl9R7IsKd+irzj6jvbhoGnRE+M0aNkyAzI07VfUQ9266L5xMARitV3wg==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.8.4.tgz", + "integrity": "sha512-2QKyfjGdvuNfHsb7qnBBlKclbD4CfshH2KvDabiijLMGXPHJXGxtDzwIF7bQP+T0ysw8fYTtxPafgfs/c1Lrqg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-transform-unicode-regex": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.7.4.tgz", - "integrity": "sha512-N77UUIV+WCvE+5yHw+oks3m18/umd7y392Zv7mYTpFqHtkpcc+QUz+gLJNTWVlWROIWeLqY0f3OjZxV5TcXnRw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.8.3.tgz", + "integrity": "sha512-+ufgJjYdmWfSQ+6NS9VGUR2ns8cjJjYbrbi11mZBTaWm+Fui/ncTLFF28Ei1okavY+xkojGr1eJxNsWYeA5aZw==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.7.4", - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-create-regexp-features-plugin": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/preset-env": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.5.5.tgz", - "integrity": "sha512-GMZQka/+INwsMz1A5UEql8tG015h5j/qjptpKY2gJ7giy8ohzU710YciJB5rcKsWGWHiW3RUnHib0E5/m3Tp3A==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.0.0", - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-async-generator-functions": "^7.2.0", - "@babel/plugin-proposal-dynamic-import": "^7.5.0", - "@babel/plugin-proposal-json-strings": "^7.2.0", - "@babel/plugin-proposal-object-rest-spread": "^7.5.5", - "@babel/plugin-proposal-optional-catch-binding": "^7.2.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-syntax-async-generators": "^7.2.0", - "@babel/plugin-syntax-dynamic-import": "^7.2.0", - "@babel/plugin-syntax-json-strings": "^7.2.0", - "@babel/plugin-syntax-object-rest-spread": "^7.2.0", - "@babel/plugin-syntax-optional-catch-binding": "^7.2.0", - "@babel/plugin-transform-arrow-functions": "^7.2.0", - "@babel/plugin-transform-async-to-generator": "^7.5.0", - "@babel/plugin-transform-block-scoped-functions": "^7.2.0", - "@babel/plugin-transform-block-scoping": "^7.5.5", - "@babel/plugin-transform-classes": "^7.5.5", - "@babel/plugin-transform-computed-properties": "^7.2.0", - "@babel/plugin-transform-destructuring": "^7.5.0", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/plugin-transform-duplicate-keys": "^7.5.0", - "@babel/plugin-transform-exponentiation-operator": "^7.2.0", - "@babel/plugin-transform-for-of": "^7.4.4", - "@babel/plugin-transform-function-name": "^7.4.4", - "@babel/plugin-transform-literals": "^7.2.0", - "@babel/plugin-transform-member-expression-literals": "^7.2.0", - "@babel/plugin-transform-modules-amd": "^7.5.0", - "@babel/plugin-transform-modules-commonjs": "^7.5.0", - "@babel/plugin-transform-modules-systemjs": "^7.5.0", - "@babel/plugin-transform-modules-umd": "^7.2.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.4.5", - "@babel/plugin-transform-new-target": "^7.4.4", - "@babel/plugin-transform-object-super": "^7.5.5", - "@babel/plugin-transform-parameters": "^7.4.4", - "@babel/plugin-transform-property-literals": "^7.2.0", - "@babel/plugin-transform-regenerator": "^7.4.5", - "@babel/plugin-transform-reserved-words": "^7.2.0", - "@babel/plugin-transform-shorthand-properties": "^7.2.0", - "@babel/plugin-transform-spread": "^7.2.0", - "@babel/plugin-transform-sticky-regex": "^7.2.0", - "@babel/plugin-transform-template-literals": "^7.4.4", - "@babel/plugin-transform-typeof-symbol": "^7.2.0", - "@babel/plugin-transform-unicode-regex": "^7.4.4", - "@babel/types": "^7.5.5", - "browserslist": "^4.6.0", - "core-js-compat": "^3.1.1", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.8.3.tgz", + "integrity": "sha512-Rs4RPL2KjSLSE2mWAx5/iCH+GC1ikKdxPrhnRS6PfFVaiZeom22VFKN4X8ZthyN61kAaR05tfXTbCvatl9WIQg==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.8.0", + "@babel/helper-compilation-targets": "^7.8.3", + "@babel/helper-module-imports": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-proposal-async-generator-functions": "^7.8.3", + "@babel/plugin-proposal-dynamic-import": "^7.8.3", + "@babel/plugin-proposal-json-strings": "^7.8.3", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-proposal-object-rest-spread": "^7.8.3", + "@babel/plugin-proposal-optional-catch-binding": "^7.8.3", + "@babel/plugin-proposal-optional-chaining": "^7.8.3", + "@babel/plugin-proposal-unicode-property-regex": "^7.8.3", + "@babel/plugin-syntax-async-generators": "^7.8.0", + "@babel/plugin-syntax-dynamic-import": "^7.8.0", + "@babel/plugin-syntax-json-strings": "^7.8.0", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0", + "@babel/plugin-syntax-object-rest-spread": "^7.8.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.0", + "@babel/plugin-syntax-top-level-await": "^7.8.3", + "@babel/plugin-transform-arrow-functions": "^7.8.3", + "@babel/plugin-transform-async-to-generator": "^7.8.3", + "@babel/plugin-transform-block-scoped-functions": "^7.8.3", + "@babel/plugin-transform-block-scoping": "^7.8.3", + "@babel/plugin-transform-classes": "^7.8.3", + "@babel/plugin-transform-computed-properties": "^7.8.3", + "@babel/plugin-transform-destructuring": "^7.8.3", + "@babel/plugin-transform-dotall-regex": "^7.8.3", + "@babel/plugin-transform-duplicate-keys": "^7.8.3", + "@babel/plugin-transform-exponentiation-operator": "^7.8.3", + "@babel/plugin-transform-for-of": "^7.8.3", + "@babel/plugin-transform-function-name": "^7.8.3", + "@babel/plugin-transform-literals": "^7.8.3", + "@babel/plugin-transform-member-expression-literals": "^7.8.3", + "@babel/plugin-transform-modules-amd": "^7.8.3", + "@babel/plugin-transform-modules-commonjs": "^7.8.3", + "@babel/plugin-transform-modules-systemjs": "^7.8.3", + "@babel/plugin-transform-modules-umd": "^7.8.3", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.8.3", + "@babel/plugin-transform-new-target": "^7.8.3", + "@babel/plugin-transform-object-super": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.8.3", + "@babel/plugin-transform-property-literals": "^7.8.3", + "@babel/plugin-transform-regenerator": "^7.8.3", + "@babel/plugin-transform-reserved-words": "^7.8.3", + "@babel/plugin-transform-shorthand-properties": "^7.8.3", + "@babel/plugin-transform-spread": "^7.8.3", + "@babel/plugin-transform-sticky-regex": "^7.8.3", + "@babel/plugin-transform-template-literals": "^7.8.3", + "@babel/plugin-transform-typeof-symbol": "^7.8.3", + "@babel/plugin-transform-unicode-regex": "^7.8.3", + "@babel/types": "^7.8.3", + "browserslist": "^4.8.2", + "core-js-compat": "^3.6.2", "invariant": "^2.2.2", - "js-levenshtein": "^1.1.3", + "levenary": "^1.1.0", "semver": "^5.5.0" + }, + "dependencies": { + "@babel/types": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", + "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/runtime": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.2.tgz", - "integrity": "sha512-JONRbXbTXc9WQE2mAZd1p0Z3DZ/6vaQIkgYMSTP3KjRCyd7rCZCcfhCyX+YjwcKxcZ82UrxbRD358bpExNgrjw==", - "requires": { - "regenerator-runtime": "^0.13.2" - } - }, - "@babel/runtime-corejs3": { - "version": "7.7.6", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.7.6.tgz", - "integrity": "sha512-NrRUehqG0sMSCaP+0XV/vOvvjNl4BQOWq3Qys1Q2KTEm5tGMo9h0dHnIzeKerj0a7SIB8LP5kYg/T1raE3FoKQ==", - "dev": true, + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.4.tgz", + "integrity": "sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ==", "requires": { - "core-js-pure": "^3.0.0", "regenerator-runtime": "^0.13.2" } }, @@ -3282,14 +3534,14 @@ } }, "@emotion/hash": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.7.3.tgz", - "integrity": "sha512-14ZVlsB9akwvydAdaEnVnvqu6J2P6ySv39hYyl/aoB6w/V+bXX0tay8cF6paqbgZsN2n5Xh15uF4pE+GvE+itw==" + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.7.4.tgz", + "integrity": "sha512-fxfMSBMX3tlIbKUdtGKxqB1fyrH6gVrX39Gsv3y8lRYKUqlgDt3UMqQyGnR1bQMa2B8aGnhLZokZgg8vT0Le+A==" }, "@flowjs/flow.js": { - "version": "2.13.2", - "resolved": "https://registry.npmjs.org/@flowjs/flow.js/-/flow.js-2.13.2.tgz", - "integrity": "sha512-N2uoQ+F8E/l3JiSoU/hIwUPEjCPDUvWeCJei0S5vA3guqSY8JtgIZacuhNC6B6TYY5cGWGR/qCOSR6v6S/K0aA==" + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@flowjs/flow.js/-/flow.js-2.14.0.tgz", + "integrity": "sha512-XRzYcgT4YcnMVu5vXZl+bai6tyiMfxopzroqG2mjgnovqlslUsi3/adYEaXjv3nA830rR+vfVm2TjQ8h/dkQgg==" }, "@flowjs/ngx-flow": { "version": "0.4.3", @@ -3300,6 +3552,12 @@ "tslib": "^1.9.0" } }, + "@istanbuljs/schema": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz", + "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", + "dev": true + }, "@mat-datetimepicker/core": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@mat-datetimepicker/core/-/core-2.0.1.tgz", @@ -3309,19 +3567,19 @@ } }, "@material-ui/core": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.7.2.tgz", - "integrity": "sha512-ZbeO6xshTEHcMU2jMNjBY26u9p5ILQFj0y7HvOPZ9WT6POaN6qNKYX2PdXnnRDE1MpN8W2K1cxM4KKkiYWNkCQ==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.9.1.tgz", + "integrity": "sha512-wehQI0ahHDsZjK+uA8Q5Cs1K1/1HXYe2icwTqARaRCt7d9bTp0bJN/C9TLe/+sRWfRIkx6OIk7ABSJT1jBqxRg==", "requires": { "@babel/runtime": "^7.4.4", - "@material-ui/styles": "^4.7.1", - "@material-ui/system": "^4.7.1", - "@material-ui/types": "^4.1.1", + "@material-ui/styles": "^4.9.0", + "@material-ui/system": "^4.9.1", + "@material-ui/types": "^5.0.0", "@material-ui/utils": "^4.7.1", "@types/react-transition-group": "^4.2.0", "clsx": "^1.0.2", "convert-css-length": "^2.0.1", - "hoist-non-react-statics": "^3.2.1", + "hoist-non-react-statics": "^3.3.2", "normalize-scroll-left": "^0.2.0", "popper.js": "^1.14.1", "prop-types": "^15.7.2", @@ -3330,19 +3588,20 @@ } }, "@material-ui/icons": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.5.1.tgz", - "integrity": "sha512-YZ/BgJbXX4a0gOuKWb30mBaHaoXRqPanlePam83JQPZ/y4kl+3aW0Wv9tlR70hB5EGAkEJGW5m4ktJwMgxQAeA==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.9.1.tgz", + "integrity": "sha512-GBitL3oBWO0hzBhvA9KxqcowRUsA0qzwKkURyC8nppnC3fw54KPKZ+d4V1Eeg/UnDRSzDaI9nGCdel/eh9AQMg==", "requires": { "@babel/runtime": "^7.4.4" } }, "@material-ui/pickers": { - "version": "3.2.8", - "resolved": "https://registry.npmjs.org/@material-ui/pickers/-/pickers-3.2.8.tgz", - "integrity": "sha512-uWGIUpfxPXZItCWnnF3ZSWgjv40wnlhceG6QhSQ1ARpqxU/4TEcnflR7FO54ULKOsyjwF+sgWPrqV/rPgGtPXA==", + "version": "3.2.10", + "resolved": "https://registry.npmjs.org/@material-ui/pickers/-/pickers-3.2.10.tgz", + "integrity": "sha512-B8G6Obn5S3RCl7hwahkQj9sKUapwXWFjiaz/Bsw1fhYFdNMnDUolRiWQSoKPb1/oKe37Dtfszoywi1Ynbo3y8w==", "requires": { "@babel/runtime": "^7.6.0", + "@date-io/core": "1.x", "@types/styled-jsx": "^2.2.8", "clsx": "^1.0.2", "react-transition-group": "^4.0.0", @@ -3350,32 +3609,32 @@ } }, "@material-ui/styles": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.7.1.tgz", - "integrity": "sha512-BBfxVThaPrglqHmKtSdrZJxnbFGJqKdZ5ZvDarj3HsmkteGCXsP1ohrDi5TWoa5JEJFo9S6q6NywqsENZn9rZA==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.9.0.tgz", + "integrity": "sha512-nJHum4RqYBPWsjL/9JET8Z02FZ9gSizlg/7LWVFpIthNzpK6OQ5OSRR4T4x9/p+wK3t1qNn3b1uI4XpnZaPxOA==", "requires": { "@babel/runtime": "^7.4.4", - "@emotion/hash": "^0.7.1", - "@material-ui/types": "^4.1.1", + "@emotion/hash": "^0.7.4", + "@material-ui/types": "^5.0.0", "@material-ui/utils": "^4.7.1", "clsx": "^1.0.2", "csstype": "^2.5.2", "hoist-non-react-statics": "^3.2.1", - "jss": "^10.0.0", - "jss-plugin-camel-case": "^10.0.0", - "jss-plugin-default-unit": "^10.0.0", - "jss-plugin-global": "^10.0.0", - "jss-plugin-nested": "^10.0.0", - "jss-plugin-props-sort": "^10.0.0", - "jss-plugin-rule-value-function": "^10.0.0", - "jss-plugin-vendor-prefixer": "^10.0.0", + "jss": "^10.0.3", + "jss-plugin-camel-case": "^10.0.3", + "jss-plugin-default-unit": "^10.0.3", + "jss-plugin-global": "^10.0.3", + "jss-plugin-nested": "^10.0.3", + "jss-plugin-props-sort": "^10.0.3", + "jss-plugin-rule-value-function": "^10.0.3", + "jss-plugin-vendor-prefixer": "^10.0.3", "prop-types": "^15.7.2" } }, "@material-ui/system": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.7.1.tgz", - "integrity": "sha512-zH02p+FOimXLSKOW/OT2laYkl9bB3dD1AvnZqsHYoseUaq0aVrpbl2BGjQi+vJ5lg8w73uYlt9zOWzb3+1UdMQ==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.9.1.tgz", + "integrity": "sha512-CLrJK2aKNWNwruGVTRf+rLz96P4jmozpY2UaCE6hBTa1oGsQ396YXOQQABQ4c0igawmdyf5iQb0zs9j5zsAf1w==", "requires": { "@babel/runtime": "^7.4.4", "@material-ui/utils": "^4.7.1", @@ -3383,12 +3642,9 @@ } }, "@material-ui/types": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@material-ui/types/-/types-4.1.1.tgz", - "integrity": "sha512-AN+GZNXytX9yxGi0JOfxHrRTbhFybjUJ05rnsBVjcB+16e466Z0Xe5IxawuOayVZgTBNDxmPKo5j4V6OnMtaSQ==", - "requires": { - "@types/react": "*" - } + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@material-ui/types/-/types-5.0.0.tgz", + "integrity": "sha512-UeH2BuKkwDndtMSS0qgx1kCzSMw+ydtj0xx/XbFtxNSTlXydKwzs5gVW5ZKsFlAkwoOOQ9TIsyoCC8hq18tOwg==" }, "@material-ui/utils": { "version": "4.7.1", @@ -3401,46 +3657,33 @@ } }, "@ngrx/effects": { - "version": "8.5.2", - "resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-8.5.2.tgz", - "integrity": "sha512-i2rKLmFcfJmPPQRul1PuXXRuBX2q1g7tK6DbWreNabU/46fYlEwqiiW6lU53t3AgJ3yd6UpeLTR6CHTflkzL+w==" + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-8.6.0.tgz", + "integrity": "sha512-JdyJLQbv/wnE0ZPY9DcDOtF9PzJuzsKWmIWgIGunHF18wdjk5O8Zpkcrxq18wDRL6geg5UTtNJRMvTQhpDbzow==" }, "@ngrx/store": { - "version": "8.5.2", - "resolved": "https://registry.npmjs.org/@ngrx/store/-/store-8.5.2.tgz", - "integrity": "sha512-JPlc23Aw3rlEKt6LCkg3a0zlo0tEgkohH3CDHVbUIYSgg3DWOnmNfwztbz4pa2u2wua5PfFCovC7HKTNmapx/w==" + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@ngrx/store/-/store-8.6.0.tgz", + "integrity": "sha512-K4cvCEa+5hw9qrETQWO+Cha3YbVCAT8yaIKJr/N35KntTL9mQMjoL+51JWLZfBwPV0e19CFgJIyrBnVUTxwr2A==" }, "@ngrx/store-devtools": { - "version": "8.5.2", - "resolved": "https://registry.npmjs.org/@ngrx/store-devtools/-/store-devtools-8.5.2.tgz", - "integrity": "sha512-3GrAAX3/J39u0AcREgWBiUwuNkZhgei+2K6/bulkAu/BHw+PJaZqq5+c+uQFvi0/aq+/8+9wjNhhCWS4Entk/Q==" + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@ngrx/store-devtools/-/store-devtools-8.6.0.tgz", + "integrity": "sha512-PWZmiOZE0J56GFfZpuzKLb7w0K2c6OXZSp/eWDeAvtdHFD4/Nas1i4TXtiWWMWWnSZeNs0hNIg4nFJXi2EddJQ==" }, "@ngtools/webpack": { - "version": "8.3.20", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-8.3.20.tgz", - "integrity": "sha512-2e9Kat6PQEzqtNsZZpnOIvoDzyGwMELiuBYBa9keZeaXOD6TxjSyCRzHHXAldAXqvh4Uj2qjTid54Sy14CxtsQ==", + "version": "8.3.24", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-8.3.24.tgz", + "integrity": "sha512-OpR7t/99qNOpADayCuM67agBVdYkdbFyEEcOLaDFYh3LsefHOSSxtAGv8M77e7dguvtaljHTiVkMxgcXFsZM0Q==", "dev": true, "requires": { - "@angular-devkit/core": "8.3.20", + "@angular-devkit/core": "8.3.24", "enhanced-resolve": "4.1.0", "rxjs": "6.4.0", - "tree-kill": "1.2.1", + "tree-kill": "1.2.2", "webpack-sources": "1.4.3" }, "dependencies": { - "@angular-devkit/core": { - "version": "8.3.20", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-8.3.20.tgz", - "integrity": "sha512-UCfW/BJBJnioJU34QennQhA4o+rLoCXWiSrI2LM7yw8/MEM9I8KbqRETP1My3HjHkQnvP+Qh3noedpcu3Nnt8A==", - "dev": true, - "requires": { - "ajv": "6.10.2", - "fast-json-stable-stringify": "2.0.0", - "magic-string": "0.25.3", - "rxjs": "6.4.0", - "source-map": "0.7.3" - } - }, "rxjs": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", @@ -3449,30 +3692,6 @@ "requires": { "tslib": "^1.9.0" } - }, - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true - }, - "webpack-sources": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", - "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", - "dev": true, - "requires": { - "source-list-map": "^2.0.0", - "source-map": "~0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } } } }, @@ -3501,23 +3720,23 @@ } }, "@schematics/angular": { - "version": "8.3.20", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-8.3.20.tgz", - "integrity": "sha512-Y20pSJhQ0KQd8Tk2kPQlmpRDNDaoIKMeOOGLT2FgCFrumxZXuIbBgN9fGDgW40iI2sq80bccOeo24RKkn3QpcA==", + "version": "8.3.24", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-8.3.24.tgz", + "integrity": "sha512-0nf/LgMHAvhjWS97Pl3JGMqS9/4PI+C0+vJoAo6D7ax8Fb+wuY5uD6Pb7ZqaZALlEnqTgE+FBQ1K8VBVbuwKbA==", "dev": true, "requires": { - "@angular-devkit/core": "8.3.20", - "@angular-devkit/schematics": "8.3.20" + "@angular-devkit/core": "8.3.24", + "@angular-devkit/schematics": "8.3.24" } }, "@schematics/update": { - "version": "0.803.20", - "resolved": "https://registry.npmjs.org/@schematics/update/-/update-0.803.20.tgz", - "integrity": "sha512-MseLreuHdnSLUEnRxZFVSHKKK+3mGXH12SgOSeirwATIL22Df74+Q5BYvsge/Kd2k6s9ak/NCuRXG7FAo8mkMA==", + "version": "0.803.24", + "resolved": "https://registry.npmjs.org/@schematics/update/-/update-0.803.24.tgz", + "integrity": "sha512-NvCKn3QfpRjx1EzL56q9IC9fRtDXZP4bMGs/2tj+wtdBNHgm6ZJMJ9qc4mGeztKGbDFLmnX3Xz0XawAl+KeYzQ==", "dev": true, "requires": { - "@angular-devkit/core": "8.3.20", - "@angular-devkit/schematics": "8.3.20", + "@angular-devkit/core": "8.3.24", + "@angular-devkit/schematics": "8.3.24", "@yarnpkg/lockfile": "1.1.0", "ini": "1.3.5", "pacote": "9.5.5", @@ -3575,9 +3794,9 @@ } }, "@types/jasmine": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.5.0.tgz", - "integrity": "sha512-kGCRI9oiCxFS6soGKlyzhMzDydfcPix9PpTkr7h11huxOxhWwP37Tg7DYBaQ18eQTNreZEuLkhpbGSqVNZPnnw==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.5.2.tgz", + "integrity": "sha512-7hrdBDFWlTb4EhrXYRyC7i3L2kKCV0TqYbzuV+gwyPNF2V4SSHw2Vs223ai26W4tEg+t4e9Wfi1vW6JLubYPiw==", "dev": true }, "@types/jasminewd2": { @@ -3624,9 +3843,9 @@ "integrity": "sha512-13gmo3M2qVvjQrWNseqM3+cR6S2Ss3grbR2NZltgMq94wOwqJYQdgn8qzwDshzgXqMlSUtyPZjysImmktu22ew==" }, "@types/node": { - "version": "12.12.17", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.17.tgz", - "integrity": "sha512-Is+l3mcHvs47sKy+afn2O1rV4ldZFU7W8101cNlOd+MRbjM4Onida8jSZnJdTe/0Pcf25g9BNIUsuugmE6puHA==", + "version": "12.12.26", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.26.tgz", + "integrity": "sha512-UmUm94/QZvU5xLcUlNR8hA7Ac+fGpO1EG/a8bcWVz0P0LqtxFmun9Y2bbtuckwGboWJIT70DoWq1r3hb56n3DA==", "dev": true }, "@types/prop-types": { @@ -3647,18 +3866,18 @@ "dev": true }, "@types/react": { - "version": "16.9.16", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.16.tgz", - "integrity": "sha512-dQ3wlehuBbYlfvRXfF5G+5TbZF3xqgkikK7DWAsQXe2KnzV+kjD4W2ea+ThCrKASZn9h98bjjPzoTYzfRqyBkw==", + "version": "16.9.19", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.19.tgz", + "integrity": "sha512-LJV97//H+zqKWMms0kvxaKYJDG05U2TtQB3chRLF8MPNs+MQh/H1aGlyDUxjaHvu08EAGerdX2z4LTBc7ns77A==", "requires": { "@types/prop-types": "*", "csstype": "^2.2.0" } }, "@types/react-dom": { - "version": "16.9.4", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.4.tgz", - "integrity": "sha512-fya9xteU/n90tda0s+FtN5Ym4tbgxpq/hb/Af24dvs6uYnYn+fspaxw5USlw0R8apDNwxsqumdRoCoKitckQqw==", + "version": "16.9.5", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.5.tgz", + "integrity": "sha512-BX6RQ8s9D+2/gDhxrj8OW+YD4R+8hj7FEM/OJHGNR0KipE1h1mSsf39YeyC81qafkq+N3rU3h3RFbLSwE5VqUg==", "dev": true, "requires": { "@types/react": "*" @@ -3713,9 +3932,9 @@ } }, "@types/webpack-sources": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-0.1.5.tgz", - "integrity": "sha512-zfvjpp7jiafSmrzJ2/i3LqOyTYTuJ7u1KOXlKgDlvsj9Rr0x7ZiYu5lZbXwobL7lmsRNtPXlBfmaUD8eU2Hu8w==", + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-0.1.6.tgz", + "integrity": "sha512-FtAWR7wR5ocJ9+nP137DV81tveD/ZgB1sadnJ/axUGM3BUVfRPx8oQNMtv3JNfTeHx3VP7cXiyfR/jmtEsVHsQ==", "dev": true, "requires": { "@types/node": "*", @@ -3943,9 +4162,9 @@ } }, "ace-builds": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.4.7.tgz", - "integrity": "sha512-gwQGVFewBopRLho08BfahyvRa9FlB43JUig5ItAKTYc9kJJsbA9QNz75p28QtQomoPQ9rJx82ymL21x4ZSZmdg==" + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.4.8.tgz", + "integrity": "sha512-8ZVAxwyCGAxQX8mOp9imSXH0hoSPkGfy8igJy+WO/7axL30saRhKgg1XPACSmxxPA7nfHVwM+ShWXT+vKsNuFg==" }, "acorn": { "version": "6.4.0", @@ -4032,9 +4251,9 @@ "dev": true }, "angular-gridster2": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/angular-gridster2/-/angular-gridster2-8.2.0.tgz", - "integrity": "sha512-O/LYOFovnDZHlZ6yidO9DDN6XGSGevabQebv37mlaxfbBu5mi/svbO+uhttDhIeKbUZAorbP9CMBWsCn1tzdvw==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/angular-gridster2/-/angular-gridster2-8.3.0.tgz", + "integrity": "sha512-uQ7StNeF2SWxYA099MPifnqoOe/16rAu6KN00/sQ6kJ3umLDkPYRA61z3iPAdrxEkYMdXmtoMbFkm4igWLA3Ww==", "requires": { "tslib": "^1.9.0" } @@ -4268,9 +4487,13 @@ "dev": true }, "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } }, "async-each": { "version": "1.0.3", @@ -4323,113 +4546,18 @@ "dev": true }, "aws4": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", + "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==", "dev": true }, "axobject-query": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.1.1.tgz", - "integrity": "sha512-lF98xa/yvy6j3fBHAgQXIYl+J4eZadOSqsPojemUqClzNbBV38wWGpUbQbVEyf4eUF5yF7eHmGgGA2JiHyjeqw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.7.4", - "@babel/runtime-corejs3": "^7.7.4" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.7.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.6.tgz", - "integrity": "sha512-BWAJxpNVa0QlE5gZdWjSxXtemZyZ9RmrmVozxt3NUXeZhVIJ5ANyqmMc0JDrivBZyxUuQvFxlvH4OWWOogGfUw==", - "dev": true, - "requires": { - "regenerator-runtime": "^0.13.2" - } - } - } - }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "babel-generator": { - "version": "6.26.1", - "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", - "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", - "dev": true, - "requires": { - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "detect-indent": "^4.0.0", - "jsesc": "^1.3.0", - "lodash": "^4.17.4", - "source-map": "^0.5.7", - "trim-right": "^1.0.1" - }, - "dependencies": { - "jsesc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", - "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "babel-messages": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", - "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.2.tgz", + "integrity": "sha512-MCeek8ZH7hKyO1rWUbKNQBbl4l2eY0ntk7OGi+q0RlafrCnfPxC06WZA+uebCfmYp4mNU9jRBP1AhGyf8+W3ww==", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "ast-types-flow": "0.0.7" } }, "babel-plugin-dynamic-import-node": { @@ -4451,9 +4579,9 @@ }, "dependencies": { "core-js": { - "version": "2.6.10", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.10.tgz", - "integrity": "sha512-I39t74+4t+zau64EN1fE5v2W31Adtc/REhzWN+gWRRXg6WH5qAsZm62DHpQ1+Yhe4047T55jvzz7MUqF/dBBlA==" + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", + "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==" }, "regenerator-runtime": { "version": "0.11.1", @@ -4462,70 +4590,6 @@ } } }, - "babel-template": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", - "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "lodash": "^4.17.4" - } - }, - "babel-traverse": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", - "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", - "dev": true, - "requires": { - "babel-code-frame": "^6.26.0", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "debug": "^2.6.8", - "globals": "^9.18.0", - "invariant": "^2.2.2", - "lodash": "^4.17.4" - }, - "dependencies": { - "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true - } - } - }, - "babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" - }, - "dependencies": { - "to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", - "dev": true - } - } - }, - "babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", - "dev": true - }, "backo2": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", @@ -4645,6 +4709,16 @@ "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", "dev": true }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dev": true, + "optional": true, + "requires": { + "file-uri-to-path": "1.0.0" + } + }, "blob": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", @@ -4822,14 +4896,14 @@ } }, "browserslist": { - "version": "4.6.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.6.6.tgz", - "integrity": "sha512-D2Nk3W9JL9Fp/gIcWei8LrERCS+eXu9AM5cfXA8WEZ84lFks+ARnZ0q/R69m2SV3Wjma83QDDPxsNKXUwdIsyA==", + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.3.tgz", + "integrity": "sha512-iU43cMMknxG1ClEZ2MDKeonKE1CCrFVkQK2AqO2YWFmvIrx4JWrvQ4w4hQez6EpVI8rHTtqh/ruHHDHSOKxvUg==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30000984", - "electron-to-chromium": "^1.3.191", - "node-releases": "^1.1.25" + "caniuse-lite": "^1.0.30001017", + "electron-to-chromium": "^1.3.322", + "node-releases": "^1.1.44" } }, "browserstack": { @@ -5010,9 +5084,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30000989", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000989.tgz", - "integrity": "sha512-vrMcvSuMz16YY6GSVZ0dWDTJP8jqk3iFQ/Aq5iqblPwxSVVZI+zxDyTX0VPqtQsDnfdrBDcsmhgTEOh5R8Lbpw==", + "version": "1.0.30001019", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001019.tgz", + "integrity": "sha512-6ljkLtF1KM5fQ+5ZN0wuyVvvebJxgJPTmScOMaFuQN2QuOzvRJnWSKfzQskQU5IOU4Gap3zasYPIinzwUjoj/g==", "dev": true }, "canonical-path": { @@ -5200,9 +5274,10 @@ } }, "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true }, "clone-deep": { "version": "4.0.1", @@ -5215,17 +5290,11 @@ "shallow-clone": "^3.0.0" } }, - "clsx": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.0.4.tgz", - "integrity": "sha512-1mQ557MIZTrL/140j+JVdRM6e31/OA4vTYxXgqIIZlndyfjHpyawKZia1Im05Vp9BWmImkcNrNtFYQMyFcgJDg==" - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, + "clsx": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.0.tgz", + "integrity": "sha512-3avwM37fSK5oP6M5rQ9CNe99lwxhXDOeSWVPAOYF6OazUTgZCMb0yWlJpmdD74REy1gkEaFiub2ULv4fq9GUhA==" + }, "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", @@ -5233,14 +5302,14 @@ "dev": true }, "codelyzer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/codelyzer/-/codelyzer-5.2.0.tgz", - "integrity": "sha512-izfUfhEOOgAizszPlEDxo71DK/C4wprZw0vkY6UWcOSTQvN1JyfXf9DXwaV7WX+/JC+hH0ShXfdtGLA9Rca7LA==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/codelyzer/-/codelyzer-5.2.1.tgz", + "integrity": "sha512-awBZXFcJUyC5HMYXiHzjr3D24tww2l1D1OqtfA9vUhEtYr32a65A+Gblm/OvsO+HuKLYzn8EDMw1inSM3VbxWA==", "dev": true, "requires": { "app-root-path": "^2.2.1", "aria-query": "^3.0.0", - "axobject-query": "^2.0.2", + "axobject-query": "2.0.2", "css-selector-tokenizer": "^0.7.1", "cssauron": "^1.4.0", "damerau-levenshtein": "^1.0.4", @@ -5357,12 +5426,20 @@ "dev": true }, "compressible": { - "version": "2.0.17", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.17.tgz", - "integrity": "sha512-BGHeLCK1GV7j1bSmQQAi26X+GgWcTjLr/0tzSvMCl3LH1w1IJ4PFSPoV5316b30cneTziC+B1a+3OjoSUcQYmw==", + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", "dev": true, "requires": { - "mime-db": ">= 1.40.0 < 2" + "mime-db": ">= 1.43.0 < 2" + }, + "dependencies": { + "mime-db": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", + "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==", + "dev": true + } } }, "compression": { @@ -5381,9 +5458,9 @@ } }, "compression-webpack-plugin": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/compression-webpack-plugin/-/compression-webpack-plugin-3.0.1.tgz", - "integrity": "sha512-FOwoBVzDiwSdJDnZTKXDpAjJU90k8SbChgxnoiYwTo15xjIDJkSC8wFKuc13DymXjgasPEqzS5+2RUgSKXdKKA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/compression-webpack-plugin/-/compression-webpack-plugin-3.1.0.tgz", + "integrity": "sha512-iqTHj3rADN4yHwXMBrQa/xrncex/uEQy8QHlaTKxGchT/hC0SdlJlmL/5eRqffmWq2ep0/Romw6Ld39JjTR/ug==", "dev": true, "requires": { "cacache": "^13.0.1", @@ -5420,45 +5497,15 @@ "unique-filename": "^1.1.1" } }, - "find-cache-dir": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.2.0.tgz", - "integrity": "sha512-1JKclkYYsf1q9WIJKLZa9S9muC+08RIjzAlLrK4QcYLJMS6mk9yombQ9qf+zJ7H9LS800k0s44L4sDq9VYzqyg==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^3.0.0", - "pkg-dir": "^4.1.0" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, "fs-minipass": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.0.0.tgz", - "integrity": "sha512-40Qz+LFXmd9tzYVnnBmZvFfvAADfUA14TXPK1s7IfElJTIZ97rA8w4Kin7Wt5JBrC3ShnnFJO/5vPjPEeJIq9A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", "dev": true, "requires": { "minipass": "^3.0.0" } }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, "lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -5476,15 +5523,6 @@ } } }, - "make-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.0.tgz", - "integrity": "sha512-grNJDhb8b1Jm1qeqW5R/O63wUo4UXo2v2HMic6YT9i/HBlF93S8jkMgH7yugvY9ABDShH4VZMn8I+U8+fCNegw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - } - }, "minipass": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.1.tgz", @@ -5494,15 +5532,6 @@ "yallist": "^4.0.0" } }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, "p-map": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", @@ -5512,43 +5541,16 @@ "aggregate-error": "^3.0.0" } }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - } - }, "schema-utils": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.1.tgz", - "integrity": "sha512-0WXHDs1VDJyo+Zqs9TKLKyD/h7yDpHUhEFsM2CzkICFdoX1av+GBq/J2xRTFfsQO5kBfhZzANf2VcIm84jqDbg==", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.4.tgz", + "integrity": "sha512-VNjcaUxVnEeun6B2fiiUDjXXBtD4ZSH7pdbfIu1pOFwgptDPLMo/z9jr4sUfsjFVPqDCEin/F7IYlq7/E6yDbQ==", "dev": true, "requires": { "ajv": "^6.10.2", "ajv-keywords": "^3.4.1" } }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "serialize-javascript": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz", - "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==", - "dev": true - }, "ssri": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/ssri/-/ssri-7.1.0.tgz", @@ -5685,12 +5687,12 @@ "dev": true }, "copy-webpack-plugin": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-5.0.4.tgz", - "integrity": "sha512-YBuYGpSzoCHSSDGyHy6VJ7SHojKp6WHT4D7ItcQFNAYx2hrwkMe56e97xfVR0/ovDuMTrMffXUiltvQljtAGeg==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-5.1.1.tgz", + "integrity": "sha512-P15M5ZC8dyCjQHWwd4Ia/dm0SgVvZJMYeykVIVYXbGyqO4dWB5oyPHp9i7wjwo5LhtlhKbiBCdS2NvM07Wlybg==", "dev": true, "requires": { - "cacache": "^11.3.3", + "cacache": "^12.0.3", "find-cache-dir": "^2.1.0", "glob-parent": "^3.1.0", "globby": "^7.1.1", @@ -5698,16 +5700,16 @@ "loader-utils": "^1.2.3", "minimatch": "^3.0.4", "normalize-path": "^3.0.0", - "p-limit": "^2.2.0", + "p-limit": "^2.2.1", "schema-utils": "^1.0.0", - "serialize-javascript": "^1.7.0", + "serialize-javascript": "^2.1.2", "webpack-log": "^2.0.0" }, "dependencies": { "cacache": { - "version": "11.3.3", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.3.tgz", - "integrity": "sha512-p8WcneCytvzPxhDvYp31PD039vi77I12W+/KfR9S8AZbaiARFBCpsPJS+9uhWfeBfeAtW7o/4vt3MUqLkbY6nA==", + "version": "12.0.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.3.tgz", + "integrity": "sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw==", "dev": true, "requires": { "bluebird": "^3.5.5", @@ -5715,6 +5717,7 @@ "figgy-pudding": "^3.5.1", "glob": "^7.1.4", "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", "lru-cache": "^5.1.1", "mississippi": "^3.0.0", "mkdirp": "^0.5.1", @@ -5755,51 +5758,28 @@ } }, "core-js": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.5.0.tgz", - "integrity": "sha512-Ifh3kj78gzQ7NAoJXeTu+XwzDld0QRIwjBLRqAMhuLhP3d2Av5wmgE9ycfnvK6NAEjTkQ1sDPeoEZAWO3Hx1Uw==" + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.4.tgz", + "integrity": "sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw==" }, "core-js-compat": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.5.0.tgz", - "integrity": "sha512-E7iJB72svRjJTnm9HDvujzNVMCm3ZcDYEedkJ/sDTNsy/0yooCd9Cg7GSzE7b4e0LfIkjijdB1tqg0pGwxWeWg==", + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.4.tgz", + "integrity": "sha512-zAa3IZPvsJ0slViBQ2z+vgyyTuhd3MFn1rBQjZSKVEgB0UMYhUkCj9jJUVPgGTGqWvsBVmfnruXgTcNyTlEiSA==", "dev": true, "requires": { - "browserslist": "^4.8.2", - "semver": "^6.3.0" + "browserslist": "^4.8.3", + "semver": "7.0.0" }, "dependencies": { - "browserslist": { - "version": "4.8.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.2.tgz", - "integrity": "sha512-+M4oeaTplPm/f1pXDw84YohEv7B1i/2Aisei8s4s6k3QsoSHa7i5sz8u/cGQkkatCPxMASKxPualR4wwYgVboA==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001015", - "electron-to-chromium": "^1.3.322", - "node-releases": "^1.1.42" - } - }, - "caniuse-lite": { - "version": "1.0.30001015", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001015.tgz", - "integrity": "sha512-/xL2AbW/XWHNu1gnIrO8UitBGoFthcsDgU9VLK1/dpsoxbaD5LscHozKze05R6WLsBvLhqv78dAPozMFQBYLbQ==", - "dev": true - }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", "dev": true } } }, - "core-js-pure": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.5.0.tgz", - "integrity": "sha512-wB0QtKAofWigiISuT1Tej3hKgq932fB//Lf1VoPbiLpTYlHY0nIDhgF+q1na0DAKFHH5wGCirkAknOmDN8ijXA==", - "dev": true - }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -5818,6 +5798,31 @@ "parse-json": "^4.0.0" } }, + "coverage-istanbul-loader": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/coverage-istanbul-loader/-/coverage-istanbul-loader-2.0.3.tgz", + "integrity": "sha512-LiGRvyIuzVYs3M1ZYK1tF0HekjH0DJ8zFdUwAZq378EJzqOgToyb1690dp3TAUlP6Y+82uu42LRjuROVeJ54CA==", + "dev": true, + "requires": { + "convert-source-map": "^1.7.0", + "istanbul-lib-instrument": "^4.0.0", + "loader-utils": "^1.2.3", + "merge-source-map": "^1.1.0", + "schema-utils": "^2.6.1" + }, + "dependencies": { + "schema-utils": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.4.tgz", + "integrity": "sha512-VNjcaUxVnEeun6B2fiiUDjXXBtD4ZSH7pdbfIu1pOFwgptDPLMo/z9jr4sUfsjFVPqDCEin/F7IYlq7/E6yDbQ==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "ajv-keywords": "^3.4.1" + } + } + } + }, "create-ecdh": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", @@ -5911,6 +5916,40 @@ "cssesc": "^0.1.0", "fastparse": "^1.1.1", "regexpu-core": "^1.0.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + }, + "regexpu-core": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", + "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=", + "dev": true, + "requires": { + "regenerate": "^1.2.1", + "regjsgen": "^0.2.0", + "regjsparser": "^0.1.4" + } + }, + "regjsgen": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", + "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", + "dev": true + }, + "regjsparser": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", + "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + } + } } }, "css-vendor": { @@ -5938,9 +5977,9 @@ "dev": true }, "csstype": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.7.tgz", - "integrity": "sha512-9Mcn9sFbGBAdmimWb2gLVDtFJzeKtDGIr76TUqmjZrw9LFXBMSU70lcs+C0/7fyCd6iBDqmksUcCOUIkisPHsQ==" + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.8.tgz", + "integrity": "sha512-msVS9qTuMT5zwAGCVm4mxfrZ18BNc6Csd0oJAtiFMZ1FAx1CCvy2+5MDmYoix63LM/6NDbNtodCiGYGmFgO0dA==" }, "custom-event": { "version": "1.0.1", @@ -5955,9 +5994,9 @@ "dev": true }, "damerau-levenshtein": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.5.tgz", - "integrity": "sha512-CBCRqFnpu715iPmw1KrdOrzRqbdFwQTwAWyyyYS42+iAgHCuXZ+/TdMgQkUENPomxEz9z1BEzuQU2Xw0kUuAgA==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz", + "integrity": "sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug==", "dev": true }, "dashdash": { @@ -6051,6 +6090,13 @@ "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", "requires": { "clone": "^1.0.2" + }, + "dependencies": { + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" + } } }, "define-properties": { @@ -6180,15 +6226,6 @@ "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", "dev": true }, - "detect-indent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", - "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", - "dev": true, - "requires": { - "repeating": "^2.0.0" - } - }, "detect-node": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", @@ -6274,9 +6311,9 @@ } }, "dom-align": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.10.2.tgz", - "integrity": "sha512-AYZUzLepy05E9bCY4ExoqHrrIlM49PEak9oF93JEFoibqKL0F7w5DLM70/rosLOawerWZ3MlepQcl+EmHskOyw==" + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.10.4.tgz", + "integrity": "sha512-wytDzaru67AmqFOY4B9GUb/hrwWagezoYYK97D/vpK+ezg+cnuZO0Q2gltUPa7KfNmIqfRIYVCF8UhRDEHAmgQ==" }, "dom-helpers": { "version": "5.1.3", @@ -6350,9 +6387,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.322", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.322.tgz", - "integrity": "sha512-Tc8JQEfGQ1MzfSzI/bTlSr7btJv/FFO7Yh6tanqVmIWOuNCu6/D1MilIEgLtmWqIrsv+o4IjpLAhgMBr/ncNAA==", + "version": "1.3.345", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.345.tgz", + "integrity": "sha512-f8nx53+Z9Y+SPWGg3YdHrbYYfIJAtbUjpFfW4X1RwTZ94iUG7geg9tV8HqzAXX7XTNgyWgAFvce4yce8ZKxKmg==", "dev": true }, "elliptic": { @@ -6528,21 +6565,33 @@ } }, "es-abstract": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.16.0.tgz", - "integrity": "sha512-xdQnfykZ9JMEiasTAJZJdMWCQ1Vm00NBw79/AWi7ELfZuuPCSOMDZbT9mkOfSctVtfhb+sAAzrm+j//GjjLHLg==", + "version": "1.17.4", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz", + "integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==", "dev": true, "requires": { - "es-to-primitive": "^1.2.0", + "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", "has": "^1.0.3", - "has-symbols": "^1.0.0", - "is-callable": "^1.1.4", - "is-regex": "^1.0.4", - "object-inspect": "^1.6.0", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", "object-keys": "^1.1.1", - "string.prototype.trimleft": "^2.1.0", - "string.prototype.trimright": "^2.1.0" + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" + }, + "dependencies": { + "is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + } } }, "es-to-primitive": { @@ -6638,9 +6687,9 @@ "dev": true }, "events": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", - "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.1.0.tgz", + "integrity": "sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg==", "dev": true }, "eventsource": { @@ -6932,9 +6981,9 @@ }, "dependencies": { "schema-utils": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.1.tgz", - "integrity": "sha512-0WXHDs1VDJyo+Zqs9TKLKyD/h7yDpHUhEFsM2CzkICFdoX1av+GBq/J2xRTFfsQO5kBfhZzANf2VcIm84jqDbg==", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.4.tgz", + "integrity": "sha512-VNjcaUxVnEeun6B2fiiUDjXXBtD4ZSH7pdbfIu1pOFwgptDPLMo/z9jr4sUfsjFVPqDCEin/F7IYlq7/E6yDbQ==", "dev": true, "requires": { "ajv": "^6.10.2", @@ -6951,6 +7000,13 @@ "tslib": "^1.9.0" } }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true, + "optional": true + }, "fileset": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz", @@ -7229,6 +7285,12 @@ "integrity": "sha512-KGDOARWVga7+rnB3z9Sd2Letx515owfk0hSxHGuqjANb1M+x2bGZGqHLiozPsYMdM2OubeMni/Hpwmjq6qIUhA==", "dev": true }, + "gensync": { + "version": "1.0.0-beta.1", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", + "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==", + "dev": true + }, "get-caller-file": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", @@ -7511,9 +7573,9 @@ } }, "hoist-non-react-statics": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", - "integrity": "sha512-wbg3bpgA/ZqWrZuMOeJi8+SKMhr7X9TesL/rXMjTzh0p0JUBo3II8DHboYbuIXWRlttrUFxwcu/5kygrCw8fJw==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", "requires": { "react-is": "^16.7.0" } @@ -7989,9 +8051,9 @@ "dev": true }, "is-callable": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", - "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", "dev": true }, "is-data-descriptor": { @@ -8056,15 +8118,6 @@ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true }, - "is-finite": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", - "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", @@ -8269,83 +8322,171 @@ } } }, - "istanbul-instrumenter-loader": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-instrumenter-loader/-/istanbul-instrumenter-loader-3.0.1.tgz", - "integrity": "sha512-a5SPObZgS0jB/ixaKSMdn6n/gXSrK2S6q/UfRJBT3e6gQmVjwZROTODQsYW5ZNwOu78hG62Y3fWlebaVOL0C+w==", + "istanbul-lib-coverage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", + "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", + "dev": true + }, + "istanbul-lib-hook": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-2.0.7.tgz", + "integrity": "sha512-vrRztU9VRRFDyC+aklfLoeXyNdTfga2EI3udDGn4cZ6fpSXpHLV9X6CHvfoMCPtggg8zvDDmC4b9xfu0z6/llA==", "dev": true, "requires": { - "convert-source-map": "^1.5.0", - "istanbul-lib-instrument": "^1.7.3", - "loader-utils": "^1.1.0", - "schema-utils": "^0.3.0" + "append-transform": "^1.0.0" + } + }, + "istanbul-lib-instrument": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.1.tgz", + "integrity": "sha512-imIchxnodll7pvQBYOqUu88EufLCU56LMeFPZZM/fJZ1irYcYdqroaV+ACK1Ila8ls09iEYArp+nqyC6lW1Vfg==", + "dev": true, + "requires": { + "@babel/core": "^7.7.5", + "@babel/parser": "^7.7.5", + "@babel/template": "^7.7.4", + "@babel/traverse": "^7.7.4", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" }, "dependencies": { - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.8.3" + } + }, + "@babel/generator": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", + "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", + "dev": true, + "requires": { + "@babel/types": "^7.8.3", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", + "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", + "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", + "dev": true, + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", + "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", + "dev": true, + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/highlight": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", + "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", + "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", + "dev": true + }, + "@babel/template": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", + "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/parser": "^7.8.3", + "@babel/types": "^7.8.3" + } + }, + "@babel/traverse": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", + "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.8.4", + "@babel/helper-function-name": "^7.8.3", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/parser": "^7.8.4", + "@babel/types": "^7.8.3", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + } + }, + "@babel/types": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", + "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" + "ms": "^2.1.1" } }, - "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true }, - "schema-utils": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.3.0.tgz", - "integrity": "sha1-9YdyIs4+kx7a4DnxfrNxbnE3+M8=", - "dev": true, - "requires": { - "ajv": "^5.0.0" - } + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true } } }, - "istanbul-lib-coverage": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz", - "integrity": "sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==", - "dev": true - }, - "istanbul-lib-hook": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-2.0.7.tgz", - "integrity": "sha512-vrRztU9VRRFDyC+aklfLoeXyNdTfga2EI3udDGn4cZ6fpSXpHLV9X6CHvfoMCPtggg8zvDDmC4b9xfu0z6/llA==", - "dev": true, - "requires": { - "append-transform": "^1.0.0" - } - }, - "istanbul-lib-instrument": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz", - "integrity": "sha512-aWHxfxDqvh/ZlxR8BBaEPVSWDPUkGD63VjGQn3jcw8jCp7sHEMKcrj4xfJn/ABzdMEHiQNyvDQhqm5o8+SQg7A==", - "dev": true, - "requires": { - "babel-generator": "^6.18.0", - "babel-template": "^6.16.0", - "babel-traverse": "^6.18.0", - "babel-types": "^6.18.0", - "babylon": "^6.18.0", - "istanbul-lib-coverage": "^1.2.1", - "semver": "^5.3.0" - } - }, "istanbul-lib-report": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz", @@ -8491,20 +8632,20 @@ "integrity": "sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw==" }, "jquery.terminal": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/jquery.terminal/-/jquery.terminal-2.9.0.tgz", - "integrity": "sha512-MK+/Or+9fcZDUZNvTXs4/PRYx4wYsyG65oxUzr8oK36UBrbTIIeHtg2SKW1jvWdR+jgP4V2rcmjcotHPUmkh5w==", + "version": "1.23.2", + "resolved": "https://registry.npmjs.org/jquery.terminal/-/jquery.terminal-1.23.2.tgz", + "integrity": "sha512-ULKxZNzL8W4CoeAx5CJZTVY80SrNoeetA4lhnBeHd792uaLAkfRXMeJeARLWhBOrzDWo1yqn2nm4td0a+EU0dg==", "requires": { - "@types/jquery": "^3.3.29", + "@types/jquery": "^3.3.6", "jquery": "~3", - "prismjs": "^1.16.0", + "prismjs": "^1.15.0", "wcwidth": "^1.0.1" } }, "js-beautify": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.10.2.tgz", - "integrity": "sha512-ZtBYyNUYJIsBWERnQP0rPN9KjkrDfJcMjuVGcvXOUJrD1zmOGwhRwQ4msG+HJ+Ni/FA7+sRQEMYVzdTQDvnzvQ==", + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.10.3.tgz", + "integrity": "sha512-wfk/IAWobz1TfApSdivH5PJ0miIHgDoYb1ugSqHcODPmaYu46rYe5FVuIEkhjg8IQiv6rDNPyhsqbsohI/C2vQ==", "requires": { "config-chain": "^1.1.12", "editorconfig": "^0.15.3", @@ -8513,12 +8654,6 @@ "nopt": "~4.0.1" } }, - "js-levenshtein": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", - "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", - "dev": true - }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -8629,9 +8764,9 @@ } }, "jss": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/jss/-/jss-10.0.0.tgz", - "integrity": "sha512-TPpDFsiBjuERiL+dFDq8QCdiF9oDasPcNqCKLGCo/qED3fNYOQ8PX2lZhknyTiAt3tZrfOFbb0lbQ9lTjPZxsQ==", + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/jss/-/jss-10.0.4.tgz", + "integrity": "sha512-GqHmeDK83qbqMAVjxyPfN1qJVTKZne533a9bdCrllZukUM8npG/k+JumEPI86IIB5ifaZAHG2HAsUziyxOiooQ==", "requires": { "@babel/runtime": "^7.3.1", "csstype": "^2.6.5", @@ -8640,69 +8775,69 @@ } }, "jss-plugin-camel-case": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.0.0.tgz", - "integrity": "sha512-yALDL00+pPR4FJh+k07A8FeDvfoPPuXU48HLy63enAubcVd3DnS+2rgqPXglHDGixIDVkCSXecl/l5GAMjzIbA==", + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.0.4.tgz", + "integrity": "sha512-+wnqxJsyfUnOn0LxVg3GgZBSjfBCrjxwx7LFxwVTUih0ceGaXKZoieheNOaTo5EM4w8bt1nbb8XonpQCj67C6A==", "requires": { "@babel/runtime": "^7.3.1", "hyphenate-style-name": "^1.0.3", - "jss": "10.0.0" + "jss": "10.0.4" } }, "jss-plugin-default-unit": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.0.0.tgz", - "integrity": "sha512-sURozIOdCtGg9ap18erQ+ijndAfEGtTaetxfU3H4qwC18Bi+fdvjlY/ahKbuu0ASs7R/+WKCP7UaRZOjUDMcdQ==", + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.0.4.tgz", + "integrity": "sha512-T0mhL/Ogp/quvod/jAHEqKvptLDxq7Cj3a+7zRuqK8HxUYkftptN89wJElZC3rshhNKiogkEYhCWenpJdFvTBg==", "requires": { "@babel/runtime": "^7.3.1", - "jss": "10.0.0" + "jss": "10.0.4" } }, "jss-plugin-global": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.0.0.tgz", - "integrity": "sha512-80ofWKSQUo62bxLtRoTNe0kFPtHgUbAJeOeR36WEGgWIBEsXLyXOnD5KNnjPqG4heuEkz9eSLccjYST50JnI7Q==", + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.0.4.tgz", + "integrity": "sha512-N8n9/GHENZce+sqE4UYiZiJtI+t+erT/BypHOrNYAfIoNEj7OYsOEKfIo2P0GpLB3QyDAYf5eo9XNdZ8veEkUA==", "requires": { "@babel/runtime": "^7.3.1", - "jss": "10.0.0" + "jss": "10.0.4" } }, "jss-plugin-nested": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.0.0.tgz", - "integrity": "sha512-waxxwl/po1hN3azTyixKnr8ReEqUv5WK7WsO+5AWB0bFndML5Yqnt8ARZ90HEg8/P6WlqE/AB2413TkCRZE8bA==", + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.0.4.tgz", + "integrity": "sha512-QM21BKVt8LDeoRfowvAMh/s+/89VYrreIIE6ch4pvw0oAXDWw1iorUPlqLZ7uCO3UL0uFtQhJq3QMLN6Lr1v0A==", "requires": { "@babel/runtime": "^7.3.1", - "jss": "10.0.0", + "jss": "10.0.4", "tiny-warning": "^1.0.2" } }, "jss-plugin-props-sort": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.0.0.tgz", - "integrity": "sha512-41mf22CImjwNdtOG3r+cdC8+RhwNm616sjHx5YlqTwtSJLyLFinbQC/a4PIFk8xqf1qpFH1kEAIw+yx9HaqZ3g==", + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.0.4.tgz", + "integrity": "sha512-WoETdOCjGskuin/OMt2uEdDPLZF3vfQuHXF+XUHGJrq0BAapoyGQDcv37SeReDlkRAbVXkEZPsIMvYrgHSHFiA==", "requires": { "@babel/runtime": "^7.3.1", - "jss": "10.0.0" + "jss": "10.0.4" } }, "jss-plugin-rule-value-function": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.0.0.tgz", - "integrity": "sha512-Jw+BZ8JIw1f12V0SERqGlBT1JEPWax3vuZpMym54NAXpPb7R1LYHiCTIlaJUyqvIfEy3kiHMtgI+r2whGgRIxQ==", + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.0.4.tgz", + "integrity": "sha512-0hrzOSWRF5ABJGaHrlnHbYZjU877Ofzfh2id3uLtBvemGQLHI+ldoL8/+6iPSRa7M8z8Ngfg2vfYhKjUA5gA0g==", "requires": { "@babel/runtime": "^7.3.1", - "jss": "10.0.0" + "jss": "10.0.4" } }, "jss-plugin-vendor-prefixer": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.0.0.tgz", - "integrity": "sha512-qslqvL0MUbWuzXJWdUxpj6mdNUX8jr4FFTo3aZnAT65nmzWL7g8oTr9ZxmTXXgdp7ANhS1QWE7036/Q2isFBpw==", + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.0.4.tgz", + "integrity": "sha512-4JgEbcrdeMda1qvxTm1CnxFJAWVV++VLpP46HNTrfH7VhVlvUpihnUNs2gAlKuRT/XSBuiWeLAkrTqF4NVrPig==", "requires": { "@babel/runtime": "^7.3.1", - "css-vendor": "^2.0.6", - "jss": "10.0.0" + "css-vendor": "^2.0.7", + "jss": "10.0.4" } }, "jstree": { @@ -8804,9 +8939,9 @@ } }, "karma-jasmine-html-reporter": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-1.4.2.tgz", - "integrity": "sha512-7g0gPj8+9JepCNJR9WjDyQ2RkZ375jpdurYQyAYv8PorUCadepl8vrD6LmMqOGcM17cnrynBawQYZHaumgDjBw==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-1.5.2.tgz", + "integrity": "sha512-ILBPsXqQ3eomq+oaQsM311/jxsypw5/d0LnZXj26XkfThwq7jZ55A2CFSKJVA5VekbbOGvMyv7d3juZj0SeTxA==", "dev": true }, "karma-source-map-support": { @@ -8854,14 +8989,6 @@ "promise": "^7.1.1", "request": "^2.83.0", "source-map": "~0.6.0" - }, - "dependencies": { - "clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", - "dev": true - } } }, "less-loader": { @@ -8873,14 +9000,21 @@ "clone": "^2.1.1", "loader-utils": "^1.1.0", "pify": "^4.0.1" - }, - "dependencies": { - "clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", - "dev": true - } + } + }, + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true + }, + "levenary": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/levenary/-/levenary-1.1.1.tgz", + "integrity": "sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ==", + "dev": true, + "requires": { + "leven": "^3.1.0" } }, "license-webpack-plugin": { @@ -9159,6 +9293,15 @@ "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", "dev": true }, + "merge-source-map": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", + "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", + "dev": true, + "requires": { + "source-map": "^0.6.1" + } + }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -9743,9 +9886,9 @@ } }, "node-releases": { - "version": "1.1.42", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.42.tgz", - "integrity": "sha512-OQ/ESmUqGawI2PRX+XIRao44qWYBBfN54ImQYdWVTQqUckuejOg76ysSqDBK8NG3zwySRVnX36JwDQ6x+9GxzA==", + "version": "1.1.47", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.47.tgz", + "integrity": "sha512-k4xjVPx5FpwBUj0Gw7uvFOTF4Ep8Hok1I6qjwL3pLfwe7Y0REQSAqOwwv9TWBCUtMHxcXfY4PgRLRozcChvTcA==", "dev": true, "requires": { "semver": "^6.3.0" @@ -9837,13 +9980,14 @@ } }, "npm-packlist": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.7.tgz", - "integrity": "sha512-vAj7dIkp5NhieaGZxBJB8fF4R0078rqsmhJcAfXZ6O7JJhjhPK96n5Ry1oZcfLXgfun0GWTZPOxaEyqv8GBykQ==", + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", + "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", "dev": true, "requires": { "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" + "npm-bundled": "^1.0.1", + "npm-normalize-package-bin": "^1.0.1" } }, "npm-pick-manifest": { @@ -10002,13 +10146,13 @@ } }, "object.getownpropertydescriptors": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", - "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", "dev": true, "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.5.1" + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" } }, "object.pick": { @@ -10258,9 +10402,9 @@ } }, "pako": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz", - "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", "dev": true }, "parallel-transform": { @@ -10453,9 +10597,9 @@ } }, "popper.js": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.0.tgz", - "integrity": "sha512-+G+EkOPoE5S/zChTpmBSSDYmhXJ5PsW8eMhH8cP/CQHMFPBG/kC9Y5IIw6qNYgdJ+/COf0ddY2li28iHaZRSjw==" + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", + "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==" }, "portfinder": { "version": "1.0.25", @@ -10468,15 +10612,6 @@ "mkdirp": "^0.5.1" }, "dependencies": { - "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "dev": true, - "requires": { - "lodash": "^4.17.14" - } - }, "debug": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", @@ -10577,9 +10712,9 @@ "dev": true }, "prismjs": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.17.1.tgz", - "integrity": "sha512-PrEDJAFdUGbOP6xK/UsfkC5ghJsPJviKgnQOoxaDbBjwc8op68Quupwt1DeAFoG8GImPhiKXAvvsH7wDSLsu1Q==", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.19.0.tgz", + "integrity": "sha512-IVFtbW9mCWm9eOIaEkNyo2Vl4NnEifis2GQ7/MLRG5TQe6t+4Sj9J5QWI9i3v+SS43uZBlCAOn+zYTVYQcPXJw==", "requires": { "clipboard": "^2.0.0" } @@ -10661,9 +10796,9 @@ } }, "protractor": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/protractor/-/protractor-5.4.2.tgz", - "integrity": "sha512-zlIj64Cr6IOWP7RwxVeD8O4UskLYPoyIcg0HboWJL9T79F1F0VWtKkGTr/9GN6BKL+/Q/GmM7C9kFVCfDbP5sA==", + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/protractor/-/protractor-5.4.3.tgz", + "integrity": "sha512-7pMAolv8Ah1yJIqaorDTzACtn3gk7BamVKPTeO5lqIGOrfosjPgXFx/z1dqSI+m5EeZc2GMJHPr5DYlodujDNA==", "dev": true, "requires": { "@types/q": "^0.0.32", @@ -10831,9 +10966,9 @@ "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, "psl": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.4.0.tgz", - "integrity": "sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz", + "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==", "dev": true }, "public-encrypt": { @@ -10994,6 +11129,28 @@ } } }, + "raw-loader": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-3.1.0.tgz", + "integrity": "sha512-lzUVMuJ06HF4rYveaz9Tv0WRlUMxJ0Y1hgSkkgg+50iEdaI0TthyEDe08KIHb0XsF6rn8WYTqPCaGTZg3sX+qA==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "schema-utils": "^2.0.1" + }, + "dependencies": { + "schema-utils": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.4.tgz", + "integrity": "sha512-VNjcaUxVnEeun6B2fiiUDjXXBtD4ZSH7pdbfIu1pOFwgptDPLMo/z9jr4sUfsjFVPqDCEin/F7IYlq7/E6yDbQ==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "ajv-keywords": "^3.4.1" + } + } + } + }, "rc-align": { "version": "2.4.5", "resolved": "https://registry.npmjs.org/rc-align/-/rc-align-2.4.5.tgz", @@ -11020,9 +11177,9 @@ } }, "rc-menu": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-7.5.3.tgz", - "integrity": "sha512-H/jUyGbJxZI/iuVdC6Iu9KHfz7tucoqK0Vn8ahDnv+ppc1PnKb4SkBbXn5LrmUyaj7thCBiaktBxVnUXSmNE2g==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-7.5.5.tgz", + "integrity": "sha512-4YJXJgrpUGEA1rMftXN7bDhrV5rPB8oBJoHqT+GVXtIWCanfQxEnM3fmhHQhatL59JoAFMZhJaNzhJIk4FUWCQ==", "requires": { "classnames": "2.x", "dom-scroll-into-view": "1.x", @@ -11036,9 +11193,9 @@ } }, "rc-select": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-9.2.1.tgz", - "integrity": "sha512-nW/Zr2OCgxN26OX8ff3xcO1wK0e1l5ixnEfyN15Rbdk7TNI/rIPJIjPCQAoihRpk9A2C/GH8pahjlvKV1Vj++g==", + "version": "9.2.3", + "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-9.2.3.tgz", + "integrity": "sha512-WhswxOMWiNnkXRbxyrj0kiIvyCfo/BaRPaYbsDetSIAU2yEDwKHF798blCP5u86KLOBKBvtxWLFCkSsQw1so5w==", "requires": { "babel-runtime": "^6.23.0", "classnames": "2.x", @@ -11069,9 +11226,9 @@ } }, "rc-util": { - "version": "4.15.7", - "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-4.15.7.tgz", - "integrity": "sha512-9pn2NU7IafaP0Hbtll7Ufu9eF9odDOMGMI/WD9lgD4yrm3xR4yCAWZ0cqQGIhKMqdkg3I7et7/dTekrftMeqJQ==", + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-4.19.0.tgz", + "integrity": "sha512-mptALlLwpeczS3nrv83DbwJNeupolbuvlIEjcvimSiWI8NUBjpF0HgG3kWp1RymiuiRCNm9yhaXqDz0a99dpgQ==", "requires": { "add-dom-event-listener": "^1.1.0", "babel-runtime": "6.x", @@ -11283,26 +11440,29 @@ } }, "regexpu-core": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", - "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=", + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.6.0.tgz", + "integrity": "sha512-YlVaefl8P5BnFYOITTNzDvan1ulLOiXJzCNZxduTIosN17b87h3bvG9yHMoHaRuo88H4mQ06Aodj5VtYGGGiTg==", "dev": true, "requires": { - "regenerate": "^1.2.1", - "regjsgen": "^0.2.0", - "regjsparser": "^0.1.4" + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^8.1.0", + "regjsgen": "^0.5.0", + "regjsparser": "^0.6.0", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.1.0" } }, "regjsgen": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", - "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.1.tgz", + "integrity": "sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg==", "dev": true }, "regjsparser": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", - "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.2.tgz", + "integrity": "sha512-E9ghzUtoLwDekPT0DYCp+c4h+bvuUpe6rRHCTYn6eGoqj1LgKXxT6I0Il4WbjhQkOghzi/V+y03bPKvbllL93Q==", "dev": true, "requires": { "jsesc": "~0.5.0" @@ -11334,15 +11494,6 @@ "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", "dev": true }, - "repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", - "dev": true, - "requires": { - "is-finite": "^1.0.0" - } - }, "request": { "version": "2.88.0", "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", @@ -11498,9 +11649,9 @@ } }, "rxjs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz", - "integrity": "sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==", + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", + "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", "requires": { "tslib": "^1.9.0" } @@ -11573,11 +11724,18 @@ } }, "schema-inspector": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/schema-inspector/-/schema-inspector-1.6.8.tgz", - "integrity": "sha1-ueU5g8xV/y29e2Xj2+CF2dEoXyo=", + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/schema-inspector/-/schema-inspector-1.6.9.tgz", + "integrity": "sha512-MNS3SOn6noecIv9R+gwroIgiOLQoRY1IRXToFvVBo2QMfnXy1E+SGRVWJFsJPqgy0lAivUfPLaVLhvAI35HKRg==", "requires": { - "async": "^1.5.0" + "async": "^3.1.0" + }, + "dependencies": { + "async": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/async/-/async-3.1.1.tgz", + "integrity": "sha512-X5Dj8hK1pJNC2Wzo2Rcp9FBVdJMGRR/S7V+lH46s8GVFhtbo5O4Le5GECCF/8PISVdkUA6mMPvgz7qTTD1rf1g==" + } } }, "schema-utils": { @@ -11592,9 +11750,9 @@ } }, "screenfull": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/screenfull/-/screenfull-5.0.0.tgz", - "integrity": "sha512-yShzhaIoE9OtOhWVyBBffA6V98CDCoyHTsp8228blmqYy1Z5bddzE/4FPiJKlr8DVR4VBiiUyfPzIQPIYDkeMA==" + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/screenfull/-/screenfull-5.0.1.tgz", + "integrity": "sha512-NgQH4KKh2V3zlj2u90l7TUcSFxr9qL/64QEvhAvCN/fu1YS39YLTBKIqZqiS3STj3QD8sN6XnsK/8jk3hRq4WA==" }, "select": { "version": "1.1.2", @@ -11693,9 +11851,9 @@ } }, "serialize-javascript": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.9.1.tgz", - "integrity": "sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz", + "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==", "dev": true }, "serve-index": { @@ -12187,17 +12345,6 @@ "requires": { "async": "^2.5.0", "loader-utils": "^1.1.0" - }, - "dependencies": { - "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "dev": true, - "requires": { - "lodash": "^4.17.14" - } - } } }, "source-map-resolve": { @@ -12327,9 +12474,9 @@ "dev": true }, "readable-stream": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", - "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.5.0.tgz", + "integrity": "sha512-gSz026xs2LfxBPudDuI41V1lka8cxg64E66SGe78zJlsUofOg/yqwezdIcdfwik6B4h8LFmWPA9ef9X3FiNFLA==", "dev": true, "requires": { "inherits": "^2.0.3", @@ -12454,9 +12601,9 @@ } }, "stream-shift": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", - "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", "dev": true }, "streamroller": { @@ -12532,9 +12679,9 @@ } }, "string.prototype.trimleft": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz", - "integrity": "sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", + "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", "dev": true, "requires": { "define-properties": "^1.1.3", @@ -12542,9 +12689,9 @@ } }, "string.prototype.trimright": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz", - "integrity": "sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", + "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", "dev": true, "requires": { "define-properties": "^1.1.3", @@ -12592,9 +12739,9 @@ }, "dependencies": { "schema-utils": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.1.tgz", - "integrity": "sha512-0WXHDs1VDJyo+Zqs9TKLKyD/h7yDpHUhEFsM2CzkICFdoX1av+GBq/J2xRTFfsQO5kBfhZzANf2VcIm84jqDbg==", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.4.tgz", + "integrity": "sha512-VNjcaUxVnEeun6B2fiiUDjXXBtD4ZSH7pdbfIu1pOFwgptDPLMo/z9jr4sUfsjFVPqDCEin/F7IYlq7/E6yDbQ==", "dev": true, "requires": { "ajv": "^6.10.2", @@ -12703,9 +12850,9 @@ } }, "terser": { - "version": "4.3.9", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.3.9.tgz", - "integrity": "sha512-NFGMpHjlzmyOtPL+fDw3G7+6Ueh/sz4mkaUYa4lJCxOPTNzd0Uj0aZJOmsDYoSQyfuVoWDMSWTPU3huyOm2zdA==", + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.6.3.tgz", + "integrity": "sha512-Lw+ieAXmY69d09IIc/yqeBqXpEQIpDGZqT34ui1QWXIUpR2RjbqEkT8X7Lgex19hslSqcWM5iMN2kM11eMsESQ==", "dev": true, "requires": { "commander": "^2.20.0", @@ -12740,22 +12887,6 @@ "make-dir": "^2.0.0", "pkg-dir": "^3.0.0" } - }, - "serialize-javascript": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz", - "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==", - "dev": true - }, - "webpack-sources": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", - "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", - "dev": true, - "requires": { - "source-list-map": "^2.0.0", - "source-map": "~0.6.1" - } } } }, @@ -12904,15 +13035,9 @@ } }, "tree-kill": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.1.tgz", - "integrity": "sha512-4hjqbObwlh2dLyW4tcz0Ymw0ggoaVDMveUB9w8kFSQScdRLo0gxO9J7WFcUBo+W3C1TLdFIEwNOWebgZZ0RH9Q==", - "dev": true - }, - "trim-right": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", - "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", "dev": true }, "ts-node": { @@ -13273,16 +13398,6 @@ "object.getownpropertydescriptors": "^2.0.3" } }, - "util.promisify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", - "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "object.getownpropertydescriptors": "^2.0.3" - } - }, "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -13290,9 +13405,9 @@ "dev": true }, "uuid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", - "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", "dev": true }, "validate-npm-package-license": { @@ -13449,14 +13564,15 @@ } }, "fsevents": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", - "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.11.tgz", + "integrity": "sha512-+ux3lx6peh0BpvY0JebGyZoiR4D+oYzdPZMKJwkZ+sFkNJzpL7tXc/wehS49gUAxg3tmMHPHZkA8JU2rhhgDHw==", "dev": true, "optional": true, "requires": { + "bindings": "^1.5.0", "nan": "^2.12.1", - "node-pre-gyp": "^0.12.0" + "node-pre-gyp": "*" }, "dependencies": { "abbrev": { @@ -13504,7 +13620,7 @@ } }, "chownr": { - "version": "1.1.1", + "version": "1.1.3", "bundled": true, "dev": true, "optional": true @@ -13534,7 +13650,7 @@ "optional": true }, "debug": { - "version": "4.1.1", + "version": "3.2.6", "bundled": true, "dev": true, "optional": true, @@ -13561,12 +13677,12 @@ "optional": true }, "fs-minipass": { - "version": "1.2.5", + "version": "1.2.7", "bundled": true, "dev": true, "optional": true, "requires": { - "minipass": "^2.2.1" + "minipass": "^2.6.0" } }, "fs.realpath": { @@ -13592,7 +13708,7 @@ } }, "glob": { - "version": "7.1.3", + "version": "7.1.6", "bundled": true, "dev": true, "optional": true, @@ -13621,7 +13737,7 @@ } }, "ignore-walk": { - "version": "3.0.1", + "version": "3.0.3", "bundled": true, "dev": true, "optional": true, @@ -13640,7 +13756,7 @@ } }, "inherits": { - "version": "2.0.3", + "version": "2.0.4", "bundled": true, "dev": true, "optional": true @@ -13682,7 +13798,7 @@ "optional": true }, "minipass": { - "version": "2.3.5", + "version": "2.9.0", "bundled": true, "dev": true, "optional": true, @@ -13692,12 +13808,12 @@ } }, "minizlib": { - "version": "1.2.1", + "version": "1.3.3", "bundled": true, "dev": true, "optional": true, "requires": { - "minipass": "^2.2.1" + "minipass": "^2.9.0" } }, "mkdirp": { @@ -13710,24 +13826,24 @@ } }, "ms": { - "version": "2.1.1", + "version": "2.1.2", "bundled": true, "dev": true, "optional": true }, "needle": { - "version": "2.3.0", + "version": "2.4.0", "bundled": true, "dev": true, "optional": true, "requires": { - "debug": "^4.1.0", + "debug": "^3.2.6", "iconv-lite": "^0.4.4", "sax": "^1.2.4" } }, "node-pre-gyp": { - "version": "0.12.0", + "version": "0.14.0", "bundled": true, "dev": true, "optional": true, @@ -13741,7 +13857,7 @@ "rc": "^1.2.7", "rimraf": "^2.6.1", "semver": "^5.3.0", - "tar": "^4" + "tar": "^4.4.2" } }, "nopt": { @@ -13755,13 +13871,22 @@ } }, "npm-bundled": { - "version": "1.0.6", + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-normalize-package-bin": { + "version": "1.0.1", "bundled": true, "dev": true, "optional": true }, "npm-packlist": { - "version": "1.4.1", + "version": "1.4.7", "bundled": true, "dev": true, "optional": true, @@ -13832,7 +13957,7 @@ "optional": true }, "process-nextick-args": { - "version": "2.0.0", + "version": "2.0.1", "bundled": true, "dev": true, "optional": true @@ -13873,7 +13998,7 @@ } }, "rimraf": { - "version": "2.6.3", + "version": "2.7.1", "bundled": true, "dev": true, "optional": true, @@ -13900,7 +14025,7 @@ "optional": true }, "semver": { - "version": "5.7.0", + "version": "5.7.1", "bundled": true, "dev": true, "optional": true @@ -13953,18 +14078,18 @@ "optional": true }, "tar": { - "version": "4.4.8", + "version": "4.4.13", "bundled": true, "dev": true, "optional": true, "requires": { "chownr": "^1.1.1", "fs-minipass": "^1.2.5", - "minipass": "^2.3.4", - "minizlib": "^1.1.1", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", "mkdirp": "^0.5.0", "safe-buffer": "^5.1.2", - "yallist": "^3.0.2" + "yallist": "^3.0.3" } }, "util-deprecate": { @@ -13989,7 +14114,7 @@ "optional": true }, "yallist": { - "version": "3.0.3", + "version": "3.1.1", "bundled": true, "dev": true, "optional": true @@ -14102,18 +14227,6 @@ "terser-webpack-plugin": "^1.4.1", "watchpack": "^1.6.0", "webpack-sources": "^1.4.1" - }, - "dependencies": { - "webpack-sources": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", - "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", - "dev": true, - "requires": { - "source-list-map": "^2.0.0", - "source-map": "~0.6.1" - } - } } }, "webpack-core": { @@ -14301,14 +14414,15 @@ } }, "fsevents": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", - "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.11.tgz", + "integrity": "sha512-+ux3lx6peh0BpvY0JebGyZoiR4D+oYzdPZMKJwkZ+sFkNJzpL7tXc/wehS49gUAxg3tmMHPHZkA8JU2rhhgDHw==", "dev": true, "optional": true, "requires": { + "bindings": "^1.5.0", "nan": "^2.12.1", - "node-pre-gyp": "^0.12.0" + "node-pre-gyp": "*" }, "dependencies": { "abbrev": { @@ -14356,7 +14470,7 @@ } }, "chownr": { - "version": "1.1.1", + "version": "1.1.3", "bundled": true, "dev": true, "optional": true @@ -14386,7 +14500,7 @@ "optional": true }, "debug": { - "version": "4.1.1", + "version": "3.2.6", "bundled": true, "dev": true, "optional": true, @@ -14413,12 +14527,12 @@ "optional": true }, "fs-minipass": { - "version": "1.2.5", + "version": "1.2.7", "bundled": true, "dev": true, "optional": true, "requires": { - "minipass": "^2.2.1" + "minipass": "^2.6.0" } }, "fs.realpath": { @@ -14444,7 +14558,7 @@ } }, "glob": { - "version": "7.1.3", + "version": "7.1.6", "bundled": true, "dev": true, "optional": true, @@ -14473,7 +14587,7 @@ } }, "ignore-walk": { - "version": "3.0.1", + "version": "3.0.3", "bundled": true, "dev": true, "optional": true, @@ -14492,7 +14606,7 @@ } }, "inherits": { - "version": "2.0.3", + "version": "2.0.4", "bundled": true, "dev": true, "optional": true @@ -14534,7 +14648,7 @@ "optional": true }, "minipass": { - "version": "2.3.5", + "version": "2.9.0", "bundled": true, "dev": true, "optional": true, @@ -14544,12 +14658,12 @@ } }, "minizlib": { - "version": "1.2.1", + "version": "1.3.3", "bundled": true, "dev": true, "optional": true, "requires": { - "minipass": "^2.2.1" + "minipass": "^2.9.0" } }, "mkdirp": { @@ -14562,24 +14676,24 @@ } }, "ms": { - "version": "2.1.1", + "version": "2.1.2", "bundled": true, "dev": true, "optional": true }, "needle": { - "version": "2.3.0", + "version": "2.4.0", "bundled": true, "dev": true, "optional": true, "requires": { - "debug": "^4.1.0", + "debug": "^3.2.6", "iconv-lite": "^0.4.4", "sax": "^1.2.4" } }, "node-pre-gyp": { - "version": "0.12.0", + "version": "0.14.0", "bundled": true, "dev": true, "optional": true, @@ -14593,7 +14707,7 @@ "rc": "^1.2.7", "rimraf": "^2.6.1", "semver": "^5.3.0", - "tar": "^4" + "tar": "^4.4.2" } }, "nopt": { @@ -14607,13 +14721,22 @@ } }, "npm-bundled": { - "version": "1.0.6", + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-normalize-package-bin": { + "version": "1.0.1", "bundled": true, "dev": true, "optional": true }, "npm-packlist": { - "version": "1.4.1", + "version": "1.4.7", "bundled": true, "dev": true, "optional": true, @@ -14684,7 +14807,7 @@ "optional": true }, "process-nextick-args": { - "version": "2.0.0", + "version": "2.0.1", "bundled": true, "dev": true, "optional": true @@ -14725,7 +14848,7 @@ } }, "rimraf": { - "version": "2.6.3", + "version": "2.7.1", "bundled": true, "dev": true, "optional": true, @@ -14752,7 +14875,7 @@ "optional": true }, "semver": { - "version": "5.7.0", + "version": "5.7.1", "bundled": true, "dev": true, "optional": true @@ -14805,18 +14928,18 @@ "optional": true }, "tar": { - "version": "4.4.8", + "version": "4.4.13", "bundled": true, "dev": true, "optional": true, "requires": { "chownr": "^1.1.1", "fs-minipass": "^1.2.5", - "minipass": "^2.3.4", - "minizlib": "^1.1.1", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", "mkdirp": "^0.5.0", "safe-buffer": "^5.1.2", - "yallist": "^3.0.2" + "yallist": "^3.0.3" } }, "util-deprecate": { @@ -14841,7 +14964,7 @@ "optional": true }, "yallist": { - "version": "3.0.3", + "version": "3.1.1", "bundled": true, "dev": true, "optional": true @@ -14948,9 +15071,9 @@ } }, "webpack-sources": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.3.0.tgz", - "integrity": "sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", "dev": true, "requires": { "source-list-map": "^2.0.0", @@ -15077,13 +15200,12 @@ } }, "xml2js": { - "version": "0.4.22", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.22.tgz", - "integrity": "sha512-MWTbxAQqclRSTnehWWe5nMKzI3VmJ8ltiJEco8akcC6j3miOhjjfzKum5sId+CWhfxdOs/1xauYr8/ZDBtQiRw==", + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", "dev": true, "requires": { "sax": ">=0.6.0", - "util.promisify": "~1.0.0", "xmlbuilder": "~11.0.0" }, "dependencies": { diff --git a/ui-ngx/package.json b/ui-ngx/package.json index 0c83064414..b602107fb2 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -25,24 +25,24 @@ "@angular/router": "~8.2.14", "@auth0/angular-jwt": "^3.0.1", "@date-io/date-fns": "^1.3.13", - "@flowjs/flow.js": "^2.13.2", + "@flowjs/flow.js": "^2.14.0", "@flowjs/ngx-flow": "^0.4.3", "@mat-datetimepicker/core": "^2.0.1", - "@material-ui/core": "^4.7.2", - "@material-ui/icons": "^4.5.1", - "@material-ui/pickers": "^3.2.8", - "@ngrx/effects": "^8.5.2", - "@ngrx/store": "^8.5.2", - "@ngrx/store-devtools": "^8.5.2", + "@material-ui/core": "^4.9.1", + "@material-ui/icons": "^4.9.1", + "@material-ui/pickers": "^3.2.10", + "@ngrx/effects": "^8.6.0", + "@ngrx/store": "^8.6.0", + "@ngrx/store-devtools": "^8.6.0", "@ngx-share/core": "^7.1.4", "@ngx-translate/core": "^11.0.1", "@ngx-translate/http-loader": "^4.0.0", - "ace-builds": "^1.4.7", - "angular-gridster2": "^8.2.0", + "ace-builds": "^1.4.8", + "angular-gridster2": "^8.3.0", "angular2-hotkeys": "^2.1.5", "base64-js": "^1.3.1", "compass-sass-mixins": "^0.12.7", - "core-js": "^3.5.0", + "core-js": "^3.6.4", "date-fns": "2.8.1", "deep-equal": "^1.1.1", "flot": "git://github.com/thingsboard/flot.git#0.9-work", @@ -51,8 +51,8 @@ "hammerjs": "^2.0.8", "javascript-detect-element-resize": "^0.5.3", "jquery": "^3.4.1", - "jquery.terminal": "^2.9.0", - "js-beautify": "^1.10.2", + "jquery.terminal": "^1.5.0", + "js-beautify": "^1.10.3", "json-schema-defaults": "^0.4.0", "jstree": "^3.3.8", "jstree-bootstrap-theme": "^1.0.1", @@ -67,15 +67,15 @@ "objectpath": "^1.2.2", "prop-types": "^15.7.2", "raphael": "^2.3.0", - "rc-select": "^9.2.1", + "rc-select": "^9.2.3", "react": "^16.12.0", "react-ace": "^8.0.0", "react-dom": "^16.12.0", "react-dropzone": "^10.2.1", "reactcss": "^1.2.3", - "rxjs": "~6.5.3", - "schema-inspector": "^1.6.8", - "screenfull": "^5.0.0", + "rxjs": "^6.5.4", + "schema-inspector": "^1.6.9", + "screenfull": "^5.0.1", "split.js": "^1.5.11", "systemjs": "0.21.5", "tinycolor2": "^1.4.1", @@ -87,24 +87,24 @@ }, "devDependencies": { "@angular-builders/custom-webpack": "^8.4.1", - "@angular-devkit/build-angular": "^0.803.20", - "@angular/cli": "~8.3.20", + "@angular-devkit/build-angular": "^0.803.24", + "@angular/cli": "^8.3.24", "@angular/compiler-cli": "~8.2.14", "@angular/language-service": "~8.2.14", "@types/flot": "0.0.31", - "@types/jasmine": "~3.5.0", + "@types/jasmine": "^3.5.2", "@types/jasminewd2": "~2.0.8", "@types/jquery": "^3.3.31", "@types/js-beautify": "^1.8.1", "@types/jstree": "^3.3.39", - "@types/node": "~12.12.17", + "@types/node": "^12.12.26", "@types/raphael": "^2.1.30", - "@types/react": "^16.9.16", - "@types/react-dom": "^16.9.4", + "@types/react": "^16.9.19", + "@types/react-dom": "^16.9.5", "@types/tinycolor2": "^1.4.2", "@types/tooltipster": "0.0.29", - "codelyzer": "~5.2.0", - "compression-webpack-plugin": "^3.0.1", + "codelyzer": "^5.2.1", + "compression-webpack-plugin": "^3.1.0", "directory-tree": "^2.2.4", "jasmine-core": "~3.5.0", "jasmine-spec-reporter": "~4.2.1", @@ -112,9 +112,9 @@ "karma-chrome-launcher": "~3.1.0", "karma-coverage-istanbul-reporter": "~2.1.1", "karma-jasmine": "~2.0.1", - "karma-jasmine-html-reporter": "^1.4.2", + "karma-jasmine-html-reporter": "^1.5.2", "ngrx-store-freeze": "^0.2.4", - "protractor": "~5.4.2", + "protractor": "^5.4.3", "ts-node": "~8.5.4", "tslint": "~5.20.1", "typescript": "~3.5.3" diff --git a/ui-ngx/proxy.conf.js b/ui-ngx/proxy.conf.js index 6b45570da1..d46e07d09e 100644 --- a/ui-ngx/proxy.conf.js +++ b/ui-ngx/proxy.conf.js @@ -14,22 +14,22 @@ * limitations under the License. */ -const ruleNodeUiforwardHost = 'localhost'; +const ruleNodeUiforwardHost = "localhost"; const ruleNodeUiforwardPort = 8080; const PROXY_CONFIG = { - '/api': { - 'target': 'http://localhost:8080', - 'secure': false + "/api": { + "target": "http://localhost:8080", + "secure": false, }, - '/static/rulenode': { - 'target': `http://${ruleNodeUiforwardHost}:${ruleNodeUiforwardPort}`, - 'secure': false + "/static/rulenode": { + "target": `http://${ruleNodeUiforwardHost}:${ruleNodeUiforwardPort}`, + "secure": false, }, - '/api/ws': { - 'target': 'ws://localhost:8080', - 'ws': true - } -} + "/api/ws": { + "target": "ws://localhost:8080", + "ws": true, + }, +}; module.exports = PROXY_CONFIG; diff --git a/ui-ngx/src/app/app.component.ts b/ui-ngx/src/app/app.component.ts index 1d30454510..6bd4f2e6d6 100644 --- a/ui-ngx/src/app/app.component.ts +++ b/ui-ngx/src/app/app.component.ts @@ -20,8 +20,8 @@ import { environment as env } from '@env/environment'; import { TranslateService } from '@ngx-translate/core'; import { select, Store } from '@ngrx/store'; -import { AppState } from './core/core.state'; -import { LocalStorageService } from './core/local-storage/local-storage.service'; +import { AppState } from '@core/core.state'; +import { LocalStorageService } from '@core/local-storage/local-storage.service'; import { DomSanitizer } from '@angular/platform-browser'; import { MatIconRegistry } from '@angular/material'; import { combineLatest } from 'rxjs'; diff --git a/ui-ngx/src/app/app.module.ts b/ui-ngx/src/app/app.module.ts index 98cc2d158b..4c7c5b8a1f 100644 --- a/ui-ngx/src/app/app.module.ts +++ b/ui-ngx/src/app/app.module.ts @@ -19,12 +19,12 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { NgModule } from '@angular/core'; import { AppRoutingModule } from './app-routing.module'; -import { CoreModule } from './core/core.module'; -import { LoginModule } from './modules/login/login.module'; -import { HomeModule } from './modules/home/home.module'; +import { CoreModule } from '@core/core.module'; +import { LoginModule } from '@modules/login/login.module'; +import { HomeModule } from '@home/home.module'; import { AppComponent } from './app.component'; -import { DashboardRoutingModule } from './modules/dashboard/dashboard-routing.module'; +import { DashboardRoutingModule } from '@modules/dashboard/dashboard-routing.module'; @NgModule({ declarations: [ diff --git a/ui-ngx/src/app/core/api/data-aggregator.ts b/ui-ngx/src/app/core/api/data-aggregator.ts index bea5709055..ae128b67b4 100644 --- a/ui-ngx/src/app/core/api/data-aggregator.ts +++ b/ui-ngx/src/app/core/api/data-aggregator.ts @@ -65,7 +65,7 @@ export class DataAggregator { private dataBuffer: SubscriptionData = {}; private data: SubscriptionData; - private lastPrevKvPairData: {[key: string]: [number, any]}; + private readonly lastPrevKvPairData: {[key: string]: [number, any]}; private aggregationMap: AggregationMap; @@ -74,7 +74,7 @@ export class DataAggregator { private noAggregation = this.aggregationType === AggregationType.NONE; private aggregationTimeout = Math.max(this.interval, 1000); - private aggFunction: AggFunction; + private readonly aggFunction: AggFunction; private intervalTimeoutHandle: Timeout; private intervalScheduledTime: number; diff --git a/ui-ngx/src/app/core/auth/auth.actions.ts b/ui-ngx/src/app/core/auth/auth.actions.ts index 8cf40da702..53a5831f07 100644 --- a/ui-ngx/src/app/core/auth/auth.actions.ts +++ b/ui-ngx/src/app/core/auth/auth.actions.ts @@ -15,7 +15,7 @@ /// import { Action } from '@ngrx/store'; -import { AuthUser, User } from '../../shared/models/user.model'; +import { User } from '@shared/models/user.model'; import { AuthPayload } from '@core/auth/auth.models'; export enum AuthActionTypes { diff --git a/ui-ngx/src/app/core/auth/auth.models.ts b/ui-ngx/src/app/core/auth/auth.models.ts index f861609514..0797b22f46 100644 --- a/ui-ngx/src/app/core/auth/auth.models.ts +++ b/ui-ngx/src/app/core/auth/auth.models.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { AuthUser, User } from '../../shared/models/user.model'; +import { AuthUser, User } from '@shared/models/user.model'; export interface AuthPayload { authUser: AuthUser; diff --git a/ui-ngx/src/app/core/auth/auth.service.ts b/ui-ngx/src/app/core/auth/auth.service.ts index 171345a20c..c4f236fb37 100644 --- a/ui-ngx/src/app/core/auth/auth.service.ts +++ b/ui-ngx/src/app/core/auth/auth.service.ts @@ -18,19 +18,19 @@ import { Injectable, NgZone } from '@angular/core'; import { JwtHelperService } from '@auth0/angular-jwt'; import { HttpClient } from '@angular/common/http'; -import { combineLatest, forkJoin, Observable, of, throwError } from 'rxjs'; -import { catchError, distinctUntilChanged, filter, map, mergeMap, skip, tap } from 'rxjs/operators'; +import { forkJoin, Observable, of, throwError } from 'rxjs'; +import { catchError, map, mergeMap, tap } from 'rxjs/operators'; -import { LoginRequest, LoginResponse, PublicLoginRequest } from '../../shared/models/login.models'; +import { LoginRequest, LoginResponse, PublicLoginRequest } from '@shared/models/login.models'; import { ActivatedRoute, Router, UrlTree } from '@angular/router'; import { defaultHttpOptions } from '../http/http-utils'; import { ReplaySubject } from 'rxjs/internal/ReplaySubject'; import { UserService } from '../http/user.service'; -import { select, Store } from '@ngrx/store'; +import { Store } from '@ngrx/store'; import { AppState } from '../core.state'; import { ActionAuthAuthenticated, ActionAuthLoadUser, ActionAuthUnauthenticated } from './auth.actions'; -import { getCurrentAuthState, getCurrentAuthUser, selectIsAuthenticated, selectIsUserLoaded } from './auth.selectors'; -import { Authority } from '../../shared/models/authority.enum'; +import { getCurrentAuthState, getCurrentAuthUser } from './auth.selectors'; +import { Authority } from '@shared/models/authority.enum'; import { ActionSettingsChangeLanguage } from '@app/core/settings/settings.actions'; import { AuthPayload, AuthState } from '@core/auth/auth.models'; import { TranslateService } from '@ngx-translate/core'; diff --git a/ui-ngx/src/app/core/css/css.js b/ui-ngx/src/app/core/css/css.js index d568f5e3b9..e468a89267 100644 --- a/ui-ngx/src/app/core/css/css.js +++ b/ui-ngx/src/app/core/css/css.js @@ -17,17 +17,17 @@ /* jshint unused:false */ /* global base64_decode, CSSWizardView, window, console, jQuery */ -var fi = function () { +var fi = function() { this.cssImportStatements = []; this.cssKeyframeStatements = []; - this.cssRegex = new RegExp('([\\s\\S]*?){([\\s\\S]*?)}', 'gi'); - this.cssMediaQueryRegex = '((@media [\\s\\S]*?){([\\s\\S]*?}\\s*?)})'; - this.cssKeyframeRegex = '((@.*?keyframes [\\s\\S]*?){([\\s\\S]*?}\\s*?)})'; - this.combinedCSSRegex = '((\\s*?@media[\\s\\S]*?){([\\s\\S]*?)}\\s*?})|(([\\s\\S]*?){([\\s\\S]*?)})'; //to match css & media queries together - this.cssCommentsRegex = '(\\/\\*[\\s\\S]*?\\*\\/)'; - this.cssImportStatementRegex = new RegExp('@import .*?;', 'gi'); + this.cssRegex = new RegExp("([\\s\\S]*?){([\\s\\S]*?)}", "gi"); + this.cssMediaQueryRegex = "((@media [\\s\\S]*?){([\\s\\S]*?}\\s*?)})"; + this.cssKeyframeRegex = "((@.*?keyframes [\\s\\S]*?){([\\s\\S]*?}\\s*?)})"; + this.combinedCSSRegex = "((\\s*?@media[\\s\\S]*?){([\\s\\S]*?)}\\s*?})|(([\\s\\S]*?){([\\s\\S]*?)})"; //to match css & media queries together + this.cssCommentsRegex = "(\\/\\*[\\s\\S]*?\\*\\/)"; + this.cssImportStatementRegex = new RegExp("@import .*?;", "gi"); }; /* @@ -37,10 +37,10 @@ var fi = function () { @return cleanedCSS contains no css comments */ -fi.prototype.stripComments = function (cssString) { - var regex = new RegExp(this.cssCommentsRegex, 'gi'); +fi.prototype.stripComments = function(cssString) { + var regex = new RegExp(this.cssCommentsRegex, "gi"); - return cssString.replace(regex, ''); + return cssString.replace(regex, ""); }; /* @@ -52,7 +52,7 @@ fi.prototype.stripComments = function (cssString) { @return object css */ -fi.prototype.parseCSS = function (source) { +fi.prototype.parseCSS = function(source) { if (source === undefined) { return []; @@ -69,17 +69,17 @@ fi.prototype.parseCSS = function (source) { if (imports !== null) { this.cssImportStatements.push(imports[0]); css.push({ - selector: '@imports', - type: 'imports', - styles: imports[0] + selector: "@imports", + type: "imports", + styles: imports[0], }); } else { break; } } - source = source.replace(this.cssImportStatementRegex, ''); + source = source.replace(this.cssImportStatementRegex, ""); //get keyframe statements - var keyframesRegex = new RegExp(this.cssKeyframeRegex, 'gi'); + var keyframesRegex = new RegExp(this.cssKeyframeRegex, "gi"); var arr; while (true) { arr = keyframesRegex.exec(source); @@ -87,44 +87,44 @@ fi.prototype.parseCSS = function (source) { break; } css.push({ - selector: '@keyframes', - type: 'keyframes', - styles: arr[0] + selector: "@keyframes", + type: "keyframes", + styles: arr[0], }); } - source = source.replace(keyframesRegex, ''); + source = source.replace(keyframesRegex, ""); //unified regex - var unified = new RegExp(this.combinedCSSRegex, 'gi'); + var unified = new RegExp(this.combinedCSSRegex, "gi"); while (true) { arr = unified.exec(source); if (arr === null) { break; } - var selector = ''; + var selector = ""; if (arr[2] === undefined) { - selector = arr[5].split('\r\n').join('\n').trim(); + selector = arr[5].split("\r\n").join("\n").trim(); } else { - selector = arr[2].split('\r\n').join('\n').trim(); + selector = arr[2].split("\r\n").join("\n").trim(); } /* fetch comments and associate it with current selector */ - var commentsRegex = new RegExp(this.cssCommentsRegex, 'gi'); + var commentsRegex = new RegExp(this.cssCommentsRegex, "gi"); var comments = commentsRegex.exec(selector); if (comments !== null) { - selector = selector.replace(commentsRegex, '').trim(); + selector = selector.replace(commentsRegex, "").trim(); } //determine the type - if (selector.indexOf('@media') !== -1) { + if (selector.indexOf("@media") !== -1) { //we have a media query var cssObject = { selector: selector, - type: 'media', - subStyles: this.parseCSS(arr[3] + '\n}') //recursively parse media query inner css + type: "media", + subStyles: this.parseCSS(arr[3] + "\n}"), //recursively parse media query inner css }; if (comments !== null) { cssObject.comments = comments[0]; @@ -135,10 +135,10 @@ fi.prototype.parseCSS = function (source) { var rules = this.parseRules(arr[6]); var style = { selector: selector, - rules: rules + rules: rules, }; - if (selector === '@font-face') { - style.type = 'font-face'; + if (selector === "@font-face") { + style.type = "font-face"; } if (comments !== null) { style.comments = comments[0]; @@ -157,9 +157,9 @@ fi.prototype.parseCSS = function (source) { @param rules, css directive string example \n\ncolor:white;\n font-size:18px;\n */ -fi.prototype.parseRules = function (rules) { +fi.prototype.parseRules = function(rules) { //convert all windows style line endings to unix style line endings - rules = rules.split('\r\n').join('\n'); + rules = rules.split("\r\n").join("\n"); var ret = []; // Split all rules but keep semicolon for base64 url data @@ -171,11 +171,11 @@ fi.prototype.parseRules = function (rules) { //determine if line is a valid css directive, ie color:white; line = line.trim(); - if (line.indexOf(':') !== -1) { + if (line.indexOf(":") !== -1) { //line contains : - line = line.split(':'); + line = line.split(":"); var cssDirective = line[0].trim(); - var cssValue = line.slice(1).join(':').trim(); + var cssValue = line.slice(1).join(":").trim(); //more checks if (cssDirective.length < 1 || cssValue.length < 1) { @@ -186,19 +186,19 @@ fi.prototype.parseRules = function (rules) { //push rule ret.push({ directive: cssDirective, - value: cssValue + value: cssValue, }); } else { //if there is no ':', but what if it was mis splitted value which starts with base64 - if (line.trim().substr(0, 7) == 'base64,') { //hack :) + if (line.trim().substr(0, 7) == "base64,") { //hack :) ret[ret.length - 1].value += line.trim(); } else { //add rule, even if it is defective if (line.length > 0) { ret.push({ - directive: '', + directive: "", value: line, - defective: true + defective: true, }); } } @@ -211,7 +211,7 @@ fi.prototype.parseRules = function (rules) { just returns the rule having given directive if not found returns false; */ -fi.prototype.findCorrespondingRule = function (rules, directive, value) { +fi.prototype.findCorrespondingRule = function(rules, directive, value) { if (value === undefined) { value = false; } @@ -231,7 +231,7 @@ fi.prototype.findCorrespondingRule = function (rules, directive, value) { Finds styles that have given selector, compress them, and returns them */ -fi.prototype.findBySelector = function (cssObjectArray, selector, contains) { +fi.prototype.findBySelector = function(cssObjectArray, selector, contains) { if (contains === undefined) { contains = false; } @@ -263,7 +263,7 @@ fi.prototype.findBySelector = function (cssObjectArray, selector, contains) { /* deletes cssObjects having given selector, and returns new array */ -fi.prototype.deleteBySelector = function (cssObjectArray, selector) { +fi.prototype.deleteBySelector = function(cssObjectArray, selector) { var ret = []; for (var i = 0; i < cssObjectArray.length; i++) { if (cssObjectArray[i].selector !== selector) { @@ -277,7 +277,7 @@ fi.prototype.deleteBySelector = function (cssObjectArray, selector) { Compresses given cssObjectArray and tries to minimize selector redundence. */ -fi.prototype.compressCSS = function (cssObjectArray) { +fi.prototype.compressCSS = function(cssObjectArray) { var compressed = []; var done = {}; for (var i = 0; i < cssObjectArray.length; i++) { @@ -315,19 +315,19 @@ fi.prototype.compressCSS = function (cssObjectArray) { @return diff css object contains changed values in css1 in regards to css2 see test input output in /test/data/css.js */ -fi.prototype.cssDiff = function (css1, css2) { +fi.prototype.cssDiff = function(css1, css2) { if (css1.selector !== css2.selector) { return false; } //if one of them is media query return false, because diff function can not operate on media queries - if ((css1.type === 'media' || css2.type === 'media')) { + if ((css1.type === "media" || css2.type === "media")) { return false; } var diff = { selector: css1.selector, - rules: [] + rules: [], }; var rule1, rule2; for (var i = 0; i < css1.rules.length; i++) { @@ -352,12 +352,11 @@ fi.prototype.cssDiff = function (css1, css2) { rule1 = this.findCorrespondingRule(css1.rules, rule2.directive); if (rule1 === false) { //rule1 is a new rule - rule2.type = 'DELETED'; //mark it as a deleted rule, so that other merge operations could be true + rule2.type = "DELETED"; //mark it as a deleted rule, so that other merge operations could be true diff.rules.push(rule2); } } - if (diff.rules.length === 0) { return false; } @@ -373,18 +372,17 @@ fi.prototype.cssDiff = function (css1, css2) { @param reverse, [optional], if given true, first parameter will be traversed on reversed order effectively giving priority to the styles in newArray */ -fi.prototype.intelligentMerge = function (cssObjectArray, newArray, reverse) { +fi.prototype.intelligentMerge = function(cssObjectArray, newArray, reverse) { if (reverse === undefined) { reverse = false; } - for (var i = 0; i < newArray.length; i++) { this.intelligentCSSPush(cssObjectArray, newArray[i], reverse); } for (i = 0; i < cssObjectArray.length; i++) { var cobj = cssObjectArray[i]; - if (cobj.type === 'media' || (cobj.type === 'keyframes')) { + if (cobj.type === "media" || (cobj.type === "keyframes")) { continue; } cobj.rules = this.compactRules(cobj.rules); @@ -400,7 +398,7 @@ fi.prototype.intelligentMerge = function (cssObjectArray, newArray, reverse) { @param reverse [optional] default is false, if given, cssObjectArray will be reversly traversed resulting more priority in minimalObject's styles */ -fi.prototype.intelligentCSSPush = function (cssObjectArray, minimalObject, reverse) { +fi.prototype.intelligentCSSPush = function(cssObjectArray, minimalObject, reverse) { var pushSelector = minimalObject.selector; //find correct selector if not found just push minimalObject into cssObject var cssObject = false; @@ -428,15 +426,15 @@ fi.prototype.intelligentCSSPush = function (cssObjectArray, minimalObject, rever if (cssObject === false) { cssObjectArray.push(minimalObject); //just push, because cssSelector is new } else { - if (minimalObject.type !== 'media') { + if (minimalObject.type !== "media") { for (var ii = 0; ii < minimalObject.rules.length; ii++) { var rule = minimalObject.rules[ii]; //find rule inside cssObject var oldRule = this.findCorrespondingRule(cssObject.rules, rule.directive); if (oldRule === false) { cssObject.rules.push(rule); - } else if (rule.type == 'DELETED') { - oldRule.type = 'DELETED'; + } else if (rule.type == "DELETED") { + oldRule.type = "DELETED"; } else { //rule found just update value @@ -457,10 +455,10 @@ fi.prototype.intelligentCSSPush = function (cssObjectArray, minimalObject, rever @returns rules array, compacted by deleting all unneccessary rules */ -fi.prototype.compactRules = function (rules) { +fi.prototype.compactRules = function(rules) { var newRules = []; for (var i = 0; i < rules.length; i++) { - if (rules[i].type !== 'DELETED') { + if (rules[i].type !== "DELETED") { newRules.push(rules[i]); } } @@ -471,18 +469,18 @@ fi.prototype.compactRules = function (rules) { @param [optional] cssBase, if given computes cssString from cssObject array */ -fi.prototype.getCSSForEditor = function (cssBase, depth) { +fi.prototype.getCSSForEditor = function(cssBase, depth) { if (depth === undefined) { depth = 0; } - var ret = ''; + var ret = ""; if (cssBase === undefined) { cssBase = this.css; } //append imports for (var i = 0; i < cssBase.length; i++) { - if (cssBase[i].type == 'imports') { - ret += cssBase[i].styles + '\n\n'; + if (cssBase[i].type == "imports") { + ret += cssBase[i].styles + "\n\n"; } } for (i = 0; i < cssBase.length; i++) { @@ -492,34 +490,34 @@ fi.prototype.getCSSForEditor = function (cssBase, depth) { } var comments = ""; if (tmp.comments !== undefined) { - comments = tmp.comments + '\n'; + comments = tmp.comments + "\n"; } - if (tmp.type == 'media') { //also put media queries to output - ret += comments + tmp.selector + '{\n'; + if (tmp.type == "media") { //also put media queries to output + ret += comments + tmp.selector + "{\n"; ret += this.getCSSForEditor(tmp.subStyles, depth + 1); - ret += '}\n\n'; - } else if (tmp.type !== 'keyframes' && tmp.type !== 'imports') { - ret += this.getSpaces(depth) + comments + tmp.selector + ' {\n'; + ret += "}\n\n"; + } else if (tmp.type !== "keyframes" && tmp.type !== "imports") { + ret += this.getSpaces(depth) + comments + tmp.selector + " {\n"; ret += this.getCSSOfRules(tmp.rules, depth + 1); - ret += this.getSpaces(depth) + '}\n\n'; + ret += this.getSpaces(depth) + "}\n\n"; } } //append keyFrames for (i = 0; i < cssBase.length; i++) { - if (cssBase[i].type == 'keyframes') { - ret += cssBase[i].styles + '\n\n'; + if (cssBase[i].type == "keyframes") { + ret += cssBase[i].styles + "\n\n"; } } return ret; }; -fi.prototype.getImports = function (cssObjectArray) { +fi.prototype.getImports = function(cssObjectArray) { var imps = []; for (var i = 0; i < cssObjectArray.length; i++) { - if (cssObjectArray[i].type == 'imports') { + if (cssObjectArray[i].type == "imports") { imps.push(cssObjectArray[i].styles); } } @@ -529,30 +527,30 @@ fi.prototype.getImports = function (cssObjectArray) { given rules array, returns visually formatted css string to be used inside editor */ -fi.prototype.getCSSOfRules = function (rules, depth) { - var ret = ''; +fi.prototype.getCSSOfRules = function(rules, depth) { + var ret = ""; for (var i = 0; i < rules.length; i++) { if (rules[i] === undefined) { continue; } if (rules[i].defective === undefined) { - ret += this.getSpaces(depth) + rules[i].directive + ' : ' + rules[i].value + ';\n'; + ret += this.getSpaces(depth) + rules[i].directive + " : " + rules[i].value + ";\n"; } else { - ret += this.getSpaces(depth) + rules[i].value + ';\n'; + ret += this.getSpaces(depth) + rules[i].value + ";\n"; } } - return ret || '\n'; + return ret || "\n"; }; /* A very simple helper function returns number of spaces appended in a single string, the number depends input parameter, namely input*2 */ -fi.prototype.getSpaces = function (num) { - var ret = ''; +fi.prototype.getSpaces = function(num) { + var ret = ""; for (var i = 0; i < num * 4; i++) { - ret += ' '; + ret += " "; } return ret; }; @@ -563,14 +561,14 @@ fi.prototype.getSpaces = function (num) { @returns css string in which this.cssPreviewNamespace prepended */ -fi.prototype.applyNamespacing = function (css, forcedNamespace) { +fi.prototype.applyNamespacing = function(css, forcedNamespace) { var cssObjectArray = css; - var namespaceClass = '.' + this.cssPreviewNamespace; + var namespaceClass = "." + this.cssPreviewNamespace; if (forcedNamespace !== undefined) { namespaceClass = forcedNamespace; } - if (typeof css === 'string') { + if (typeof css === "string") { cssObjectArray = this.parseCSS(css); } @@ -578,21 +576,21 @@ fi.prototype.applyNamespacing = function (css, forcedNamespace) { var obj = cssObjectArray[i]; //bypass namespacing for @font-face @keyframes @import - if (obj.selector.indexOf('@font-face') > -1 || obj.selector.indexOf('keyframes') > -1 || obj.selector.indexOf('@import') > -1 || obj.selector.indexOf('.form-all') > -1 || obj.selector.indexOf('#stage') > -1) { + if (obj.selector.indexOf("@font-face") > -1 || obj.selector.indexOf("keyframes") > -1 || obj.selector.indexOf("@import") > -1 || obj.selector.indexOf(".form-all") > -1 || obj.selector.indexOf("#stage") > -1) { continue; } - if (obj.type !== 'media') { - var selector = obj.selector.split(','); + if (obj.type !== "media") { + var selector = obj.selector.split(","); var newSelector = []; for (var j = 0; j < selector.length; j++) { - if (selector[j].indexOf('.supernova') === -1) { //do not apply namespacing to selectors including supernova - newSelector.push(namespaceClass + ' ' + selector[j]); + if (selector[j].indexOf(".supernova") === -1) { //do not apply namespacing to selectors including supernova + newSelector.push(namespaceClass + " " + selector[j]); } else { newSelector.push(selector[j]); } } - obj.selector = newSelector.join(','); + obj.selector = newSelector.join(","); } else { obj.subStyles = this.applyNamespacing(obj.subStyles, forcedNamespace); //handle media queries as well } @@ -605,26 +603,26 @@ fi.prototype.applyNamespacing = function (css, forcedNamespace) { given css string or object array, clears possible namespacing from all of the selectors inside the css */ -fi.prototype.clearNamespacing = function (css, returnObj) { +fi.prototype.clearNamespacing = function(css, returnObj) { if (returnObj === undefined) { returnObj = false; } var cssObjectArray = css; - var namespaceClass = '.' + this.cssPreviewNamespace; - if (typeof css === 'string') { + var namespaceClass = "." + this.cssPreviewNamespace; + if (typeof css === "string") { cssObjectArray = this.parseCSS(css); } for (var i = 0; i < cssObjectArray.length; i++) { var obj = cssObjectArray[i]; - if (obj.type !== 'media') { - var selector = obj.selector.split(','); + if (obj.type !== "media") { + var selector = obj.selector.split(","); var newSelector = []; for (var j = 0; j < selector.length; j++) { - newSelector.push(selector[j].split(namespaceClass + ' ').join('')); + newSelector.push(selector[j].split(namespaceClass + " ").join("")); } - obj.selector = newSelector.join(','); + obj.selector = newSelector.join(","); } else { obj.subStyles = this.clearNamespacing(obj.subStyles, true); //handle media queries as well } @@ -641,17 +639,17 @@ fi.prototype.clearNamespacing = function (css, returnObj) { creates a new style tag (also destroys the previous one) and injects given css string into that css tag */ -fi.prototype.createStyleElement = function (id, css, format) { +fi.prototype.createStyleElement = function(id, css, format) { if (format === undefined) { format = false; } - if (this.testMode === false && format !== 'nonamespace') { + if (this.testMode === false && format !== "nonamespace") { //apply namespacing classes css = this.applyNamespacing(css); } - if (typeof css != 'string') { + if (typeof css != "string") { css = this.getCSSForEditor(css); } //apply formatting for css @@ -660,7 +658,7 @@ fi.prototype.createStyleElement = function (id, css, format) { } if (this.testMode !== false) { - return this.testMode('create style #' + id, css); //if test mode, just pass result to callback + return this.testMode("create style #" + id, css); //if test mode, just pass result to callback } var __el = document.getElementById(id); @@ -668,11 +666,11 @@ fi.prototype.createStyleElement = function (id, css, format) { __el.parentNode.removeChild(__el); } - var head = document.head || document.getElementsByTagName('head')[0], - style = document.createElement('style'); + var head = document.head || document.getElementsByTagName("head")[0], + style = document.createElement("style"); style.id = id; - style.type = 'text/css'; + style.type = "text/css"; head.appendChild(style); diff --git a/ui-ngx/src/app/core/http/admin.service.ts b/ui-ngx/src/app/core/http/admin.service.ts index 0d7fef6477..37ba5adf28 100644 --- a/ui-ngx/src/app/core/http/admin.service.ts +++ b/ui-ngx/src/app/core/http/admin.service.ts @@ -15,8 +15,8 @@ /// import { Injectable } from '@angular/core'; -import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; -import { Observable } from 'rxjs/index'; +import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; +import { Observable } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { AdminSettings, MailServerSettings, SecuritySettings, UpdateMessage } from '@shared/models/settings.models'; diff --git a/ui-ngx/src/app/core/http/asset.service.ts b/ui-ngx/src/app/core/http/asset.service.ts index e92d307ef0..91bb32b947 100644 --- a/ui-ngx/src/app/core/http/asset.service.ts +++ b/ui-ngx/src/app/core/http/asset.service.ts @@ -16,7 +16,7 @@ import { Injectable } from '@angular/core'; import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; -import { Observable } from 'rxjs/index'; +import { Observable } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { PageLink } from '@shared/models/page/page-link'; import { PageData } from '@shared/models/page/page-data'; diff --git a/ui-ngx/src/app/core/http/attribute.service.ts b/ui-ngx/src/app/core/http/attribute.service.ts index 852d15cfb8..192763795d 100644 --- a/ui-ngx/src/app/core/http/attribute.service.ts +++ b/ui-ngx/src/app/core/http/attribute.service.ts @@ -15,8 +15,8 @@ /// import { Injectable } from '@angular/core'; -import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; -import { forkJoin, Observable, of } from 'rxjs/index'; +import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; +import { forkJoin, Observable, of } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { EntityId } from '@shared/models/id/entity-id'; import { AttributeData, AttributeScope } from '@shared/models/telemetry/telemetry.models'; diff --git a/ui-ngx/src/app/core/http/audit-log.service.ts b/ui-ngx/src/app/core/http/audit-log.service.ts index 3621ac6b04..8464743a6e 100644 --- a/ui-ngx/src/app/core/http/audit-log.service.ts +++ b/ui-ngx/src/app/core/http/audit-log.service.ts @@ -15,10 +15,10 @@ /// import { Injectable } from '@angular/core'; -import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; -import { Observable } from 'rxjs/index'; +import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; +import { Observable } from 'rxjs'; import { HttpClient } from '@angular/common/http'; -import { PageLink, TimePageLink } from '@shared/models/page/page-link'; +import { TimePageLink } from '@shared/models/page/page-link'; import { PageData } from '@shared/models/page/page-data'; import { AuditLog } from '@shared/models/audit-log.models'; import { EntityId } from '@shared/models/id/entity-id'; diff --git a/ui-ngx/src/app/core/http/customer.service.ts b/ui-ngx/src/app/core/http/customer.service.ts index 037ce99e0b..5260883550 100644 --- a/ui-ngx/src/app/core/http/customer.service.ts +++ b/ui-ngx/src/app/core/http/customer.service.ts @@ -15,8 +15,8 @@ /// import { Injectable } from '@angular/core'; -import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; -import { Observable } from 'rxjs/index'; +import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; +import { Observable } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { PageLink } from '@shared/models/page/page-link'; import { PageData } from '@shared/models/page/page-data'; diff --git a/ui-ngx/src/app/core/http/dashboard.service.ts b/ui-ngx/src/app/core/http/dashboard.service.ts index ba45fed342..0846832540 100644 --- a/ui-ngx/src/app/core/http/dashboard.service.ts +++ b/ui-ngx/src/app/core/http/dashboard.service.ts @@ -14,15 +14,15 @@ /// limitations under the License. /// -import {Inject, Injectable} from '@angular/core'; +import { Inject, Injectable } from '@angular/core'; import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; -import { Observable, ReplaySubject, Subject } from 'rxjs/index'; -import {HttpClient} from '@angular/common/http'; -import {PageLink} from '@shared/models/page/page-link'; -import {PageData} from '@shared/models/page/page-data'; -import {Dashboard, DashboardInfo} from '@shared/models/dashboard.models'; -import {WINDOW} from '@core/services/window.service'; -import { ActivationEnd, NavigationEnd, Router } from '@angular/router'; +import { Observable } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; +import { PageLink } from '@shared/models/page/page-link'; +import { PageData } from '@shared/models/page/page-data'; +import { Dashboard, DashboardInfo } from '@shared/models/dashboard.models'; +import { WINDOW } from '@core/services/window.service'; +import { NavigationEnd, Router } from '@angular/router'; import { filter, map, publishReplay, refCount } from 'rxjs/operators'; // @dynamic diff --git a/ui-ngx/src/app/core/http/device.service.ts b/ui-ngx/src/app/core/http/device.service.ts index 216dafd4ec..ca6221227f 100644 --- a/ui-ngx/src/app/core/http/device.service.ts +++ b/ui-ngx/src/app/core/http/device.service.ts @@ -15,17 +15,14 @@ /// import { Injectable } from '@angular/core'; -import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; -import { Observable, Subject, ReplaySubject } from 'rxjs/index'; +import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; +import { Observable, ReplaySubject } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { PageLink } from '@shared/models/page/page-link'; import { PageData } from '@shared/models/page/page-data'; -import { Tenant } from '@shared/models/tenant.model'; -import {DashboardInfo, Dashboard} from '@shared/models/dashboard.models'; -import {map} from 'rxjs/operators'; -import { DeviceInfo, Device, DeviceCredentials, DeviceSearchQuery } from '@app/shared/models/device.models'; -import {EntitySubtype} from '@app/shared/models/entity-type.models'; -import {AuthService} from '../auth/auth.service'; +import { Device, DeviceCredentials, DeviceInfo, DeviceSearchQuery } from '@app/shared/models/device.models'; +import { EntitySubtype } from '@app/shared/models/entity-type.models'; +import { AuthService } from '@core/auth/auth.service'; @Injectable({ providedIn: 'root' diff --git a/ui-ngx/src/app/core/http/entity-relation.service.ts b/ui-ngx/src/app/core/http/entity-relation.service.ts index 091ce6a980..cfa5ab8c4d 100644 --- a/ui-ngx/src/app/core/http/entity-relation.service.ts +++ b/ui-ngx/src/app/core/http/entity-relation.service.ts @@ -15,8 +15,8 @@ /// import { Injectable } from '@angular/core'; -import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; -import { Observable } from 'rxjs/index'; +import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; +import { Observable } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { EntityRelation, EntityRelationInfo, EntityRelationsQuery } from '@shared/models/relation.models'; import { EntityId } from '@app/shared/models/id/entity-id'; diff --git a/ui-ngx/src/app/core/http/entity-view.service.ts b/ui-ngx/src/app/core/http/entity-view.service.ts index 4735ffd102..8b88671c61 100644 --- a/ui-ngx/src/app/core/http/entity-view.service.ts +++ b/ui-ngx/src/app/core/http/entity-view.service.ts @@ -14,15 +14,14 @@ /// limitations under the License. /// -import {Injectable} from '@angular/core'; -import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; -import {Observable} from 'rxjs/index'; -import {HttpClient} from '@angular/common/http'; -import {PageLink} from '@shared/models/page/page-link'; -import {PageData} from '@shared/models/page/page-data'; -import {EntitySubtype} from '@app/shared/models/entity-type.models'; +import { Injectable } from '@angular/core'; +import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; +import { Observable } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; +import { PageLink } from '@shared/models/page/page-link'; +import { PageData } from '@shared/models/page/page-data'; +import { EntitySubtype } from '@app/shared/models/entity-type.models'; import { EntityView, EntityViewInfo, EntityViewSearchQuery } from '@app/shared/models/entity-view.models'; -import { Asset, AssetSearchQuery } from '@shared/models/asset.models'; @Injectable({ providedIn: 'root' diff --git a/ui-ngx/src/app/core/http/entity.service.ts b/ui-ngx/src/app/core/http/entity.service.ts index 98deee4337..e050797735 100644 --- a/ui-ngx/src/app/core/http/entity.service.ts +++ b/ui-ngx/src/app/core/http/entity.service.ts @@ -15,7 +15,7 @@ /// import { Injectable } from '@angular/core'; -import { EMPTY, forkJoin, Observable, of, throwError } from 'rxjs/index'; +import { EMPTY, forkJoin, Observable, of, throwError } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { PageLink } from '@shared/models/page/page-link'; import { AliasEntityType, EntityType } from '@shared/models/entity-type.models'; @@ -28,7 +28,7 @@ import { UserService } from './user.service'; import { DashboardService } from '@core/http/dashboard.service'; import { Direction } from '@shared/models/page/sort-order'; import { PageData } from '@shared/models/page/page-data'; -import { getCurrentAuthUser } from '../auth/auth.selectors'; +import { getCurrentAuthUser } from '@core/auth/auth.selectors'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { Authority } from '@shared/models/authority.enum'; @@ -44,7 +44,7 @@ import { AliasInfo, StateParams, SubscriptionInfo } from '@core/api/widget-api.m import { Datasource, DatasourceType, KeyInfo } from '@app/shared/models/widget.models'; import { UtilsService } from '@core/services/utils.service'; import { AliasFilterType, EntityAlias, EntityAliasFilter, EntityAliasFilterResult } from '@shared/models/alias.models'; -import { EntityInfo, ImportEntityData, ImportEntitiesResultInfo } from '@shared/models/entity.models'; +import { EntityInfo, ImportEntitiesResultInfo, ImportEntityData } from '@shared/models/entity.models'; import { EntityRelationInfo, EntityRelationsQuery, @@ -52,7 +52,7 @@ import { EntitySearchQuery } from '@shared/models/relation.models'; import { EntityRelationService } from '@core/http/entity-relation.service'; -import { isDefined } from '../utils'; +import { isDefined } from '@core/utils'; import { Asset, AssetSearchQuery } from '@shared/models/asset.models'; import { Device, DeviceCredentialsType, DeviceSearchQuery } from '@shared/models/device.models'; import { EntityViewSearchQuery } from '@shared/models/entity-view.models'; diff --git a/ui-ngx/src/app/core/http/event.service.ts b/ui-ngx/src/app/core/http/event.service.ts index 8224f8564a..5ce18818db 100644 --- a/ui-ngx/src/app/core/http/event.service.ts +++ b/ui-ngx/src/app/core/http/event.service.ts @@ -15,8 +15,8 @@ /// import { Injectable } from '@angular/core'; -import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; -import { Observable } from 'rxjs/index'; +import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; +import { Observable } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { TimePageLink } from '@shared/models/page/page-link'; import { PageData } from '@shared/models/page/page-data'; diff --git a/ui-ngx/src/app/core/http/rule-chain.service.ts b/ui-ngx/src/app/core/http/rule-chain.service.ts index 1c32171bbc..73e015ee65 100644 --- a/ui-ngx/src/app/core/http/rule-chain.service.ts +++ b/ui-ngx/src/app/core/http/rule-chain.service.ts @@ -16,22 +16,26 @@ import { ComponentFactory, Injectable } from '@angular/core'; import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; -import { forkJoin, Observable, of } from 'rxjs/index'; +import { forkJoin, Observable, of } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { PageLink } from '@shared/models/page/page-link'; import { PageData } from '@shared/models/page/page-data'; import { ResolvedRuleChainMetaData, - RuleChain, RuleChainConnectionInfo, + RuleChain, + RuleChainConnectionInfo, RuleChainMetaData, ruleChainNodeComponent, - ruleNodeTypeComponentTypes, unknownNodeComponent + ruleNodeTypeComponentTypes, + unknownNodeComponent } from '@shared/models/rule-chain.models'; import { ComponentDescriptorService } from './component-descriptor.service'; import { IRuleNodeConfigurationComponent, LinkLabel, - RuleNodeComponentDescriptor, TestScriptInputParams, TestScriptResult + RuleNodeComponentDescriptor, + TestScriptInputParams, + TestScriptResult } from '@app/shared/models/rule-node.models'; import { ResourcesService } from '../services/resources.service'; import { catchError, map, mergeMap } from 'rxjs/operators'; diff --git a/ui-ngx/src/app/core/http/tenant.service.ts b/ui-ngx/src/app/core/http/tenant.service.ts index 4b144cef98..0068318049 100644 --- a/ui-ngx/src/app/core/http/tenant.service.ts +++ b/ui-ngx/src/app/core/http/tenant.service.ts @@ -15,8 +15,8 @@ /// import { Injectable } from '@angular/core'; -import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; -import { Observable } from 'rxjs/index'; +import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; +import { Observable } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { PageLink } from '@shared/models/page/page-link'; import { PageData } from '@shared/models/page/page-data'; diff --git a/ui-ngx/src/app/core/http/user.service.ts b/ui-ngx/src/app/core/http/user.service.ts index 85986b3da1..312c648845 100644 --- a/ui-ngx/src/app/core/http/user.service.ts +++ b/ui-ngx/src/app/core/http/user.service.ts @@ -15,11 +15,10 @@ /// import { Injectable } from '@angular/core'; -import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; -import { User } from '../../shared/models/user.model'; -import { Observable } from 'rxjs/index'; -import { HttpClient, HttpResponse } from '@angular/common/http'; -import { AdminSettings } from '@shared/models/settings.models'; +import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; +import { User } from '@shared/models/user.model'; +import { Observable } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; import { PageLink } from '@shared/models/page/page-link'; import { PageData } from '@shared/models/page/page-data'; diff --git a/ui-ngx/src/app/core/http/widget.service.ts b/ui-ngx/src/app/core/http/widget.service.ts index 177c1eb3ba..5077d7bf23 100644 --- a/ui-ngx/src/app/core/http/widget.service.ts +++ b/ui-ngx/src/app/core/http/widget.service.ts @@ -15,18 +15,18 @@ /// import { Injectable } from '@angular/core'; -import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; -import { Observable, Subject, of, ReplaySubject } from 'rxjs/index'; +import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; +import { Observable, of, ReplaySubject, Subject } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { PageLink } from '@shared/models/page/page-link'; import { PageData } from '@shared/models/page/page-data'; import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; -import { Widget, WidgetType, widgetType, WidgetTypeData, widgetTypesData } from '@shared/models/widget.models'; +import { Widget, WidgetType, widgetType, widgetTypesData } from '@shared/models/widget.models'; import { UtilsService } from '@core/services/utils.service'; import { TranslateService } from '@ngx-translate/core'; import { ResourcesService } from '../services/resources.service'; -import { toWidgetInfo, WidgetInfo, toWidgetType } from '@app/modules/home/models/widget-component.models'; -import { map, tap, mergeMap, filter } from 'rxjs/operators'; +import { toWidgetInfo, toWidgetType, WidgetInfo } from '@app/modules/home/models/widget-component.models'; +import { filter, map, mergeMap, tap } from 'rxjs/operators'; import { WidgetTypeId } from '@shared/models/id/widget-type-id'; import { NULL_UUID } from '@shared/models/id/has-uuid'; import { ActivationEnd, Router } from '@angular/router'; diff --git a/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts b/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts index 5205256ea2..c66ca15aec 100644 --- a/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts +++ b/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts @@ -24,15 +24,15 @@ import { } from '@angular/common/http'; import { Observable } from 'rxjs/internal/Observable'; import { Inject, Injectable } from '@angular/core'; -import { AuthService } from '../auth/auth.service'; -import { Constants } from '../../shared/models/constants'; +import { AuthService } from '@core/auth/auth.service'; +import { Constants } from '@shared/models/constants'; import { InterceptorHttpParams } from './interceptor-http-params'; -import {catchError, delay, switchMap, tap, map, mergeMap} from 'rxjs/operators'; +import { catchError, delay, mergeMap, switchMap, tap } from 'rxjs/operators'; import { throwError } from 'rxjs/internal/observable/throwError'; import { of } from 'rxjs/internal/observable/of'; import { InterceptorConfig } from './interceptor-config'; import { Store } from '@ngrx/store'; -import { AppState } from '../core.state'; +import { AppState } from '@core/core.state'; import { ActionLoadFinish, ActionLoadStart } from './load.actions'; import { ActionNotificationShow } from '@app/core/notification/notification.actions'; import { DialogService } from '@core/services/dialog.service'; @@ -127,7 +127,8 @@ export class GlobalHttpInterceptor implements HttpInterceptor { const resendRequest = config.resendRequest; const errorCode = errorResponse.error ? errorResponse.error.errorCode : null; if (errorResponse.error && errorResponse.error.refreshTokenPending || errorResponse.status === 401) { - if (errorResponse.error && errorResponse.error.refreshTokenPending || errorCode && errorCode === Constants.serverErrorCode.jwtTokenExpired) { + if (errorResponse.error && errorResponse.error.refreshTokenPending || + errorCode && errorCode === Constants.serverErrorCode.jwtTokenExpired) { return this.refreshTokenAndRetry(req, next); } else if (errorCode !== Constants.serverErrorCode.credentialsExpired) { unhandled = true; diff --git a/ui-ngx/src/app/core/services/item-buffer.service.ts b/ui-ngx/src/app/core/services/item-buffer.service.ts index 48b3e851b5..7d29785c5b 100644 --- a/ui-ngx/src/app/core/services/item-buffer.service.ts +++ b/ui-ngx/src/app/core/services/item-buffer.service.ts @@ -240,7 +240,10 @@ export class ItemBufferService { nodes: [], connections: [] }; - let top = -1, left = -1, bottom = -1, right = -1; + let top = -1; + let left = -1; + let bottom = -1; + let right = -1; for (let i = 0; i < nodes.length; i++) { const origNode = nodes[i]; const node: FcRuleNode = { @@ -253,7 +256,7 @@ export class ItemBufferService { y: origNode.y, name: origNode.name, componentClazz: origNode.component.clazz, - } + }; if (origNode.targetRuleChainId) { node.targetRuleChainId = origNode.targetRuleChainId; } @@ -261,7 +264,7 @@ export class ItemBufferService { node.error = origNode.error; } ruleNodes.nodes.push(node); - if (i==0) { + if (i === 0) { top = node.y; left = node.x; bottom = node.y + 50; @@ -273,8 +276,8 @@ export class ItemBufferService { right = Math.max(right, node.x + 170); } } - ruleNodes.originX = left + (right-left)/2; - ruleNodes.originY = top + (bottom-top)/2; + ruleNodes.originX = left + (right - left) / 2; + ruleNodes.originY = top + (bottom - top) / 2; connections.forEach(connection => { ruleNodes.connections.push(connection); }); diff --git a/ui-ngx/src/app/core/services/raf.service.ts b/ui-ngx/src/app/core/services/raf.service.ts index fbe6639935..b6f6dcc1f2 100644 --- a/ui-ngx/src/app/core/services/raf.service.ts +++ b/ui-ngx/src/app/core/services/raf.service.ts @@ -26,8 +26,8 @@ export type CancelAnimationFrame = () => void; }) export class RafService { - private rafFunction: (frameCallback: () => void) => CancelAnimationFrame; - private rafSupported: boolean; + private readonly rafFunction: (frameCallback: () => void) => CancelAnimationFrame; + private readonly rafSupported: boolean; constructor( @Inject(WINDOW) private window: Window, diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index 0d5af8c8cc..9db6351283 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -400,5 +400,5 @@ export function snakeCase(name: string, separator: string): string { } export function getDescendantProp(obj: any, path: string): any { - return path.split('.').reduce((acc, part) => acc && acc[part], obj) + return path.split('.').reduce((acc, part) => acc && acc[part], obj); } diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-table-header.component.html b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-header.component.html index e30afb1baf..b3291bf323 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-table-header.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-header.component.html @@ -21,7 +21,7 @@ - {{ alarmSearchStatusTranslationsMap.get(status) | translate }} + {{ alarmSearchStatusTranslationsMap.get(alarmSearchStatusEnum[status]) | translate }} diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-table-header.component.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-header.component.ts index 6f075f8517..09f9eeec37 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-table-header.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-header.component.ts @@ -31,6 +31,7 @@ export class AlarmTableHeaderComponent extends EntityTableHeaderComponent - +

{{ 'attribute.add' | translate }}

diff --git a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html index f3630fa73f..a55261b5ff 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html +++ b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html @@ -26,7 +26,7 @@ matInput [ngModel]="attributeScope" (ngModelChange)="attributeScopeChanged($event)"> - {{ telemetryTypeTranslationsMap.get(scope) | translate }} + {{ telemetryTypeTranslationsMap.get(toTelemetryTypeFunc(scope)) | translate }} @@ -135,7 +135,7 @@
+ matSort [matSortActive]="pageLink.sortOrder.property" [matSortDirection]="pageLink.sortDirection()" matSortDisableClear> = []; attributeScope: TelemetryType; + toTelemetryTypeFunc = toTelemetryType; displayedColumns = ['select', 'lastUpdateTs', 'key', 'value']; pageLink: PageLink; diff --git a/ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.ts b/ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.ts index 902d62da03..375fd147ae 100644 --- a/ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.ts @@ -36,7 +36,8 @@ import { Router } from '@angular/router'; providers: [{provide: ErrorStateMatcher, useExisting: AddEntityDialogComponent}], styleUrls: ['./add-entity-dialog.component.scss'] }) -export class AddEntityDialogComponent extends DialogComponent> implements OnInit, ErrorStateMatcher { +export class AddEntityDialogComponent extends + DialogComponent> implements OnInit, ErrorStateMatcher { entityComponent: EntityComponent>; detailsForm: NgForm; diff --git a/ui-ngx/src/app/modules/home/components/entity/contact-based.component.ts b/ui-ngx/src/app/modules/home/components/entity/contact-based.component.ts index 845fe5741e..81c66c994b 100644 --- a/ui-ngx/src/app/modules/home/components/entity/contact-based.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/contact-based.component.ts @@ -25,8 +25,8 @@ import {EntityComponent} from './entity.component'; export abstract class ContactBasedComponent> extends EntityComponent implements AfterViewInit { - constructor(protected store: Store, - protected fb: FormBuilder) { + protected constructor(protected store: Store, + protected fb: FormBuilder) { super(store); } 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 f6d0a108fc..476410c463 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 @@ -136,7 +136,7 @@
+ matSort [matSortActive]="pageLink.sortOrder.property" [matSortDirection]="pageLink.sortDirection()" matSortDisableClear> relation.direction - {{ directionTypeTranslations.get(type) | translate }} + {{ directionTypeTranslations.get(directionTypeEnum[type]) | translate }} @@ -196,7 +196,7 @@ relation.direction - {{ directionTypeTranslations.get(type) | translate }} + {{ directionTypeTranslations.get(directionTypeEnum[type]) | translate }} diff --git a/ui-ngx/src/app/modules/home/components/entity/entity-filter.component.ts b/ui-ngx/src/app/modules/home/components/entity/entity-filter.component.ts index 3d00d6dfbf..2e45489dee 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entity-filter.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/entity-filter.component.ts @@ -62,6 +62,7 @@ export class EntityFilterComponent implements ControlValueAccessor, OnInit { directionTypes = Object.keys(EntitySearchDirection); directionTypeTranslations = entitySearchDirectionTranslations; + directionTypeEnum = EntitySearchDirection; private propagateChange = null; 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 14f93fecb5..5737769590 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 @@ -191,7 +191,7 @@ export class EventTableConfig extends EntityTableConfig { }), entity => entity.body.msgType), new EntityTableColumn('relationType', 'event.relation-type', '100px', - (entity) => entity.body.relationType, entity => ({padding: '0 12px 0 0',}),false, key => ({ + (entity) => entity.body.relationType, entity => ({padding: '0 12px 0 0', }), false, key => ({ padding: '0 12px 0 0' })), new EntityActionTableColumn('data', 'event.data', diff --git a/ui-ngx/src/app/modules/home/components/import-export/import-dialog.component.ts b/ui-ngx/src/app/modules/home/components/import-export/import-dialog.component.ts index 1b15984776..530ebd109d 100644 --- a/ui-ngx/src/app/modules/home/components/import-export/import-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/import-export/import-dialog.component.ts @@ -51,7 +51,7 @@ export interface ImportDialogData { providers: [{provide: ErrorStateMatcher, useExisting: ImportDialogComponent}], styleUrls: [] }) -export class ImportDialogComponent extends DialogComponent +export class ImportDialogComponent extends DialogComponent implements OnInit, ErrorStateMatcher { importTitle: string; @@ -65,7 +65,7 @@ export class ImportDialogComponent extends DialogComponent, + public dialogRef: MatDialogRef, private fb: FormBuilder) { super(store, router, dialogRef); this.importTitle = data.importTitle; diff --git a/ui-ngx/src/app/modules/home/components/import-export/import-export.service.ts b/ui-ngx/src/app/modules/home/components/import-export/import-export.service.ts index af388baeb5..e67d43c783 100644 --- a/ui-ngx/src/app/modules/home/components/import-export/import-export.service.ts +++ b/ui-ngx/src/app/modules/home/components/import-export/import-export.service.ts @@ -426,7 +426,7 @@ export class ImportExportService { private prepareRuleChainMetaData(ruleChainMetaData: RuleChainMetaData) { delete ruleChainMetaData.ruleChainId; for (let i = 0; i < ruleChainMetaData.nodes.length; i++) { - var node = ruleChainMetaData.nodes[i]; + const node = ruleChainMetaData.nodes[i]; delete node.ruleChainId; ruleChainMetaData.nodes[i] = this.prepareExport(node); } diff --git a/ui-ngx/src/app/modules/home/components/relation/relation-table.component.html b/ui-ngx/src/app/modules/home/components/relation/relation-table.component.html index 183f14aa0f..ff99b0faa4 100644 --- a/ui-ngx/src/app/modules/home/components/relation/relation-table.component.html +++ b/ui-ngx/src/app/modules/home/components/relation/relation-table.component.html @@ -26,7 +26,7 @@ - {{ directionTypeTranslations.get(type) | translate }} + {{ directionTypeTranslations.get(directions[type]) | translate }} @@ -85,7 +85,7 @@
+ matSort [matSortActive]="pageLink.sortOrder.property" [matSortDirection]="pageLink.sortDirection()" matSortDisableClear>
+ matSort [matSortActive]="pageLink.sortOrder.property" [matSortDirection]="pageLink.sortDirection()" matSortDisableClear> {{ 'widget-config.action-source' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html index 06dcb65a68..69932b804a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html @@ -58,7 +58,7 @@ widget-config.action-type - {{ widgetActionTypeTranslations.get(actionType) | translate }} + {{ widgetActionTypeTranslations.get(widgetActionType[actionType]) | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog-container.component.ts b/ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog-container.component.ts index 9d17557ac9..312215030a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog-container.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog-container.component.ts @@ -46,14 +46,14 @@ export interface CustomDialogContainerData { selector: 'tb-custom-dialog-container-component', template: '' }) -export class CustomDialogContainerComponent extends DialogComponent implements OnDestroy { +export class CustomDialogContainerComponent extends DialogComponent implements OnDestroy { - private customComponentRef: ComponentRef; + private readonly customComponentRef: ComponentRef; constructor(protected store: Store, protected router: Router, public viewContainerRef: ViewContainerRef, - public dialogRef: MatDialogRef, + public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: CustomDialogContainerData) { super(store, router, dialogRef); let customDialogData: CustomDialogData = { diff --git a/ui-ngx/src/app/modules/home/components/widget/legend-config-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/legend-config-panel.component.html index d2525770bd..a918802afe 100644 --- a/ui-ngx/src/app/modules/home/components/widget/legend-config-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/legend-config-panel.component.html @@ -24,7 +24,7 @@ legend.direction - {{ legendDirectionTranslations.get(direction) | translate }} + {{ legendDirectionTranslations.get(legendDirection[direction]) | translate }} @@ -34,7 +34,7 @@ - {{ legendPositionTranslations.get(pos) | translate }} + {{ legendPositionTranslations.get(legendPosition[pos]) | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/alarm-status-filter-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/alarm-status-filter-panel.component.html index 7c3b4e95d7..38ea1385f2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/alarm-status-filter-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/alarm-status-filter-panel.component.html @@ -19,7 +19,7 @@ - {{ alarmSearchStatusTranslationMap.get(searchStatus) | translate }} + {{ alarmSearchStatusTranslationMap.get(alarmSearchStatusEnum[searchStatus]) | translate }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/alarm-status-filter-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/alarm-status-filter-panel.component.ts index a260c22b89..278708c852 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/alarm-status-filter-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/alarm-status-filter-panel.component.ts @@ -35,6 +35,7 @@ export class AlarmStatusFilterPanelComponent { alarmSearchStatuses = Object.keys(AlarmSearchStatus); alarmSearchStatusTranslationMap = alarmSearchStatusTranslations; + alarmSearchStatusEnum = AlarmSearchStatus; constructor(@Inject(ALARM_STATUS_FILTER_PANEL_DATA) public data: AlarmStatusFilterPanelData) { this.subscription = this.data.subscription; 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 278cf3f951..ae49d68708 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 @@ -62,7 +62,7 @@
+ matSort [matSortActive]="sortOrderProperty" [matSortDirection]="pageLink.sortDirection()" matSortDisableClear> 0) { this.defaultPageSize = pageSize; } - this.pageSizeOptions = [this.defaultPageSize, this.defaultPageSize*2, this.defaultPageSize*3]; + this.pageSizeOptions = [this.defaultPageSize, this.defaultPageSize * 2, this.defaultPageSize * 3]; this.pageLink.pageSize = this.displayPagination ? this.defaultPageSize : Number.POSITIVE_INFINITY; const cssString = constructTableCssString(this.widgetConfig); @@ -320,11 +317,11 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, } if (this.alarmSource) { - this.alarmSource.dataKeys.forEach((_dataKey) => { - const dataKey: EntityColumn = deepClone(_dataKey) as EntityColumn; + this.alarmSource.dataKeys.forEach((alarmDataKey) => { + const dataKey: EntityColumn = deepClone(alarmDataKey) as EntityColumn; dataKey.title = this.utils.customTranslation(dataKey.label, dataKey.label); dataKey.def = 'def' + this.columns.length; - const keySettings: AlarmsTableDataKeySettings = dataKey.settings; + const keySettings: TableWidgetDataKeySettings = dataKey.settings; this.stylesInfo[dataKey.def] = getCellStyleInfo(keySettings); this.contentsInfo[dataKey.def] = getCellContentInfo(keySettings, 'value, alarm, ctx'); @@ -383,7 +380,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, title: column.title, def: column.def, display: this.displayedColumns.indexOf(column.def) > -1 - } + }; }); const injectionTokens = new WeakMap([ @@ -481,7 +478,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, const columnWidth = this.columnWidth[key.def]; return { width: columnWidth - } + }; } public cellStyle(alarm: AlarmInfo, key: EntityColumn): any { @@ -629,7 +626,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, } if (this.alarmsDatasource.selection.hasValue()) { const alarms = this.alarmsDatasource.selection.selected.filter( - (alarm) => { return alarm.id.id !== NULL_UUID } + (alarm) => alarm.id.id !== NULL_UUID ); if (alarms.length) { const title = this.translate.instant('alarm.aknowledge-alarms-title', {count: alarms.length}); @@ -685,7 +682,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, } if (this.alarmsDatasource.selection.hasValue()) { const alarms = this.alarmsDatasource.selection.selected.filter( - (alarm) => { return alarm.id.id !== NULL_UUID } + (alarm) => alarm.id.id !== NULL_UUID ); if (alarms.length) { const title = this.translate.instant('alarm.clear-alarms-title', {count: alarms.length}); @@ -725,8 +722,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, return this.translate.instant(alarmStatusTranslations.get(value)); } else if (alarmField.value === alarmFields.originatorType.value) { return this.translate.instant(entityTypeTranslations.get(value).type); - } - else { + } else { return value; } } else { @@ -741,7 +737,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, if (isDefined(value)) { const alarmField = alarmFields[key.name]; if (alarmField) { - if (alarmField.value == alarmFields.severity.value) { + if (alarmField.value === alarmFields.severity.value) { return { fontWeight: 'bold', color: alarmSeverityColors.get(value) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.component.ts index bde8f744ff..a0fc8f6ad3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.component.ts @@ -227,7 +227,7 @@ export class EntitiesHierarchyWidgetComponent extends PageComponent implements O if (datasource.nodeId) { const node = this.nodesMap[datasource.nodeId]; const key = datasourceData.dataKey.label; - let value = undefined; + let value; if (datasourceData.data && datasourceData.data.length) { value = datasourceData.data[0][1]; } @@ -292,7 +292,7 @@ export class EntitiesHierarchyWidgetComponent extends PageComponent implements O cb([]); } } - }; + } public onNodeSelected: NodeSelectedCallback = (node, event) => { let nodeId; @@ -311,7 +311,7 @@ export class EntitiesHierarchyWidgetComponent extends PageComponent implements O } } } - }; + } public onNodesInserted: NodesInsertedCallback = (nodes, parent) => { if (nodes) { @@ -323,7 +323,7 @@ export class EntitiesHierarchyWidgetComponent extends PageComponent implements O } }); } - }; + } public searchCallback: NodeSearchCallback = (searchText, node) => { const theNode = this.nodesMap[node.id]; @@ -331,7 +331,7 @@ export class EntitiesHierarchyWidgetComponent extends PageComponent implements O return theNode.data.searchText.includes(searchText.toLowerCase()); } return false; - }; + } private updateNodeStyle(node: HierarchyNavTreeNode) { const newText = this.prepareNodeText(node); @@ -404,7 +404,7 @@ export class EntitiesHierarchyWidgetComponent extends PageComponent implements O map(entity => { if (entity !== null) { const node: HierarchyNavTreeNode = { - id: (++this.nodeIdCounter)+'' + id: (++this.nodeIdCounter) + '' }; this.nodesMap[node.id] = node; datasource.nodeId = node.id; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.models.ts index 585d40e20c..7a8cdf24cd 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.models.ts @@ -44,7 +44,7 @@ export interface HierarchyNavTreeNode extends NavTreeNode { datasource: HierarchyNodeDatasource; nodeCtx: HierarchyNodeContext; searchText?: string; - } + }; } export interface HierarchyNodeDatasource extends Datasource { @@ -64,7 +64,7 @@ export type NodeOpenedFunction = (nodeCtx: HierarchyNodeContext) => boolean; export type NodeHasChildrenFunction = (nodeCtx: HierarchyNodeContext) => boolean; export type NodesSortFunction = (nodeCtx1: HierarchyNodeContext, nodeCtx2: HierarchyNodeContext) => number; -export function loadNodeCtxFunction(functionBody: string, argNames: string, ...args: any[]): F { +export function loadNodeCtxFunction any>(functionBody: string, argNames: string, ...args: any[]): F { let nodeCtxFunction: F = null; if (isDefined(functionBody) && functionBody.length) { try { @@ -81,11 +81,11 @@ export function loadNodeCtxFunction(functionBody: string, ar } export function materialIconHtml(materialIcon: string): string { - return ''+materialIcon+''; + return '' + materialIcon + ''; } export function iconUrlHtml(iconUrl: string): string { - return '
 
'; + return '
 
'; } export const defaultNodeRelationQueryFunction: NodeRelationQueryFunction = nodeCtx => { @@ -100,7 +100,7 @@ export const defaultNodeRelationQueryFunction: NodeRelationQueryFunction = nodeC }, filters: [ { - relationType: "Contains", + relationType: 'Contains', entityTypes: [] } ] 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 ba0cb90c0e..e7a13a4fa0 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 @@ -39,7 +39,7 @@
+ matSort [matSortActive]="sortOrderProperty" [matSortDirection]="pageLink.sortDirection()" matSortDisableClear> {{ column.title }} 0) { this.defaultPageSize = pageSize; } - this.pageSizeOptions = [this.defaultPageSize, this.defaultPageSize*2, this.defaultPageSize*3]; + this.pageSizeOptions = [this.defaultPageSize, this.defaultPageSize * 2, this.defaultPageSize * 3]; this.pageLink.pageSize = this.displayPagination ? this.defaultPageSize : Number.POSITIVE_INFINITY; const cssString = constructTableCssString(this.widgetConfig); @@ -253,13 +250,13 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni title: entityNameColumnTitle } as EntityColumn ); - this.contentsInfo['entityName'] = { + this.contentsInfo.entityName = { useCellContentFunction: false }; - this.stylesInfo['entityName'] = { + this.stylesInfo.entityName = { useCellStyleFunction: false }; - this.columnWidth['entityName'] = '0px'; + this.columnWidth.entityName = '0px'; } if (displayEntityType) { this.columns.push( @@ -270,13 +267,13 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni title: this.translate.instant('entity.entity-type'), } as EntityColumn ); - this.contentsInfo['entityType'] = { + this.contentsInfo.entityType = { useCellContentFunction: false }; - this.stylesInfo['entityType'] = { + this.stylesInfo.entityType = { useCellStyleFunction: false }; - this.columnWidth['entityType'] = '0px'; + this.columnWidth.entityType = '0px'; } const dataKeys: Array = []; @@ -284,8 +281,8 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni const datasource = this.subscription.datasources[0]; if (datasource) { - datasource.dataKeys.forEach((_dataKey) => { - const dataKey: EntityColumn = deepClone(_dataKey) as EntityColumn; + datasource.dataKeys.forEach((entityDataKey) => { + const dataKey: EntityColumn = deepClone(entityDataKey) as EntityColumn; if (dataKey.type === DataKeyType.function) { dataKey.name = dataKey.label; } @@ -293,7 +290,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni dataKey.title = this.utils.customTranslation(dataKey.label, dataKey.label); dataKey.def = 'def' + this.columns.length; - const keySettings: EntitiesTableDataKeySettings = dataKey.settings; + const keySettings: TableWidgetDataKeySettings = dataKey.settings; this.stylesInfo[dataKey.def] = getCellStyleInfo(keySettings); this.contentsInfo[dataKey.def] = getCellContentInfo(keySettings, 'value, entity, ctx'); @@ -345,7 +342,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni title: column.title, def: column.def, display: this.displayedColumns.indexOf(column.def) > -1 - } + }; }); const injectionTokens = new WeakMap([ @@ -407,7 +404,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni const columnWidth = this.columnWidth[key.def]; return { width: columnWidth - } + }; } public cellStyle(entity: EntityData, key: EntityColumn): any { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts index e5db3c2666..0870da001f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts @@ -398,8 +398,7 @@ export function flotSettingsSchema(chartType: ChartType): JsonSettingsSchema { return schema; } -export const flotPieSettingsSchema: JsonSettingsSchema = - { +export const flotPieSettingsSchema: JsonSettingsSchema = { schema: { type: 'object', title: 'Settings', diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts index b4b50e0b1c..bce7a37253 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts @@ -51,23 +51,23 @@ export class TbFlot { private settings: TbFlotSettings; - private tooltip: JQuery; + private readonly tooltip: JQuery; - private yAxisTickFormatter: TbFlotTicksFormatterFunction; + private readonly yAxisTickFormatter: TbFlotTicksFormatterFunction; private ticksFormatterFunction: TbFlotTicksFormatterFunction; - private yaxis: TbFlotAxisOptions; + private readonly yaxis: TbFlotAxisOptions; private yaxes: Array; - private options: JQueryPlotOptions; + private readonly options: JQueryPlotOptions; private subscription: IWidgetSubscription; private $element: JQuery; - private trackUnits: string; - private trackDecimals: number; - private tooltipIndividual: boolean; - private tooltipCumulative: boolean; + private readonly trackUnits: string; + private readonly trackDecimals: number; + private readonly tooltipIndividual: boolean; + private readonly tooltipCumulative: boolean; - private defaultBarWidth: number; + private readonly defaultBarWidth: number; private plotInited = false; private plot: JQueryPlot; @@ -85,7 +85,7 @@ export class TbFlot { private mouseupHandler = this.onFlotMouseUp.bind(this); private mouseleaveHandler = this.onFlotMouseLeave.bind(this); - private animatedPie: boolean; + private readonly animatedPie: boolean; private pieDataAnimationDuration: number; private pieData: DatasourceData[]; private pieRenderedData: any[]; @@ -110,7 +110,7 @@ export class TbFlot { return flotDatakeySettingsSchema(defaultShowLines); } - constructor(private ctx: WidgetContext, private chartType: ChartType) { + constructor(private ctx: WidgetContext, private readonly chartType: ChartType) { this.chartType = this.chartType || 'line'; this.settings = ctx.settings as TbFlotSettings; this.tooltip = $('#flot-series-tooltip'); 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 f40514a572..5bcb66d090 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 @@ -56,21 +56,25 @@ export interface DisplayColumn { display: boolean; } +export type CellContentFunction = (...args: any[]) => string; + export interface CellContentInfo { useCellContentFunction: boolean; - cellContentFunction?: Function; + cellContentFunction?: CellContentFunction; units?: string; decimals?: number; } +export type CellStyleFunction = (value: any) => any; + export interface CellStyleInfo { useCellStyleFunction: boolean; - cellStyleFunction?: Function; + cellStyleFunction?: CellStyleFunction; } export function findColumnProperty(searchProperty: string, searchValue: string, columnProperty: string, columns: EntityColumn[]): string { let res = searchValue; - const column = columns.find(column => column[searchProperty] === searchValue); + const column = columns.find(theColumn => theColumn[searchProperty] === searchValue); if (column) { res = column[columnProperty]; } @@ -107,13 +111,13 @@ export function getAlarmValue(alarm: AlarmInfo, key: EntityColumn) { } export function getCellStyleInfo(keySettings: TableWidgetDataKeySettings): CellStyleInfo { - let cellStyleFunction: Function = null; + let cellStyleFunction: CellStyleFunction = null; let useCellStyleFunction = false; if (keySettings.useCellStyleFunction === true) { if (isDefined(keySettings.cellStyleFunction) && keySettings.cellStyleFunction.length > 0) { try { - cellStyleFunction = new Function('value', keySettings.cellStyleFunction); + cellStyleFunction = new Function('value', keySettings.cellStyleFunction) as CellStyleFunction; useCellStyleFunction = true; } catch (e) { cellStyleFunction = null; @@ -128,13 +132,13 @@ export function getCellStyleInfo(keySettings: TableWidgetDataKeySettings): CellS } export function getCellContentInfo(keySettings: TableWidgetDataKeySettings, ...args: string[]): CellContentInfo { - let cellContentFunction: Function = null; + let cellContentFunction: CellContentFunction = null; let useCellContentFunction = false; if (keySettings.useCellContentFunction === true) { if (isDefined(keySettings.cellContentFunction) && keySettings.cellContentFunction.length > 0) { try { - cellContentFunction = new Function(...args, keySettings.cellContentFunction); + cellContentFunction = new Function(...args, keySettings.cellContentFunction) as CellContentFunction; useCellContentFunction = true; } catch (e) { cellContentFunction = null; @@ -173,94 +177,94 @@ export function constructTableCssString(widgetConfig: WidgetConfig): string { const cssString = '.mat-input-element::placeholder {\n' + - ' color: ' + mdDarkSecondary + ';\n'+ + ' color: ' + mdDarkSecondary + ';\n' + '}\n' + '.mat-input-element::-moz-placeholder {\n' + - ' color: ' + mdDarkSecondary + ';\n'+ + ' color: ' + mdDarkSecondary + ';\n' + '}\n' + '.mat-input-element::-webkit-input-placeholder {\n' + - ' color: ' + mdDarkSecondary + ';\n'+ + ' color: ' + mdDarkSecondary + ';\n' + '}\n' + '.mat-input-element:-ms-input-placeholder {\n' + - ' color: ' + mdDarkSecondary + ';\n'+ + ' color: ' + mdDarkSecondary + ';\n' + + '}\n' + + 'mat-toolbar.mat-table-toolbar {\n' + + 'color: ' + mdDark + ';\n' + + '}\n' + + 'mat-toolbar.mat-table-toolbar:not([color="primary"]) button.mat-icon-button mat-icon {\n' + + 'color: ' + mdDarkSecondary + ';\n' + + '}\n' + + '.mat-tab-label {\n' + + 'color: ' + mdDark + ';\n' + + '}\n' + + '.mat-tab-header-pagination-chevron {\n' + + 'border-color: ' + mdDark + ';\n' + + '}\n' + + '.mat-tab-header-pagination-disabled .mat-tab-header-pagination-chevron {\n' + + 'border-color: ' + mdDarkDisabled2 + ';\n' + + '}\n' + + '.mat-table .mat-header-row {\n' + + 'background-color: ' + origBackgroundColor + ';\n' + + '}\n' + + '.mat-table .mat-header-cell {\n' + + 'color: ' + mdDarkSecondary + ';\n' + + '}\n' + + '.mat-table .mat-header-cell .mat-sort-header-arrow {\n' + + 'color: ' + mdDarkDisabled + ';\n' + + '}\n' + + '.mat-table .mat-cell, .mat-table .mat-header-cell {\n' + + 'border-bottom-color: ' + mdDarkDivider + ';\n' + + '}\n' + + '.mat-table .mat-cell .mat-checkbox-frame, .mat-table .mat-header-cell .mat-checkbox-frame {\n' + + 'border-color: ' + mdDarkSecondary + ';\n' + + '}\n' + + '.mat-table .mat-row .mat-cell.mat-table-sticky {\n' + + 'transition: background-color .2s;\n' + + '}\n' + + '.mat-table .mat-row.tb-current-entity {\n' + + 'background-color: ' + currentEntityColor + ';\n' + + '}\n' + + '.mat-table .mat-row.tb-current-entity .mat-cell.mat-table-sticky {\n' + + 'background-color: ' + currentEntityStickyColor + ';\n' + + '}\n' + + '.mat-table .mat-row:hover:not(.tb-current-entity) {\n' + + 'background-color: ' + hoverColor + ';\n' + + '}\n' + + '.mat-table .mat-row:hover:not(.tb-current-entity) .mat-cell.mat-table-sticky {\n' + + 'background-color: ' + hoverStickyColor + ';\n' + + '}\n' + + '.mat-table .mat-row.mat-row-select.mat-selected:not(.tb-current-entity) {\n' + + 'background-color: ' + selectedColor + ';\n' + + '}\n' + + '.mat-table .mat-row.mat-row-select.mat-selected:not(.tb-current-entity) .mat-cell.mat-table-sticky {\n' + + 'background-color: ' + selectedStickyColor + ';\n' + + '}\n' + + '.mat-table .mat-row .mat-cell.mat-table-sticky, .mat-table .mat-header-cell.mat-table-sticky {\n' + + 'background-color: ' + origBackgroundColor + ';\n' + + '}\n' + + '.mat-table .mat-cell {\n' + + 'color: ' + mdDark + ';\n' + + '}\n' + + '.mat-table .mat-cell button.mat-icon-button mat-icon {\n' + + 'color: ' + mdDarkSecondary + ';\n' + + '}\n' + + '.mat-table .mat-cell button.mat-icon-button[disabled][disabled] mat-icon {\n' + + 'color: ' + mdDarkDisabled + ';\n' + + '}\n' + + '.mat-divider {\n' + + 'border-top-color: ' + mdDarkDivider + ';\n' + + '}\n' + + '.mat-paginator {\n' + + 'color: ' + mdDarkSecondary + ';\n' + + '}\n' + + '.mat-paginator button.mat-icon-button {\n' + + 'color: ' + mdDarkSecondary + ';\n' + + '}\n' + + '.mat-paginator button.mat-icon-button[disabled][disabled] {\n' + + 'color: ' + mdDarkDisabled + ';\n' + '}\n' + - 'mat-toolbar.mat-table-toolbar {\n'+ - 'color: ' + mdDark + ';\n'+ - '}\n'+ - 'mat-toolbar.mat-table-toolbar:not([color="primary"]) button.mat-icon-button mat-icon {\n'+ - 'color: ' + mdDarkSecondary + ';\n'+ - '}\n'+ - '.mat-tab-label {\n'+ - 'color: ' + mdDark + ';\n'+ - '}\n'+ - '.mat-tab-header-pagination-chevron {\n'+ - 'border-color: ' + mdDark + ';\n'+ - '}\n'+ - '.mat-tab-header-pagination-disabled .mat-tab-header-pagination-chevron {\n'+ - 'border-color: ' + mdDarkDisabled2 + ';\n'+ - '}\n'+ - '.mat-table .mat-header-row {\n'+ - 'background-color: ' + origBackgroundColor + ';\n'+ - '}\n'+ - '.mat-table .mat-header-cell {\n'+ - 'color: ' + mdDarkSecondary + ';\n'+ - '}\n'+ - '.mat-table .mat-header-cell .mat-sort-header-arrow {\n'+ - 'color: ' + mdDarkDisabled + ';\n'+ - '}\n'+ - '.mat-table .mat-cell, .mat-table .mat-header-cell {\n'+ - 'border-bottom-color: '+mdDarkDivider+';\n'+ - '}\n'+ - '.mat-table .mat-cell .mat-checkbox-frame, .mat-table .mat-header-cell .mat-checkbox-frame {\n'+ - 'border-color: '+mdDarkSecondary+';\n'+ - '}\n'+ - '.mat-table .mat-row .mat-cell.mat-table-sticky {\n'+ - 'transition: background-color .2s;\n'+ - '}\n'+ - '.mat-table .mat-row.tb-current-entity {\n'+ - 'background-color: ' + currentEntityColor + ';\n'+ - '}\n'+ - '.mat-table .mat-row.tb-current-entity .mat-cell.mat-table-sticky {\n'+ - 'background-color: ' + currentEntityStickyColor + ';\n'+ - '}\n'+ - '.mat-table .mat-row:hover:not(.tb-current-entity) {\n'+ - 'background-color: ' + hoverColor + ';\n'+ - '}\n'+ - '.mat-table .mat-row:hover:not(.tb-current-entity) .mat-cell.mat-table-sticky {\n'+ - 'background-color: ' + hoverStickyColor + ';\n'+ - '}\n'+ - '.mat-table .mat-row.mat-row-select.mat-selected:not(.tb-current-entity) {\n'+ - 'background-color: ' + selectedColor + ';\n'+ - '}\n'+ - '.mat-table .mat-row.mat-row-select.mat-selected:not(.tb-current-entity) .mat-cell.mat-table-sticky {\n'+ - 'background-color: ' + selectedStickyColor + ';\n'+ - '}\n'+ - '.mat-table .mat-row .mat-cell.mat-table-sticky, .mat-table .mat-header-cell.mat-table-sticky {\n'+ - 'background-color: ' + origBackgroundColor + ';\n'+ - '}\n'+ - '.mat-table .mat-cell {\n'+ - 'color: ' + mdDark + ';\n'+ - '}\n'+ - '.mat-table .mat-cell button.mat-icon-button mat-icon {\n'+ - 'color: ' + mdDarkSecondary + ';\n'+ - '}\n'+ - '.mat-table .mat-cell button.mat-icon-button[disabled][disabled] mat-icon {\n'+ - 'color: ' + mdDarkDisabled + ';\n'+ - '}\n'+ - '.mat-divider {\n'+ - 'border-top-color: ' + mdDarkDivider + ';\n'+ - '}\n'+ - '.mat-paginator {\n'+ - 'color: ' + mdDarkSecondary + ';\n'+ - '}\n'+ - '.mat-paginator button.mat-icon-button {\n'+ - 'color: ' + mdDarkSecondary + ';\n'+ - '}\n'+ - '.mat-paginator button.mat-icon-button[disabled][disabled] {\n'+ - 'color: ' + mdDarkDisabled + ';\n'+ - '}\n'+ - '.mat-paginator .mat-select-value {\n'+ - 'color: ' + mdDarkSecondary + ';\n'+ + '.mat-paginator .mat-select-value {\n' + + 'color: ' + mdDarkSecondary + ';\n' + '}'; return cssString; } 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 c6820dc351..0f48d98e44 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 @@ -42,7 +42,7 @@
+ matSort [matSortActive]="source.pageLink.sortOrder.property" [matSortDirection]="source.pageLink.sortDirection()" matSortDisableClear> Timestamp 0) { this.defaultPageSize = pageSize; } - this.pageSizeOptions = [this.defaultPageSize, this.defaultPageSize*2, this.defaultPageSize*3]; + this.pageSizeOptions = [this.defaultPageSize, this.defaultPageSize * 2, this.defaultPageSize * 3]; let cssString = constructTableCssString(this.widgetConfig); const origBackgroundColor = this.widgetConfig.backgroundColor || 'rgb(255, 255, 255)'; - cssString += '.tb-table-widget mat-toolbar.mat-table-toolbar:not([color=primary]) {\n'+ - 'background-color: ' + origBackgroundColor + ' !important;\n'+ + cssString += '.tb-table-widget mat-toolbar.mat-table-toolbar:not([color=primary]) {\n' + + 'background-color: ' + origBackgroundColor + ' !important;\n' + '}\n'; const cssParser = new cssjs(); @@ -262,13 +259,13 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI source.stylesInfo = []; source.contentsInfo = []; source.rowDataTemplate = {}; - source.rowDataTemplate['Timestamp'] = null; + source.rowDataTemplate.Timestamp = null; if (this.showTimestamp) { source.displayedColumns.push('0'); } for (let a = 0; a < datasource.dataKeys.length; a++ ) { const dataKey = datasource.dataKeys[a]; - const keySettings: TimeseriesTableDataKeySettings = dataKey.settings; + const keySettings: TableWidgetDataKeySettings = dataKey.settings; const index = a + 1; source.header.push({ index, @@ -371,14 +368,14 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI return header.index; } - public trackByRowIndex(index: number, row: TimeseriesRow) { + public trackByRowIndex(index: number) { return index; } public cellStyle(source: TimeseriesTableSource, index: number, value: any): any { let style: any = {}; if (index > 0) { - const styleInfo = source.stylesInfo[index-1]; + const styleInfo = source.stylesInfo[index - 1]; if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) { try { style = styleInfo.cellStyleFunction(value); @@ -394,16 +391,15 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI if (index === 0) { return row.formattedTs; } else { - let content = ''; - const contentInfo = source.contentsInfo[index-1]; + let content; + const contentInfo = source.contentsInfo[index - 1]; if (contentInfo.useCellContentFunction && contentInfo.cellContentFunction) { try { const rowData = source.rowDataTemplate; - rowData['Timestamp'] = row[0]; - for (let h=0; h < source.header.length; h++) { - const headerInfo = source.header[h]; + rowData.Timestamp = row[0]; + source.header.forEach((headerInfo) => { rowData[headerInfo.dataKey.name] = row[headerInfo.index]; - } + }); content = contentInfo.cellContentFunction(value, rowData, this.ctx); } catch (e) { content = '' + value; @@ -498,8 +494,7 @@ class TimeseriesDatasource implements DataSource { const rowsMap: {[timestamp: number]: TimeseriesRow} = {}; for (let d = 0; d < data.length; d++) { const columnData = data[d].data; - for (let i = 0; i < columnData.length; i++) { - const cellData = columnData[i]; + columnData.forEach((cellData) => { const timestamp = cellData[0]; let row = rowsMap[timestamp]; if (!row) { @@ -508,20 +503,21 @@ class TimeseriesDatasource implements DataSource { }; row[0] = timestamp; for (let c = 0; c < data.length; c++) { - row[c+1] = undefined; + row[c + 1] = undefined; } rowsMap[timestamp] = row; } - row[d+1] = cellData[1]; - } + row[d + 1] = cellData[1]; + }); } const rows: TimeseriesRow[] = []; for (const t of Object.keys(rowsMap)) { if (this.hideEmptyLines) { let hideLine = true; - for (let _c = 0; (_c < data.length) && hideLine; _c++) { - if (rowsMap[t][_c+1]) + for (let c = 0; (c < data.length) && hideLine; c++) { + if (rowsMap[t][c + 1]) { hideLine = false; + } } if (!hideLine) { rows.push(rowsMap[t]); diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts index 2f34e67c9f..a07724201c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts @@ -58,7 +58,7 @@ import { MatDialog } from '@angular/material/dialog'; import { EntityService } from '@core/http/entity.service'; import { JsonFormComponentData } from '@shared/components/json-form/json-form-component.models'; import { WidgetActionsData } from './action/manage-widget-actions.component.models'; -import { Dashboard } from '@shared/models/dashboard.models'; +import { Dashboard, DashboardState } from '@shared/models/dashboard.models'; const emptySettingsSchema = { type: 'object', @@ -107,7 +107,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont functionsOnly: boolean; @Input() - dashboardStates: Array; + dashboardStates: {[id: string]: DashboardState }; @Input() disabled: boolean; diff --git a/ui-ngx/src/app/modules/home/home.component.html b/ui-ngx/src/app/modules/home/home.component.html index df432c4aa2..179153afb6 100644 --- a/ui-ngx/src/app/modules/home/home.component.html +++ b/ui-ngx/src/app/modules/home/home.component.html @@ -50,9 +50,9 @@ (click)="closeSearch()"> arrow_back -
-
+ +
diff --git a/ui-ngx/src/app/modules/home/home.component.ts b/ui-ngx/src/app/modules/home/home.component.ts index daf22ee81a..2d0632bba5 100644 --- a/ui-ngx/src/app/modules/home/home.component.ts +++ b/ui-ngx/src/app/modules/home/home.component.ts @@ -50,7 +50,7 @@ export class HomeComponent extends PageComponent implements AfterViewInit, OnIni activeComponent: any; searchableComponent: ISearchableComponent; - sidenavMode = 'side'; + sidenavMode: 'over' | 'push' | 'side' = 'side'; sidenavOpened = true; logo = require('../../../assets/logo_title_white.svg'); 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 d3395043e2..6cfb596f44 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 @@ -134,11 +134,10 @@ export class DashboardWidgets implements Iterable { } if (widgetLayoutChange !== null) { widgetLayoutChange.forEachChangedItem((changed) => { - let operation = updateRecords.find((record) => record.widgetId === changed.key); + const operation = updateRecords.find((record) => record.widgetId === changed.key); if (!operation) { - let index = this.dashboardWidgets.findIndex((dashboardWidget) => dashboardWidget.widgetId === changed.key); - if (index > -1) { - const widget = this.dashboardWidgets[index]; + const widget = this.dashboardWidgets.find((dashboardWidget) => dashboardWidget.widgetId === changed.key); + if (widget) { updateRecords.push({ widget: widget.widget, widgetId: changed.key, @@ -167,7 +166,8 @@ export class DashboardWidgets implements Iterable { index = this.dashboardWidgets.findIndex((dashboardWidget) => dashboardWidget.widgetId === record.widgetId); if (index > -1) { const prevDashboardWidget = this.dashboardWidgets[index]; - if (!deepEqual(prevDashboardWidget.widget, record.widget) || !deepEqual(prevDashboardWidget.widgetLayout, record.widgetLayout)) { + if (!deepEqual(prevDashboardWidget.widget, record.widget) || + !deepEqual(prevDashboardWidget.widgetLayout, record.widgetLayout)) { this.dashboardWidgets[index] = new DashboardWidget(this.dashboard, record.widget, record.widgetLayout); this.dashboardWidgets[index].highlighted = prevDashboardWidget.highlighted; this.dashboardWidgets[index].selected = prevDashboardWidget.selected; 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 818f94391e..e4f307f4ab 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 @@ -108,36 +108,10 @@ export class WidgetContext { } set changeDetector(cd: ChangeDetectorRef) { - this._changeDetector = cd; + this.changeDetectorValue = cd; } - private _changeDetector: ChangeDetectorRef; - - detectChanges(updateWidgetParams: boolean = false) { - if (!this.destroyed) { - if (updateWidgetParams) { - this.dashboardWidget.updateWidgetParams(); - } - this._changeDetector.detectChanges(); - } - } - - updateWidgetParams() { - if (!this.destroyed) { - setTimeout(() => { - this.dashboardWidget.updateWidgetParams(); - }, 0); - } - } - - reset() { - this.destroyed = false; - this.hideTitlePanel = false; - this.widgetTitleTemplate = undefined; - this.widgetTitle = undefined; - this.customHeaderActions = undefined; - this.widgetActions = undefined; - } + private changeDetectorValue: ChangeDetectorRef; inited = false; destroyed = false; @@ -209,6 +183,32 @@ export class WidgetContext { $injector?: Injector; ngZone?: NgZone; + + detectChanges(updateWidgetParams: boolean = false) { + if (!this.destroyed) { + if (updateWidgetParams) { + this.dashboardWidget.updateWidgetParams(); + } + this.changeDetectorValue.detectChanges(); + } + } + + updateWidgetParams() { + if (!this.destroyed) { + setTimeout(() => { + this.dashboardWidget.updateWidgetParams(); + }, 0); + } + } + + reset() { + this.destroyed = false; + this.hideTitlePanel = false; + this.widgetTitleTemplate = undefined; + this.widgetTitle = undefined; + this.customHeaderActions = undefined; + this.widgetActions = undefined; + } } export interface IDynamicWidgetComponent { 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 0b36672d72..9fa87c6600 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 @@ -20,8 +20,8 @@ import { Routes, RouterModule } from '@angular/router'; import { MailServerComponent } from '@modules/home/pages/admin/mail-server.component'; import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard'; import { Authority } from '@shared/models/authority.enum'; -import {GeneralSettingsComponent} from "@modules/home/pages/admin/general-settings.component"; -import {SecuritySettingsComponent} from "@modules/home/pages/admin/security-settings.component"; +import {GeneralSettingsComponent} from '@modules/home/pages/admin/general-settings.component'; +import {SecuritySettingsComponent} from '@modules/home/pages/admin/security-settings.component'; const routes: Routes = [ { diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.html b/ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.html index 681ff8cd59..663cc4f96c 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.html @@ -76,7 +76,7 @@
+ matSort [matSortActive]="pageLink.sortOrder.property" [matSortDirection]="pageLink.sortDirection()" matSortDisableClear> {{ 'dashboard.state-name' | translate }} diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/state-controller.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/states/state-controller.component.ts index 0a9eafa09d..2e96f1c5b7 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/states/state-controller.component.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/state-controller.component.ts @@ -89,10 +89,10 @@ export abstract class StateControllerComponent implements IStateControllerCompon private inited = false; - constructor(protected router: Router, - protected route: ActivatedRoute, - protected ngZone: NgZone, - protected statesControllerService: StatesControllerService) { + protected constructor(protected router: Router, + protected route: ActivatedRoute, + protected ngZone: NgZone, + protected statesControllerService: StatesControllerService) { } ngOnInit(): void { diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/states-component.directive.ts b/ui-ngx/src/app/modules/home/pages/dashboard/states/states-component.directive.ts index 534eb1d6f7..c2455b1d6a 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/states/states-component.directive.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/states-component.directive.ts @@ -34,6 +34,7 @@ import { IStateController } from '@core/api/widget-api.models'; import { IStateControllerComponent } from '@home/pages/dashboard/states/state-controller.models'; @Directive({ + // tslint:disable-next-line:directive-selector selector: 'tb-states-component' }) export class StatesComponentDirective implements OnInit, OnDestroy, OnChanges { diff --git a/ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.html b/ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.html index 95975082a9..1ee3635765 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.html @@ -35,7 +35,7 @@ - {{ credentialTypeNamesMap.get(credentialsType) }} + {{ credentialTypeNamesMap.get(deviceCredentialsType[credentialsType]) }} 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 1eac5e3b67..3f9c2a988f 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 @@ -817,7 +817,7 @@ export class RuleChainPageComponent extends PageComponent menuItems: [] }; const sourceNode: FcRuleNode = this.ruleChainCanvas.modelService.nodes.getNodeByConnectorId(edge.source); - if (sourceNode.component.type != RuleNodeType.INPUT) { + if (sourceNode.component.type !== RuleNodeType.INPUT) { contextInfo.menuItems.push( { action: () => { @@ -900,14 +900,14 @@ export class RuleChainPageComponent extends PageComponent edges.forEach((edge) => { const sourceNode = this.ruleChainCanvas.modelService.nodes.getNodeByConnectorId(edge.source); const destNode = this.ruleChainCanvas.modelService.nodes.getNodeByConnectorId(edge.destination); - const isInputSource = sourceNode.component.type == RuleNodeType.INPUT; + const isInputSource = sourceNode.component.type === RuleNodeType.INPUT; const fromIndex = nodes.indexOf(sourceNode); const toIndex = nodes.indexOf(destNode); if ( (isInputSource || fromIndex > -1) && toIndex > -1 ) { const connection: RuleNodeConnection = { - isInputSource: isInputSource, - fromIndex: fromIndex, - toIndex: toIndex, + isInputSource, + fromIndex, + toIndex, label: edge.label, labels: edge.labels }; @@ -929,8 +929,8 @@ export class RuleChainPageComponent extends PageComponent const scrollParent = canvas.parent(); const scrollTop = scrollParent.scrollTop(); const scrollLeft = scrollParent.scrollLeft(); - x = scrollLeft + scrollParent.width()/2; - y = scrollTop + scrollParent.height()/2; + x = scrollLeft + scrollParent.width() / 2; + y = scrollTop + scrollParent.height() / 2; } const ruleNodes = this.itembuffer.pasteRuleNodes(x, y); if (ruleNodes) { @@ -972,19 +972,21 @@ export class RuleChainPageComponent extends PageComponent this.ruleChainCanvas.modelService.edges.delete(found); } } else { - const sourceConnectors = this.ruleChainCanvas.modelService.nodes.getConnectorsByType(sourceNode, FlowchartConstants.rightConnectorType); + const sourceConnectors = this.ruleChainCanvas.modelService.nodes + .getConnectorsByType(sourceNode, FlowchartConstants.rightConnectorType); if (sourceConnectors && sourceConnectors.length) { source = sourceConnectors[0].id; } } - const destConnectors = this.ruleChainCanvas.modelService.nodes.getConnectorsByType(destNode, FlowchartConstants.leftConnectorType); + const destConnectors = this.ruleChainCanvas.modelService.nodes + .getConnectorsByType(destNode, FlowchartConstants.leftConnectorType); if (destConnectors && destConnectors.length) { destination = destConnectors[0].id; } if (source && destination) { const edge: FcRuleEdge = { - source: source, - destination: destination, + source, + destination, label: connection.label, labels: connection.labels }; @@ -1203,7 +1205,7 @@ export class RuleChainPageComponent extends PageComponent nodes.push(node); } }); - const firstNodeEdge = this.ruleChainModel.edges.find((edge) => edge.source === this.inputConnectorId+''); + const firstNodeEdge = this.ruleChainModel.edges.find((edge) => edge.source === this.inputConnectorId + ''); if (firstNodeEdge) { const firstNode = this.ruleChainCanvas.modelService.nodes.getNodeByConnectorId(firstNodeEdge.destination); ruleChainMetaData.firstNodeIndex = nodes.indexOf(firstNode); @@ -1241,8 +1243,8 @@ export class RuleChainPageComponent extends PageComponent } } }); - this.ruleChainService.saveAndGetResolvedRuleChainMetadata(ruleChainMetaData).subscribe((ruleChainMetaData) => { - this.ruleChainMetaData = ruleChainMetaData; + this.ruleChainService.saveAndGetResolvedRuleChainMetadata(ruleChainMetaData).subscribe((savedRuleChainMetaData) => { + this.ruleChainMetaData = savedRuleChainMetaData; if (this.isImport) { this.isDirtyValue = false; this.isImport = false; diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts index 9fb8b17daa..cf768adebb 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts @@ -113,7 +113,8 @@ export class RuleChainImportGuard implements CanActivate { private router: Router) { } - canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable | Promise | boolean | UrlTree { + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): + Observable | Promise | boolean | UrlTree { if (this.itembuffer.hasRuleChainImport()) { return true; } else { diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulenode.component.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rulenode.component.ts index d6bb3cc588..26af6b7061 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulenode.component.ts +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulenode.component.ts @@ -19,6 +19,7 @@ import { Component, OnInit } from '@angular/core'; import { FcNodeComponent } from 'ngx-flowchart/dist/ngx-flowchart'; @Component({ + // tslint:disable-next-line:component-selector selector: 'rule-node', templateUrl: './rulenode.component.html', styleUrls: ['./rulenode.component.scss'] diff --git a/ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.html b/ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.html index a6444c325c..340971b510 100644 --- a/ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.html @@ -35,7 +35,7 @@ user.activation-method - {{ activationMethodTranslations.get(activationMethod) | translate }} + {{ activationMethodTranslations.get(activationMethodEnum[activationMethod]) | translate }} diff --git a/ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.ts index b6edc66ee6..fbbd7cf6aa 100644 --- a/ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.ts @@ -50,6 +50,7 @@ export class AddUserDialogComponent extends DialogComponent
diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html index e530d85203..7df68508a9 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html @@ -29,7 +29,7 @@ - {{ widgetTypesDataMap.get(type).name | translate }} + {{ widgetTypesDataMap.get(widgetTypes[type]).name | translate }}
diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.ts b/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.ts index 795f164775..89be4d594c 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.ts +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.ts @@ -88,7 +88,8 @@ export class WidgetLibraryComponent extends PageComponent implements OnInit { getStateParams(): StateParams { return {}; } - } as IStateController}, + } as IStateController; + }, {}); @ViewChild('dashboard', {static: true}) dashboard: IDashboardComponent; diff --git a/ui-ngx/src/app/modules/login/login-routing.module.ts b/ui-ngx/src/app/modules/login/login-routing.module.ts index bfc37edba2..50b1867c32 100644 --- a/ui-ngx/src/app/modules/login/login-routing.module.ts +++ b/ui-ngx/src/app/modules/login/login-routing.module.ts @@ -15,10 +15,10 @@ /// import { NgModule } from '@angular/core'; -import { Routes, RouterModule } from '@angular/router'; +import { RouterModule, Routes } from '@angular/router'; import { LoginComponent } from './pages/login/login.component'; -import { AuthGuard } from '../../core/guards/auth.guard'; +import { AuthGuard } from '@core/guards/auth.guard'; import { ResetPasswordRequestComponent } from '@modules/login/pages/login/reset-password-request.component'; import { ResetPasswordComponent } from '@modules/login/pages/login/reset-password.component'; import { CreatePasswordComponent } from '@modules/login/pages/login/create-password.component'; diff --git a/ui-ngx/src/app/modules/login/pages/login/create-password.component.ts b/ui-ngx/src/app/modules/login/pages/login/create-password.component.ts index 2e471db8a3..620aab2146 100644 --- a/ui-ngx/src/app/modules/login/pages/login/create-password.component.ts +++ b/ui-ngx/src/app/modules/login/pages/login/create-password.component.ts @@ -15,17 +15,15 @@ /// import { Component, OnDestroy, OnInit } from '@angular/core'; -import { AuthService } from '../../../../core/auth/auth.service'; -import { LoginRequest } from '../../../../shared/models/login.models'; +import { AuthService } from '@core/auth/auth.service'; import { Store } from '@ngrx/store'; -import { AppState } from '../../../../core/core.state'; -import { PageComponent } from '../../../../shared/components/page.component'; +import { AppState } from '@core/core.state'; +import { PageComponent } from '@shared/components/page.component'; import { FormBuilder } from '@angular/forms'; import { ActionNotificationShow } from '@core/notification/notification.actions'; import { TranslateService } from '@ngx-translate/core'; -import { ActivatedRoute, ActivatedRouteSnapshot } from '@angular/router'; -import { Observable, Subscription } from 'rxjs'; -import { map } from 'rxjs/operators'; +import { ActivatedRoute } from '@angular/router'; +import { Subscription } from 'rxjs'; @Component({ selector: 'tb-create-password', diff --git a/ui-ngx/src/app/modules/login/pages/login/login.component.ts b/ui-ngx/src/app/modules/login/pages/login/login.component.ts index c048276a57..f34d7beef0 100644 --- a/ui-ngx/src/app/modules/login/pages/login/login.component.ts +++ b/ui-ngx/src/app/modules/login/pages/login/login.component.ts @@ -15,11 +15,10 @@ /// import { Component, OnInit } from '@angular/core'; -import { AuthService } from '../../../../core/auth/auth.service'; -import { LoginRequest } from '../../../../shared/models/login.models'; +import { AuthService } from '@core/auth/auth.service'; import { Store } from '@ngrx/store'; -import { AppState } from '../../../../core/core.state'; -import { PageComponent } from '../../../../shared/components/page.component'; +import { AppState } from '@core/core.state'; +import { PageComponent } from '@shared/components/page.component'; import { FormBuilder } from '@angular/forms'; import { HttpErrorResponse } from '@angular/common/http'; import { Constants } from '@shared/models/constants'; diff --git a/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.ts b/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.ts index b8ac67df3f..8259ad9ccf 100644 --- a/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.ts +++ b/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.ts @@ -15,11 +15,10 @@ /// import { Component, OnInit } from '@angular/core'; -import { AuthService } from '../../../../core/auth/auth.service'; -import { LoginRequest } from '../../../../shared/models/login.models'; +import { AuthService } from '@core/auth/auth.service'; import { Store } from '@ngrx/store'; -import { AppState } from '../../../../core/core.state'; -import { PageComponent } from '../../../../shared/components/page.component'; +import { AppState } from '@core/core.state'; +import { PageComponent } from '@shared/components/page.component'; import { FormBuilder } from '@angular/forms'; import { ActionNotificationShow } from '@core/notification/notification.actions'; import { TranslateService } from '@ngx-translate/core'; diff --git a/ui-ngx/src/app/modules/login/pages/login/reset-password.component.ts b/ui-ngx/src/app/modules/login/pages/login/reset-password.component.ts index 7ad658fcba..067aeae677 100644 --- a/ui-ngx/src/app/modules/login/pages/login/reset-password.component.ts +++ b/ui-ngx/src/app/modules/login/pages/login/reset-password.component.ts @@ -15,17 +15,15 @@ /// import { Component, OnDestroy, OnInit } from '@angular/core'; -import { AuthService } from '../../../../core/auth/auth.service'; -import { LoginRequest } from '../../../../shared/models/login.models'; +import { AuthService } from '@core/auth/auth.service'; import { Store } from '@ngrx/store'; -import { AppState } from '../../../../core/core.state'; -import { PageComponent } from '../../../../shared/components/page.component'; +import { AppState } from '@core/core.state'; +import { PageComponent } from '@shared/components/page.component'; import { FormBuilder } from '@angular/forms'; import { ActionNotificationShow } from '@core/notification/notification.actions'; import { TranslateService } from '@ngx-translate/core'; import { ActivatedRoute } from '@angular/router'; -import { Observable, Subscription } from 'rxjs'; -import { map } from 'rxjs/operators'; +import { Subscription } from 'rxjs'; @Component({ selector: 'tb-reset-password', diff --git a/ui-ngx/src/app/shared/components/breadcrumb.component.ts b/ui-ngx/src/app/shared/components/breadcrumb.component.ts index 57c99d4340..8b1ec0853c 100644 --- a/ui-ngx/src/app/shared/components/breadcrumb.component.ts +++ b/ui-ngx/src/app/shared/components/breadcrumb.component.ts @@ -22,7 +22,7 @@ import { distinctUntilChanged, filter, map } from 'rxjs/operators'; import { TranslateService } from '@ngx-translate/core'; @Component({ - selector: '[tb-breadcrumb]', + selector: 'tb-breadcrumb', templateUrl: './breadcrumb.component.html', styleUrls: ['./breadcrumb.component.scss'] }) diff --git a/ui-ngx/src/app/shared/components/cheatsheet.component.ts b/ui-ngx/src/app/shared/components/cheatsheet.component.ts index 3c7412add8..995b62dccc 100644 --- a/ui-ngx/src/app/shared/components/cheatsheet.component.ts +++ b/ui-ngx/src/app/shared/components/cheatsheet.component.ts @@ -128,7 +128,7 @@ import { Hotkey, HotkeysService } from 'angular2-hotkeys'; export class TbCheatSheetComponent implements OnInit, OnDestroy { helpVisible = false; - @Input() title: string = 'Keyboard Shortcuts:'; + @Input() title = 'Keyboard Shortcuts:'; @Input() hotkeys: Hotkey[]; @@ -137,9 +137,9 @@ export class TbCheatSheetComponent implements OnInit, OnDestroy { private mousetrap: MousetrapInstance; - constructor(private _elementRef: ElementRef, + constructor(private elementRef: ElementRef, private hotkeysService: HotkeysService) { - this.mousetrap = new Mousetrap(this._elementRef.nativeElement); + this.mousetrap = new Mousetrap(this.elementRef.nativeElement); this.mousetrap.bind('?', (event: KeyboardEvent, combo: string) => { this.toggleCheatSheet(); }); diff --git a/ui-ngx/src/app/shared/components/entity/entity-list-select.component.ts b/ui-ngx/src/app/shared/components/entity/entity-list-select.component.ts index c10d37d9a8..09e712330f 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-list-select.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-list-select.component.ts @@ -66,7 +66,7 @@ export class EntityListSelectComponent implements ControlValueAccessor, OnInit, displayEntityTypeSelect: boolean; - private defaultEntityType: EntityType | AliasEntityType = null; + private readonly defaultEntityType: EntityType | AliasEntityType = null; private propagateChange = (v: any) => { }; 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 6465fb6fa0..252a05fff6 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 @@ -60,7 +60,7 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte displayEntityTypeSelect: boolean; - private defaultEntityType: EntityType | AliasEntityType = null; + private readonly defaultEntityType: EntityType | AliasEntityType = null; private propagateChange = (v: any) => { }; diff --git a/ui-ngx/src/app/shared/components/entity/entity-type-select.component.ts b/ui-ngx/src/app/shared/components/entity/entity-type-select.component.ts index 4b445572a6..415b19ac13 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-type-select.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-type-select.component.ts @@ -21,7 +21,7 @@ import {AppState} from '@app/core/core.state'; import {TranslateService} from '@ngx-translate/core'; import {AliasEntityType, EntityType, entityTypeTranslations} from '@app/shared/models/entity-type.models'; import {EntityService} from '@core/http/entity.service'; -import {coerceBooleanProperty} from "@angular/cdk/coercion"; +import {coerceBooleanProperty} from '@angular/cdk/coercion'; @Component({ selector: 'tb-entity-type-select', diff --git a/ui-ngx/src/app/shared/components/fab-toolbar.component.ts b/ui-ngx/src/app/shared/components/fab-toolbar.component.ts index 144a3910aa..f6b813c3da 100644 --- a/ui-ngx/src/app/shared/components/fab-toolbar.component.ts +++ b/ui-ngx/src/app/shared/components/fab-toolbar.component.ts @@ -38,6 +38,7 @@ class MatFabToolbarBase { const MatFabToolbarMixinBase: CanColorCtor & typeof MatFabToolbarBase = mixinColor(MatFabToolbarBase); @Directive({ + // tslint:disable-next-line:directive-selector selector: 'mat-fab-trigger' }) export class FabTriggerDirective { @@ -48,6 +49,7 @@ export class FabTriggerDirective { } @Directive({ + // tslint:disable-next-line:directive-selector selector: 'mat-fab-actions' }) export class FabActionsDirective implements OnInit { @@ -65,6 +67,7 @@ export class FabActionsDirective implements OnInit { // @dynamic @Component({ + // tslint:disable-next-line:component-selector selector: 'mat-fab-toolbar', templateUrl: './fab-toolbar.component.html', styleUrls: ['./fab-toolbar.component.scss'], diff --git a/ui-ngx/src/app/shared/components/help.component.ts b/ui-ngx/src/app/shared/components/help.component.ts index 0bc85834d4..217e02dfce 100644 --- a/ui-ngx/src/app/shared/components/help.component.ts +++ b/ui-ngx/src/app/shared/components/help.component.ts @@ -18,6 +18,7 @@ import { Component, Input } from '@angular/core'; import { HelpLinks } from '@shared/models/constants'; @Component({ + // tslint:disable-next-line:component-selector selector: '[tb-help]', templateUrl: './help.component.html' }) diff --git a/ui-ngx/src/app/shared/components/hotkeys.directive.ts b/ui-ngx/src/app/shared/components/hotkeys.directive.ts index c39aaed204..bcd737c754 100644 --- a/ui-ngx/src/app/shared/components/hotkeys.directive.ts +++ b/ui-ngx/src/app/shared/components/hotkeys.directive.ts @@ -29,21 +29,21 @@ export class TbHotkeysDirective implements OnInit, OnDestroy { private mousetrap: MousetrapInstance; private hotkeysList: Hotkey[] = []; - private _preventIn = ['INPUT', 'SELECT', 'TEXTAREA']; + private preventIn = ['INPUT', 'SELECT', 'TEXTAREA']; - constructor(private _elementRef: ElementRef) { - this.mousetrap = new Mousetrap(this._elementRef.nativeElement); - (this._elementRef.nativeElement as HTMLElement).tabIndex = -1; - (this._elementRef.nativeElement as HTMLElement).style.outline = '0'; + constructor(private elementRef: ElementRef) { + this.mousetrap = new Mousetrap(this.elementRef.nativeElement); + (this.elementRef.nativeElement as HTMLElement).tabIndex = -1; + (this.elementRef.nativeElement as HTMLElement).style.outline = '0'; } ngOnInit() { - for (let hotkey of this.hotkeys) { + for (const hotkey of this.hotkeys) { this.hotkeysList.push(hotkey); this.bindEvent(hotkey); } if (this.cheatSheet) { - let hotkeyObj: Hotkey = new Hotkey( + const hotkeyObj: Hotkey = new Hotkey( '?', (event: KeyboardEvent) => { this.cheatSheet.toggleCheatSheet(); @@ -59,26 +59,27 @@ export class TbHotkeysDirective implements OnInit, OnDestroy { } private bindEvent(hotkey: Hotkey): void { - this.mousetrap.bind((hotkey).combo, (event: KeyboardEvent, combo: string) => { + this.mousetrap.bind((hotkey as Hotkey).combo, (event: KeyboardEvent, combo: string) => { let shouldExecute = true; - if(event) { - let target: HTMLElement = (event.target || event.srcElement); - let nodeName: string = target.nodeName.toUpperCase(); - if((' ' + target.className + ' ').indexOf(' mousetrap ') > -1) { + if (event) { + const target: HTMLElement = (event.target || event.srcElement) as HTMLElement; + const nodeName: string = target.nodeName.toUpperCase(); + if ((' ' + target.className + ' ').indexOf(' mousetrap ') > -1) { shouldExecute = true; - } else if(this._preventIn.indexOf(nodeName) > -1 && (hotkey).allowIn.map(allow => allow.toUpperCase()).indexOf(nodeName) === -1) { + } else if (this.preventIn.indexOf(nodeName) > -1 && (hotkey as Hotkey). + allowIn.map(allow => allow.toUpperCase()).indexOf(nodeName) === -1) { shouldExecute = false; } } - if(shouldExecute) { - return (hotkey).callback.apply(this, [event, combo]); + if (shouldExecute) { + return (hotkey as Hotkey).callback.apply(this, [event, combo]); } }); } ngOnDestroy() { - for (let hotkey of this.hotkeysList) { + for (const hotkey of this.hotkeysList) { this.mousetrap.unbind(hotkey.combo); } } diff --git a/ui-ngx/src/app/shared/components/js-func.component.ts b/ui-ngx/src/app/shared/components/js-func.component.ts index ddfbeb37f8..215ed1d4df 100644 --- a/ui-ngx/src/app/shared/components/js-func.component.ts +++ b/ui-ngx/src/app/shared/components/js-func.component.ts @@ -66,7 +66,7 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor, @Input() functionArgs: Array; - @Input() validationArgs: Array; + @Input() validationArgs: Array; @Input() resultType: string; diff --git a/ui-ngx/src/app/shared/components/json-form/react/json-form-schema-form.tsx b/ui-ngx/src/app/shared/components/json-form/react/json-form-schema-form.tsx index 45e9513ef9..153a9b0d7e 100644 --- a/ui-ngx/src/app/shared/components/json-form/react/json-form-schema-form.tsx +++ b/ui-ngx/src/app/shared/components/json-form/react/json-form-schema-form.tsx @@ -41,7 +41,7 @@ const tinycolor = tinycolor_; class ThingsboardSchemaForm extends React.Component { private hasConditions: boolean; - private mapper: {[type: string]: any}; + private readonly mapper: {[type: string]: any}; constructor(props) { super(props); diff --git a/ui-ngx/src/app/shared/components/mat-chip-draggable.directive.ts b/ui-ngx/src/app/shared/components/mat-chip-draggable.directive.ts index 3d20f45a39..32e0dc61ac 100644 --- a/ui-ngx/src/app/shared/components/mat-chip-draggable.directive.ts +++ b/ui-ngx/src/app/shared/components/mat-chip-draggable.directive.ts @@ -98,7 +98,7 @@ let globalDraggingChipListId = null; class DraggableChip { private chipElement: HTMLElement; - private handle: HTMLElement; + private readonly handle: HTMLElement; private dragging = false; private counter = 0; diff --git a/ui-ngx/src/app/shared/components/nav-tree.component.ts b/ui-ngx/src/app/shared/components/nav-tree.component.ts index 81d3a303fd..02e221bf92 100644 --- a/ui-ngx/src/app/shared/components/nav-tree.component.ts +++ b/ui-ngx/src/app/shared/components/nav-tree.component.ts @@ -103,10 +103,10 @@ export class NavTreeComponent implements OnInit { private initTree() { const loadNodes: LoadNodesCallback = (node, cb) => { - const outCb = (_nodes: NavTreeNode[]) => { + const outCb = (nodes: NavTreeNode[]) => { const copied: NavTreeNode[] = []; - if (_nodes) { - _nodes.forEach((n) => { + if (nodes) { + nodes.forEach((n) => { copied.push(deepClone(n, ['data'])); }); } diff --git a/ui-ngx/src/app/shared/components/page.component.ts b/ui-ngx/src/app/shared/components/page.component.ts index 64922008c4..e027ea2132 100644 --- a/ui-ngx/src/app/shared/components/page.component.ts +++ b/ui-ngx/src/app/shared/components/page.component.ts @@ -16,9 +16,9 @@ import { OnDestroy } from '@angular/core'; import { select, Store } from '@ngrx/store'; -import { AppState } from '../../core/core.state'; +import { AppState } from '@core/core.state'; import { Observable, Subscription } from 'rxjs'; -import { selectIsLoading } from '../../core/interceptors/load.selectors'; +import { selectIsLoading } from '@core/interceptors/load.selectors'; import { delay, share } from 'rxjs/operators'; import { AbstractControl } from '@angular/forms'; diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html index a1c86ee82b..d7c32c5bf2 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.html @@ -63,7 +63,7 @@ aggregation.function - {{ aggregationTypesTranslations.get(aggregation) | translate }} + {{ aggregationTypesTranslations.get(aggregationTypes[aggregation]) | translate }} diff --git a/ui-ngx/src/app/shared/components/value-input.component.html b/ui-ngx/src/app/shared/components/value-input.component.html index 741b0f49d5..b7fa9d591d 100644 --- a/ui-ngx/src/app/shared/components/value-input.component.html +++ b/ui-ngx/src/app/shared/components/value-input.component.html @@ -25,8 +25,8 @@ {{ valueTypes.get(valueType).name | translate }} - - {{ valueTypes.get(valueType).name | translate }} + + {{ valueTypes.get(valueTypeEnum[valueType]).name | translate }} diff --git a/ui-ngx/src/app/shared/models/alarm.models.ts b/ui-ngx/src/app/shared/models/alarm.models.ts index cae70c9863..57ce7a373d 100644 --- a/ui-ngx/src/app/shared/models/alarm.models.ts +++ b/ui-ngx/src/app/shared/models/alarm.models.ts @@ -116,13 +116,13 @@ export const simulatedAlarm: AlarmInfo = { originatorName: 'Simulated', originator: { entityType: EntityType.DEVICE, - id: "1" + id: '1' }, type: 'TEMPERATURE', severity: AlarmSeverity.MAJOR, status: AlarmStatus.ACTIVE_UNACK, details: { - message: "Temperature is high!" + message: 'Temperature is high!' }, propagate: false }; diff --git a/ui-ngx/src/app/shared/models/page/page-link.ts b/ui-ngx/src/app/shared/models/page/page-link.ts index 8cd5c7fe56..c60023944d 100644 --- a/ui-ngx/src/app/shared/models/page/page-link.ts +++ b/ui-ngx/src/app/shared/models/page/page-link.ts @@ -17,6 +17,7 @@ import { Direction, SortOrder } from '@shared/models/page/sort-order'; import { emptyPageData, PageData } from '@shared/models/page/page-data'; import { getDescendantProp, isObject } from '@core/utils'; +import { SortDirection } from '@angular/material/sort'; export type PageLinkSearchFunction = (entity: T, textSearch: string) => boolean; @@ -113,6 +114,14 @@ export class PageLink { return pageData; } + public sortDirection(): SortDirection { + if (this.sortOrder) { + return (this.sortOrder.direction + '').toLowerCase() as SortDirection; + } else { + return '' as SortDirection; + } + } + } export class TimePageLink extends PageLink { diff --git a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts index cf2f9d93db..6d356208ef 100644 --- a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts +++ b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts @@ -46,6 +46,14 @@ export enum TelemetryFeature { export type TelemetryType = LatestTelemetry | AttributeScope; +export function toTelemetryType(val: string): TelemetryType { + if (LatestTelemetry[val]) { + return LatestTelemetry[val]; + } else { + return AttributeScope[val]; + } +} + export const telemetryTypeTranslations = new Map( [ [LatestTelemetry.LATEST_TELEMETRY, 'attribute.scope-latest-telemetry'], diff --git a/ui-ngx/src/karma.conf.js b/ui-ngx/src/karma.conf.js index 637bf8fbff..1ad4573140 100644 --- a/ui-ngx/src/karma.conf.js +++ b/ui-ngx/src/karma.conf.js @@ -16,32 +16,32 @@ // Karma configuration file, see link for more information // https://karma-runner.github.io/1.0/config/configuration-file.html -module.exports = function (config) { +module.exports = function(config) { config.set({ - basePath: '', - frameworks: ['jasmine', '@angular-devkit/build-angular'], + basePath: "", + frameworks: ["jasmine", "@angular-devkit/build-angular"], plugins: [ - require('karma-jasmine'), - require('karma-chrome-launcher'), - require('karma-jasmine-html-reporter'), - require('karma-coverage-istanbul-reporter'), - require('@angular-devkit/build-angular/plugins/karma') + require("karma-jasmine"), + require("karma-chrome-launcher"), + require("karma-jasmine-html-reporter"), + require("karma-coverage-istanbul-reporter"), + require("@angular-devkit/build-angular/plugins/karma"), ], client: { - clearContext: false // leave Jasmine Spec Runner output visible in browser + clearContext: false, // leave Jasmine Spec Runner output visible in browser }, coverageIstanbulReporter: { - dir: require('path').join(__dirname, '../coverage/tb-license-server'), - reports: ['html', 'lcovonly', 'text-summary'], - fixWebpackSourcePaths: true + dir: require("path").join(__dirname, "../coverage/tb-license-server"), + reports: ["html", "lcovonly", "text-summary"], + fixWebpackSourcePaths: true, }, - reporters: ['progress', 'kjhtml'], + reporters: ["progress", "kjhtml"], port: 9876, colors: true, logLevel: config.LOG_INFO, autoWatch: true, - browsers: ['Chrome'], + browsers: ["Chrome"], singleRun: false, - restartOnFileChange: true + restartOnFileChange: true, }); }; diff --git a/ui-ngx/src/main.ts b/ui-ngx/src/main.ts index f8673a520d..efc77ee56d 100644 --- a/ui-ngx/src/main.ts +++ b/ui-ngx/src/main.ts @@ -19,8 +19,8 @@ import 'hammerjs'; import { enableProdMode } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; -import { AppModule } from './app/app.module'; -import { environment } from './environments/environment'; +import { AppModule } from '@app/app.module'; +import { environment } from '@env/environment'; if (environment.production) { enableProdMode(); diff --git a/ui-ngx/src/tslint.json b/ui-ngx/src/tslint.json index 87b632c398..839fcd0d77 100644 --- a/ui-ngx/src/tslint.json +++ b/ui-ngx/src/tslint.json @@ -5,7 +5,7 @@ true, "attribute", "tb", - "camelCase" + "kebab-case" ], "component-selector": [ true, From 09065dee388b51d30dd08840e4b11479a197d70b Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 4 Feb 2020 19:50:18 +0200 Subject: [PATCH 088/133] Table layout improvement --- .../attribute/attribute-table.component.html | 18 +++++----- .../attribute/attribute-table.component.scss | 6 ++-- .../entity/entities-table.component.html | 10 ++++-- .../entity/entities-table.component.ts | 18 +++++++--- .../table-columns-assignment.component.html | 8 ++--- .../table-columns-assignment.component.ts | 1 - .../relation/relation-table.component.html | 16 ++++----- .../manage-widget-actions.component.html | 12 +++---- .../lib/alarms-table-widget.component.html | 10 ++++-- .../lib/alarms-table-widget.component.ts | 8 ++--- .../lib/entities-table-widget.component.html | 8 +++-- .../lib/entities-table-widget.component.ts | 34 +++++++++---------- .../widget/lib/table-widget.models.ts | 11 ++++++ .../timeseries-table-widget.component.html | 8 +++-- ...age-dashboard-states-dialog.component.html | 10 +++--- .../rulechains-table-config.resolver.ts | 2 +- .../src/app/shared/models/page/page-link.ts | 6 ++++ ui-ngx/src/styles.scss | 15 ++++++++ 18 files changed, 128 insertions(+), 73 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html index a55261b5ff..9daadb8a80 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html +++ b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html @@ -137,7 +137,7 @@ - + @@ -151,26 +151,28 @@ - {{ 'attribute.last-update-time' | translate }} + {{ 'attribute.last-update-time' | translate }} {{ attribute.lastUpdateTs | date:'yyyy-MM-dd HH:mm:ss' }} - {{ 'attribute.key' | translate }} + {{ 'attribute.key' | translate }} {{ attribute.key }} - {{ 'attribute.value' | translate }} + {{ 'attribute.value' | translate }} - {{attribute.value}} - - edit - +
+ {{attribute.value}} + + edit + +
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 e5cbdb8404..3d514fe063 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 @@ -54,9 +54,9 @@ mat-cell.tb-value-cell { cursor: pointer; mat-icon { - height: 16px; - width: 16px; - font-size: 16px; + height: 24px; + width: 24px; + font-size: 24px; color: #757575 } } 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 476410c463..1e5342a2b5 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 @@ -138,7 +138,7 @@ - + @@ -178,10 +178,14 @@ - + {{ entitiesTableConfig.actionsColumnTitle ? (entitiesTableConfig.actionsColumnTitle | translate) : '' }} - +
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 dfdaaded22..a6b4f2b379 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 @@ -419,7 +419,7 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn } return res; } else { - return null; + return ''; } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.scss index c469bf9cfe..c2992a2984 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.scss @@ -18,11 +18,9 @@ height: 100%; .tb-table-widget { mat-footer-row, mat-row { - min-height: 38px; height: 38px; } mat-header-row { - min-height: 40px; height: 40px; } mat-toolbar { 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 80ddf9ceb0..c91a533c55 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 @@ -426,7 +426,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI this.initialize().subscribe( () => { - this.cd.detectChanges(); + this.detectChanges(); this.onInit(); }, (err) => { @@ -435,6 +435,12 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI ); } + private detectChanges() { + if (!this.destroyed) { + this.cd.detectChanges(); + } + } + private isReady(): boolean { return this.subscriptionInited && this.widgetSizeDetected; } @@ -546,7 +552,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI this.widgetContext.reset(); this.subscriptionInited = true; this.configureDynamicWidgetComponent(); - this.cd.detectChanges(); + this.detectChanges(); this.onInit(); } }, @@ -564,7 +570,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI this.widgetContext.reset(); this.subscriptionInited = true; this.configureDynamicWidgetComponent(); - this.cd.detectChanges(); + this.detectChanges(); this.onInit(); } } @@ -760,22 +766,18 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI dataLoading: (subscription) => { if (this.loadingData !== subscription.loadingData) { this.loadingData = subscription.loadingData; - if (!this.destroyed) { - this.cd.detectChanges(); - } + this.detectChanges(); } }, legendDataUpdated: (subscription, detectChanges) => { - if (detectChanges && !this.destroyed) { - this.cd.detectChanges(); + if (detectChanges) { + this.detectChanges(); } }, timeWindowUpdated: (subscription, timeWindowConfig) => { this.ngZone.run(() => { this.widget.config.timewindow = timeWindowConfig; - if (!this.destroyed) { - this.cd.detectChanges(); - } + this.detectChanges(); }); } }; @@ -836,7 +838,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI if (this.dynamicWidgetComponent) { this.dynamicWidgetComponent.rpcEnabled = subscription.rpcEnabled; this.dynamicWidgetComponent.executingRpcRequest = subscription.executingRpcRequest; - this.cd.detectChanges(); + this.detectChanges(); } }, onRpcSuccess: (subscription) => { @@ -844,7 +846,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI this.dynamicWidgetComponent.executingRpcRequest = subscription.executingRpcRequest; this.dynamicWidgetComponent.rpcErrorText = subscription.rpcErrorText; this.dynamicWidgetComponent.rpcRejection = subscription.rpcRejection; - this.cd.detectChanges(); + this.detectChanges(); } }, onRpcFailed: (subscription) => { @@ -852,14 +854,14 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI this.dynamicWidgetComponent.executingRpcRequest = subscription.executingRpcRequest; this.dynamicWidgetComponent.rpcErrorText = subscription.rpcErrorText; this.dynamicWidgetComponent.rpcRejection = subscription.rpcRejection; - this.cd.detectChanges(); + this.detectChanges(); } }, onRpcErrorCleared: (subscription) => { if (this.dynamicWidgetComponent) { this.dynamicWidgetComponent.rpcErrorText = null; this.dynamicWidgetComponent.rpcRejection = null; - this.cd.detectChanges(); + this.detectChanges(); } } }; @@ -873,16 +875,16 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI createSubscriptionSubject.error(err); } ); - this.cd.detectChanges(); + this.detectChanges(); } else if (this.widget.type === widgetType.static) { this.loadingData = false; createSubscriptionSubject.next(); createSubscriptionSubject.complete(); - this.cd.detectChanges(); + this.detectChanges(); } else { createSubscriptionSubject.next(); createSubscriptionSubject.complete(); - this.cd.detectChanges(); + this.detectChanges(); } return createSubscriptionSubject.asObservable(); } diff --git a/ui-ngx/src/app/modules/home/models/datasource/entity-datasource.ts b/ui-ngx/src/app/modules/home/models/datasource/entity-datasource.ts index d0ab2bec71..70c51c46c4 100644 --- a/ui-ngx/src/app/modules/home/models/datasource/entity-datasource.ts +++ b/ui-ngx/src/app/modules/home/models/datasource/entity-datasource.ts @@ -20,7 +20,7 @@ import { BehaviorSubject, Observable, of, ReplaySubject } from 'rxjs'; import { emptyPageData, PageData } from '@shared/models/page/page-data'; import { BaseData, HasId } from '@shared/models/base-data'; import { CollectionViewer, DataSource } from '@angular/cdk/typings/collections'; -import { catchError, map, take, tap } from 'rxjs/operators'; +import { catchError, map, share, take, tap } from 'rxjs/operators'; import { SelectionModel } from '@angular/cdk/collections'; import {EntityBooleanFunction} from '@home/models/entity/entities-table-config.models'; @@ -78,19 +78,22 @@ export class EntitiesDataSource, P extends PageLink = isAllSelected(): Observable { const numSelected = this.selection.selected.length; return this.entitiesSubject.pipe( - map((entities) => numSelected === this.selectableEntitiesCount(entities)) + map((entities) => numSelected === this.selectableEntitiesCount(entities)), + share() ); } isEmpty(): Observable { return this.entitiesSubject.pipe( - map((entities) => !entities.length) + map((entities) => !entities.length), + share() ); } total(): Observable { return this.pageDataSubject.pipe( - map((pageData) => pageData.totalElements) + map((pageData) => pageData.totalElements), + share() ); } 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 ed819866ca..6f947dd960 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 @@ -84,7 +84,7 @@ export class EntityTableColumn> extends BaseEntityTabl constructor(public key: string, public title: string, public width: string = '0px', - public cellContentFunction: CellContentFunction = (entity, property) => entity[property], + public cellContentFunction: CellContentFunction = (entity, property) => entity[property] ? entity[property] : '', public cellStyleFunction: CellStyleFunction = () => ({}), public sortable: boolean = true, public headerCellStyleFunction: HeaderCellStyleFunction = () => ({}), diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-link.component.html b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-link.component.html index 4b54c92e6b..9c2545e504 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-link.component.html +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-link.component.html @@ -18,7 +18,6 @@
customerInfo.title) .join(', '); } else { - return null; + return ''; } } diff --git a/ui-ngx/src/styles.scss b/ui-ngx/src/styles.scss index ae0eb63c60..1dddf94f5c 100644 --- a/ui-ngx/src/styles.scss +++ b/ui-ngx/src/styles.scss @@ -521,15 +521,17 @@ mat-label { } mat-header-row { - min-height: 60px; height: 60px; } mat-footer-row, mat-row { - min-height: 52px; height: 52px; } + mat-header-row, mat-footer-row, mat-row { + min-height: auto; + } + .mat-row, .mat-header-row { display: table-row; @@ -537,17 +539,23 @@ mat-label { .mat-header-row.mat-table-sticky { + background-clip: padding-box; .mat-header-cell { position: sticky; top: 0; z-index: 10; background: inherit; + background-clip: padding-box; &.mat-table-sticky { z-index: 11 !important; } } } + .mat-cell.mat-table-sticky { + background-clip: padding-box; + } + .mat-row { transition: background-color .2s; &:hover:not(.tb-current-entity) { From ec8f53aea9c38f42fe4e6c28d1f31705d996cb04 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 6 Feb 2020 13:00:39 +0200 Subject: [PATCH 091/133] Rule chain UI performance improvements --- .../pages/rulechain/rulenode.component.html | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulenode.component.html b/ui-ngx/src/app/modules/home/pages/rulechain/rulenode.component.html index 65ca7d13b4..5cee2166d3 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulenode.component.html +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulenode.component.html @@ -20,8 +20,10 @@ (mousedown)="userNodeCallbacks.mouseDown($event, node)" (mouseenter)="userNodeCallbacks.mouseEnter($event, node)" (mouseleave)="userNodeCallbacks.mouseLeave($event, node)"> -
-
+
+
{{node.icon}}
@@ -32,6 +34,7 @@
@@ -41,16 +44,19 @@
-
- -
-
- × -
+
+
+ +
+
+ × +
+
From bd142f8646311a58c98be4ec1dd4c8bde3c839cf Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 6 Feb 2020 19:56:10 +0200 Subject: [PATCH 092/133] Control widgets --- .../system/widget_bundles/control_widgets.json | 14 +++++++------- ui-ngx/src/app/core/http/attribute.service.ts | 4 ++-- .../home/components/widget/widget.component.ts | 2 ++ .../rulechain/rulechain-page.component.html | 2 +- .../rulechain/rulechain-page.component.scss | 16 ++++++++++++---- 5 files changed, 24 insertions(+), 14 deletions(-) diff --git a/application/src/main/data/json/system/widget_bundles/control_widgets.json b/application/src/main/data/json/system/widget_bundles/control_widgets.json index 2890ede377..f8395c656f 100644 --- a/application/src/main/data/json/system/widget_bundles/control_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/control_widgets.json @@ -31,7 +31,7 @@ "resources": [], "templateHtml": "
", "templateCss": ".cmd .cursor.blink {\n -webkit-animation-name: terminal-underline;\n -moz-animation-name: terminal-underline;\n -ms-animation-name: terminal-underline;\n animation-name: terminal-underline;\n}\n.terminal .inverted, .cmd .inverted {\n border-bottom-color: #aaa;\n}\n", - "controllerScript": "var requestTimeout = 500;\nconst commandStatusPollingInterval = 200;\n\nconst welcome = 'Welcome to ThingsBoard RPC remote shell.\\n';\n\nvar terminal, rpcEnabled, simulated, deviceName, cwd;\nvar commandExecuting = false;\n\nself.onInit = function() {\n var subscription = self.ctx.defaultSubscription;\n rpcEnabled = subscription.rpcEnabled;\n if (subscription.targetDeviceName && subscription.targetDeviceName.length) {\n deviceName = subscription.targetDeviceName;\n } else {\n deviceName = 'Simulated';\n simulated = true;\n }\n if (self.ctx.settings.requestTimeout) {\n requestTimeout = self.ctx.settings.requestTimeout;\n }\n \n terminal = $('#device-terminal', self.ctx.$container).terminal(\n function (command) {\n if (command && command.trim().length) {\n try {\n if (simulated) {\n this.echo(command);\n } else {\n sendCommand(this, command);\n }\n } catch(e) {\n this.error(new String(e));\n }\n } else {\n this.echo('');\n }\n }, {\n greetings: false,\n enabled: rpcEnabled,\n prompt: rpcEnabled ? currentPrompt : '',\n name: 'shell',\n pauseEvents: false,\n keydown: (e, term) => {\n if ((e.which == 67 || e.which == 68) && e.ctrlKey) { // CTRL+C || CTRL+D\n if (commandExecuting) {\n terminateCommand(term);\n return false;\n }\n }\n },\n onInit: initTerm\n }\n );\n \n}\n\nfunction initTerm(terminal) {\n terminal.echo(welcome);\n if (!rpcEnabled) {\n terminal.error('Target device is not set!\\n');\n } else {\n terminal.echo(`Current target device for RPC terminal: [[b;#fff;]${deviceName}]\\n`);\n if (!simulated) {\n terminal.pause();\n getTermInfo(terminal,\n (remoteTermInfo) => {\n if (remoteTermInfo) {\n terminal.echo(`Remote platform info:`);\n terminal.echo(`OS: [[b;#fff;]${remoteTermInfo.platform}]`);\n if (remoteTermInfo.release) {\n terminal.echo(`OS release: [[b;#fff;]${remoteTermInfo.release}]`);\n }\n terminal.echo('\\r');\n } else {\n terminal.echo('[[;#f00;]Unable to get remote platform info.\\nDevice is not responding.]\\n');\n }\n terminal.resume();\n });\n }\n }\n}\n\nfunction currentPrompt(callback) {\n if (cwd) {\n callback('[[b;#2196f3;]' + deviceName +']: [[b;#8bc34a;]' + cwd +']> ');\n } else {\n callback('[[b;#8bc34a;]' + deviceName +']> ');\n }\n}\n\nfunction getTermInfo(terminal, callback) {\n self.ctx.controlApi.sendTwoWayCommand('getTermInfo', null, requestTimeout).subscribe(\n (termInfo) => {\n cwd = termInfo.cwd;\n if (callback) {\n callback(termInfo);\n } \n },\n () => {\n if (callback) {\n callback(null);\n }\n }\n );\n}\n\nfunction sendCommand(terminal, command) {\n terminal.pause();\n var sendCommandRequest = {\n command: command,\n cwd: cwd\n };\n self.ctx.controlApi.sendTwoWayCommand('sendCommand', sendCommandRequest, requestTimeout).subscribe(\n (responseBody) => {\n if (responseBody && responseBody.ok) {\n commandExecuting = true;\n setTimeout( pollCommandStatus.bind(null,terminal), commandStatusPollingInterval );\n } else {\n var error = responseBody ? responseBody.error : 'Unhandled error.';\n terminal.error(error);\n terminal.resume();\n }\n },\n () => {\n onRpcError(terminal);\n }\n );\n}\n\nfunction terminateCommand(terminal) {\n self.ctx.controlApi.sendTwoWayCommand('terminateCommand', null, requestTimeout).subscribe(\n (responseBody) => {\n if (!responseBody.ok) {\n commandExecuting = false;\n terminal.error(responseBody.error);\n terminal.resume();\n } \n },\n () => {\n onRpcError(terminal);\n }\n ); \n}\n\nfunction onRpcError(terminal) {\n var errorText = self.ctx.defaultSubscription.rpcErrorText;\n terminal.error(errorText);\n terminal.resume();\n}\n\nfunction pollCommandStatus(terminal) {\n self.ctx.controlApi.sendTwoWayCommand('getCommandStatus', null, requestTimeout).subscribe(\n (commandStatusResponse) => {\n commandStatusResponse.data.forEach((dataElement) => {\n if (dataElement.stdout) {\n terminal.echo(dataElement.stdout);\n }\n if (dataElement.stderr) {\n terminal.error(dataElement.stderr);\n }\n }); \n if (commandStatusResponse.done) {\n commandExecuting = false;\n cwd = commandStatusResponse.cwd;\n terminal.resume();\n } else {\n var interval = commandStatusPollingInterval;\n if (!commandStatusResponse.data.length) {\n interval *=5;\n }\n setTimeout( pollCommandStatus.bind(null,terminal), interval );\n }\n },\n () => {\n commandExecuting = false;\n onRpcError(terminal);\n }\n );\n}\n\nself.onResize = function () {\n if (terminal) {\n terminal.resize(self.ctx.width, self.ctx.height);\n }\n}\n\nself.onDestroy = function() {\n}\n", + "controllerScript": "var requestTimeout = 500;\nvar commandStatusPollingInterval = 200;\n\nvar welcome = 'Welcome to ThingsBoard RPC remote shell.\\n';\n\nvar terminal, rpcEnabled, simulated, deviceName, cwd;\nvar commandExecuting = false;\n\nself.onInit = function() {\n var subscription = self.ctx.defaultSubscription;\n rpcEnabled = subscription.rpcEnabled;\n if (subscription.targetDeviceName && subscription.targetDeviceName.length) {\n deviceName = subscription.targetDeviceName;\n } else {\n deviceName = 'Simulated';\n simulated = true;\n }\n if (self.ctx.settings.requestTimeout) {\n requestTimeout = self.ctx.settings.requestTimeout;\n }\n \n terminal = $('#device-terminal', self.ctx.$container).terminal(\n function (command) {\n if (command && command.trim().length) {\n try {\n if (simulated) {\n this.echo(command);\n } else {\n sendCommand(this, command);\n }\n } catch(e) {\n this.error(e + '');\n }\n } else {\n this.echo('');\n }\n }, {\n greetings: false,\n enabled: rpcEnabled,\n prompt: rpcEnabled ? currentPrompt : '',\n name: 'shell',\n pauseEvents: false,\n keydown: function (e, term) {\n if ((e.which == 67 || e.which == 68) && e.ctrlKey) { // CTRL+C || CTRL+D\n if (commandExecuting) {\n terminateCommand(term);\n return false;\n }\n }\n },\n onInit: initTerm\n }\n );\n \n};\n\nfunction initTerm(terminal) {\n terminal.echo(welcome);\n if (!rpcEnabled) {\n terminal.error('Target device is not set!\\n');\n } else {\n terminal.echo('Current target device for RPC terminal: [[b;#fff;]' + deviceName + ']\\n');\n if (!simulated) {\n terminal.pause();\n getTermInfo(terminal,\n function (remoteTermInfo) {\n if (remoteTermInfo) {\n terminal.echo('Remote platform info:');\n terminal.echo('OS: [[b;#fff;]' + remoteTermInfo.platform + ']');\n if (remoteTermInfo.release) {\n terminal.echo('OS release: [[b;#fff;]' + remoteTermInfo.release + ']');\n }\n terminal.echo('\\r');\n } else {\n terminal.echo('[[;#f00;]Unable to get remote platform info.\\nDevice is not responding.]\\n');\n }\n terminal.resume();\n });\n }\n }\n}\n\nfunction currentPrompt(callback) {\n if (cwd) {\n callback('[[b;#2196f3;]' + deviceName +']: [[b;#8bc34a;]' + cwd +']> ');\n } else {\n callback('[[b;#8bc34a;]' + deviceName +']> ');\n }\n}\n\nfunction getTermInfo(terminal, callback) {\n self.ctx.controlApi.sendTwoWayCommand('getTermInfo', null, requestTimeout).subscribe(\n function (termInfo) {\n cwd = termInfo.cwd;\n if (callback) {\n callback(termInfo);\n } \n },\n function () {\n if (callback) {\n callback(null);\n }\n }\n );\n}\n\nfunction sendCommand(terminal, command) {\n terminal.pause();\n var sendCommandRequest = {\n command: command,\n cwd: cwd\n };\n self.ctx.controlApi.sendTwoWayCommand('sendCommand', sendCommandRequest, requestTimeout).subscribe(\n function (responseBody) {\n if (responseBody && responseBody.ok) {\n commandExecuting = true;\n setTimeout( pollCommandStatus.bind(null,terminal), commandStatusPollingInterval );\n } else {\n var error = responseBody ? responseBody.error : 'Unhandled error.';\n terminal.error(error);\n terminal.resume();\n }\n },\n function () {\n onRpcError(terminal);\n }\n );\n}\n\nfunction terminateCommand(terminal) {\n self.ctx.controlApi.sendTwoWayCommand('terminateCommand', null, requestTimeout).subscribe(\n function (responseBody) {\n if (!responseBody.ok) {\n commandExecuting = false;\n terminal.error(responseBody.error);\n terminal.resume();\n } \n },\n function () {\n onRpcError(terminal);\n }\n ); \n}\n\nfunction onRpcError(terminal) {\n var errorText = self.ctx.defaultSubscription.rpcErrorText;\n terminal.error(errorText);\n terminal.resume();\n}\n\nfunction pollCommandStatus(terminal) {\n self.ctx.controlApi.sendTwoWayCommand('getCommandStatus', null, requestTimeout).subscribe(\n function (commandStatusResponse) {\n for (var i=0;i\n
\n {{title}}\n
\n
\n
\n \n {{buttonLable}}\n \n
\n
\n
\n {{ error }}\n
\n
", - "templateCss": ".tb-rpc-button {\n width: 100%;\n height: 100%;\n}\n\n.tb-rpc-button .title-container {\n font-weight: 500;\n white-space: nowrap;\n margin: 10px 0;\n}\n\n.tb-rpc-button .button-container div{\n min-width: 80%\n}\n\n.tb-rpc-button .button-container .md-button{\n width: 100%;\n margin: 0;\n}\n\n.tb-rpc-button .error-container {\n position: absolute;\n top: 2%;\n right: 0;\n left: 0;\n z-index: 4;\n height: 14px;\n}\n\n.tb-rpc-button .error-container .button-error {\n color: #ff3315;\n white-space: nowrap;\n}", - "controllerScript": "self.onInit = function() {\n let rpcEnabled = self.ctx.defaultSubscription.rpcEnabled;\n\n self.ctx.$scope.buttonLable = self.ctx.settings.buttonText;\n self.ctx.$scope.showTitle = self.ctx.settings.title &&\n self.ctx.settings.title.length ? true : false;\n self.ctx.$scope.title = self.ctx.settings.title;\n self.ctx.$scope.styleButton = self.ctx.settings.styleButton;\n\n if (self.ctx.settings.styleButton.isPrimary ===\n false) {\n self.ctx.$scope.customStyle = {\n 'background-color': self.ctx.$scope.styleButton.bgColor,\n 'color': self.ctx.$scope.styleButton.textColor\n };\n }\n\n if (!rpcEnabled) {\n self.ctx.$scope.error =\n 'Target device is not set!';\n }\n\n self.ctx.$scope.sendCommand = function() {\n var rpcMethod = self.ctx.settings.methodName;\n var rpcParams = self.ctx.settings.methodParams;\n var timeout = self.ctx.settings.requestTimeout;\n var oneWayElseTwoWay = self.ctx.settings.oneWayElseTwoWay ?\n true : false;\n\n var commandPromise;\n if (oneWayElseTwoWay) {\n commandPromise = self.ctx.controlApi.sendOneWayCommand(\n rpcMethod, rpcParams, timeout);\n } else {\n commandPromise = self.ctx.controlApi.sendTwoWayCommand(\n rpcMethod, rpcParams, timeout);\n }\n commandPromise.then(\n function success() {\n self.ctx.$scope.error = \"\";\n },\n function fail(rejection) {\n if (self.ctx.settings.showError) {\n self.ctx.$scope.error =\n rejection.status + \": \" +\n rejection.statusText;\n }\n }\n );\n };\n\n};", + "templateHtml": "
\n
\n {{title}}\n
\n
\n
\n \n
\n
\n
\n {{ error }}\n
\n
", + "templateCss": ".tb-rpc-button {\n width: 100%;\n height: 100%;\n}\n\n.tb-rpc-button .title-container {\n font-weight: 500;\n white-space: nowrap;\n margin: 10px 0;\n}\n\n.tb-rpc-button .button-container div{\n min-width: 80%\n}\n\n.tb-rpc-button .button-container .mat-button{\n width: 100%;\n margin: 0;\n}\n\n.tb-rpc-button .error-container {\n position: absolute;\n top: 2%;\n right: 0;\n left: 0;\n z-index: 4;\n height: 14px;\n}\n\n.tb-rpc-button .error-container .button-error {\n color: #ff3315;\n white-space: nowrap;\n}", + "controllerScript": "self.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges();\n });\n};\n\nfunction init() {\n let rpcEnabled = self.ctx.defaultSubscription.rpcEnabled;\n\n self.ctx.$scope.buttonLable = self.ctx.settings.buttonText;\n self.ctx.$scope.showTitle = self.ctx.settings.title &&\n self.ctx.settings.title.length ? true : false;\n self.ctx.$scope.title = self.ctx.settings.title;\n self.ctx.$scope.styleButton = self.ctx.settings.styleButton;\n\n if (self.ctx.settings.styleButton.isPrimary ===\n false) {\n self.ctx.$scope.customStyle = {\n 'background-color': self.ctx.$scope.styleButton.bgColor,\n 'color': self.ctx.$scope.styleButton.textColor\n };\n }\n\n if (!rpcEnabled) {\n self.ctx.$scope.error =\n 'Target device is not set!';\n }\n\n self.ctx.$scope.sendCommand = function() {\n var rpcMethod = self.ctx.settings.methodName;\n var rpcParams = self.ctx.settings.methodParams;\n var timeout = self.ctx.settings.requestTimeout;\n var oneWayElseTwoWay = self.ctx.settings.oneWayElseTwoWay ?\n true : false;\n\n var commandPromise;\n if (oneWayElseTwoWay) {\n commandPromise = self.ctx.controlApi.sendOneWayCommand(\n rpcMethod, rpcParams, timeout);\n } else {\n commandPromise = self.ctx.controlApi.sendTwoWayCommand(\n rpcMethod, rpcParams, timeout);\n }\n commandPromise.subscribe(\n function success() {\n self.ctx.$scope.error = \"\";\n self.ctx.detectChanges();\n },\n function fail(rejection) {\n if (self.ctx.settings.showError) {\n self.ctx.$scope.error =\n rejection.status + \": \" +\n rejection.statusText;\n self.ctx.detectChanges();\n }\n }\n );\n };\n}\n", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"title\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"buttonText\": {\n \"title\": \"Button label\",\n \"type\": \"string\",\n \"default\": \"Send RPC\"\n },\n \"oneWayElseTwoWay\": {\n \"title\": \"Is One Way Command\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showError\": {\n \"title\": \"Show RPC command execution error\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"methodName\": {\n \"title\": \"RPC method\",\n \"type\": \"string\",\n \"default\": \"rpcCommand\"\n },\n \"methodParams\": {\n \"title\": \"RPC method params\",\n \"type\": \"string\",\n \"default\": \"{}\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 5000\n },\n \"styleButton\": {\n \"type\": \"object\",\n \"title\": \"Button Style\",\n \"properties\": {\n \"isRaised\": {\n \"type\": \"boolean\",\n \"title\": \"Raised\",\n \"default\": true\n },\n \"isPrimary\": {\n \"type\": \"boolean\",\n \"title\": \"Primary color\",\n \"default\": false\n },\n \"bgColor\": {\n \"type\": \"string\",\n \"title\": \"Button background color\",\n \"default\": null\n },\n \"textColor\": {\n \"type\": \"string\",\n \"title\": \"Button text color\",\n \"default\": null\n }\n }\n },\n \"required\": []\n }\n },\n \"form\": [\n \"title\",\n \"buttonText\",\n \"oneWayElseTwoWay\",\n \"showError\",\n \"methodName\",\n {\n \"key\": \"methodParams\",\n \"type\": \"json\"\n },\n \"requestTimeout\",\n {\n \"key\": \"styleButton\",\n \"items\": [\n \"styleButton.isRaised\",\n \"styleButton.isPrimary\",\n {\n \"key\": \"styleButton.bgColor\",\n \"type\": \"color\"\n },\n {\n \"key\": \"styleButton.textColor\",\n \"type\": \"color\"\n }\n ]\n }\n ]\n\n}", "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":5000,\"oneWayElseTwoWay\":true,\"buttonText\":\"Send RPC\",\"styleButton\":{\"isRaised\":true,\"isPrimary\":false},\"methodName\":\"rpcCommand\",\"methodParams\":\"{}\"},\"title\":\"RPC Button\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" @@ -125,9 +125,9 @@ "sizeX": 4, "sizeY": 2, "resources": [], - "templateHtml": "
\n
\n {{title}}\n
\n
\n
\n \n {{buttonLable}}\n \n
\n
\n
\n {{ error }}\n
\n
", - "templateCss": ".tb-rpc-button {\n width: 100%;\n height: 100%;\n}\n\n.tb-rpc-button .title-container {\n font-weight: 500;\n white-space: nowrap;\n margin: 10px 0;\n}\n\n.tb-rpc-button .button-container div{\n min-width: 80%\n}\n\n.tb-rpc-button .button-container .md-button{\n width: 100%;\n margin: 0;\n}\n\n.tb-rpc-button .error-container {\n position: absolute;\n top: 2%;\n right: 0;\n left: 0;\n z-index: 4;\n height: 14px;\n}\n\n.tb-rpc-button .error-container .button-error {\n color: #ff3315;\n white-space: nowrap;\n}", - "controllerScript": "self.onInit = function() {\n self.ctx.$scope.buttonLable = self.ctx.settings.buttonText;\n self.ctx.$scope.showTitle = self.ctx.settings.title &&\n self.ctx.settings.title.length ? true : false;\n self.ctx.$scope.title = self.ctx.settings.title;\n self.ctx.$scope.styleButton = self.ctx.settings.styleButton;\n let entityAttributeType = self.ctx.settings.entityAttributeType;\n let entityParameters = JSON.parse(self.ctx.settings.entityParameters);\n\n if (self.ctx.settings.styleButton.isPrimary ===\n false) {\n self.ctx.$scope.customStyle = {\n 'background-color': self.ctx.$scope.styleButton\n .bgColor,\n 'color': self.ctx.$scope.styleButton.textColor\n };\n }\n\n console.log(self.ctx);\n\n let attributeService = self.ctx.$scope.$injector.get('attributeService');\n\n self.ctx.$scope.sendUpdate = function() {\n let attributes = [];\n for (let key in entityParameters) {\n attributes.push({\n \"key\": key,\n \"value\": entityParameters[key]\n });\n }\n \n \n attributeService.saveEntityAttributes(\"DEVICE\", self.ctx.defaultSubscription.targetDeviceId,\n entityAttributeType, attributes).then(\n function success() {\n self.ctx.$scope.error = \"\";\n },\n function fail(rejection) {\n if (self.ctx.settings.showError) {\n self.ctx.$scope.error =\n rejection.status + \": \" +\n rejection.statusText;\n }\n console.log(rejection);\n }\n\n );\n };\n\n};", + "templateHtml": "
\n
\n {{title}}\n
\n
\n
\n \n
\n
\n
\n {{ error }}\n
\n
", + "templateCss": ".tb-rpc-button {\n width: 100%;\n height: 100%;\n}\n\n.tb-rpc-button .title-container {\n font-weight: 500;\n white-space: nowrap;\n margin: 10px 0;\n}\n\n.tb-rpc-button .button-container div{\n min-width: 80%\n}\n\n.tb-rpc-button .button-container .mat-button{\n width: 100%;\n margin: 0;\n}\n\n.tb-rpc-button .error-container {\n position: absolute;\n top: 2%;\n right: 0;\n left: 0;\n z-index: 4;\n height: 14px;\n}\n\n.tb-rpc-button .error-container .button-error {\n color: #ff3315;\n white-space: nowrap;\n}", + "controllerScript": "self.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges();\n });\n};\n\nfunction init() {\n self.ctx.$scope.buttonLable = self.ctx.settings.buttonText;\n self.ctx.$scope.showTitle = self.ctx.settings.title &&\n self.ctx.settings.title.length ? true : false;\n self.ctx.$scope.title = self.ctx.settings.title;\n self.ctx.$scope.styleButton = self.ctx.settings.styleButton;\n let entityAttributeType = self.ctx.settings.entityAttributeType;\n let entityParameters = JSON.parse(self.ctx.settings.entityParameters);\n\n if (self.ctx.settings.styleButton.isPrimary ===\n false) {\n self.ctx.$scope.customStyle = {\n 'background-color': self.ctx.$scope.styleButton\n .bgColor,\n 'color': self.ctx.$scope.styleButton.textColor\n };\n }\n\n let attributeService = self.ctx.$scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n\n self.ctx.$scope.sendUpdate = function() {\n let attributes = [];\n for (let key in entityParameters) {\n attributes.push({\n \"key\": key,\n \"value\": entityParameters[key]\n });\n }\n \n let entityId = {\n entityType: \"DEVICE\",\n id: self.ctx.defaultSubscription.targetDeviceId\n };\n attributeService.saveEntityAttributes(entityId,\n entityAttributeType, attributes).subscribe(\n function success() {\n self.ctx.$scope.error = \"\";\n self.ctx.detectChanges();\n },\n function fail(rejection) {\n if (self.ctx.settings.showError) {\n self.ctx.$scope.error =\n rejection.status + \": \" +\n rejection.statusText;\n self.ctx.detectChanges();\n }\n }\n\n );\n };\n}\n", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"title\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"buttonText\": {\n \"title\": \"Button label\",\n \"type\": \"string\",\n \"default\": \"Update device attribute\"\n },\n \"entityAttributeType\": {\n \"title\": \"Device attribute scope\",\n \"type\": \"string\",\n \"default\": \"SERVER_SCOPE\"\n },\n \"entityParameters\": {\n \"title\": \"Device attribute parameters\",\n \"type\": \"string\",\n \"default\": \"{}\"\n },\n \"styleButton\": {\n \"type\": \"object\",\n \"title\": \"Button Style\",\n \"properties\": {\n \"isRaised\": {\n \"type\": \"boolean\",\n \"title\": \"Raised\",\n \"default\": true\n },\n \"isPrimary\": {\n \"type\": \"boolean\",\n \"title\": \"Primary color\",\n \"default\": false\n },\n \"bgColor\": {\n \"type\": \"string\",\n \"title\": \"Button background color\",\n \"default\": null\n },\n \"textColor\": {\n \"type\": \"string\",\n \"title\": \"Button text color\",\n \"default\": null\n }\n }\n },\n \"required\": []\n }\n },\n \"form\": [\n \"title\",\n \"buttonText\",\n {\n \"key\": \"entityAttributeType\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [{\n \"value\": \"SERVER_SCOPE\",\n \"label\": \"Server attribute\"\n }, {\n \"value\": \"SHARED_SCOPE\",\n \"label\": \"Shared attribute\"\n }]\n }, {\n \"key\": \"entityParameters\",\n \"type\": \"json\"\n },\n {\n \"key\": \"styleButton\",\n \"items\": [\n \"styleButton.isRaised\",\n \"styleButton.isPrimary\",\n {\n \"key\": \"styleButton.bgColor\",\n \"type\": \"color\"\n },\n {\n \"key\": \"styleButton.textColor\",\n \"type\": \"color\"\n }\n ]\n }\n ]\n\n}", "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"styleButton\":{\"isRaised\":true,\"isPrimary\":false},\"entityParameters\":\"{}\",\"entityAttributeType\":\"SERVER_SCOPE\",\"buttonText\":\"Update device attribute\"},\"title\":\"Update device attribute\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"targetDeviceAliases\":[]}" diff --git a/ui-ngx/src/app/core/http/attribute.service.ts b/ui-ngx/src/app/core/http/attribute.service.ts index 192763795d..c7a6be71a5 100644 --- a/ui-ngx/src/app/core/http/attribute.service.ts +++ b/ui-ngx/src/app/core/http/attribute.service.ts @@ -77,7 +77,7 @@ export class AttributeService { } else { saveEntityAttributesObservable = of(null); } - return forkJoin(saveEntityAttributesObservable, deleteEntityAttributesObservable); + return forkJoin([saveEntityAttributesObservable, deleteEntityAttributesObservable]); } public saveEntityTimeseries(entityId: EntityId, timeseriesScope: string, timeseries: Array, @@ -105,6 +105,6 @@ export class AttributeService { } else { saveEntityTimeseriesObservable = of(null); } - return forkJoin(saveEntityTimeseriesObservable, deleteEntityTimeseriesObservable); + return forkJoin([saveEntityTimeseriesObservable, deleteEntityTimeseriesObservable]); } } 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 c91a533c55..adfcd079b9 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 @@ -94,10 +94,12 @@ import { AssetService } from '@core/http/asset.service'; import { DialogService } from '@core/services/dialog.service'; import { CustomDialogService } from '@home/components/widget/dialog/custom-dialog.service'; import { DatePipe } from '@angular/common'; +import { AttributeService } from '@core/http/attribute.service'; const ServicesMap = new Map>(); ServicesMap.set('deviceService', DeviceService); ServicesMap.set('assetService', AssetService); +ServicesMap.set('attributeService', AttributeService); ServicesMap.set('dialogs', DialogService); ServicesMap.set('customDialog', CustomDialogService); ServicesMap.set('date', DatePipe); 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 dbab9a2d6b..b4eded080a 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 @@ -74,7 +74,7 @@ (mouseenter)="typeHeaderMouseEnter($event, ruleNodeType)" (mouseleave)="destroyTooltips()"> - {{ ruleNodeTypeDescriptorsMap.get(ruleNodeType).icon }} + {{ ruleNodeTypeDescriptorsMap.get(ruleNodeType).icon }}
{{ ruleNodeTypeDescriptorsMap.get(ruleNodeType).name }}
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 399105d3a9..7bd554a3d0 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 @@ -124,6 +124,18 @@ &:hover { background: #dadada; } + .mat-expansion-panel-header-title { + line-height: 48px; + height: 48px; + .mat-icon { + min-width: 24px; + margin: auto 8px auto auto; + } + .tb-panel-title { + min-width: 140px; + user-select: none; + } + } } &.mat-expanded { .mat-expansion-panel-header { @@ -135,10 +147,6 @@ .mat-expansion-panel-body { padding: 0; } - .tb-panel-title { - min-width: 140px; - user-select: none; - } .fc-canvas { background: #f9f9f9; } From 538abeb14067c39e99bac100389ee403990d6baa Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 10 Feb 2020 13:04:56 +0200 Subject: [PATCH 093/133] Input widgets --- .../core/notification/notification.models.ts | 2 + ui-ngx/src/app/core/services/utils.service.ts | 21 +++- .../widget/dynamic-widget.component.ts | 47 +++++++- .../components/widget/widget.component.ts | 8 +- .../home/models/widget-component.models.ts | 1 + .../components/snack-bar-component.html | 2 +- .../app/shared/components/toast.directive.ts | 31 +++++- .../app/shared/pipe/keyboard-shortcut.pipe.ts | 1 - .../assets/locale/locale.constant-de_DE.json | 27 ++++- .../assets/locale/locale.constant-en_US.json | 40 +++++++ .../assets/locale/locale.constant-es_ES.json | 25 +++++ .../assets/locale/locale.constant-fr_FR.json | 27 ++++- .../assets/locale/locale.constant-it_IT.json | 27 ++++- .../assets/locale/locale.constant-ru_RU.json | 40 +++++++ .../assets/locale/locale.constant-uk_UA.json | 103 +++++++++++++++++- 15 files changed, 385 insertions(+), 17 deletions(-) diff --git a/ui-ngx/src/app/core/notification/notification.models.ts b/ui-ngx/src/app/core/notification/notification.models.ts index 26df85b101..88bf992a1e 100644 --- a/ui-ngx/src/app/core/notification/notification.models.ts +++ b/ui-ngx/src/app/core/notification/notification.models.ts @@ -29,8 +29,10 @@ export class NotificationMessage { type: NotificationType; target?: string; duration?: number; + forceDismiss?: boolean; horizontalPosition?: NotificationHorizontalPosition; verticalPosition?: NotificationVerticalPosition; + panelClass?: string | string[]; } export class HideNotification { diff --git a/ui-ngx/src/app/core/services/utils.service.ts b/ui-ngx/src/app/core/services/utils.service.ts index 3ab20bb36d..50faae512b 100644 --- a/ui-ngx/src/app/core/services/utils.service.ts +++ b/ui-ngx/src/app/core/services/utils.service.ts @@ -20,7 +20,7 @@ import { Inject, Injectable, NgZone } from '@angular/core'; import { WINDOW } from '@core/services/window.service'; import { ExceptionData } from '@app/shared/models/error.models'; -import { deepClone, deleteNullProperties, guid, isDefined, isUndefined } from '@core/utils'; +import { deepClone, deleteNullProperties, guid, isDefined, isDefinedAndNotNull, isUndefined } from '@core/utils'; import { WindowMessage } from '@shared/models/window-message.model'; import { TranslateService } from '@ngx-translate/core'; import { customTranslationsPrefix } from '@app/shared/models/constants'; @@ -441,4 +441,23 @@ export class UtilsService { this.window.history.replaceState({}, '', baseUrl + params); } + public deepClone(target: T, ignoreFields?: string[]): T { + return deepClone(target, ignoreFields); + } + + public isUndefined(value: any): boolean { + return isUndefined(value); + } + + public isDefined(value: any): boolean { + return isDefined(value); + } + + public defaultValue(value: any, defaultValue: any): any { + if (isDefinedAndNotNull(value)) { + return value; + } else { + return defaultValue; + } + } } diff --git a/ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts index b70f694c36..4107c1bb5f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts @@ -15,14 +15,19 @@ /// import { PageComponent } from '@shared/components/page.component'; -import { Inject, Injector, Input, OnDestroy, OnInit } from '@angular/core'; +import { Inject, Injector, OnDestroy, OnInit } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { WidgetContext, IDynamicWidgetComponent } from '@home/models/widget-component.models'; -import { ExceptionData } from '@shared/models/error.models'; +import { IDynamicWidgetComponent, WidgetContext } from '@home/models/widget-component.models'; import { HttpErrorResponse } from '@angular/common/http'; import { RafService } from '@core/services/raf.service'; -import { DeviceService } from '@core/http/device.service'; +import { ActionNotificationShow } from '@core/notification/notification.actions'; +import { + NotificationHorizontalPosition, + NotificationType, + NotificationVerticalPosition +} from '@core/notification/notification.models'; +import { FormBuilder, Validators } from '@angular/forms'; export class DynamicWidgetComponent extends PageComponent implements IDynamicWidgetComponent, OnInit, OnDestroy { @@ -33,8 +38,11 @@ export class DynamicWidgetComponent extends PageComponent implements IDynamicWid [key: string]: any; + validators = Validators; + constructor(@Inject(RafService) public raf: RafService, @Inject(Store) protected store: Store, + @Inject(FormBuilder) public fb: FormBuilder, @Inject(Injector) private $injector: Injector, @Inject('widgetContext') public readonly ctx: WidgetContext, @Inject('errorMessages') public readonly errorMessages: string[]) { @@ -63,4 +71,35 @@ export class DynamicWidgetComponent extends PageComponent implements IDynamicWid } } + showSuccessToast(message: string, duration: number = 1000, + verticalPosition: NotificationVerticalPosition = 'bottom', + horizontalPosition: NotificationHorizontalPosition = 'left', + target?: string) { + this.showToast('success', message, duration, verticalPosition, horizontalPosition, target); + } + + showErrorToast(message: string, + verticalPosition: NotificationVerticalPosition = 'bottom', + horizontalPosition: NotificationHorizontalPosition = 'left', + target?: string) { + this.showToast('error', message, undefined, verticalPosition, horizontalPosition, target); + } + + showToast(type: NotificationType, message: string, duration: number = 1000, + verticalPosition: NotificationVerticalPosition = 'bottom', + horizontalPosition: NotificationHorizontalPosition = 'left', + target?: string) { + this.store.dispatch(new ActionNotificationShow( + { + message, + type, + duration, + verticalPosition, + horizontalPosition, + target, + panelClass: this.ctx.widgetNamespace, + forceDismiss: true + })); + } + } 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 adfcd079b9..d636945ffb 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 @@ -95,6 +95,8 @@ import { DialogService } from '@core/services/dialog.service'; import { CustomDialogService } from '@home/components/widget/dialog/custom-dialog.service'; import { DatePipe } from '@angular/common'; import { AttributeService } from '@core/http/attribute.service'; +import { TranslateService } from '@ngx-translate/core'; +import { HttpClient } from '@angular/common/http'; const ServicesMap = new Map>(); ServicesMap.set('deviceService', DeviceService); @@ -104,6 +106,8 @@ ServicesMap.set('dialogs', DialogService); ServicesMap.set('customDialog', CustomDialogService); ServicesMap.set('date', DatePipe); ServicesMap.set('utils', UtilsService); +ServicesMap.set('translate', TranslateService); +ServicesMap.set('http', HttpClient); @Component({ selector: 'tb-widget', @@ -390,10 +394,10 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI } private loadFromWidgetInfo() { - const widgetNamespace = `widget-type-${(this.widget.isSystemType ? 'sys-' : '')}${this.widget.bundleAlias}-${this.widget.typeAlias}`; + this.widgetContext.widgetNamespace = `widget-type-${(this.widget.isSystemType ? 'sys-' : '')}${this.widget.bundleAlias}-${this.widget.typeAlias}`; const elem = this.elementRef.nativeElement; elem.classList.add('tb-widget'); - elem.classList.add(widgetNamespace); + elem.classList.add(this.widgetContext.widgetNamespace); this.widgetType = this.widgetInfo.widgetTypeFunction; this.typeParameters = this.widgetInfo.typeParameters; 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 e4f307f4ab..0460792ae8 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 @@ -161,6 +161,7 @@ export class WidgetContext { isEdit: boolean; isMobile: boolean; + widgetNamespace?: string; subscriptionApi?: WidgetSubscriptionApi; actionsApi?: WidgetActionsApi; diff --git a/ui-ngx/src/app/shared/components/snack-bar-component.html b/ui-ngx/src/app/shared/components/snack-bar-component.html index 4dabc33d75..dd001db296 100644 --- a/ui-ngx/src/app/shared/components/snack-bar-component.html +++ b/ui-ngx/src/app/shared/components/snack-bar-component.html @@ -22,5 +22,5 @@ 'info-toast': notification.type === 'info' }">
- +
diff --git a/ui-ngx/src/app/shared/components/toast.directive.ts b/ui-ngx/src/app/shared/components/toast.directive.ts index b9c2198f11..3d62912b17 100644 --- a/ui-ngx/src/app/shared/components/toast.directive.ts +++ b/ui-ngx/src/app/shared/components/toast.directive.ts @@ -15,12 +15,12 @@ /// import { - AfterViewInit, + AfterViewInit, ApplicationRef, ChangeDetectorRef, Component, Directive, ElementRef, Inject, Input, NgZone, - OnDestroy, + OnDestroy, ViewChild, ViewContainerRef } from '@angular/core'; import { @@ -35,6 +35,8 @@ import { Subscription } from 'rxjs'; import { NotificationService } from '@app/core/services/notification.service'; import { BreakpointObserver } from '@angular/cdk/layout'; import { MediaBreakpoints } from '@shared/models/constants'; +import Timeout = NodeJS.Timeout; +import { MatButton } from '@angular/material/button'; @Directive({ selector: '[tb-toast]' @@ -50,6 +52,8 @@ export class ToastDirective implements AfterViewInit, OnDestroy { private snackBarRef: MatSnackBarRef = null; private currentMessage: NotificationMessage = null; + private dismissTimeout: Timeout = null; + constructor(public elementRef: ElementRef, public viewContainerRef: ViewContainerRef, private notificationService: NotificationService, @@ -73,6 +77,7 @@ export class ToastDirective implements AfterViewInit, OnDestroy { verticalPosition: !isGtSm ? 'bottom' : (notificationMessage.verticalPosition || 'top'), viewContainerRef: this.viewContainerRef, duration: notificationMessage.duration, + panelClass: notificationMessage.panelClass, data }; this.ngZone.run(() => { @@ -80,7 +85,23 @@ export class ToastDirective implements AfterViewInit, OnDestroy { this.snackBarRef.dismiss(); } this.snackBarRef = this.snackBar.openFromComponent(TbSnackBarComponent, config); + if (notificationMessage.duration && notificationMessage.duration > 0 && notificationMessage.forceDismiss) { + if (this.dismissTimeout !== null) { + clearTimeout(this.dismissTimeout); + this.dismissTimeout = null; + } + this.dismissTimeout = setTimeout(() => { + if (this.snackBarRef) { + this.snackBarRef.instance.actionButton._elementRef.nativeElement.click(); + } + this.dismissTimeout = null; + }, notificationMessage.duration); + } this.snackBarRef.afterDismissed().subscribe(() => { + if (this.dismissTimeout !== null) { + clearTimeout(this.dismissTimeout); + this.dismissTimeout = null; + } this.snackBarRef = null; this.currentMessage = null; }); @@ -134,11 +155,15 @@ export class ToastDirective implements AfterViewInit, OnDestroy { styleUrls: ['snack-bar-component.scss'] }) export class TbSnackBarComponent implements AfterViewInit, OnDestroy { + + @ViewChild('actionButton', {static: true}) actionButton: MatButton; + private parentEl: HTMLElement; - private snackBarContainerEl: HTMLElement; + public snackBarContainerEl: HTMLElement; private parentScrollSubscription: Subscription = null; public notification: NotificationMessage; constructor(@Inject(MAT_SNACK_BAR_DATA) public data: any, private elementRef: ElementRef, + public cd: ChangeDetectorRef, public snackBarRef: MatSnackBarRef) { this.notification = data.notification; } diff --git a/ui-ngx/src/app/shared/pipe/keyboard-shortcut.pipe.ts b/ui-ngx/src/app/shared/pipe/keyboard-shortcut.pipe.ts index 0c6402aeb5..a88f3b5dce 100644 --- a/ui-ngx/src/app/shared/pipe/keyboard-shortcut.pipe.ts +++ b/ui-ngx/src/app/shared/pipe/keyboard-shortcut.pipe.ts @@ -44,7 +44,6 @@ export class KeyboardShortcutPipe implements PipeTransform { const last = index === keys.length - 1; return last ? key : abbreviations[key]; }).join(seperator); - return (!value) ? '' : value.replace(/ /g, ''); } } diff --git a/ui-ngx/src/assets/locale/locale.constant-de_DE.json b/ui-ngx/src/assets/locale/locale.constant-de_DE.json index f7048368b4..3c7c57b1c3 100644 --- a/ui-ngx/src/assets/locale/locale.constant-de_DE.json +++ b/ui-ngx/src/assets/locale/locale.constant-de_DE.json @@ -1607,6 +1607,31 @@ "Step size": "Schrittlänge", "Ok": "Ok" } + }, + "input-widgets": { + "attribute-not-allowed": "Attributparameter können in diesem Widget nicht verwendet werden", + "date": "Datum", + "discard-changes": "Änderungen verwerfen", + "entity-attribute-required": "Entitätsattribut ist erforderlich", + "entity-timeseries-required": "Zeitreihen für Entitäten sind erforderlich", + "not-allowed-entity": "Die ausgewählte Entität kann keine gemeinsamen Attribute haben", + "no-attribute-selected": "Es ist kein Attribut ausgewählt", + "no-datakey-selected": "Es ist kein Datenschlüssel ausgewählt", + "no-entity-selected": "Keine Entität ausgewählt", + "no-image": "Kein Bild", + "no-support-web-camera": "Keine unterstützte Webcam", + "no-timeseries-selected": "Keine Zeitreihen ausgewählt", + "switch-attribute-value": "Entitätsattributwert wechseln", + "switch-camera": "Kamera wechseln", + "switch-timeseries-value": "Wert für Zeitreihen von Entitäten wechseln", + "take-photo": "Foto machen", + "time": "Zeit", + "timeseries-not-allowed": "Der Timeseries-Parameter kann in diesem Widget nicht verwendet werden", + "update-failed": "Aktualisierung fehlgeschlagen", + "update-successful": "Aktualisierung erfolgreich", + "update-attribute": "Attribut aktualisieren", + "update-timeseries": "Zeitreihen aktualisieren", + "value": "Wert" } }, "icon": { @@ -1641,7 +1666,7 @@ "tr_TR": "Türkisch", "fa_IR": "Persisch", "uk_UA": "Ukrainisch", - "cs_CZ": "Tschechisch" + "cs_CZ": "Tschechisch" } } } 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 d806f61f42..b9cffa96a6 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1728,6 +1728,46 @@ "Step size": "Step size", "Ok": "Ok" } + }, + "input-widgets": { + "attribute-not-allowed": "Attribute parameter cannot be used in this widget", + "blocked-location": "Geolocation is blocked in your browser", + "claim-device": "Claim device", + "claim-failed": "Failed to claim the device!", + "claim-not-found": "Device not found!", + "claim-successful": "Device was successfully claimed!", + "date": "Date", + "device-name": "Device name", + "device-name-required": "Device name is required", + "discard-changes": "Discard changes", + "entity-attribute-required": "Entity attribute is required", + "entity-coordinate-required": "Both fields, latitude and longitude, are required", + "entity-timeseries-required": "Entity timeseries is required", + "get-location": "Get current location", + "latitude": "Latitude", + "longitude": "Longitude", + "not-allowed-entity": "Selected entity cannot have shared attributes", + "no-attribute-selected": "No attribute is selected", + "no-datakey-selected": "No datakey is selected", + "no-coordinate-specified": "Datakey for latitude/longitude doesn't specified", + "no-entity-selected": "No entity selected", + "no-image": "No image", + "no-support-geolocation": "Your browser doesn't support geolocation", + "no-support-web-camera": "No supported web camera", + "no-timeseries-selected": "No timeseries selected", + "secret-key": "Secret key", + "secret-key-required": "Secret key is required", + "switch-attribute-value": "Switch entity attribute value", + "switch-camera": "Switch camera", + "switch-timeseries-value": "Switch entity timeseries value", + "take-photo": "Take photo", + "time": "Time", + "timeseries-not-allowed": "Timeseries parameter cannot be used in this widget", + "update-failed": "Update failed", + "update-successful": "Update successful", + "update-attribute": "Update attribute", + "update-timeseries": "Update timeseries", + "value": "Value" } }, "icon": { 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 2465365563..cc52b202b5 100644 --- a/ui-ngx/src/assets/locale/locale.constant-es_ES.json +++ b/ui-ngx/src/assets/locale/locale.constant-es_ES.json @@ -1680,6 +1680,31 @@ "Step size": "Numero de pie", "Ok": "De acuerdo" } + }, + "input-widgets": { + "attribute-not-allowed": "El parámetro de atributo no se puede usar en este widget", + "date": "Fecha", + "discard-changes": "Descartar los cambios", + "entity-attribute-required": "Se requiere atributo de entidad", + "entity-timeseries-required": "Se requiere la serie de tiempo de la entidad", + "not-allowed-entity": "La entidad seleccionada no puede tener atributos compartidos", + "no-attribute-selected": "No se seleccionó ningún atributo", + "no-datakey-selected": "No se seleccionó ninguna clave de datos", + "no-entity-selected": "Ninguna entidad seleccionada", + "no-image": "Sin imágen", + "no-support-web-camera": "No hay cámara web compatible", + "no-timeseries-selected": "No hay series de tiempo seleccionadas", + "switch-attribute-value": "Cambiar el valor del atributo de entidad", + "switch-camera": "Cambiar de cámara", + "switch-timeseries-value": "Cambiar el valor de la serie de tiempo de la entidad", + "take-photo": "Tomar foto", + "time": "Tiempo", + "timeseries-not-allowed": "El parámetro Timeseries no se puede usar en este widget", + "update-failed": "Actualización fallida", + "update-successful": "Actualización exitosa", + "update-attribute": "Actualizar atributo", + "update-timeseries": "Actualizar series de tiempo", + "value": "Valor" } }, "icon": { 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 9603442080..9b935b44eb 100644 --- a/ui-ngx/src/assets/locale/locale.constant-fr_FR.json +++ b/ui-ngx/src/assets/locale/locale.constant-fr_FR.json @@ -1018,7 +1018,7 @@ "tr_TR": "Turc", "fa_IR": "Persane", "uk_UA": "Ukrainien", - "cs_CZ": "Tchèque" + "cs_CZ": "Tchèque" } }, "layout": { @@ -1501,6 +1501,31 @@ "Step size": "Taille de pas", "Ok": "Ok" } + }, + "input-widgets": { + "attribute-not-allowed": "Le paramètre d'attribut ne peut pas être utilisé dans ce widget", + "date": "Date", + "discard-changes": "Annuler les modifications", + "entity-attribute-required": "L'attribut d'entité est requis", + "entity-timeseries-required": "Entité timeseries est requis", + "not-allowed-entity": "L'entité sélectionnée ne peut pas avoir d'attributs partagés", + "no-attribute-selected": "Aucun attribut n'est sélectionné", + "no-datakey-selected": "Aucune date n'est sélectionnée", + "no-entity-selected": "Aucune entité sélectionnée", + "no-image": "Pas d'image", + "no-support-web-camera": "Pas de webcam supportée", + "no-timeseries-selected": "Aucune série temporelle sélectionnée", + "switch-attribute-value": "Changer la valeur de l'attribut d'entité", + "switch-camera": "Changer de caméra", + "switch-timeseries-value": "Changer la valeur de l'entité série temporelle", + "take-photo": "Prendre une photo", + "time": "Temps", + "timeseries-not-allowed": "Le paramètre série temporelle ne peut pas être utilisé dans ce widget", + "update-failed": "Mise à jour a échoué", + "update-successful": "Mise à jour réussie", + "update-attribute": "Attribut de mise à jour", + "update-timeseries": "Mise à jour de la série temporelle", + "value": "Valeur" } }, "widgets-bundle": { diff --git a/ui-ngx/src/assets/locale/locale.constant-it_IT.json b/ui-ngx/src/assets/locale/locale.constant-it_IT.json index 2f36ba5f52..e9c670586b 100644 --- a/ui-ngx/src/assets/locale/locale.constant-it_IT.json +++ b/ui-ngx/src/assets/locale/locale.constant-it_IT.json @@ -1615,6 +1615,31 @@ "Step size": "Dimensione del passo", "Ok": "Ok" } + }, + "input-widgets": { + "attribute-not-allowed": "Questo widget non può usare un parametro di tipo attributo", + "date": "Data", + "discard-changes": "Annulla modifiche", + "entity-attribute-required": "E' richiesta un'entità di tipo attributo", + "entity-timeseries-required": "E' richiesta un'entità di tipo serie temporale", + "not-allowed-entity": "L'entità selezionata non può avere attributi condivisi", + "no-attribute-selected": "Nessun attributo selezionato", + "no-datakey-selected": "Nessuna datakey selezionata", + "no-entity-selected": "Nessuna entità selezionata", + "no-image": "Nessuna immagine", + "no-support-web-camera": "Web camera non supportata", + "no-timeseries-selected": "Nessuna serie temporale selezionata", + "switch-attribute-value": "Cambia il valore dell'attributo", + "switch-camera": "Cambia camera", + "switch-timeseries-value": "Cambia il valore della serie temporale", + "take-photo": "Fai una foto", + "time": "Tempo", + "timeseries-not-allowed": "Questo widget non può usare un parametro di tipo serie temporale", + "update-failed": "Aggiornamento fallito", + "update-successful": "Aggiornamento eseguito con successo", + "update-attribute": "Aggiorna attributo", + "update-timeseries": "Aggiorna serie temporale", + "value": "Valore" } }, "icon": { @@ -1649,7 +1674,7 @@ "tr_TR": "Turco", "fa_IR": "Persiana", "uk_UA": "Ucraino", - "cs_CZ": "Ceco" + "cs_CZ": "Ceco" } } } diff --git a/ui-ngx/src/assets/locale/locale.constant-ru_RU.json b/ui-ngx/src/assets/locale/locale.constant-ru_RU.json index ae69c79681..0ba4023be2 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ru_RU.json +++ b/ui-ngx/src/assets/locale/locale.constant-ru_RU.json @@ -1611,6 +1611,46 @@ "Step size": "Размер шага", "Ok": "Ok" } + }, + "input-widgets": { + "attribute-not-allowed": "Атрибут не может быть выбран в этом виджете", + "date": "Дата", + "blocked-location": "Геолокация заблокирована в вашем браузере", + "claim-device": "Подтвердить устройство", + "claim-failed": "Не удалось подтвердить устройство!", + "claim-not-found": "Устройство не найдено!", + "claim-successful": "Устройство успешно подтверждено!", + "discard-changes": "Отменить изменения", + "device-name": "Название устройства", + "device-name-required": "Необходимо указать название устройства", + "entity-attribute-required": "Значение атрибута обязателено", + "entity-coordinate-required": "Необходимо указать широту и долготу", + "entity-timeseries-required": "Значение телеметрии обязательно", + "get-location": "Получить текущее местоположение", + "latitude": "Широта", + "longitude": "Долгота", + "not-allowed-entity": "Выбраный объект не имеет общих атрибутов", + "no-attribute-selected": "Атрибут не выбран", + "no-datakey-selected": "Ни один datakey не выбран", + "no-entity-selected": "Объект не выбран", + "no-coordinate-specified": "Ключ для широты/долготы не указан", + "no-support-geolocation": "Ваш браузер не поддерживает геолокацию", + "no-image": "Нет изображения", + "no-support-web-camera": "Нет поддерживаемой веб-камеры", + "no-timeseries-selected": "Параметр телеметрии не выбран", + "secret-key": "Секретный ключ", + "secret-key-required": "Необходимо указать секретный ключ", + "switch-attribute-value": "Изменить значение атрибута", + "switch-camera": "Изменить камеру", + "switch-timeseries-value": "Изменить значение телеметрии", + "take-photo": "Сделать фото", + "time": "Время", + "timeseries-not-allowed": "Телеметрия не может быть выбрана в этом виджете", + "update-failed": "Не удалось обновить", + "update-successful": "Успешно обновлено", + "update-attribute": "Обновить атрибут", + "update-timeseries": "Обновить телеметрию", + "value": "Значение" } }, "icon": { diff --git a/ui-ngx/src/assets/locale/locale.constant-uk_UA.json b/ui-ngx/src/assets/locale/locale.constant-uk_UA.json index 082f8a9b41..cd9ad87ba9 100644 --- a/ui-ngx/src/assets/locale/locale.constant-uk_UA.json +++ b/ui-ngx/src/assets/locale/locale.constant-uk_UA.json @@ -1,7 +1,7 @@ { "access": { "unauthorized": "Неавторизований", - "unauthorized-access": "Неавторизований доступ", + "unauthorized-access": "Неавторизований доступ", "unauthorized-access-text": "Щоб отримати доступ до цього ресурсу, потрібно ввійти в систему!", "access-forbidden": "Доступ заборонено", "access-forbidden-text": "Недостатньо прав для доступу!
Спробуйте увійти як інший користувач, якщо ви все ще хочете отримати доступ до цього ресурсу.", @@ -246,7 +246,7 @@ "assign-assets": "Надати активи", "assign-assets-text": "Надати { count, plural, 1 {1 актив} other {# активи} } клієнту", "delete-assets": "Видалити активи", - "unassign-assets": "Позбавити активів", + "unassign-assets": "Позбавити активів", "unassign-assets-action-title": "Позбавити { count, plural, 1 {1 актив} other {# активи} } клієнта", "assign-new-asset": "Надати новий актив", "delete-asset-title": "Ви впевнені, що хочете видалити актив '{{assetName}}'?", @@ -2123,6 +2123,105 @@ "widget-type-file": "Файл типу віджета", "invalid-widget-type-file-error": "Неможливо імпортувати тип віджету: неправильна структура даних типу віджета." }, + "widgets": { + "date-range-navigator": { + "localizationMap": { + "Sun": "Нд", + "Mon": "Пн", + "Tue": "Вт", + "Wed": "Ср", + "Thu": "Чт", + "Fri": "Пт", + "Sat": "Сб", + "Jan": "Січ.", + "Feb": "Лют.", + "Mar": "Берез.", + "Apr": "Квіт.", + "May": "Трав.", + "Jun": "Черв.", + "Jul": "Лип.", + "Aug": "Серп.", + "Sep": "Верес.", + "Oct": "Жовт.", + "Nov": "Листоп.", + "Dec": "Груд.", + "January": "Січень", + "February": "Лютий", + "March": "Березень", + "April": "Квітень", + "June": "Червень", + "July": "Липень", + "August": "Серпень", + "September": "Вересень", + "October": "Жовтень", + "November": "Листопад", + "December": "Грудень", + "Custom Date Range": "Користувацький діапазон дат", + "Date Range Template": "Шаблон діапазону дат", + "Today": "Сьогодні", + "Yesterday": "Вчора", + "This Week": "Цього тижня", + "Last Week": "Минулий тиждень", + "This Month": "Цей місяць", + "Last Month": "Минулий місяць", + "Year": "Рік", + "This Year": "Цього року", + "Last Year": "Минулий рік", + "Date picker": "Вибір дати", + "Hour": "Година", + "Day": "День", + "Week": "Тиждень", + "2 weeks": "2 Тижні", + "Month": "Місяць", + "3 months": "3 Місяці", + "6 months": "6 Місяців", + "Custom interval": "Користувацький інтервал", + "Interval": "Інтервал", + "Step size": "Розмір кроку", + "Ok": "Ok" + } + }, + "input-widgets": { + "attribute-not-allowed": "Атрибут не може бути вибраний в цьому віджеті", + "date": "Дата", + "blocked-location": "Геолокація заблокована у вашому браузері", + "claim-device": "Підтвердити пристрій", + "claim-failed": "Не вдалося підтвердити пристрій!", + "claim-not-found": "Пристрій не знайдено!", + "claim-successful": "Пристрій успішно підтверджено!", + "discard-changes": "Скасувати зміни", + "device-name": "Назва пристрою", + "device-name-required": "Необхідно вказати назву пристрою", + "entity-attribute-required": "Значення атрибута обов'язкове", + "entity-coordinate-required": "Необхідно вказати широту та довготу", + "entity-timeseries-required": "Значення телеметрії обов'язкове", + "get-location": "Отримати поточне місцезнаходження", + "latitude": "Широта", + "longitude": "Довгота", + "not-allowed-entity": "Обрана сутність не має спільних атрибутів", + "no-attribute-selected": "Атрибут не вибрано", + "no-datakey-selected": "Ні один datakey не обраний", + "no-entity-selected": "Сутність не вибрано", + "no-coordinate-specified": "Ключ для широти/довготи не вказаний", + "no-support-geolocation": "Ваш браузер не підтримує геолокацію", + "no-image": "Немає зображення", + "no-support-web-camera": "Нет поддерживаемой веб-камеры", + "no-timeseries-selected": "Параметр телеметрії не вибрано", + "secret-key": "Секретний ключ", + "secret-key-required": "Необхідно вказати секретний ключ", + "switch-attribute-value": "Змінити значення атрибута", + "switch-camera": "Змінити камеру", + "switch-timeseries-value": "Змінити значення телеметрії", + "take-photo": "Зробити фото", + "time": "Час", + "timeseries-not-allowed": "Телеметрія не може бути вибрана в цьому віджеті", + "update-failed": "Не вдалося оновити", + "update-successful": "Успішно оновлено", + "update-attribute": "Оновити атрибут", + "update-timeseries": "Оновити телеметрію", + "value": "Значення" + } + }, "white-labeling": { "white-labeling": "Білий маркування", "login-white-labeling": "Login White Labeling", From b7344da12cdf761139f39214486f35ad0ec0c63e Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 10 Feb 2020 13:06:34 +0200 Subject: [PATCH 094/133] Update angular version --- ui-ngx/package-lock.json | 170 +++++++++++++++------------------------ ui-ngx/package.json | 4 +- 2 files changed, 68 insertions(+), 106 deletions(-) diff --git a/ui-ngx/package-lock.json b/ui-ngx/package-lock.json index d283c9b2c4..116b4dce63 100644 --- a/ui-ngx/package-lock.json +++ b/ui-ngx/package-lock.json @@ -16,12 +16,12 @@ } }, "@angular-devkit/architect": { - "version": "0.803.24", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.803.24.tgz", - "integrity": "sha512-ONY/Ppzyvtb0tqgwnzQvlGlexb5nTyy58ljgL1aQLTO3cNTkpl4IQYUCTdvn61gGA+FWPAXMCCbNqOPZMsOZCQ==", + "version": "0.803.25", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.803.25.tgz", + "integrity": "sha512-usV/zEncKCKQuF6AD3pRU6N5i5fbaAux/qZb+nbOz9/2G5jrXwe5sH+y3vxbgqB83e3LqusEQCTu7/tfg6LwZg==", "dev": true, "requires": { - "@angular-devkit/core": "8.3.24", + "@angular-devkit/core": "8.3.25", "rxjs": "6.4.0" }, "dependencies": { @@ -37,23 +37,23 @@ } }, "@angular-devkit/build-angular": { - "version": "0.803.24", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.803.24.tgz", - "integrity": "sha512-uA789spMVghXehwAhl5zK0loY/wfxblUiL+y21T24LMCJc15a9QX5dwbXH72ioHz7qdzb/agXk7AK+foc2/0Hw==", + "version": "0.803.25", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.803.25.tgz", + "integrity": "sha512-WY0E7NgXuog3phhz5ZdutZPWQ9nbOr+omGN5KI1e8MZs1sJO4xkyaGRT8zOulkogkqJ2NboTBq3j9uSbZkcYeg==", "dev": true, "requires": { - "@angular-devkit/architect": "0.803.24", - "@angular-devkit/build-optimizer": "0.803.24", - "@angular-devkit/build-webpack": "0.803.24", - "@angular-devkit/core": "8.3.24", + "@angular-devkit/architect": "0.803.25", + "@angular-devkit/build-optimizer": "0.803.25", + "@angular-devkit/build-webpack": "0.803.25", + "@angular-devkit/core": "8.3.25", "@babel/core": "7.8.3", "@babel/preset-env": "7.8.3", - "@ngtools/webpack": "8.3.24", + "@ngtools/webpack": "8.3.25", "ajv": "6.10.2", "autoprefixer": "9.6.1", - "browserslist": "4.8.3", + "browserslist": "4.8.6", "cacache": "12.0.2", - "caniuse-lite": "1.0.30001019", + "caniuse-lite": "1.0.30001024", "circular-dependency-plugin": "5.2.0", "clean-css": "4.2.1", "copy-webpack-plugin": "5.1.1", @@ -171,9 +171,9 @@ } }, "@angular-devkit/build-optimizer": { - "version": "0.803.24", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.803.24.tgz", - "integrity": "sha512-Z+d7M+WpBq7AWWRwbxzb1l9O9qkylxnDRKxXvq3Tzjn43g+2WyspE91dMyrg1ISc+p8jgX6xKSblRLvtWqpA8w==", + "version": "0.803.25", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.803.25.tgz", + "integrity": "sha512-MiQimuEs8QeM3xo7bR3Yk1OWHHlp2pGCc2GLUMIcWhKqM+QjoRky0HoGoBazbznx292l+xjFjANvPEKbqJ2v7Q==", "dev": true, "requires": { "loader-utils": "1.2.3", @@ -192,13 +192,13 @@ } }, "@angular-devkit/build-webpack": { - "version": "0.803.24", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.803.24.tgz", - "integrity": "sha512-Bbd5KUGaE+edN0sp8K3azuqS/JTBmeWXIumdBEtqWyL6VsohX7fL+toJlSvRkj8lg02LVyozAFetXKnyaBkfCQ==", + "version": "0.803.25", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.803.25.tgz", + "integrity": "sha512-WR7HWJIWL6TB3WHG7ZFn8s0z3WlojeQlod75UIKl5i+f4OU90kp8kxcoH5G6OCXu56x5w40oIi1ve5ljjWSJkw==", "dev": true, "requires": { - "@angular-devkit/architect": "0.803.24", - "@angular-devkit/core": "8.3.24", + "@angular-devkit/architect": "0.803.25", + "@angular-devkit/core": "8.3.25", "rxjs": "6.4.0" }, "dependencies": { @@ -214,9 +214,9 @@ } }, "@angular-devkit/core": { - "version": "8.3.24", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-8.3.24.tgz", - "integrity": "sha512-xpT5yg+ddGDnifryBv2sRSYtq5F3iZIS+lN/K2AhhEa50B7Z+QaCVlEzoV/IfrGd6sLArdnKYwjLHFZ0LElUuw==", + "version": "8.3.25", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-8.3.25.tgz", + "integrity": "sha512-l7Gqy1tMrTpRmPVlovcFX8UA3mtXRlgO8kcSsbJ9MKRKNTCcxlfsWEYY5igyDBUVh6ADkgSIu0nuk31ZGTe0lw==", "dev": true, "requires": { "ajv": "6.10.2", @@ -244,12 +244,12 @@ } }, "@angular-devkit/schematics": { - "version": "8.3.24", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-8.3.24.tgz", - "integrity": "sha512-HrwDCgw7i3GrNns0Ce5zStWkxBqlcLuDkMcLY6981jpvVzgXMIQ+YqDrJ2kD46xHh979ev7hhw1d6jwPXh85Xw==", + "version": "8.3.25", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-8.3.25.tgz", + "integrity": "sha512-/p1MkfursfLy+JRGXlJGPEmX55lrFCsR/2khWAVXZcMaFR3QlR/b6/zvB8I2pHFfr0/XWnYTT/BsF7rJjO3RmA==", "dev": true, "requires": { - "@angular-devkit/core": "8.3.24", + "@angular-devkit/core": "8.3.25", "rxjs": "6.4.0" }, "dependencies": { @@ -282,16 +282,16 @@ } }, "@angular/cli": { - "version": "8.3.24", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-8.3.24.tgz", - "integrity": "sha512-cUB6H+BAISMdaFsstcyvC+17hOV3ET4MaVgcmgT2cL7A4vMBRBxJ0cW4r3D9c6e7m4wipyJzOUESYoIHu0cp4A==", + "version": "8.3.25", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-8.3.25.tgz", + "integrity": "sha512-CPJI5nnbBvvyBUFwOHfRXy/KVwsiYlcbDAeIk1klcjQjbVFYZbnY0iAhNupy9j7rPQhb7jle5oslU3TLfbqOTQ==", "dev": true, "requires": { - "@angular-devkit/architect": "0.803.24", - "@angular-devkit/core": "8.3.24", - "@angular-devkit/schematics": "8.3.24", - "@schematics/angular": "8.3.24", - "@schematics/update": "0.803.24", + "@angular-devkit/architect": "0.803.25", + "@angular-devkit/core": "8.3.25", + "@angular-devkit/schematics": "8.3.25", + "@schematics/angular": "8.3.25", + "@schematics/update": "0.803.25", "@yarnpkg/lockfile": "1.1.0", "ansi-colors": "4.1.1", "debug": "^4.1.1", @@ -1226,25 +1226,6 @@ "browserslist": "^4.8.5", "invariant": "^2.2.4", "semver": "^5.5.0" - }, - "dependencies": { - "browserslist": { - "version": "4.8.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.6.tgz", - "integrity": "sha512-ZHao85gf0eZ0ESxLfCp73GG9O/VTytYDIkIiZDlURppLTI9wErSM/5yAKEq6rcUdxBLjMELmrYUJGg5sxGKMHg==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001023", - "electron-to-chromium": "^1.3.341", - "node-releases": "^1.1.47" - } - }, - "caniuse-lite": { - "version": "1.0.30001025", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001025.tgz", - "integrity": "sha512-SKyFdHYfXUZf5V85+PJgLYyit27q4wgvZuf8QTOk1osbypcROihMBlx9GRar2/pIcKH2r4OehdlBr9x6PXetAQ==", - "dev": true - } } }, "@babel/core": { @@ -1636,25 +1617,6 @@ "invariant": "^2.2.4", "levenary": "^1.1.1", "semver": "^5.5.0" - }, - "dependencies": { - "browserslist": { - "version": "4.8.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.6.tgz", - "integrity": "sha512-ZHao85gf0eZ0ESxLfCp73GG9O/VTytYDIkIiZDlURppLTI9wErSM/5yAKEq6rcUdxBLjMELmrYUJGg5sxGKMHg==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001023", - "electron-to-chromium": "^1.3.341", - "node-releases": "^1.1.47" - } - }, - "caniuse-lite": { - "version": "1.0.30001025", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001025.tgz", - "integrity": "sha512-SKyFdHYfXUZf5V85+PJgLYyit27q4wgvZuf8QTOk1osbypcROihMBlx9GRar2/pIcKH2r4OehdlBr9x6PXetAQ==", - "dev": true - } } }, "@babel/helper-create-regexp-features-plugin": { @@ -3672,12 +3634,12 @@ "integrity": "sha512-PWZmiOZE0J56GFfZpuzKLb7w0K2c6OXZSp/eWDeAvtdHFD4/Nas1i4TXtiWWMWWnSZeNs0hNIg4nFJXi2EddJQ==" }, "@ngtools/webpack": { - "version": "8.3.24", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-8.3.24.tgz", - "integrity": "sha512-OpR7t/99qNOpADayCuM67agBVdYkdbFyEEcOLaDFYh3LsefHOSSxtAGv8M77e7dguvtaljHTiVkMxgcXFsZM0Q==", + "version": "8.3.25", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-8.3.25.tgz", + "integrity": "sha512-yHvgxXUXlgdWijtzcRjTaUqzK+6TVK/8p7PreBR00GsLxhl4U1jQSC6yDaZUCjOaEkiczFWl4hEuC4wTU/hLdg==", "dev": true, "requires": { - "@angular-devkit/core": "8.3.24", + "@angular-devkit/core": "8.3.25", "enhanced-resolve": "4.1.0", "rxjs": "6.4.0", "tree-kill": "1.2.2", @@ -3720,23 +3682,23 @@ } }, "@schematics/angular": { - "version": "8.3.24", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-8.3.24.tgz", - "integrity": "sha512-0nf/LgMHAvhjWS97Pl3JGMqS9/4PI+C0+vJoAo6D7ax8Fb+wuY5uD6Pb7ZqaZALlEnqTgE+FBQ1K8VBVbuwKbA==", + "version": "8.3.25", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-8.3.25.tgz", + "integrity": "sha512-/vEPtE+fvgsWPml/MVqzmlGPBujadPPNwaTuuj5Uz1aVcKeEYzLkbN8YQOpml4vxZHCF8RDwNdGiU4SZg63Jfg==", "dev": true, "requires": { - "@angular-devkit/core": "8.3.24", - "@angular-devkit/schematics": "8.3.24" + "@angular-devkit/core": "8.3.25", + "@angular-devkit/schematics": "8.3.25" } }, "@schematics/update": { - "version": "0.803.24", - "resolved": "https://registry.npmjs.org/@schematics/update/-/update-0.803.24.tgz", - "integrity": "sha512-NvCKn3QfpRjx1EzL56q9IC9fRtDXZP4bMGs/2tj+wtdBNHgm6ZJMJ9qc4mGeztKGbDFLmnX3Xz0XawAl+KeYzQ==", + "version": "0.803.25", + "resolved": "https://registry.npmjs.org/@schematics/update/-/update-0.803.25.tgz", + "integrity": "sha512-VIlqhJsCStA3aO4llxZ7lAOvQUqppyZdrEO7f/ApIJmuofPQTkO5Hx21tnv0dyExwoqPCSIHzEu4Tmc0/TWM1A==", "dev": true, "requires": { - "@angular-devkit/core": "8.3.24", - "@angular-devkit/schematics": "8.3.24", + "@angular-devkit/core": "8.3.25", + "@angular-devkit/schematics": "8.3.25", "@yarnpkg/lockfile": "1.1.0", "ini": "1.3.5", "pacote": "9.5.5", @@ -4896,14 +4858,14 @@ } }, "browserslist": { - "version": "4.8.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.3.tgz", - "integrity": "sha512-iU43cMMknxG1ClEZ2MDKeonKE1CCrFVkQK2AqO2YWFmvIrx4JWrvQ4w4hQez6EpVI8rHTtqh/ruHHDHSOKxvUg==", + "version": "4.8.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.6.tgz", + "integrity": "sha512-ZHao85gf0eZ0ESxLfCp73GG9O/VTytYDIkIiZDlURppLTI9wErSM/5yAKEq6rcUdxBLjMELmrYUJGg5sxGKMHg==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001017", - "electron-to-chromium": "^1.3.322", - "node-releases": "^1.1.44" + "caniuse-lite": "^1.0.30001023", + "electron-to-chromium": "^1.3.341", + "node-releases": "^1.1.47" } }, "browserstack": { @@ -5084,9 +5046,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001019", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001019.tgz", - "integrity": "sha512-6ljkLtF1KM5fQ+5ZN0wuyVvvebJxgJPTmScOMaFuQN2QuOzvRJnWSKfzQskQU5IOU4Gap3zasYPIinzwUjoj/g==", + "version": "1.0.30001024", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001024.tgz", + "integrity": "sha512-LubRSEPpOlKlhZw9wGlLHo8ZVj6ugGU3xGUfLPneNBledSd9lIM5cCGZ9Mz/mMCJUhEt4jZpYteZNVRdJw5FRA==", "dev": true }, "canonical-path": { @@ -6387,9 +6349,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.345", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.345.tgz", - "integrity": "sha512-f8nx53+Z9Y+SPWGg3YdHrbYYfIJAtbUjpFfW4X1RwTZ94iUG7geg9tV8HqzAXX7XTNgyWgAFvce4yce8ZKxKmg==", + "version": "1.3.346", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.346.tgz", + "integrity": "sha512-Yy4jF5hJd57BWmGPt0KjaXc25AmWZeQK75kdr4zIzksWVtiT6DwaNtvTb9dt+LkQKwUpvBfCyyPsXXtbY/5GYw==", "dev": true }, "elliptic": { @@ -9886,9 +9848,9 @@ } }, "node-releases": { - "version": "1.1.47", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.47.tgz", - "integrity": "sha512-k4xjVPx5FpwBUj0Gw7uvFOTF4Ep8Hok1I6qjwL3pLfwe7Y0REQSAqOwwv9TWBCUtMHxcXfY4PgRLRozcChvTcA==", + "version": "1.1.48", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.48.tgz", + "integrity": "sha512-Hr8BbmUl1ujAST0K0snItzEA5zkJTQup8VNTKNfT6Zw8vTJkIiagUPNfxHmgDOyfFYNfKAul40sD0UEYTvwebw==", "dev": true, "requires": { "semver": "^6.3.0" diff --git a/ui-ngx/package.json b/ui-ngx/package.json index 203b3c038c..a8f073cfac 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -87,8 +87,8 @@ }, "devDependencies": { "@angular-builders/custom-webpack": "^8.4.1", - "@angular-devkit/build-angular": "^0.803.24", - "@angular/cli": "^8.3.24", + "@angular-devkit/build-angular": "^0.803.25", + "@angular/cli": "^8.3.25", "@angular/compiler-cli": "~8.2.14", "@angular/language-service": "~8.2.14", "@types/flot": "0.0.31", From 544d50c6c7acdbd62e17d56f05602ef1568d353e Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 10 Feb 2020 13:10:14 +0200 Subject: [PATCH 095/133] Migrate to Angular 9 --- ui-ngx/package-lock.json | 6494 +++++++++-------- ui-ngx/package.json | 32 +- .../src/app/core/services/window.service.ts | 3 +- .../dashboard/dashboard-routing.module.ts | 2 +- .../alias/aliases-entity-select.component.ts | 2 +- .../alias/entity-alias-select.component.ts | 4 +- .../attribute/attribute-table.component.ts | 6 +- .../entity/entities-table.component.ts | 6 +- .../entity/entity-table-header.component.ts | 3 +- .../entity/entity-tabs.component.ts | 12 +- .../components/entity/entity.component.ts | 3 +- .../relation/relation-table.component.ts | 6 +- .../action/manage-widget-actions.component.ts | 6 +- .../action/widget-action-dialog.component.ts | 2 +- .../widget/data-key-config.component.ts | 6 +- .../components/widget/data-keys.component.ts | 6 +- .../widget/legend-config.component.ts | 2 +- .../lib/alarms-table-widget.component.ts | 6 +- .../entities-hierarchy-widget.component.ts | 2 +- .../lib/entities-table-widget.component.ts | 6 +- .../lib/timeseries-table-widget.component.ts | 2 +- .../app/modules/home/home-routing.module.ts | 2 +- ui-ngx/src/app/modules/home/home.component.ts | 4 +- .../dashboard/dashboard-page.component.ts | 2 +- ...anage-dashboard-states-dialog.component.ts | 6 +- .../pages/rulechain/link-labels.component.ts | 6 +- .../rulechain/rule-node-config.component.ts | 2 +- .../rulechain/rule-node-details.component.ts | 2 +- .../rulechain/rulechain-page.component.ts | 6 +- .../components/dashboard-select.component.ts | 2 +- .../entity/entity-keys-list.component.ts | 6 +- .../entity/entity-list.component.ts | 4 +- .../entity/entity-subtype-list.component.ts | 4 +- .../entity/entity-type-list.component.ts | 4 +- .../mat-chip-draggable.directive.ts | 13 +- .../components/time/timewindow.component.ts | 2 +- ui-ngx/tsconfig.json | 2 +- 37 files changed, 3701 insertions(+), 2977 deletions(-) diff --git a/ui-ngx/package-lock.json b/ui-ngx/package-lock.json index 116b4dce63..839fe9a570 100644 --- a/ui-ngx/package-lock.json +++ b/ui-ngx/package-lock.json @@ -16,19 +16,19 @@ } }, "@angular-devkit/architect": { - "version": "0.803.25", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.803.25.tgz", - "integrity": "sha512-usV/zEncKCKQuF6AD3pRU6N5i5fbaAux/qZb+nbOz9/2G5jrXwe5sH+y3vxbgqB83e3LqusEQCTu7/tfg6LwZg==", + "version": "0.900.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.900.1.tgz", + "integrity": "sha512-zzB3J0fXFoYeJpgF5tsmZ7byygzjJn1IPiXBdnbNqcMbil1OPOhq+KdD4ZFPyXNwBQ3w02kOwPdNqB++jbPmlQ==", "dev": true, "requires": { - "@angular-devkit/core": "8.3.25", - "rxjs": "6.4.0" + "@angular-devkit/core": "9.0.1", + "rxjs": "6.5.3" }, "dependencies": { "rxjs": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", - "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==", + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz", + "integrity": "sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==", "dev": true, "requires": { "tslib": "^1.9.0" @@ -37,73 +37,115 @@ } }, "@angular-devkit/build-angular": { - "version": "0.803.25", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.803.25.tgz", - "integrity": "sha512-WY0E7NgXuog3phhz5ZdutZPWQ9nbOr+omGN5KI1e8MZs1sJO4xkyaGRT8zOulkogkqJ2NboTBq3j9uSbZkcYeg==", - "dev": true, - "requires": { - "@angular-devkit/architect": "0.803.25", - "@angular-devkit/build-optimizer": "0.803.25", - "@angular-devkit/build-webpack": "0.803.25", - "@angular-devkit/core": "8.3.25", - "@babel/core": "7.8.3", - "@babel/preset-env": "7.8.3", - "@ngtools/webpack": "8.3.25", + "version": "0.900.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.900.1.tgz", + "integrity": "sha512-e1/EiNI9UAKJxI9+7KA59A15Rkx2QA86evb9iUuwxWGvIsTsN/sg/oXUZA//nTUQTAht+qWJp3I2amd/nyQZLQ==", + "dev": true, + "requires": { + "@angular-devkit/architect": "0.900.1", + "@angular-devkit/build-optimizer": "0.900.1", + "@angular-devkit/build-webpack": "0.900.1", + "@angular-devkit/core": "9.0.1", + "@babel/core": "7.7.7", + "@babel/generator": "7.7.7", + "@babel/preset-env": "7.7.7", + "@ngtools/webpack": "9.0.1", "ajv": "6.10.2", - "autoprefixer": "9.6.1", - "browserslist": "4.8.6", - "cacache": "12.0.2", - "caniuse-lite": "1.0.30001024", + "autoprefixer": "9.7.1", + "babel-loader": "8.0.6", + "browserslist": "4.8.3", + "cacache": "13.0.1", + "caniuse-lite": "1.0.30001020", "circular-dependency-plugin": "5.2.0", - "clean-css": "4.2.1", "copy-webpack-plugin": "5.1.1", - "core-js": "3.6.4", + "core-js": "3.6.0", "coverage-istanbul-loader": "2.0.3", + "cssnano": "4.1.10", "file-loader": "4.2.0", "find-cache-dir": "3.0.0", - "glob": "7.1.4", + "glob": "7.1.5", "jest-worker": "24.9.0", "karma-source-map-support": "1.4.0", - "less": "3.9.0", + "less": "3.10.3", "less-loader": "5.0.0", - "license-webpack-plugin": "2.1.2", + "license-webpack-plugin": "2.1.3", "loader-utils": "1.2.3", + "magic-string": "0.25.4", "mini-css-extract-plugin": "0.8.0", "minimatch": "3.0.4", - "open": "6.4.0", + "open": "7.0.0", "parse5": "4.0.0", - "postcss": "7.0.17", + "postcss": "7.0.21", "postcss-import": "12.0.1", "postcss-loader": "3.0.0", "raw-loader": "3.1.0", "regenerator-runtime": "0.13.3", - "rxjs": "6.4.0", - "sass": "1.22.9", - "sass-loader": "7.2.0", + "rimraf": "3.0.0", + "rollup": "1.25.2", + "rxjs": "6.5.3", + "sass": "1.23.3", + "sass-loader": "8.0.0", "semver": "6.3.0", "source-map": "0.7.3", "source-map-loader": "0.2.4", - "source-map-support": "0.5.13", + "source-map-support": "0.5.16", "speed-measure-webpack-plugin": "1.3.1", "style-loader": "1.0.0", - "stylus": "0.54.5", + "stylus": "0.54.7", "stylus-loader": "3.0.2", - "terser": "4.6.3", - "terser-webpack-plugin": "1.4.3", + "terser": "4.5.1", + "terser-webpack-plugin": "2.3.3", "tree-kill": "1.2.2", - "webpack": "4.39.2", + "webpack": "4.41.2", "webpack-dev-middleware": "3.7.2", "webpack-dev-server": "3.9.0", - "webpack-merge": "4.2.1", + "webpack-merge": "4.2.2", "webpack-sources": "1.4.3", - "webpack-subresource-integrity": "1.1.0-rc.6", + "webpack-subresource-integrity": "1.3.4", "worker-plugin": "3.2.0" }, "dependencies": { + "@babel/generator": { + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.7.7.tgz", + "integrity": "sha512-/AOIBpHh/JU1l0ZFS4kiRCBnLi6OTHzh0RPk3h9isBxkkqELtQNFi1Vr/tiG9p1yfoUdKVwISuXWQR+hwwM4VQ==", + "dev": true, + "requires": { + "@babel/types": "^7.7.4", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/types": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", + "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "core-js": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.0.tgz", + "integrity": "sha512-AHPTNKzyB+YwgDWoSOCaid9PUSEF6781vsfiK8qUz62zRR448/XgK2NtCbpiUGizbep8Lrpt0Du19PpGGZvw3Q==", + "dev": true + }, "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.5.tgz", + "integrity": "sha512-J9dlskqUXK1OeTOYBEn5s8aMukWMwWfs+rPTn/jn50Ux4MNXVhubL1wu/j2t+H4NVI+cXEcCaYellqaPVGXNqQ==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -120,10 +162,19 @@ "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", "dev": true }, + "rimraf": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.0.tgz", + "integrity": "sha512-NDGVxTsjqfunkds7CqsOiEnxln4Bo7Nddl3XhS4pXg5OzwkLqJ971ZVAAnB+DDLnF76N+VnDEiBHaVV8I06SUg==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, "rxjs": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", - "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==", + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz", + "integrity": "sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==", "dev": true, "requires": { "tslib": "^1.9.0" @@ -140,46 +191,19 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", "dev": true - }, - "source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "webpack-merge": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.1.tgz", - "integrity": "sha512-4p8WQyS98bUJcCvFMbdGZyZmsKuWjWVnVHnAS3FFg0HDaRVrPbkivx2RYCre8UiemD67RsiFFLfn4JhLAin8Vw==", - "dev": true, - "requires": { - "lodash": "^4.17.5" - } } } }, "@angular-devkit/build-optimizer": { - "version": "0.803.25", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.803.25.tgz", - "integrity": "sha512-MiQimuEs8QeM3xo7bR3Yk1OWHHlp2pGCc2GLUMIcWhKqM+QjoRky0HoGoBazbznx292l+xjFjANvPEKbqJ2v7Q==", + "version": "0.900.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.900.1.tgz", + "integrity": "sha512-EnIU+ogiJrUPf8+fuPE5xQ+j/qUZDZ/SmLs8XAOmvoOBpZ0vPNedrHBHCxmV+ACbCxHGmIKQ/ZL29XUYVasteg==", "dev": true, "requires": { "loader-utils": "1.2.3", "source-map": "0.7.3", "tslib": "1.10.0", - "typescript": "3.5.3", + "typescript": "3.6.4", "webpack-sources": "1.4.3" }, "dependencies": { @@ -188,24 +212,30 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", "dev": true + }, + "typescript": { + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.4.tgz", + "integrity": "sha512-unoCll1+l+YK4i4F8f22TaNVPRHcD9PA3yCuZ8g5e0qGqlVlJ/8FSateOLLSagn+Yg5+ZwuPkL8LFUc0Jcvksg==", + "dev": true } } }, "@angular-devkit/build-webpack": { - "version": "0.803.25", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.803.25.tgz", - "integrity": "sha512-WR7HWJIWL6TB3WHG7ZFn8s0z3WlojeQlod75UIKl5i+f4OU90kp8kxcoH5G6OCXu56x5w40oIi1ve5ljjWSJkw==", + "version": "0.900.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.900.1.tgz", + "integrity": "sha512-GwV+jht42S2XZZbvy07mXqZ5us9ppbIi/gCL5SiUh+xtSdZGbfE6RoFZXmeOuxBn9FY0vUMTFtKCK5Mx8O3WYg==", "dev": true, "requires": { - "@angular-devkit/architect": "0.803.25", - "@angular-devkit/core": "8.3.25", - "rxjs": "6.4.0" + "@angular-devkit/architect": "0.900.1", + "@angular-devkit/core": "9.0.1", + "rxjs": "6.5.3" }, "dependencies": { "rxjs": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", - "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==", + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz", + "integrity": "sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==", "dev": true, "requires": { "tslib": "^1.9.0" @@ -214,22 +244,22 @@ } }, "@angular-devkit/core": { - "version": "8.3.25", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-8.3.25.tgz", - "integrity": "sha512-l7Gqy1tMrTpRmPVlovcFX8UA3mtXRlgO8kcSsbJ9MKRKNTCcxlfsWEYY5igyDBUVh6ADkgSIu0nuk31ZGTe0lw==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-9.0.1.tgz", + "integrity": "sha512-HboJI/x+SJD9clSOAMjHRv0eXAGRAdEaqJGmjDfdFMP2wznfsBiC6cgcHC17oM4jRWFhmWMR8Omc7CjLZJawJg==", "dev": true, "requires": { "ajv": "6.10.2", "fast-json-stable-stringify": "2.0.0", - "magic-string": "0.25.3", - "rxjs": "6.4.0", + "magic-string": "0.25.4", + "rxjs": "6.5.3", "source-map": "0.7.3" }, "dependencies": { "rxjs": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", - "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==", + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz", + "integrity": "sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==", "dev": true, "requires": { "tslib": "^1.9.0" @@ -244,19 +274,20 @@ } }, "@angular-devkit/schematics": { - "version": "8.3.25", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-8.3.25.tgz", - "integrity": "sha512-/p1MkfursfLy+JRGXlJGPEmX55lrFCsR/2khWAVXZcMaFR3QlR/b6/zvB8I2pHFfr0/XWnYTT/BsF7rJjO3RmA==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-9.0.1.tgz", + "integrity": "sha512-Cuub9eJm1TWygKTOowRbxMASA8QWeHWzNEU2V3TqUF1Tqy/iPf4cpuMijkFysXjTn2bi2HA9t26AwQkwymbliA==", "dev": true, "requires": { - "@angular-devkit/core": "8.3.25", - "rxjs": "6.4.0" + "@angular-devkit/core": "9.0.1", + "ora": "4.0.2", + "rxjs": "6.5.3" }, "dependencies": { "rxjs": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", - "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==", + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz", + "integrity": "sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==", "dev": true, "requires": { "tslib": "^1.9.0" @@ -265,12 +296,9 @@ } }, "@angular/animations": { - "version": "8.2.14", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-8.2.14.tgz", - "integrity": "sha512-3Vc9TnNpKdtvKIXcWDFINSsnwgEMiDmLzjceWg1iYKwpeZGQahUXPoesLwQazBMmxJzQiA4HOMj0TTXKZ+Jzkg==", - "requires": { - "tslib": "^1.9.0" - } + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-9.0.0.tgz", + "integrity": "sha512-jB8+SC3vMztW5zt5UYVmtVwqIWE33UyEjbP5JPba3I3bLRK5E059LcJmN1rSdJHItgIAdG9Y1I0WJ6aiSFyp4Q==" }, "@angular/cdk": { "version": "8.2.3", @@ -282,25 +310,25 @@ } }, "@angular/cli": { - "version": "8.3.25", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-8.3.25.tgz", - "integrity": "sha512-CPJI5nnbBvvyBUFwOHfRXy/KVwsiYlcbDAeIk1klcjQjbVFYZbnY0iAhNupy9j7rPQhb7jle5oslU3TLfbqOTQ==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-9.0.1.tgz", + "integrity": "sha512-/nykTIqZq1plxaXVoMzAqjnExGhkYoSoq88AE4Mb31d6n/SW2DFh62C3hze+atI6YLqeFaPhYuA5zG+z3oOXbQ==", "dev": true, "requires": { - "@angular-devkit/architect": "0.803.25", - "@angular-devkit/core": "8.3.25", - "@angular-devkit/schematics": "8.3.25", - "@schematics/angular": "8.3.25", - "@schematics/update": "0.803.25", + "@angular-devkit/architect": "0.900.1", + "@angular-devkit/core": "9.0.1", + "@angular-devkit/schematics": "9.0.1", + "@schematics/angular": "9.0.1", + "@schematics/update": "0.900.1", "@yarnpkg/lockfile": "1.1.0", "ansi-colors": "4.1.1", "debug": "^4.1.1", "ini": "1.3.5", - "inquirer": "6.5.1", - "npm-package-arg": "6.1.0", + "inquirer": "7.0.0", + "npm-package-arg": "6.1.1", "npm-pick-manifest": "3.0.2", - "open": "6.4.0", - "pacote": "9.5.5", + "open": "7.0.0", + "pacote": "9.5.8", "read-package-tree": "5.3.1", "rimraf": "3.0.0", "semver": "6.3.0", @@ -348,36 +376,31 @@ } }, "@angular/common": { - "version": "8.2.14", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-8.2.14.tgz", - "integrity": "sha512-Qmt+aX2quUW54kaNT7QH7WGXnFxr/cC2C6sf5SW5SdkZfDQSiz8IaItvieZfXVQUbBOQKFRJ7TlSkt0jI/yjvw==", - "requires": { - "tslib": "^1.9.0" - } + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-9.0.0.tgz", + "integrity": "sha512-ZMmEClGtUNJwV5CBlqcSHPIsNyz6WU/GvKWFzJ5VZc68oeg1e7lqfNMNIC47TjyolNJ7VSpNlyrKjzfdBlmqVw==" }, "@angular/compiler": { - "version": "8.2.14", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-8.2.14.tgz", - "integrity": "sha512-ABZO4E7eeFA1QyJ2trDezxeQM5ZFa1dXw1Mpl/+1vuXDKNjJgNyWYwKp/NwRkLmrsuV0yv4UDCDe4kJOGbPKnw==", - "requires": { - "tslib": "^1.9.0" - } + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-9.0.0.tgz", + "integrity": "sha512-ctjwuntPfZZT2mNj2NDIVu51t9cvbhl/16epc5xEwyzyDt76pX9UgwvY+MbXrf/C/FWwdtmNtfP698BKI+9leQ==" }, "@angular/compiler-cli": { - "version": "8.2.14", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-8.2.14.tgz", - "integrity": "sha512-XDrTyrlIZM+0NquVT+Kbg5bn48AaWFT+B3bAT288PENrTdkuxuF9AhjFRZj8jnMdmaE4O2rioEkXBtl6z3zptA==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-9.0.0.tgz", + "integrity": "sha512-6L3swd3Z2ceAapmioml6z7yu3bYC2aVm3/rgK7eCoZtPcevuvTpGnXcFSVvNgByV51GntgInThPbMx0xY23Rvw==", "dev": true, "requires": { "canonical-path": "1.0.0", - "chokidar": "^2.1.1", + "chokidar": "^3.0.0", "convert-source-map": "^1.5.1", "dependency-graph": "^0.7.2", + "fs-extra": "4.0.2", "magic-string": "^0.25.0", "minimist": "^1.2.0", "reflect-metadata": "^0.1.2", + "semver": "^6.3.0", "source-map": "^0.6.1", - "tslib": "^1.9.0", "yargs": "13.1.0" }, "dependencies": { @@ -387,749 +410,90 @@ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true }, - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "fs-extra": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.2.tgz", + "integrity": "sha1-+RcExT0bRh+JNFKwwwfZmXZHq2s=", "dev": true, "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - }, - "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - } + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" } }, - "binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - } + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true }, - "chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "dev": true, "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "fsevents": "^1.2.7", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" } }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "ansi-regex": "^4.1.0" } }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "yargs": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.1.0.tgz", + "integrity": "sha512-1UhJbXfzHiPqkfXNHYhiz79qM/kZqjTE8yGlEjZa85Q+3+OwcV6NRkV7XOV1W2Eom2bzILeUn55pQYffjVOLAg==", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" + "cliui": "^4.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "os-locale": "^3.1.0", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.0.0" } }, - "fsevents": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", - "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", - "dev": true, - "optional": true, - "requires": { - "nan": "^2.12.1", - "node-pre-gyp": "^0.12.0" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "debug": { - "version": "4.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ms": "^2.1.1" - } - }, - "deep-extend": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.3", - "bundled": true, - "dev": true, - "optional": true - }, - "fs-minipass": { - "version": "1.2.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.2.1" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "glob": { - "version": "7.1.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "iconv-lite": { - "version": "0.4.24", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore-walk": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "bundled": true, - "dev": true, - "optional": true - }, - "ini": { - "version": "1.3.5", - "bundled": true, - "dev": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true, - "dev": true, - "optional": true - }, - "minipass": { - "version": "2.3.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.2.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.2.1" - } - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "needle": { - "version": "2.3.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "debug": "^4.1.0", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.12.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4" - } - }, - "nopt": { - "version": "4.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "optional": true - }, - "npm-packlist": { - "version": "1.4.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "process-nextick-args": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "rc": { - "version": "1.2.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "rimraf": { - "version": "2.6.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "sax": { - "version": "1.2.4", - "bundled": true, - "dev": true, - "optional": true - }, - "semver": { - "version": "5.7.0", - "bundled": true, - "dev": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "tar": { - "version": "4.4.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.3.4", - "minizlib": "^1.1.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.2" - } - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "wide-align": { - "version": "1.1.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "yallist": { - "version": "3.0.3", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "dev": true, - "requires": { - "binary-extensions": "^1.0.0" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, - "readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - } - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - }, - "yargs": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.1.0.tgz", - "integrity": "sha512-1UhJbXfzHiPqkfXNHYhiz79qM/kZqjTE8yGlEjZa85Q+3+OwcV6NRkV7XOV1W2Eom2bzILeUn55pQYffjVOLAg==", - "dev": true, - "requires": { - "cliui": "^4.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "os-locale": "^3.1.0", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.0.0" - } - }, - "yargs-parser": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", - "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", + "yargs-parser": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", + "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", "dev": true, "requires": { "camelcase": "^5.0.0", @@ -1139,12 +503,9 @@ } }, "@angular/core": { - "version": "8.2.14", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-8.2.14.tgz", - "integrity": "sha512-zeePkigi+hPh3rN7yoNENG/YUBUsIvUXdxx+AZq+QPaFeKEA2FBSrKn36ojHFrdJUjKzl0lPMEiGC2b6a6bo6g==", - "requires": { - "tslib": "^1.9.0" - } + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-9.0.0.tgz", + "integrity": "sha512-6Pxgsrf0qF9iFFqmIcWmjJGkkCaCm6V5QNnxMy2KloO3SDq6QuMVRbN9RtC8Urmo25LP+eZ6ZgYqFYpdD8Hd9w==" }, "@angular/flex-layout": { "version": "8.0.0-beta.27", @@ -1155,17 +516,14 @@ } }, "@angular/forms": { - "version": "8.2.14", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-8.2.14.tgz", - "integrity": "sha512-zhyKL3CFIqcyHJ/TQF/h1OZztK611a6rxuPHCrt/5Sn1SuBTJJQ1pPTkOYIDy6IrCrtyANc8qB6P17Mao71DNQ==", - "requires": { - "tslib": "^1.9.0" - } + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-9.0.0.tgz", + "integrity": "sha512-SIYJc0Rgaihow1t+iiwSFGEvvRgssgUuxwIYbMfCp1Sx513K+JX9nVFXqU+dcGj/eF1u5wwYwbvlVyuMQLzmXg==" }, "@angular/language-service": { - "version": "8.2.14", - "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-8.2.14.tgz", - "integrity": "sha512-7EhN9JJbAJcH2xCa+rIOmekjiEuB0qwPdHuD5qn/wwMfRzMZo+Db4hHbR9KHrLH6H82PTwYKye/LLpDaZqoHOA==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-9.0.0.tgz", + "integrity": "sha512-tOMtXY8DFpTWMF77BOTXZmMMtqvdy6fbyOkJSccn6VatcPrNXOs5rKur+KNwdSlK+djjss6Y+LA8fQAvjNvUqw==", "dev": true }, "@angular/material": { @@ -1177,28 +535,19 @@ } }, "@angular/platform-browser": { - "version": "8.2.14", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-8.2.14.tgz", - "integrity": "sha512-MtJptptyKzsE37JZ2VB/tI4cvMrdAH+cT9pMBYZd66YSZfKjIj5s+AZo7z8ncoskQSB1o3HMfDjSK7QXGx1mLQ==", - "requires": { - "tslib": "^1.9.0" - } + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-9.0.0.tgz", + "integrity": "sha512-2PR/o57HjZvKEnAF8ODeqxmeC90oth9dLTMrJNoI5MET0IeErKeI/9Sl5cLQuXC+lSVN5rOMCvDb74VWSno5yw==" }, "@angular/platform-browser-dynamic": { - "version": "8.2.14", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-8.2.14.tgz", - "integrity": "sha512-mO2JPR5kLU/A3AQngy9+R/Q5gaF9csMStBQjwsCRI0wNtlItOIGL6+wTYpiTuh/ux+WVN1F2sLcEYU4Zf1ud9A==", - "requires": { - "tslib": "^1.9.0" - } + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-9.0.0.tgz", + "integrity": "sha512-F1kbEpmDottTemRPEOAz2Te5ABVJ7wypfzBllxqXbdxPHvYLfL8db2dXyiGqABQ3ZFHPLNilrkUTy0sbuuU4OA==" }, "@angular/router": { - "version": "8.2.14", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-8.2.14.tgz", - "integrity": "sha512-DHA2BhODqV7F0g6ZKgFaZgbsqzHHWRcfWchCOrOVKu2rYiKUTwwHVLBgZAhrpNeinq2pWanVYSIhMr7wy+LfEA==", - "requires": { - "tslib": "^1.9.0" - } + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-9.0.0.tgz", + "integrity": "sha512-yyOcStpgN5t8wGRNO85mo0jplXkntP+v2tmSxNx45pahqmofSFm+QCEFa2zHQuMr7NoiGERhd0Tae7NDCCjtjA==" }, "@auth0/angular-jwt": { "version": "3.0.1", @@ -1217,33 +566,21 @@ "@babel/highlight": "^7.0.0" } }, - "@babel/compat-data": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.8.5.tgz", - "integrity": "sha512-jWYUqQX/ObOhG1UiEkbH5SANsE/8oKXiQWjj7p7xgj9Zmnt//aUvyz4dBkK0HNsS8/cbyC5NmmH87VekW+mXFg==", - "dev": true, - "requires": { - "browserslist": "^4.8.5", - "invariant": "^2.2.4", - "semver": "^5.5.0" - } - }, "@babel/core": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.8.3.tgz", - "integrity": "sha512-4XFkf8AwyrEG7Ziu3L2L0Cv+WyY47Tcsp70JFmpftbAA1K7YL/sgE9jh9HyNj08Y/U50ItUchpN0w6HxAoX1rA==", + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.7.7.tgz", + "integrity": "sha512-jlSjuj/7z138NLZALxVgrx13AOtqip42ATZP7+kYl53GvDV6+4dCek1mVUo8z8c8Xnw/mx2q3d9HWh3griuesQ==", "dev": true, "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.3", - "@babel/helpers": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.3", + "@babel/code-frame": "^7.5.5", + "@babel/generator": "^7.7.7", + "@babel/helpers": "^7.7.4", + "@babel/parser": "^7.7.7", + "@babel/template": "^7.7.4", + "@babel/traverse": "^7.7.4", + "@babel/types": "^7.7.4", "convert-source-map": "^1.7.0", "debug": "^4.1.0", - "gensync": "^1.0.0-beta.1", "json5": "^2.1.0", "lodash": "^4.17.13", "resolve": "^1.3.2", @@ -1251,15 +588,6 @@ "source-map": "^0.5.0" }, "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.8.3" - } - }, "@babel/generator": { "version": "7.8.4", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", @@ -1327,6 +655,17 @@ "@babel/code-frame": "^7.8.3", "@babel/parser": "^7.8.3", "@babel/types": "^7.8.3" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.8.3" + } + } } }, "@babel/traverse": { @@ -1344,6 +683,17 @@ "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.13" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.8.3" + } + } } }, "@babel/types": { @@ -1606,19 +956,6 @@ } } }, - "@babel/helper-compilation-targets": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.4.tgz", - "integrity": "sha512-3k3BsKMvPp5bjxgMdrFyq0UaEO48HciVrOVF0+lon8pp95cyJ2ujAh0TrBHNMnJGT2rr0iKOJPFFbSqjDyf/Pg==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.8.4", - "browserslist": "^4.8.5", - "invariant": "^2.2.4", - "levenary": "^1.1.1", - "semver": "^5.5.0" - } - }, "@babel/helper-create-regexp-features-plugin": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.3.tgz", @@ -2726,16 +2063,6 @@ "@babel/plugin-syntax-json-strings": "^7.8.0" } }, - "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-TS9MlfzXpXKt6YYomudb/KU7nQI6/xnapG6in1uZxoxDghuSMZsPb6D2fyUwNYSAp4l1iR7QtFOjkqcRYcUsfw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" - } - }, "@babel/plugin-proposal-object-rest-spread": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.8.3.tgz", @@ -2756,16 +2083,6 @@ "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" } }, - "@babel/plugin-proposal-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.8.3.tgz", - "integrity": "sha512-QIoIR9abkVn+seDE3OjA08jWcs3eZ9+wJCKSRgo3WdEU2csFYgdScb+8qHB3+WXsGJD55u+5hWCISI7ejXS+kg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.0" - } - }, "@babel/plugin-proposal-unicode-property-regex": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.8.3.tgz", @@ -2803,15 +2120,6 @@ "@babel/helper-plugin-utils": "^7.8.0" } }, - "@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, "@babel/plugin-syntax-object-rest-spread": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", @@ -2830,15 +2138,6 @@ "@babel/helper-plugin-utils": "^7.8.0" } }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, "@babel/plugin-syntax-top-level-await": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.8.3.tgz", @@ -3327,67 +2626,61 @@ } }, "@babel/preset-env": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.8.3.tgz", - "integrity": "sha512-Rs4RPL2KjSLSE2mWAx5/iCH+GC1ikKdxPrhnRS6PfFVaiZeom22VFKN4X8ZthyN61kAaR05tfXTbCvatl9WIQg==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.8.0", - "@babel/helper-compilation-targets": "^7.8.3", - "@babel/helper-module-imports": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-proposal-async-generator-functions": "^7.8.3", - "@babel/plugin-proposal-dynamic-import": "^7.8.3", - "@babel/plugin-proposal-json-strings": "^7.8.3", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-proposal-object-rest-spread": "^7.8.3", - "@babel/plugin-proposal-optional-catch-binding": "^7.8.3", - "@babel/plugin-proposal-optional-chaining": "^7.8.3", - "@babel/plugin-proposal-unicode-property-regex": "^7.8.3", - "@babel/plugin-syntax-async-generators": "^7.8.0", - "@babel/plugin-syntax-dynamic-import": "^7.8.0", - "@babel/plugin-syntax-json-strings": "^7.8.0", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0", - "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.0", - "@babel/plugin-syntax-top-level-await": "^7.8.3", - "@babel/plugin-transform-arrow-functions": "^7.8.3", - "@babel/plugin-transform-async-to-generator": "^7.8.3", - "@babel/plugin-transform-block-scoped-functions": "^7.8.3", - "@babel/plugin-transform-block-scoping": "^7.8.3", - "@babel/plugin-transform-classes": "^7.8.3", - "@babel/plugin-transform-computed-properties": "^7.8.3", - "@babel/plugin-transform-destructuring": "^7.8.3", - "@babel/plugin-transform-dotall-regex": "^7.8.3", - "@babel/plugin-transform-duplicate-keys": "^7.8.3", - "@babel/plugin-transform-exponentiation-operator": "^7.8.3", - "@babel/plugin-transform-for-of": "^7.8.3", - "@babel/plugin-transform-function-name": "^7.8.3", - "@babel/plugin-transform-literals": "^7.8.3", - "@babel/plugin-transform-member-expression-literals": "^7.8.3", - "@babel/plugin-transform-modules-amd": "^7.8.3", - "@babel/plugin-transform-modules-commonjs": "^7.8.3", - "@babel/plugin-transform-modules-systemjs": "^7.8.3", - "@babel/plugin-transform-modules-umd": "^7.8.3", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.8.3", - "@babel/plugin-transform-new-target": "^7.8.3", - "@babel/plugin-transform-object-super": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.8.3", - "@babel/plugin-transform-property-literals": "^7.8.3", - "@babel/plugin-transform-regenerator": "^7.8.3", - "@babel/plugin-transform-reserved-words": "^7.8.3", - "@babel/plugin-transform-shorthand-properties": "^7.8.3", - "@babel/plugin-transform-spread": "^7.8.3", - "@babel/plugin-transform-sticky-regex": "^7.8.3", - "@babel/plugin-transform-template-literals": "^7.8.3", - "@babel/plugin-transform-typeof-symbol": "^7.8.3", - "@babel/plugin-transform-unicode-regex": "^7.8.3", - "@babel/types": "^7.8.3", - "browserslist": "^4.8.2", - "core-js-compat": "^3.6.2", + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.7.7.tgz", + "integrity": "sha512-pCu0hrSSDVI7kCVUOdcMNQEbOPJ52E+LrQ14sN8uL2ALfSqePZQlKrOy+tM4uhEdYlCHi4imr8Zz2cZe9oSdIg==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.7.4", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-async-generator-functions": "^7.7.4", + "@babel/plugin-proposal-dynamic-import": "^7.7.4", + "@babel/plugin-proposal-json-strings": "^7.7.4", + "@babel/plugin-proposal-object-rest-spread": "^7.7.7", + "@babel/plugin-proposal-optional-catch-binding": "^7.7.4", + "@babel/plugin-proposal-unicode-property-regex": "^7.7.7", + "@babel/plugin-syntax-async-generators": "^7.7.4", + "@babel/plugin-syntax-dynamic-import": "^7.7.4", + "@babel/plugin-syntax-json-strings": "^7.7.4", + "@babel/plugin-syntax-object-rest-spread": "^7.7.4", + "@babel/plugin-syntax-optional-catch-binding": "^7.7.4", + "@babel/plugin-syntax-top-level-await": "^7.7.4", + "@babel/plugin-transform-arrow-functions": "^7.7.4", + "@babel/plugin-transform-async-to-generator": "^7.7.4", + "@babel/plugin-transform-block-scoped-functions": "^7.7.4", + "@babel/plugin-transform-block-scoping": "^7.7.4", + "@babel/plugin-transform-classes": "^7.7.4", + "@babel/plugin-transform-computed-properties": "^7.7.4", + "@babel/plugin-transform-destructuring": "^7.7.4", + "@babel/plugin-transform-dotall-regex": "^7.7.7", + "@babel/plugin-transform-duplicate-keys": "^7.7.4", + "@babel/plugin-transform-exponentiation-operator": "^7.7.4", + "@babel/plugin-transform-for-of": "^7.7.4", + "@babel/plugin-transform-function-name": "^7.7.4", + "@babel/plugin-transform-literals": "^7.7.4", + "@babel/plugin-transform-member-expression-literals": "^7.7.4", + "@babel/plugin-transform-modules-amd": "^7.7.5", + "@babel/plugin-transform-modules-commonjs": "^7.7.5", + "@babel/plugin-transform-modules-systemjs": "^7.7.4", + "@babel/plugin-transform-modules-umd": "^7.7.4", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.7.4", + "@babel/plugin-transform-new-target": "^7.7.4", + "@babel/plugin-transform-object-super": "^7.7.4", + "@babel/plugin-transform-parameters": "^7.7.7", + "@babel/plugin-transform-property-literals": "^7.7.4", + "@babel/plugin-transform-regenerator": "^7.7.5", + "@babel/plugin-transform-reserved-words": "^7.7.4", + "@babel/plugin-transform-shorthand-properties": "^7.7.4", + "@babel/plugin-transform-spread": "^7.7.4", + "@babel/plugin-transform-sticky-regex": "^7.7.4", + "@babel/plugin-transform-template-literals": "^7.7.4", + "@babel/plugin-transform-typeof-symbol": "^7.7.4", + "@babel/plugin-transform-unicode-regex": "^7.7.4", + "@babel/types": "^7.7.4", + "browserslist": "^4.6.0", + "core-js-compat": "^3.6.0", "invariant": "^2.2.2", - "levenary": "^1.1.0", + "js-levenshtein": "^1.1.3", "semver": "^5.5.0" }, "dependencies": { @@ -3634,22 +2927,21 @@ "integrity": "sha512-PWZmiOZE0J56GFfZpuzKLb7w0K2c6OXZSp/eWDeAvtdHFD4/Nas1i4TXtiWWMWWnSZeNs0hNIg4nFJXi2EddJQ==" }, "@ngtools/webpack": { - "version": "8.3.25", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-8.3.25.tgz", - "integrity": "sha512-yHvgxXUXlgdWijtzcRjTaUqzK+6TVK/8p7PreBR00GsLxhl4U1jQSC6yDaZUCjOaEkiczFWl4hEuC4wTU/hLdg==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-9.0.1.tgz", + "integrity": "sha512-SG1MDVSC7pIuaX1QYTh94k/YJa6w2OR2RNbghkDXToDzDv6bKnTQYoJPyXk+gwfDTVD4V5z2dKSNbxFzWleFpg==", "dev": true, "requires": { - "@angular-devkit/core": "8.3.25", - "enhanced-resolve": "4.1.0", - "rxjs": "6.4.0", - "tree-kill": "1.2.2", + "@angular-devkit/core": "9.0.1", + "enhanced-resolve": "4.1.1", + "rxjs": "6.5.3", "webpack-sources": "1.4.3" }, "dependencies": { "rxjs": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", - "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==", + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz", + "integrity": "sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==", "dev": true, "requires": { "tslib": "^1.9.0" @@ -3682,35 +2974,56 @@ } }, "@schematics/angular": { - "version": "8.3.25", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-8.3.25.tgz", - "integrity": "sha512-/vEPtE+fvgsWPml/MVqzmlGPBujadPPNwaTuuj5Uz1aVcKeEYzLkbN8YQOpml4vxZHCF8RDwNdGiU4SZg63Jfg==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-9.0.1.tgz", + "integrity": "sha512-lQ8Qc697ef2jvEf1+tElAUsbOnbUAMo3dnOUVw9RlYO90pHeG3/OdWBMH1kjn3jbjuKuvCVZH3voJUUcLDx6eg==", "dev": true, "requires": { - "@angular-devkit/core": "8.3.25", - "@angular-devkit/schematics": "8.3.25" + "@angular-devkit/core": "9.0.1", + "@angular-devkit/schematics": "9.0.1" } }, "@schematics/update": { - "version": "0.803.25", - "resolved": "https://registry.npmjs.org/@schematics/update/-/update-0.803.25.tgz", - "integrity": "sha512-VIlqhJsCStA3aO4llxZ7lAOvQUqppyZdrEO7f/ApIJmuofPQTkO5Hx21tnv0dyExwoqPCSIHzEu4Tmc0/TWM1A==", + "version": "0.900.1", + "resolved": "https://registry.npmjs.org/@schematics/update/-/update-0.900.1.tgz", + "integrity": "sha512-p2xfctTtT5kMAaCTBENxi69m5IhsvdTwwwokb9zVHJYAC6D1K//q1bl30mTe6U2YE3hSPWND2S14ahXw8PyN8g==", "dev": true, "requires": { - "@angular-devkit/core": "8.3.25", - "@angular-devkit/schematics": "8.3.25", + "@angular-devkit/core": "9.0.1", + "@angular-devkit/schematics": "9.0.1", "@yarnpkg/lockfile": "1.1.0", "ini": "1.3.5", - "pacote": "9.5.5", - "rxjs": "6.4.0", + "npm-package-arg": "^7.0.0", + "pacote": "9.5.8", + "rxjs": "6.5.3", "semver": "6.3.0", "semver-intersect": "1.4.0" }, "dependencies": { + "npm-package-arg": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-7.0.0.tgz", + "integrity": "sha512-xXxr8y5U0kl8dVkz2oK7yZjPBvqM2fwaO5l3Yg13p03v8+E3qQcD0JNhHzjL1vyGgxcKkD0cco+NLR72iuPk3g==", + "dev": true, + "requires": { + "hosted-git-info": "^3.0.2", + "osenv": "^0.1.5", + "semver": "^5.6.0", + "validate-npm-package-name": "^3.0.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, "rxjs": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", - "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==", + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz", + "integrity": "sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==", "dev": true, "requires": { "tslib": "^1.9.0" @@ -3724,6 +3037,12 @@ } } }, + "@types/estree": { + "version": "0.0.42", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.42.tgz", + "integrity": "sha512-K1DPVvnBCPxzD+G51/cxVIoc2X8uUVl1zpJeE6iKcgHMj4+tbat5Xu4TjV7v2QSDbIeAfLi2hIk+u2+s0MlpUQ==", + "dev": true + }, "@types/events": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", @@ -4129,9 +3448,9 @@ "integrity": "sha512-8ZVAxwyCGAxQX8mOp9imSXH0hoSPkGfy8igJy+WO/7axL30saRhKgg1XPACSmxxPA7nfHVwM+ShWXT+vKsNuFg==" }, "acorn": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.0.tgz", - "integrity": "sha512-gac8OEcQ2Li1dxIEWGZzsp2BitJxwkwcOm0zHAJLcPJaVvm58FRnk6RkuLRpU1EujipU2ZFODv2P9DLMfnV8mw==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz", + "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==", "dev": true }, "add-dom-event-listener": { @@ -4206,10 +3525,10 @@ "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==", "dev": true }, - "amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "alphanum-sort": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", + "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", "dev": true }, "angular-gridster2": { @@ -4487,18 +3806,18 @@ "integrity": "sha512-I9SDP4Wvh2ItYYoafEg8hFpsBe96pfQ+eabceShXt3sw2fbIP96+Aoj9zZE0vkZNAkXXzHJATVRuWz+h9FxJxQ==" }, "autoprefixer": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.6.1.tgz", - "integrity": "sha512-aVo5WxR3VyvyJxcJC3h4FKfwCQvQWb1tSI5VHNibddCVWrcD1NvlxEweg3TSgiPztMnWfjpy2FURKA2kvDE+Tw==", + "version": "9.7.1", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.7.1.tgz", + "integrity": "sha512-w3b5y1PXWlhYulevrTJ0lizkQ5CyqfeU6BIRDbuhsMupstHQOeb1Ur80tcB1zxSu7AwyY/qCQ7Vvqklh31ZBFw==", "dev": true, "requires": { - "browserslist": "^4.6.3", - "caniuse-lite": "^1.0.30000980", + "browserslist": "^4.7.2", + "caniuse-lite": "^1.0.30001006", "chalk": "^2.4.2", "normalize-range": "^0.1.2", "num2fraction": "^1.2.2", - "postcss": "^7.0.17", - "postcss-value-parser": "^4.0.0" + "postcss": "^7.0.21", + "postcss-value-parser": "^4.0.2" } }, "aws-sign2": { @@ -4522,6 +3841,31 @@ "ast-types-flow": "0.0.7" } }, + "babel-loader": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.0.6.tgz", + "integrity": "sha512-4BmWKtBOBm13uoUwd08UwjZlaw3O9GWf456R9j+5YykFZ6LUIjIKLc0zEZf+hauxPOJs96C8k6FvYD09vWzhYw==", + "dev": true, + "requires": { + "find-cache-dir": "^2.0.0", + "loader-utils": "^1.0.2", + "mkdirp": "^0.5.1", + "pify": "^4.0.1" + }, + "dependencies": { + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + } + } + }, "babel-plugin-dynamic-import-node": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz", @@ -4762,6 +4106,12 @@ "multicast-dns-service-types": "^1.1.0" } }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "dev": true + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -4858,14 +4208,14 @@ } }, "browserslist": { - "version": "4.8.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.6.tgz", - "integrity": "sha512-ZHao85gf0eZ0ESxLfCp73GG9O/VTytYDIkIiZDlURppLTI9wErSM/5yAKEq6rcUdxBLjMELmrYUJGg5sxGKMHg==", + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.3.tgz", + "integrity": "sha512-iU43cMMknxG1ClEZ2MDKeonKE1CCrFVkQK2AqO2YWFmvIrx4JWrvQ4w4hQez6EpVI8rHTtqh/ruHHDHSOKxvUg==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001023", - "electron-to-chromium": "^1.3.341", - "node-releases": "^1.1.47" + "caniuse-lite": "^1.0.30001017", + "electron-to-chromium": "^1.3.322", + "node-releases": "^1.1.44" } }, "browserstack": { @@ -4953,26 +4303,29 @@ "dev": true }, "cacache": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.2.tgz", - "integrity": "sha512-ifKgxH2CKhJEg6tNdAwziu6Q33EvuG26tYcda6PT3WKisZcYDXsnEdnRv67Po3yCzFfaSoMjGZzJyD2c3DT1dg==", + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-13.0.1.tgz", + "integrity": "sha512-5ZvAxd05HDDU+y9BVvcqYu2LLXmPnQ0hW62h32g4xBTgL/MppR4/04NHfj/ycM2y6lmTnbw6HVi+1eN0Psba6w==", "dev": true, "requires": { - "bluebird": "^3.5.5", - "chownr": "^1.1.1", + "chownr": "^1.1.2", "figgy-pudding": "^3.5.1", + "fs-minipass": "^2.0.0", "glob": "^7.1.4", - "graceful-fs": "^4.1.15", - "infer-owner": "^1.0.3", + "graceful-fs": "^4.2.2", + "infer-owner": "^1.0.4", "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", + "minipass": "^3.0.0", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", "mkdirp": "^0.5.1", "move-concurrently": "^1.0.1", + "p-map": "^3.0.0", "promise-inflight": "^1.0.1", - "rimraf": "^2.6.3", - "ssri": "^6.0.1", - "unique-filename": "^1.1.1", - "y18n": "^4.0.0" + "rimraf": "^2.7.1", + "ssri": "^7.0.0", + "unique-filename": "^1.1.1" }, "dependencies": { "lru-cache": { @@ -5045,10 +4398,22 @@ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, + "caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, "caniuse-lite": { - "version": "1.0.30001024", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001024.tgz", - "integrity": "sha512-LubRSEPpOlKlhZw9wGlLHo8ZVj6ugGU3xGUfLPneNBledSd9lIM5cCGZ9Mz/mMCJUhEt4jZpYteZNVRdJw5FRA==", + "version": "1.0.30001020", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001020.tgz", + "integrity": "sha512-yWIvwA68wRHKanAVS1GjN8vajAv7MBFshullKCeq/eKpK7pJBVDgFFEqvgWTkcP2+wIDeQGYFRXECjKZnLkUjA==", "dev": true }, "canonical-path": { @@ -5166,15 +4531,6 @@ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz", "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==" }, - "clean-css": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", - "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==", - "dev": true, - "requires": { - "source-map": "~0.6.0" - } - }, "clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -5190,6 +4546,12 @@ "restore-cursor": "^3.1.0" } }, + "cli-spinners": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.2.0.tgz", + "integrity": "sha512-tgU3fKwzYjiLEQgPMD9Jt+JjHVL9kW93FiIMX/l7rivvOD4/LL0Mf7gda3+4U2KJBloybwgj5KEoQgGRioMiKQ==", + "dev": true + }, "cli-width": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", @@ -5252,11 +4614,30 @@ "shallow-clone": "^3.0.0" } }, - "clsx": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.0.tgz", - "integrity": "sha512-3avwM37fSK5oP6M5rQ9CNe99lwxhXDOeSWVPAOYF6OazUTgZCMb0yWlJpmdD74REy1gkEaFiub2ULv4fq9GUhA==" - }, + "clsx": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.0.tgz", + "integrity": "sha512-3avwM37fSK5oP6M5rQ9CNe99lwxhXDOeSWVPAOYF6OazUTgZCMb0yWlJpmdD74REy1gkEaFiub2ULv4fq9GUhA==" + }, + "coa": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", + "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", + "dev": true, + "requires": { + "@types/q": "^1.5.1", + "chalk": "^2.4.1", + "q": "^1.1.2" + }, + "dependencies": { + "@types/q": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz", + "integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==", + "dev": true + } + } + }, "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", @@ -5304,6 +4685,16 @@ "object-visit": "^1.0.0" } }, + "color": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/color/-/color-3.1.2.tgz", + "integrity": "sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg==", + "dev": true, + "requires": { + "color-convert": "^1.9.1", + "color-string": "^1.5.2" + } + }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -5319,6 +4710,16 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, + "color-string": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", + "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", + "dev": true, + "requires": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, "colors": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", @@ -5711,6 +5112,15 @@ "yallist": "^3.0.2" } }, + "ssri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1" + } + }, "yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -5854,6 +5264,18 @@ "randomfill": "^1.0.3" } }, + "css": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz", + "integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "source-map": "^0.6.1", + "source-map-resolve": "^0.5.2", + "urix": "^0.1.0" + } + }, "css-animation": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/css-animation/-/css-animation-1.6.1.tgz", @@ -5863,10 +5285,47 @@ "component-classes": "^1.2.5" } }, + "css-color-names": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", + "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=", + "dev": true + }, + "css-declaration-sorter": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz", + "integrity": "sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA==", + "dev": true, + "requires": { + "postcss": "^7.0.1", + "timsort": "^0.3.0" + } + }, "css-parse": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/css-parse/-/css-parse-1.7.0.tgz", - "integrity": "sha1-Mh9s9zeCpv91ERE5D8BeLGV9jJs=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/css-parse/-/css-parse-2.0.0.tgz", + "integrity": "sha1-pGjuZnwW2BzPBcWMONKpfHgNv9Q=", + "dev": true, + "requires": { + "css": "^2.0.0" + } + }, + "css-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", + "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", + "dev": true, + "requires": { + "boolbase": "^1.0.0", + "css-what": "^3.2.1", + "domutils": "^1.7.0", + "nth-check": "^1.0.2" + } + }, + "css-select-base-adapter": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", + "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==", "dev": true }, "css-selector-tokenizer": { @@ -5914,6 +5373,22 @@ } } }, + "css-tree": { + "version": "1.0.0-alpha.37", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", + "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", + "dev": true, + "requires": { + "mdn-data": "2.0.4", + "source-map": "^0.6.1" + } + }, + "css-unit-converter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.1.tgz", + "integrity": "sha1-2bkoGtz9jO2TW9urqDeGiX9k6ZY=", + "dev": true + }, "css-vendor": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.7.tgz", @@ -5923,6 +5398,12 @@ "is-in-browser": "^1.0.2" } }, + "css-what": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.2.1.tgz", + "integrity": "sha512-WwOrosiQTvyms+Ti5ZC5vGEK0Vod3FTt1ca+payZqvKuGJF+dq7bG63DstxtN0dpm6FxY27a/zS3Wten+gEtGw==", + "dev": true + }, "cssauron": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/cssauron/-/cssauron-1.4.0.tgz", @@ -5938,6 +5419,92 @@ "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=", "dev": true }, + "cssnano": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.10.tgz", + "integrity": "sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ==", + "dev": true, + "requires": { + "cosmiconfig": "^5.0.0", + "cssnano-preset-default": "^4.0.7", + "is-resolvable": "^1.0.0", + "postcss": "^7.0.0" + } + }, + "cssnano-preset-default": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz", + "integrity": "sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA==", + "dev": true, + "requires": { + "css-declaration-sorter": "^4.0.1", + "cssnano-util-raw-cache": "^4.0.1", + "postcss": "^7.0.0", + "postcss-calc": "^7.0.1", + "postcss-colormin": "^4.0.3", + "postcss-convert-values": "^4.0.1", + "postcss-discard-comments": "^4.0.2", + "postcss-discard-duplicates": "^4.0.2", + "postcss-discard-empty": "^4.0.1", + "postcss-discard-overridden": "^4.0.1", + "postcss-merge-longhand": "^4.0.11", + "postcss-merge-rules": "^4.0.3", + "postcss-minify-font-values": "^4.0.2", + "postcss-minify-gradients": "^4.0.2", + "postcss-minify-params": "^4.0.2", + "postcss-minify-selectors": "^4.0.2", + "postcss-normalize-charset": "^4.0.1", + "postcss-normalize-display-values": "^4.0.2", + "postcss-normalize-positions": "^4.0.2", + "postcss-normalize-repeat-style": "^4.0.2", + "postcss-normalize-string": "^4.0.2", + "postcss-normalize-timing-functions": "^4.0.2", + "postcss-normalize-unicode": "^4.0.1", + "postcss-normalize-url": "^4.0.1", + "postcss-normalize-whitespace": "^4.0.2", + "postcss-ordered-values": "^4.1.2", + "postcss-reduce-initial": "^4.0.3", + "postcss-reduce-transforms": "^4.0.2", + "postcss-svgo": "^4.0.2", + "postcss-unique-selectors": "^4.0.1" + } + }, + "cssnano-util-get-arguments": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz", + "integrity": "sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8=", + "dev": true + }, + "cssnano-util-get-match": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz", + "integrity": "sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0=", + "dev": true + }, + "cssnano-util-raw-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz", + "integrity": "sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA==", + "dev": true, + "requires": { + "postcss": "^7.0.0" + } + }, + "cssnano-util-same-parent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz", + "integrity": "sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q==", + "dev": true + }, + "csso": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.0.2.tgz", + "integrity": "sha512-kS7/oeNVXkHWxby5tHVxlhjizRCSv8QdU7hB2FpdAibDU8FjTAolhNjKNTiLzXtUrKT6HwClE81yXwEk1309wg==", + "dev": true, + "requires": { + "css-tree": "1.0.0-alpha.37" + } + }, "csstype": { "version": "2.6.8", "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.8.tgz", @@ -6145,6 +5712,12 @@ "dev": true } } + }, + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true } } }, @@ -6303,12 +5876,55 @@ "void-elements": "^2.0.0" } }, + "dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + }, + "dependencies": { + "domelementtype": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz", + "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==", + "dev": true + } + } + }, "domain-browser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", "dev": true }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true + }, + "domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "dot-prop": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", + "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", + "dev": true, + "requires": { + "is-obj": "^1.0.0" + } + }, "duplexify": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", @@ -6370,9 +5986,9 @@ } }, "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, "emojis-list": { @@ -6486,13 +6102,13 @@ } }, "enhanced-resolve": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz", - "integrity": "sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz", + "integrity": "sha512-98p2zE+rL7/g/DzMHMTF4zZlCgeVdJ7yr6xzEpJRYwFYrGi9ANdn5DnJURg6RpBkyk60XYDnWIv51VfIhfNGuA==", "dev": true, "requires": { "graceful-fs": "^4.1.2", - "memory-fs": "^0.4.0", + "memory-fs": "^0.5.0", "tapable": "^1.0.0" } }, @@ -6502,6 +6118,12 @@ "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", "dev": true }, + "entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz", + "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==", + "dev": true + }, "err-code": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/err-code/-/err-code-1.1.2.tgz", @@ -7204,12 +6826,12 @@ } }, "fs-minipass": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", - "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", "dev": true, "requires": { - "minipass": "^2.6.0" + "minipass": "^3.0.0" } }, "fs-write-stream-atomic": { @@ -7247,12 +6869,6 @@ "integrity": "sha512-KGDOARWVga7+rnB3z9Sd2Letx515owfk0hSxHGuqjANb1M+x2bGZGqHLiozPsYMdM2OubeMni/Hpwmjq6qIUhA==", "dev": true }, - "gensync": { - "version": "1.0.0-beta.1", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", - "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==", - "dev": true - }, "get-caller-file": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", @@ -7523,6 +7139,12 @@ "minimalistic-assert": "^1.0.1" } }, + "hex-color-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", + "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==", + "dev": true + }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -7543,10 +7165,30 @@ } }, "hosted-git-info": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz", - "integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==", - "dev": true + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.2.tgz", + "integrity": "sha512-ezZMWtHXm7Eb7Rq4Mwnx2vs79WUx2QmRg3+ZqeGroKzfDO+EprOcgRPYghsOP9JuYBfK18VojmRTGCg8Ma+ktw==", + "dev": true, + "requires": { + "lru-cache": "^5.1.1" + }, + "dependencies": { + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } + } }, "hpack.js": { "version": "2.1.6", @@ -7560,6 +7202,24 @@ "wbuf": "^1.1.0" } }, + "hsl-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hsl-regex/-/hsl-regex-1.0.0.tgz", + "integrity": "sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4=", + "dev": true + }, + "hsla-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz", + "integrity": "sha1-wc56MWjIxmFAM6S194d/OyJfnDg=", + "dev": true + }, + "html-comment-regex": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz", + "integrity": "sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==", + "dev": true + }, "html-entities": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz", @@ -7806,6 +7466,12 @@ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true }, + "indexes-of": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", + "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", + "dev": true + }, "indexof": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", @@ -7838,9 +7504,9 @@ "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" }, "inquirer": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.1.tgz", - "integrity": "sha512-uxNHBeQhRXIoHWTSNYUFhQVrHYFThIt6IVo2fFmSe8aBwdR3/w6b58hJpiL/fMukFkvGzjg+hSxFtwvVmKZmXw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.0.0.tgz", + "integrity": "sha512-rSdC7zelHdRQFkWnhsMu2+2SO41mpv2oF2zy4tMhmiLWkcKbOAs87fWAJhVXttKVwhdZvymvnuM95EyEXg2/tQ==", "dev": true, "requires": { "ansi-escapes": "^4.2.1", @@ -7864,12 +7530,6 @@ "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", "dev": true }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -7961,9 +7621,9 @@ "dev": true }, "is-absolute-url": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", - "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", + "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=", "dev": true }, "is-accessor-descriptor": { @@ -8018,6 +7678,20 @@ "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", "dev": true }, + "is-color-stop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz", + "integrity": "sha1-z/9HGu5N1cnhWFmPvhKWe1za00U=", + "dev": true, + "requires": { + "css-color-names": "^0.0.4", + "hex-color-regex": "^1.1.0", + "hsl-regex": "^1.0.0", + "hsla-regex": "^1.0.0", + "rgb-regex": "^1.0.1", + "rgba-regex": "^1.0.0" + } + }, "is-data-descriptor": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", @@ -8100,12 +7774,24 @@ "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", "integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU=" }, + "is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true + }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, + "is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", + "dev": true + }, "is-path-cwd": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", @@ -8159,12 +7845,27 @@ "has": "^1.0.1" } }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true + }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", "dev": true }, + "is-svg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz", + "integrity": "sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ==", + "dev": true, + "requires": { + "html-comment-regex": "^1.1.0" + } + }, "is-symbol": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", @@ -8187,9 +7888,9 @@ "dev": true }, "is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.1.1.tgz", + "integrity": "sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog==", "dev": true }, "isarray": { @@ -8434,1550 +8135,2086 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "istanbul-lib-report": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz", + "integrity": "sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "supports-color": "^6.1.0" + }, + "dependencies": { + "istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", + "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "rimraf": "^2.6.3", + "source-map": "^0.6.1" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "istanbul-reports": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.6.tgz", + "integrity": "sha512-SKi4rnMyLBKe0Jy2uUdx28h8oG7ph2PPuQPvIAh31d+Ci+lSiEu4C+h3oBPuJ9+mPKhOyW0M8gY4U5NM1WLeXA==", + "dev": true, + "requires": { + "handlebars": "^4.1.2" + } + }, + "jasmine": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz", + "integrity": "sha1-awicChFXax8W3xG4AUbZHU6Lij4=", + "dev": true, + "requires": { + "exit": "^0.1.2", + "glob": "^7.0.6", + "jasmine-core": "~2.8.0" + }, + "dependencies": { + "jasmine-core": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz", + "integrity": "sha1-vMl5rh+f0FcB5F5S5l06XWPxok4=", "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + } + } + }, + "jasmine-core": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.5.0.tgz", + "integrity": "sha512-nCeAiw37MIMA9w9IXso7bRaLl+c/ef3wnxsoSAlYrzS+Ot0zTG6nU8G/cIfGkqpkjX2wNaIW9RFG0TwIFnG6bA==", + "dev": true + }, + "jasmine-spec-reporter": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-4.2.1.tgz", + "integrity": "sha512-FZBoZu7VE5nR7Nilzy+Np8KuVIOxF4oXDPDknehCYBDE080EnlPu0afdZNmpGDBRCUBv3mj5qgqCRmk6W/K8vg==", + "dev": true, + "requires": { + "colors": "1.1.2" + } + }, + "jasminewd2": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/jasminewd2/-/jasminewd2-2.2.0.tgz", + "integrity": "sha1-43zwsX8ZnM4jvqcbIDk5Uka07E4=", + "dev": true + }, + "javascript-detect-element-resize": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/javascript-detect-element-resize/-/javascript-detect-element-resize-0.5.3.tgz", + "integrity": "sha1-GnHNUd/lZZB/KZAS/nOilBBAJd4=" + }, + "jest-worker": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz", + "integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==", + "dev": true, + "requires": { + "merge-stream": "^2.0.0", + "supports-color": "^6.1.0" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "jquery": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.4.1.tgz", + "integrity": "sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw==" + }, + "jquery.terminal": { + "version": "1.23.2", + "resolved": "https://registry.npmjs.org/jquery.terminal/-/jquery.terminal-1.23.2.tgz", + "integrity": "sha512-ULKxZNzL8W4CoeAx5CJZTVY80SrNoeetA4lhnBeHd792uaLAkfRXMeJeARLWhBOrzDWo1yqn2nm4td0a+EU0dg==", + "requires": { + "@types/jquery": "^3.3.6", + "jquery": "~3", + "prismjs": "^1.15.0", + "wcwidth": "^1.0.1" + } + }, + "js-beautify": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.10.3.tgz", + "integrity": "sha512-wfk/IAWobz1TfApSdivH5PJ0miIHgDoYb1ugSqHcODPmaYu46rYe5FVuIEkhjg8IQiv6rDNPyhsqbsohI/C2vQ==", + "requires": { + "config-chain": "^1.1.12", + "editorconfig": "^0.15.3", + "glob": "^7.1.3", + "mkdirp": "~0.5.1", + "nopt": "~4.0.1" + } + }, + "js-levenshtein": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-schema-defaults": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema-defaults/-/json-schema-defaults-0.4.0.tgz", + "integrity": "sha512-UsUrkDVNvHTneyeQOYHH9ZHb3+6OjwYfJ831SdO0yjtXtYZ7Jh8BKWsuJYUQW7qckP5JhHawsg4GI6A5fMaR/Q==", + "requires": { + "argparse": "^1.0.9" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "json3": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz", + "integrity": "sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==", + "dev": true + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } } }, - "istanbul-lib-report": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz", - "integrity": "sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==", - "dev": true, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "dev": true + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "jss": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/jss/-/jss-10.0.4.tgz", + "integrity": "sha512-GqHmeDK83qbqMAVjxyPfN1qJVTKZne533a9bdCrllZukUM8npG/k+JumEPI86IIB5ifaZAHG2HAsUziyxOiooQ==", + "requires": { + "@babel/runtime": "^7.3.1", + "csstype": "^2.6.5", + "is-in-browser": "^1.1.3", + "tiny-warning": "^1.0.2" + } + }, + "jss-plugin-camel-case": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.0.4.tgz", + "integrity": "sha512-+wnqxJsyfUnOn0LxVg3GgZBSjfBCrjxwx7LFxwVTUih0ceGaXKZoieheNOaTo5EM4w8bt1nbb8XonpQCj67C6A==", + "requires": { + "@babel/runtime": "^7.3.1", + "hyphenate-style-name": "^1.0.3", + "jss": "10.0.4" + } + }, + "jss-plugin-default-unit": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.0.4.tgz", + "integrity": "sha512-T0mhL/Ogp/quvod/jAHEqKvptLDxq7Cj3a+7zRuqK8HxUYkftptN89wJElZC3rshhNKiogkEYhCWenpJdFvTBg==", + "requires": { + "@babel/runtime": "^7.3.1", + "jss": "10.0.4" + } + }, + "jss-plugin-global": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.0.4.tgz", + "integrity": "sha512-N8n9/GHENZce+sqE4UYiZiJtI+t+erT/BypHOrNYAfIoNEj7OYsOEKfIo2P0GpLB3QyDAYf5eo9XNdZ8veEkUA==", "requires": { - "istanbul-lib-coverage": "^2.0.5", - "make-dir": "^2.1.0", - "supports-color": "^6.1.0" - }, - "dependencies": { - "istanbul-lib-coverage": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", - "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } + "@babel/runtime": "^7.3.1", + "jss": "10.0.4" } }, - "istanbul-lib-source-maps": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", - "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", - "dev": true, + "jss-plugin-nested": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.0.4.tgz", + "integrity": "sha512-QM21BKVt8LDeoRfowvAMh/s+/89VYrreIIE6ch4pvw0oAXDWw1iorUPlqLZ7uCO3UL0uFtQhJq3QMLN6Lr1v0A==", "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^2.0.5", - "make-dir": "^2.1.0", - "rimraf": "^2.6.3", - "source-map": "^0.6.1" - }, - "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "istanbul-lib-coverage": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", - "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } + "@babel/runtime": "^7.3.1", + "jss": "10.0.4", + "tiny-warning": "^1.0.2" } }, - "istanbul-reports": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.6.tgz", - "integrity": "sha512-SKi4rnMyLBKe0Jy2uUdx28h8oG7ph2PPuQPvIAh31d+Ci+lSiEu4C+h3oBPuJ9+mPKhOyW0M8gY4U5NM1WLeXA==", - "dev": true, + "jss-plugin-props-sort": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.0.4.tgz", + "integrity": "sha512-WoETdOCjGskuin/OMt2uEdDPLZF3vfQuHXF+XUHGJrq0BAapoyGQDcv37SeReDlkRAbVXkEZPsIMvYrgHSHFiA==", "requires": { - "handlebars": "^4.1.2" + "@babel/runtime": "^7.3.1", + "jss": "10.0.4" } }, - "jasmine": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz", - "integrity": "sha1-awicChFXax8W3xG4AUbZHU6Lij4=", - "dev": true, + "jss-plugin-rule-value-function": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.0.4.tgz", + "integrity": "sha512-0hrzOSWRF5ABJGaHrlnHbYZjU877Ofzfh2id3uLtBvemGQLHI+ldoL8/+6iPSRa7M8z8Ngfg2vfYhKjUA5gA0g==", "requires": { - "exit": "^0.1.2", - "glob": "^7.0.6", - "jasmine-core": "~2.8.0" - }, - "dependencies": { - "jasmine-core": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz", - "integrity": "sha1-vMl5rh+f0FcB5F5S5l06XWPxok4=", - "dev": true - } + "@babel/runtime": "^7.3.1", + "jss": "10.0.4" } }, - "jasmine-core": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.5.0.tgz", - "integrity": "sha512-nCeAiw37MIMA9w9IXso7bRaLl+c/ef3wnxsoSAlYrzS+Ot0zTG6nU8G/cIfGkqpkjX2wNaIW9RFG0TwIFnG6bA==", - "dev": true + "jss-plugin-vendor-prefixer": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.0.4.tgz", + "integrity": "sha512-4JgEbcrdeMda1qvxTm1CnxFJAWVV++VLpP46HNTrfH7VhVlvUpihnUNs2gAlKuRT/XSBuiWeLAkrTqF4NVrPig==", + "requires": { + "@babel/runtime": "^7.3.1", + "css-vendor": "^2.0.7", + "jss": "10.0.4" + } }, - "jasmine-spec-reporter": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-4.2.1.tgz", - "integrity": "sha512-FZBoZu7VE5nR7Nilzy+Np8KuVIOxF4oXDPDknehCYBDE080EnlPu0afdZNmpGDBRCUBv3mj5qgqCRmk6W/K8vg==", - "dev": true, + "jstree": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/jstree/-/jstree-3.3.8.tgz", + "integrity": "sha512-0/nhGxVLSGfGQyVg+q59ocqSEKWRDKHoA8wNrcOIvlzCCw19tzvcMNGJ19hf+U0b7fycABowkny7fQPcLgUwwA==", "requires": { - "colors": "1.1.2" + "jquery": ">=1.9.1" } }, - "jasminewd2": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/jasminewd2/-/jasminewd2-2.2.0.tgz", - "integrity": "sha1-43zwsX8ZnM4jvqcbIDk5Uka07E4=", - "dev": true + "jstree-bootstrap-theme": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/jstree-bootstrap-theme/-/jstree-bootstrap-theme-1.0.1.tgz", + "integrity": "sha1-fV7cc6hG6Np/lPV6HMXd7p2eq0s=", + "requires": { + "jquery": ">=1.9.1" + } }, - "javascript-detect-element-resize": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/javascript-detect-element-resize/-/javascript-detect-element-resize-0.5.3.tgz", - "integrity": "sha1-GnHNUd/lZZB/KZAS/nOilBBAJd4=" + "jszip": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.2.2.tgz", + "integrity": "sha512-NmKajvAFQpbg3taXQXr/ccS2wcucR1AZ+NtyWp2Nq7HHVsXhcJFR8p0Baf32C2yVvBylFWVeKf+WI2AnvlPhpA==", + "dev": true, + "requires": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "set-immediate-shim": "~1.0.1" + } }, - "jest-worker": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz", - "integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==", + "karma": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/karma/-/karma-4.4.1.tgz", + "integrity": "sha512-L5SIaXEYqzrh6b1wqYC42tNsFMx2PWuxky84pK9coK09MvmL7mxii3G3bZBh/0rvD27lqDd0le9jyhzvwif73A==", "dev": true, "requires": { - "merge-stream": "^2.0.0", - "supports-color": "^6.1.0" + "bluebird": "^3.3.0", + "body-parser": "^1.16.1", + "braces": "^3.0.2", + "chokidar": "^3.0.0", + "colors": "^1.1.0", + "connect": "^3.6.0", + "di": "^0.0.1", + "dom-serialize": "^2.2.0", + "flatted": "^2.0.0", + "glob": "^7.1.1", + "graceful-fs": "^4.1.2", + "http-proxy": "^1.13.0", + "isbinaryfile": "^3.0.0", + "lodash": "^4.17.14", + "log4js": "^4.0.0", + "mime": "^2.3.1", + "minimatch": "^3.0.2", + "optimist": "^0.6.1", + "qjobs": "^1.1.4", + "range-parser": "^1.2.0", + "rimraf": "^2.6.0", + "safe-buffer": "^5.0.1", + "socket.io": "2.1.1", + "source-map": "^0.6.1", + "tmp": "0.0.33", + "useragent": "2.3.0" }, "dependencies": { - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } + "mime": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", + "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", + "dev": true } } }, - "jquery": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.4.1.tgz", - "integrity": "sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw==" - }, - "jquery.terminal": { - "version": "1.23.2", - "resolved": "https://registry.npmjs.org/jquery.terminal/-/jquery.terminal-1.23.2.tgz", - "integrity": "sha512-ULKxZNzL8W4CoeAx5CJZTVY80SrNoeetA4lhnBeHd792uaLAkfRXMeJeARLWhBOrzDWo1yqn2nm4td0a+EU0dg==", + "karma-chrome-launcher": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.1.0.tgz", + "integrity": "sha512-3dPs/n7vgz1rxxtynpzZTvb9y/GIaW8xjAwcIGttLbycqoFtI7yo1NGnQi6oFTherRE+GIhCAHZC4vEqWGhNvg==", + "dev": true, "requires": { - "@types/jquery": "^3.3.6", - "jquery": "~3", - "prismjs": "^1.15.0", - "wcwidth": "^1.0.1" + "which": "^1.2.1" } }, - "js-beautify": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.10.3.tgz", - "integrity": "sha512-wfk/IAWobz1TfApSdivH5PJ0miIHgDoYb1ugSqHcODPmaYu46rYe5FVuIEkhjg8IQiv6rDNPyhsqbsohI/C2vQ==", + "karma-coverage-istanbul-reporter": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-2.1.1.tgz", + "integrity": "sha512-CH8lTi8+kKXGvrhy94+EkEMldLCiUA0xMOiL31vvli9qK0T+qcXJAwWBRVJWnVWxYkTmyWar8lPz63dxX6/z1A==", + "dev": true, "requires": { - "config-chain": "^1.1.12", - "editorconfig": "^0.15.3", - "glob": "^7.1.3", - "mkdirp": "~0.5.1", - "nopt": "~4.0.1" + "istanbul-api": "^2.1.6", + "minimatch": "^3.0.4" } }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "karma-jasmine": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-2.0.1.tgz", + "integrity": "sha512-iuC0hmr9b+SNn1DaUD2QEYtUxkS1J+bSJSn7ejdEexs7P8EYvA1CWkEdrDQ+8jVH3AgWlCNwjYsT1chjcNW9lA==", "dev": true, "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "jasmine-core": "^3.3" } }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "karma-jasmine-html-reporter": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-1.5.2.tgz", + "integrity": "sha512-ILBPsXqQ3eomq+oaQsM311/jxsypw5/d0LnZXj26XkfThwq7jZ55A2CFSKJVA5VekbbOGvMyv7d3juZj0SeTxA==", "dev": true }, - "json-schema-defaults": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema-defaults/-/json-schema-defaults-0.4.0.tgz", - "integrity": "sha512-UsUrkDVNvHTneyeQOYHH9ZHb3+6OjwYfJ831SdO0yjtXtYZ7Jh8BKWsuJYUQW7qckP5JhHawsg4GI6A5fMaR/Q==", + "karma-source-map-support": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz", + "integrity": "sha512-RsBECncGO17KAoJCYXjv+ckIz+Ii9NCi+9enk+rq6XC81ezYkb4/RHE6CTXdA7IOJqoF3wcaLfVG0CPmE5ca6A==", + "dev": true, "requires": { - "argparse": "^1.0.9" + "source-map-support": "^0.5.5" } }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "killable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", + "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==", "dev": true }, - "json3": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz", - "integrity": "sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==", + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true }, - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", "dev": true, "requires": { - "minimist": "^1.2.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } + "invert-kv": "^2.0.0" } }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "less": { + "version": "3.10.3", + "resolved": "https://registry.npmjs.org/less/-/less-3.10.3.tgz", + "integrity": "sha512-vz32vqfgmoxF1h3K4J+yKCtajH0PWmjkIFgbs5d78E/c/e+UQTnI+lWK+1eQRE95PXM2mC3rJlLSSP9VQHnaow==", "dev": true, "requires": { - "graceful-fs": "^4.1.6" + "clone": "^2.1.2", + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "mime": "^1.4.1", + "mkdirp": "^0.5.0", + "promise": "^7.1.1", + "request": "^2.83.0", + "source-map": "~0.6.0" } }, - "jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", - "dev": true - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "less-loader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-5.0.0.tgz", + "integrity": "sha512-bquCU89mO/yWLaUq0Clk7qCsKhsF/TZpJUzETRvJa9KSVEL9SO3ovCvdEHISBhrC81OwC8QSVX7E0bzElZj9cg==", "dev": true, "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" + "clone": "^2.1.1", + "loader-utils": "^1.1.0", + "pify": "^4.0.1" } }, - "jss": { - "version": "10.0.4", - "resolved": "https://registry.npmjs.org/jss/-/jss-10.0.4.tgz", - "integrity": "sha512-GqHmeDK83qbqMAVjxyPfN1qJVTKZne533a9bdCrllZukUM8npG/k+JumEPI86IIB5ifaZAHG2HAsUziyxOiooQ==", + "license-webpack-plugin": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-2.1.3.tgz", + "integrity": "sha512-vTSY5r9HOq4sxR2BIxdIXWKI+9n3b+DoQkhKHedB3TdSxTfXUDRxKXdAj5iejR+qNXprXsxvEu9W+zOhgGIkAw==", + "dev": true, "requires": { - "@babel/runtime": "^7.3.1", - "csstype": "^2.6.5", - "is-in-browser": "^1.1.3", - "tiny-warning": "^1.0.2" + "@types/webpack-sources": "^0.1.5", + "webpack-sources": "^1.2.0" } }, - "jss-plugin-camel-case": { - "version": "10.0.4", - "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.0.4.tgz", - "integrity": "sha512-+wnqxJsyfUnOn0LxVg3GgZBSjfBCrjxwx7LFxwVTUih0ceGaXKZoieheNOaTo5EM4w8bt1nbb8XonpQCj67C6A==", + "lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, "requires": { - "@babel/runtime": "^7.3.1", - "hyphenate-style-name": "^1.0.3", - "jss": "10.0.4" + "immediate": "~3.0.5" } }, - "jss-plugin-default-unit": { - "version": "10.0.4", - "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.0.4.tgz", - "integrity": "sha512-T0mhL/Ogp/quvod/jAHEqKvptLDxq7Cj3a+7zRuqK8HxUYkftptN89wJElZC3rshhNKiogkEYhCWenpJdFvTBg==", - "requires": { - "@babel/runtime": "^7.3.1", - "jss": "10.0.4" - } + "loader-runner": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", + "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", + "dev": true }, - "jss-plugin-global": { - "version": "10.0.4", - "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.0.4.tgz", - "integrity": "sha512-N8n9/GHENZce+sqE4UYiZiJtI+t+erT/BypHOrNYAfIoNEj7OYsOEKfIo2P0GpLB3QyDAYf5eo9XNdZ8veEkUA==", + "loader-utils": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", + "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", + "dev": true, "requires": { - "@babel/runtime": "^7.3.1", - "jss": "10.0.4" + "big.js": "^5.2.2", + "emojis-list": "^2.0.0", + "json5": "^1.0.1" } }, - "jss-plugin-nested": { - "version": "10.0.4", - "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.0.4.tgz", - "integrity": "sha512-QM21BKVt8LDeoRfowvAMh/s+/89VYrreIIE6ch4pvw0oAXDWw1iorUPlqLZ7uCO3UL0uFtQhJq3QMLN6Lr1v0A==", + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, "requires": { - "@babel/runtime": "^7.3.1", - "jss": "10.0.4", - "tiny-warning": "^1.0.2" + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" } }, - "jss-plugin-props-sort": { - "version": "10.0.4", - "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.0.4.tgz", - "integrity": "sha512-WoETdOCjGskuin/OMt2uEdDPLZF3vfQuHXF+XUHGJrq0BAapoyGQDcv37SeReDlkRAbVXkEZPsIMvYrgHSHFiA==", + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" + }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" + }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", + "dev": true + }, + "lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", + "dev": true + }, + "log-symbols": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", + "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", + "dev": true, "requires": { - "@babel/runtime": "^7.3.1", - "jss": "10.0.4" + "chalk": "^2.4.2" } }, - "jss-plugin-rule-value-function": { - "version": "10.0.4", - "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.0.4.tgz", - "integrity": "sha512-0hrzOSWRF5ABJGaHrlnHbYZjU877Ofzfh2id3uLtBvemGQLHI+ldoL8/+6iPSRa7M8z8Ngfg2vfYhKjUA5gA0g==", + "log4js": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-4.5.1.tgz", + "integrity": "sha512-EEEgFcE9bLgaYUKuozyFfytQM2wDHtXn4tAN41pkaxpNjAykv11GVdeI4tHtmPWW4Xrgh9R/2d7XYghDVjbKKw==", + "dev": true, "requires": { - "@babel/runtime": "^7.3.1", - "jss": "10.0.4" + "date-format": "^2.0.0", + "debug": "^4.1.1", + "flatted": "^2.0.0", + "rfdc": "^1.1.4", + "streamroller": "^1.0.6" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } } }, - "jss-plugin-vendor-prefixer": { - "version": "10.0.4", - "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.0.4.tgz", - "integrity": "sha512-4JgEbcrdeMda1qvxTm1CnxFJAWVV++VLpP46HNTrfH7VhVlvUpihnUNs2gAlKuRT/XSBuiWeLAkrTqF4NVrPig==", + "loglevel": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.6.tgz", + "integrity": "sha512-Sgr5lbboAUBo3eXCSPL4/KoVz3ROKquOjcctxmHIt+vol2DrqTQe3SwkKKuYhEiWB5kYa13YyopJ69deJ1irzQ==", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "requires": { - "@babel/runtime": "^7.3.1", - "css-vendor": "^2.0.7", - "jss": "10.0.4" + "js-tokens": "^3.0.0 || ^4.0.0" } }, - "jstree": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/jstree/-/jstree-3.3.8.tgz", - "integrity": "sha512-0/nhGxVLSGfGQyVg+q59ocqSEKWRDKHoA8wNrcOIvlzCCw19tzvcMNGJ19hf+U0b7fycABowkny7fQPcLgUwwA==", + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", "requires": { - "jquery": ">=1.9.1" + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" } }, - "jstree-bootstrap-theme": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/jstree-bootstrap-theme/-/jstree-bootstrap-theme-1.0.1.tgz", - "integrity": "sha1-fV7cc6hG6Np/lPV6HMXd7p2eq0s=", + "magic-string": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.4.tgz", + "integrity": "sha512-oycWO9nEVAP2RVPbIoDoA4Y7LFIJ3xRYov93gAyJhZkET1tNuB0u7uWkZS2LpBWTJUWnmau/To8ECWRC+jKNfw==", + "dev": true, "requires": { - "jquery": ">=1.9.1" + "sourcemap-codec": "^1.4.4" } }, - "jszip": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.2.2.tgz", - "integrity": "sha512-NmKajvAFQpbg3taXQXr/ccS2wcucR1AZ+NtyWp2Nq7HHVsXhcJFR8p0Baf32C2yVvBylFWVeKf+WI2AnvlPhpA==", + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", "dev": true, "requires": { - "lie": "~3.3.0", - "pako": "~1.0.2", - "readable-stream": "~2.3.6", - "set-immediate-shim": "~1.0.1" + "pify": "^4.0.1", + "semver": "^5.6.0" } }, - "karma": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/karma/-/karma-4.4.1.tgz", - "integrity": "sha512-L5SIaXEYqzrh6b1wqYC42tNsFMx2PWuxky84pK9coK09MvmL7mxii3G3bZBh/0rvD27lqDd0le9jyhzvwif73A==", + "make-error": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", + "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", + "dev": true + }, + "make-fetch-happen": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-5.0.2.tgz", + "integrity": "sha512-07JHC0r1ykIoruKO8ifMXu+xEU8qOXDFETylktdug6vJDACnP+HKevOu3PXyNPzFyTSlz8vrBYlBO1JZRe8Cag==", "dev": true, "requires": { - "bluebird": "^3.3.0", - "body-parser": "^1.16.1", - "braces": "^3.0.2", - "chokidar": "^3.0.0", - "colors": "^1.1.0", - "connect": "^3.6.0", - "di": "^0.0.1", - "dom-serialize": "^2.2.0", - "flatted": "^2.0.0", - "glob": "^7.1.1", - "graceful-fs": "^4.1.2", - "http-proxy": "^1.13.0", - "isbinaryfile": "^3.0.0", - "lodash": "^4.17.14", - "log4js": "^4.0.0", - "mime": "^2.3.1", - "minimatch": "^3.0.2", - "optimist": "^0.6.1", - "qjobs": "^1.1.4", - "range-parser": "^1.2.0", - "rimraf": "^2.6.0", - "safe-buffer": "^5.0.1", - "socket.io": "2.1.1", - "source-map": "^0.6.1", - "tmp": "0.0.33", - "useragent": "2.3.0" + "agentkeepalive": "^3.4.1", + "cacache": "^12.0.0", + "http-cache-semantics": "^3.8.1", + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "node-fetch-npm": "^2.0.2", + "promise-retry": "^1.1.1", + "socks-proxy-agent": "^4.0.0", + "ssri": "^6.0.0" }, "dependencies": { - "mime": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", - "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", + "cacache": { + "version": "12.0.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.3.tgz", + "integrity": "sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw==", + "dev": true, + "requires": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "ssri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true } } }, - "karma-chrome-launcher": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.1.0.tgz", - "integrity": "sha512-3dPs/n7vgz1rxxtynpzZTvb9y/GIaW8xjAwcIGttLbycqoFtI7yo1NGnQi6oFTherRE+GIhCAHZC4vEqWGhNvg==", - "dev": true, + "make-plural": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-4.3.0.tgz", + "integrity": "sha512-xTYd4JVHpSCW+aqDof6w/MebaMVNTVYBZhbB/vi513xXdiPT92JMVCo0Jq8W2UZnzYRFeVbQiQ+I25l13JuKvA==", "requires": { - "which": "^1.2.1" + "minimist": "^1.2.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "optional": true + } } }, - "karma-coverage-istanbul-reporter": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-2.1.1.tgz", - "integrity": "sha512-CH8lTi8+kKXGvrhy94+EkEMldLCiUA0xMOiL31vvli9qK0T+qcXJAwWBRVJWnVWxYkTmyWar8lPz63dxX6/z1A==", + "mamacro": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/mamacro/-/mamacro-0.0.3.tgz", + "integrity": "sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA==", + "dev": true + }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", "dev": true, "requires": { - "istanbul-api": "^2.1.6", - "minimatch": "^3.0.4" + "p-defer": "^1.0.0" } }, - "karma-jasmine": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-2.0.1.tgz", - "integrity": "sha512-iuC0hmr9b+SNn1DaUD2QEYtUxkS1J+bSJSn7ejdEexs7P8EYvA1CWkEdrDQ+8jVH3AgWlCNwjYsT1chjcNW9lA==", + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", "dev": true, "requires": { - "jasmine-core": "^3.3" + "object-visit": "^1.0.0" } }, - "karma-jasmine-html-reporter": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-1.5.2.tgz", - "integrity": "sha512-ILBPsXqQ3eomq+oaQsM311/jxsypw5/d0LnZXj26XkfThwq7jZ55A2CFSKJVA5VekbbOGvMyv7d3juZj0SeTxA==", - "dev": true + "material-design-icons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/material-design-icons/-/material-design-icons-3.0.1.tgz", + "integrity": "sha1-mnHEh0chjrylHlGmbaaCA4zct78=" }, - "karma-source-map-support": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz", - "integrity": "sha512-RsBECncGO17KAoJCYXjv+ckIz+Ii9NCi+9enk+rq6XC81ezYkb4/RHE6CTXdA7IOJqoF3wcaLfVG0CPmE5ca6A==", + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", "dev": true, "requires": { - "source-map-support": "^0.5.5" + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" } }, - "killable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", - "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==", + "mdn-data": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", + "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==", "dev": true }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", "dev": true }, - "lcid": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", - "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", - "dev": true, - "requires": { - "invert-kv": "^2.0.0" - } - }, - "less": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/less/-/less-3.9.0.tgz", - "integrity": "sha512-31CmtPEZraNUtuUREYjSqRkeETFdyEHSEPAGq4erDlUXtda7pzNmctdljdIagSb589d/qXGWiiP31R5JVf+v0w==", + "mem": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", "dev": true, "requires": { - "clone": "^2.1.2", - "errno": "^0.1.1", - "graceful-fs": "^4.1.2", - "image-size": "~0.5.0", - "mime": "^1.4.1", - "mkdirp": "^0.5.0", - "promise": "^7.1.1", - "request": "^2.83.0", - "source-map": "~0.6.0" + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" } }, - "less-loader": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-5.0.0.tgz", - "integrity": "sha512-bquCU89mO/yWLaUq0Clk7qCsKhsF/TZpJUzETRvJa9KSVEL9SO3ovCvdEHISBhrC81OwC8QSVX7E0bzElZj9cg==", + "memory-fs": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", + "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", "dev": true, "requires": { - "clone": "^2.1.1", - "loader-utils": "^1.1.0", - "pify": "^4.0.1" + "errno": "^0.1.3", + "readable-stream": "^2.0.1" } }, - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", "dev": true }, - "levenary": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/levenary/-/levenary-1.1.1.tgz", - "integrity": "sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ==", + "merge-source-map": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", + "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", "dev": true, "requires": { - "leven": "^3.1.0" + "source-map": "^0.6.1" } }, - "license-webpack-plugin": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-2.1.2.tgz", - "integrity": "sha512-7poZHRla+ae0eEButlwMrPpkXyhNVBf2EHePYWT0jyLnI6311/OXJkTI2sOIRungRpQgU2oDMpro5bSFPT5F0A==", - "dev": true, - "requires": { - "@types/webpack-sources": "^0.1.5", - "webpack-sources": "^1.2.0" - } + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true }, - "lie": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", - "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "dev": true, + "messageformat": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/messageformat/-/messageformat-2.3.0.tgz", + "integrity": "sha512-uTzvsv0lTeQxYI2y1NPa1lItL5VRI8Gb93Y2K2ue5gBPyrbJxfDi/EYWxh2PKv5yO42AJeeqblS9MJSh/IEk4w==", "requires": { - "immediate": "~3.0.5" + "make-plural": "^4.3.0", + "messageformat-formatters": "^2.0.1", + "messageformat-parser": "^4.1.2" } }, - "loader-runner": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", - "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", + "messageformat-formatters": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/messageformat-formatters/-/messageformat-formatters-2.0.1.tgz", + "integrity": "sha512-E/lQRXhtHwGuiQjI7qxkLp8AHbMD5r2217XNe/SREbBlSawe0lOqsFb7rflZJmlQFSULNLIqlcjjsCPlB3m3Mg==" + }, + "messageformat-parser": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/messageformat-parser/-/messageformat-parser-4.1.2.tgz", + "integrity": "sha512-7dWuifeyldz7vhEuL96Kwq1fhZXBW+TUfbnHN4UCrCxoXQTYjHnR78eI66Gk9LaLLsAvzPNVJBaa66DRfFNaiA==" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", "dev": true }, - "loader-utils": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", - "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, "requires": { - "big.js": "^5.2.2", - "emojis-list": "^2.0.0", - "json5": "^1.0.1" + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "dependencies": { + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } } }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", "dev": true, "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "bn.js": "^4.0.0", + "brorand": "^1.0.1" } }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true }, - "lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "mime-db": { + "version": "1.42.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.42.0.tgz", + "integrity": "sha512-UbfJCR4UAVRNgMpfImz05smAXK7+c+ZntjaA26ANtkXLlOe947Aag5zdIcKQULAiF9Cq4WxBi9jUs5zkA84bYQ==", "dev": true }, - "lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" + "mime-types": { + "version": "2.1.25", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.25.tgz", + "integrity": "sha512-5KhStqB5xpTAeGqKBAMgwaYMnQik7teQN4IAzC7npDv6kzeU6prfkR67bc87J1kWMPGkoaZSq1npmexMgkmEVg==", + "dev": true, + "requires": { + "mime-db": "1.42.0" + } }, - "lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true }, - "log4js": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-4.5.1.tgz", - "integrity": "sha512-EEEgFcE9bLgaYUKuozyFfytQM2wDHtXn4tAN41pkaxpNjAykv11GVdeI4tHtmPWW4Xrgh9R/2d7XYghDVjbKKw==", + "mini-css-extract-plugin": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.8.0.tgz", + "integrity": "sha512-MNpRGbNA52q6U92i0qbVpQNsgk7LExy41MdAlG84FeytfDOtRIf/mCHdEgG8rpTKOaNKiqUnZdlptF469hxqOw==", "dev": true, "requires": { - "date-format": "^2.0.0", - "debug": "^4.1.1", - "flatted": "^2.0.0", - "rfdc": "^1.1.4", - "streamroller": "^1.0.6" + "loader-utils": "^1.1.0", + "normalize-url": "1.9.1", + "schema-utils": "^1.0.0", + "webpack-sources": "^1.1.0" }, "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "normalize-url": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", + "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", "dev": true, "requires": { - "ms": "^2.1.1" + "object-assign": "^4.0.1", + "prepend-http": "^1.0.0", + "query-string": "^4.1.0", + "sort-keys": "^1.0.0" } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true } } }, - "loglevel": { - "version": "1.6.6", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.6.tgz", - "integrity": "sha512-Sgr5lbboAUBo3eXCSPL4/KoVz3ROKquOjcctxmHIt+vol2DrqTQe3SwkKKuYhEiWB5kYa13YyopJ69deJ1irzQ==", + "mini-store": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mini-store/-/mini-store-2.0.0.tgz", + "integrity": "sha512-EG0CuwpQmX+XL4QVS0kxNwHW5ftSbhygu1qxQH0pipugjnPkbvkalCdQbEihMwtQY6d3MTN+MS0q+aurs+RfLQ==", + "requires": { + "hoist-non-react-statics": "^2.3.1", + "prop-types": "^15.6.0", + "react-lifecycles-compat": "^3.0.4", + "shallowequal": "^1.0.2" + }, + "dependencies": { + "hoist-non-react-statics": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", + "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==" + } + } + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", "dev": true }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true }, - "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" + "brace-expansion": "^1.1.7" } }, - "magic-string": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.3.tgz", - "integrity": "sha512-6QK0OpF/phMz0Q2AxILkX2mFhi7m+WMwTRg0LQKq/WBB0cDP4rYH3Wp4/d3OTXlrPLVJT/RFqj8tFeAR4nk8AA==", - "dev": true, - "requires": { - "sourcemap-codec": "^1.4.4" - } + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "minipass": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.1.tgz", + "integrity": "sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w==", "dev": true, "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" + "yallist": "^4.0.0" + }, + "dependencies": { + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } } }, - "make-error": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", - "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", - "dev": true - }, - "make-fetch-happen": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-5.0.2.tgz", - "integrity": "sha512-07JHC0r1ykIoruKO8ifMXu+xEU8qOXDFETylktdug6vJDACnP+HKevOu3PXyNPzFyTSlz8vrBYlBO1JZRe8Cag==", + "minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", "dev": true, "requires": { - "agentkeepalive": "^3.4.1", - "cacache": "^12.0.0", - "http-cache-semantics": "^3.8.1", - "http-proxy-agent": "^2.1.0", - "https-proxy-agent": "^2.2.3", - "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", - "node-fetch-npm": "^2.0.2", - "promise-retry": "^1.1.1", - "socks-proxy-agent": "^4.0.0", - "ssri": "^6.0.0" + "minipass": "^3.0.0" }, "dependencies": { - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "minipass": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.1.tgz", + "integrity": "sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w==", "dev": true, "requires": { - "yallist": "^3.0.2" + "yallist": "^4.0.0" } }, "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true } } }, - "make-plural": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-4.3.0.tgz", - "integrity": "sha512-xTYd4JVHpSCW+aqDof6w/MebaMVNTVYBZhbB/vi513xXdiPT92JMVCo0Jq8W2UZnzYRFeVbQiQ+I25l13JuKvA==", + "minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, "requires": { - "minimist": "^1.2.0" + "minipass": "^3.0.0" }, "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "optional": true + "minipass": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.1.tgz", + "integrity": "sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true } } }, - "mamacro": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/mamacro/-/mamacro-0.0.3.tgz", - "integrity": "sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA==", - "dev": true - }, - "map-age-cleaner": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", - "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "minipass-pipeline": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.2.tgz", + "integrity": "sha512-3JS5A2DKhD2g0Gg8x3yamO0pj7YeKGwVlDS90pF++kxptwx/F+B//roxf9SqYil5tQo65bijy+dAuAFZmYOouA==", "dev": true, "requires": { - "p-defer": "^1.0.0" + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.1.tgz", + "integrity": "sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } } }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "minizlib": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", + "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", "dev": true, "requires": { - "object-visit": "^1.0.0" + "minipass": "^2.9.0" + }, + "dependencies": { + "minipass": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } } }, - "material-design-icons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/material-design-icons/-/material-design-icons-3.0.1.tgz", - "integrity": "sha1-mnHEh0chjrylHlGmbaaCA4zct78=" - }, - "md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "mississippi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", + "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", "dev": true, "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" } }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", - "dev": true - }, - "mem": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", - "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", "dev": true, "requires": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^2.0.0", - "p-is-promise": "^2.0.0" + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } } }, - "memory-fs": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", - "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", - "dev": true, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" + "minimist": "0.0.8" } }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", - "dev": true + "moment": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", + "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" }, - "merge-source-map": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", - "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", + "mousetrap": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/mousetrap/-/mousetrap-1.6.3.tgz", + "integrity": "sha512-bd+nzwhhs9ifsUrC2tWaSgm24/oo2c83zaRyZQF06hYA6sANfsXHtnZ19AbbbDXCDzeH5nZBSQ4NvCjgD62tJA==" + }, + "move-concurrently": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", + "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", "dev": true, "requires": { - "source-map": "^0.6.1" + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" } }, - "merge-stream": { + "ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, - "messageformat": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/messageformat/-/messageformat-2.3.0.tgz", - "integrity": "sha512-uTzvsv0lTeQxYI2y1NPa1lItL5VRI8Gb93Y2K2ue5gBPyrbJxfDi/EYWxh2PKv5yO42AJeeqblS9MJSh/IEk4w==", + "multicast-dns": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", + "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", + "dev": true, "requires": { - "make-plural": "^4.3.0", - "messageformat-formatters": "^2.0.1", - "messageformat-parser": "^4.1.2" + "dns-packet": "^1.3.1", + "thunky": "^1.0.2" } }, - "messageformat-formatters": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/messageformat-formatters/-/messageformat-formatters-2.0.1.tgz", - "integrity": "sha512-E/lQRXhtHwGuiQjI7qxkLp8AHbMD5r2217XNe/SREbBlSawe0lOqsFb7rflZJmlQFSULNLIqlcjjsCPlB3m3Mg==" + "multicast-dns-service-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", + "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", + "dev": true }, - "messageformat-parser": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/messageformat-parser/-/messageformat-parser-4.1.2.tgz", - "integrity": "sha512-7dWuifeyldz7vhEuL96Kwq1fhZXBW+TUfbnHN4UCrCxoXQTYjHnR78eI66Gk9LaLLsAvzPNVJBaa66DRfFNaiA==" + "mutationobserver-shim": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/mutationobserver-shim/-/mutationobserver-shim-0.3.3.tgz", + "integrity": "sha512-gciOLNN8Vsf7YzcqRjKzlAJ6y7e+B86u7i3KXes0xfxx/nfLmozlW1Vn+Sc9x3tPIePFgc1AeIFhtRgkqTjzDQ==" }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", + "dev": true, + "optional": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", "dev": true, "requires": { "arr-diff": "^4.0.0", "array-unique": "^0.3.2", - "braces": "^2.3.1", "define-property": "^2.0.2", "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", "object.pick": "^1.3.0", "regex-not": "^1.0.0", "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "dependencies": { - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - } - } - }, - "miller-rabin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", - "dev": true, - "requires": { - "bn.js": "^4.0.0", - "brorand": "^1.0.1" + "to-regex": "^3.0.1" } }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", "dev": true }, - "mime-db": { - "version": "1.42.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.42.0.tgz", - "integrity": "sha512-UbfJCR4UAVRNgMpfImz05smAXK7+c+ZntjaA26ANtkXLlOe947Aag5zdIcKQULAiF9Cq4WxBi9jUs5zkA84bYQ==", + "neo-async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", + "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", "dev": true }, - "mime-types": { - "version": "2.1.25", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.25.tgz", - "integrity": "sha512-5KhStqB5xpTAeGqKBAMgwaYMnQik7teQN4IAzC7npDv6kzeU6prfkR67bc87J1kWMPGkoaZSq1npmexMgkmEVg==", + "ngrx-store-freeze": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/ngrx-store-freeze/-/ngrx-store-freeze-0.2.4.tgz", + "integrity": "sha512-90awpbbMa/x2H81eWWYniyli3LJ1PZU/FaztL10d9Rp/4kw2+97pqyLjdxSPxcOv9St//m9kfuWZ7gyoVDjgcg==", "dev": true, "requires": { - "mime-db": "1.42.0" + "deep-freeze-strict": "^1.1.1" } }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true + "ngx-clipboard": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/ngx-clipboard/-/ngx-clipboard-12.3.0.tgz", + "integrity": "sha512-ToSsuDv9I1L0g+TcthePcZ4B859/MpoarlHVr2KnHWy3pR8SxfJlNyP2i9STYRQkJ5bSEg65RFErW4tx52lHYQ==", + "requires": { + "ngx-window-token": "^2.0.0", + "tslib": "^1.9.0" + } + }, + "ngx-color-picker": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/ngx-color-picker/-/ngx-color-picker-8.2.0.tgz", + "integrity": "sha512-rzR+cByjNG9M/UskU5vNoH7cUc6oM8STTDFKOZmnlX4ALOuM1+61CBjsNTGETWfo9a/h5mbGX02oh5/iNAa7vA==" + }, + "ngx-flowchart": { + "version": "git://github.com/thingsboard/ngx-flowchart.git#671b505b2484806a4a1c376344d0a12e5716679a", + "from": "git://github.com/thingsboard/ngx-flowchart.git#master" + }, + "ngx-hm-carousel": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/ngx-hm-carousel/-/ngx-hm-carousel-1.7.2.tgz", + "integrity": "sha512-l53iWKO+brHPCgveqz9eBYvs0/YUp3EAIaxY30c361heMfegNI9yX/xDNXcwCZiKY80KKeT04Qqxos+EyZl/VQ==", + "requires": { + "hammerjs": "^2.0.8", + "resize-observer-polyfill": "^1.5.1", + "tslib": "^1.9.0" + } }, - "mini-css-extract-plugin": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.8.0.tgz", - "integrity": "sha512-MNpRGbNA52q6U92i0qbVpQNsgk7LExy41MdAlG84FeytfDOtRIf/mCHdEgG8rpTKOaNKiqUnZdlptF469hxqOw==", - "dev": true, + "ngx-translate-messageformat-compiler": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/ngx-translate-messageformat-compiler/-/ngx-translate-messageformat-compiler-4.5.0.tgz", + "integrity": "sha512-FasG50bAcF39d+tRa4QRqxtSY9N2xfPelJHSn/OpZF4uU08ioC+eERo4x9HcOXZDtZH2vrbw9GNv/Xwzg/Irbw==", "requires": { - "loader-utils": "^1.1.0", - "normalize-url": "1.9.1", - "schema-utils": "^1.0.0", - "webpack-sources": "^1.1.0" + "tslib": "^1.9.0" } }, - "mini-store": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mini-store/-/mini-store-2.0.0.tgz", - "integrity": "sha512-EG0CuwpQmX+XL4QVS0kxNwHW5ftSbhygu1qxQH0pipugjnPkbvkalCdQbEihMwtQY6d3MTN+MS0q+aurs+RfLQ==", + "ngx-window-token": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ngx-window-token/-/ngx-window-token-2.0.1.tgz", + "integrity": "sha512-rvqdqJEfnWXQFU5fyfYt06E10tR/UtFOYdF3QebfcOh5VIJhnTKiprX8e4B9OrX7WEVFm9BT8uV72xXcEgsaKA==", "requires": { - "hoist-non-react-statics": "^2.3.1", - "prop-types": "^15.6.0", - "react-lifecycles-compat": "^3.0.4", - "shallowequal": "^1.0.2" - }, - "dependencies": { - "hoist-non-react-statics": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", - "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==" - } + "tslib": "^1.9.0" } }, - "minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, - "minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "node-fetch-npm": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-fetch-npm/-/node-fetch-npm-2.0.2.tgz", + "integrity": "sha512-nJIxm1QmAj4v3nfCvEeCrYSoVwXyxLnaPBK5W1W5DGEJwjlKuC2VEUycGw5oxk+4zZahRrB84PUJJgEmhFTDFw==", + "dev": true, + "requires": { + "encoding": "^0.1.11", + "json-parse-better-errors": "^1.0.0", + "safe-buffer": "^5.1.1" + } + }, + "node-forge": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.0.tgz", + "integrity": "sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==", "dev": true }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "node-libs-browser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", + "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", + "dev": true, "requires": { - "brace-expansion": "^1.1.7" + "assert": "^1.1.1", + "browserify-zlib": "^0.2.0", + "buffer": "^4.3.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.11.0", + "domain-browser": "^1.1.1", + "events": "^3.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "0.0.1", + "process": "^0.11.10", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.3.3", + "stream-browserify": "^2.0.1", + "stream-http": "^2.7.2", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", + "tty-browserify": "0.0.0", + "url": "^0.11.0", + "util": "^0.11.0", + "vm-browserify": "^1.0.1" } }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" - }, - "minipass": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "node-releases": { + "version": "1.1.48", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.48.tgz", + "integrity": "sha512-Hr8BbmUl1ujAST0K0snItzEA5zkJTQup8VNTKNfT6Zw8vTJkIiagUPNfxHmgDOyfFYNfKAul40sD0UEYTvwebw==", "dev": true, "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" + "semver": "^6.3.0" }, "dependencies": { - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true } } }, - "minipass-collect": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", - "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "nopt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "dev": true, "requires": { - "minipass": "^3.0.0" + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" }, "dependencies": { - "minipass": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.1.tgz", - "integrity": "sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "hosted-git-info": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz", + "integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==", "dev": true } } }, - "minipass-flush": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", + "dev": true + }, + "normalize-scroll-left": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/normalize-scroll-left/-/normalize-scroll-left-0.2.0.tgz", + "integrity": "sha512-t5oCENZJl8TGusJKoCJm7+asaSsPuNmK6+iEjrZ5TyBj2f02brCRsd4c83hwtu+e5d4LCSBZ0uoDlMjBo+A8yA==" + }, + "normalize-url": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", + "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==", + "dev": true + }, + "npm-bundled": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", + "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", "dev": true, "requires": { - "minipass": "^3.0.0" - }, - "dependencies": { - "minipass": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.1.tgz", - "integrity": "sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } + "npm-normalize-package-bin": "^1.0.1" } }, - "minipass-pipeline": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.2.tgz", - "integrity": "sha512-3JS5A2DKhD2g0Gg8x3yamO0pj7YeKGwVlDS90pF++kxptwx/F+B//roxf9SqYil5tQo65bijy+dAuAFZmYOouA==", + "npm-normalize-package-bin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", + "dev": true + }, + "npm-package-arg": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.1.tgz", + "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==", "dev": true, "requires": { - "minipass": "^3.0.0" + "hosted-git-info": "^2.7.1", + "osenv": "^0.1.5", + "semver": "^5.6.0", + "validate-npm-package-name": "^3.0.0" }, "dependencies": { - "minipass": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.1.tgz", - "integrity": "sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "hosted-git-info": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz", + "integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==", "dev": true } } }, - "minizlib": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", - "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", + "npm-packlist": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", + "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", "dev": true, "requires": { - "minipass": "^2.9.0" + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1", + "npm-normalize-package-bin": "^1.0.1" } }, - "mississippi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", - "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", + "npm-pick-manifest": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-3.0.2.tgz", + "integrity": "sha512-wNprTNg+X5nf+tDi+hbjdHhM4bX+mKqv6XmPh7B5eG+QY9VARfQPfCEH013H5GqfNj6ee8Ij2fg8yk0mzps1Vw==", "dev": true, "requires": { - "concat-stream": "^1.5.0", - "duplexify": "^3.4.2", - "end-of-stream": "^1.1.0", - "flush-write-stream": "^1.0.0", - "from2": "^2.1.0", - "parallel-transform": "^1.1.0", - "pump": "^3.0.0", - "pumpify": "^1.3.3", - "stream-each": "^1.1.0", - "through2": "^2.0.0" + "figgy-pudding": "^3.5.1", + "npm-package-arg": "^6.0.0", + "semver": "^5.4.1" } }, - "mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "npm-registry-fetch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-4.0.2.tgz", + "integrity": "sha512-Z0IFtPEozNdeZRPh3aHHxdG+ZRpzcbQaJLthsm3VhNf6DScicTFRHZzK82u8RsJUsUHkX+QH/zcB/5pmd20H4A==", "dev": true, "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" + "JSONStream": "^1.3.4", + "bluebird": "^3.5.1", + "figgy-pudding": "^3.4.1", + "lru-cache": "^5.1.1", + "make-fetch-happen": "^5.0.0", + "npm-package-arg": "^6.1.0", + "safe-buffer": "^5.2.0" }, "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, "requires": { - "is-plain-object": "^2.0.4" + "yallist": "^3.0.2" } + }, + "safe-buffer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", + "dev": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true } } }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, "requires": { - "minimist": "0.0.8" + "path-key": "^2.0.0" } }, - "moment": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", - "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" - }, - "mousetrap": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/mousetrap/-/mousetrap-1.6.3.tgz", - "integrity": "sha512-bd+nzwhhs9ifsUrC2tWaSgm24/oo2c83zaRyZQF06hYA6sANfsXHtnZ19AbbbDXCDzeH5nZBSQ4NvCjgD62tJA==" - }, - "move-concurrently": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", - "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", "dev": true, "requires": { - "aproba": "^1.1.1", - "copy-concurrently": "^1.0.0", - "fs-write-stream-atomic": "^1.0.8", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.3" + "boolbase": "~1.0.0" } }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "num2fraction": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", + "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", "dev": true }, - "multicast-dns": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", - "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", - "dev": true, - "requires": { - "dns-packet": "^1.3.1", - "thunky": "^1.0.2" - } + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true }, - "multicast-dns-service-types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", - "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", "dev": true }, - "mutationobserver-shim": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/mutationobserver-shim/-/mutationobserver-shim-0.3.3.tgz", - "integrity": "sha512-gciOLNN8Vsf7YzcqRjKzlAJ6y7e+B86u7i3KXes0xfxx/nfLmozlW1Vn+Sc9x3tPIePFgc1AeIFhtRgkqTjzDQ==" + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, - "mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "object-component": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", + "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=", "dev": true }, - "nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", - "dev": true, - "optional": true - }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", "dev": true, "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, - "negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", "dev": true }, - "neo-async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", - "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", - "dev": true + "object-is": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.1.tgz", + "integrity": "sha1-CqYOyZiaCz7Xlc9NBvYs8a1lObY=" }, - "ngrx-store-freeze": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/ngrx-store-freeze/-/ngrx-store-freeze-0.2.4.tgz", - "integrity": "sha512-90awpbbMa/x2H81eWWYniyli3LJ1PZU/FaztL10d9Rp/4kw2+97pqyLjdxSPxcOv9St//m9kfuWZ7gyoVDjgcg==", + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", "dev": true, "requires": { - "deep-freeze-strict": "^1.1.1" + "isobject": "^3.0.0" } }, - "ngx-clipboard": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/ngx-clipboard/-/ngx-clipboard-12.3.0.tgz", - "integrity": "sha512-ToSsuDv9I1L0g+TcthePcZ4B859/MpoarlHVr2KnHWy3pR8SxfJlNyP2i9STYRQkJ5bSEg65RFErW4tx52lHYQ==", + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, "requires": { - "ngx-window-token": "^2.0.0", - "tslib": "^1.9.0" + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" } }, - "ngx-color-picker": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/ngx-color-picker/-/ngx-color-picker-8.2.0.tgz", - "integrity": "sha512-rzR+cByjNG9M/UskU5vNoH7cUc6oM8STTDFKOZmnlX4ALOuM1+61CBjsNTGETWfo9a/h5mbGX02oh5/iNAa7vA==" - }, - "ngx-flowchart": { - "version": "git://github.com/thingsboard/ngx-flowchart.git#671b505b2484806a4a1c376344d0a12e5716679a", - "from": "git://github.com/thingsboard/ngx-flowchart.git#master" - }, - "ngx-hm-carousel": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/ngx-hm-carousel/-/ngx-hm-carousel-1.7.2.tgz", - "integrity": "sha512-l53iWKO+brHPCgveqz9eBYvs0/YUp3EAIaxY30c361heMfegNI9yX/xDNXcwCZiKY80KKeT04Qqxos+EyZl/VQ==", + "object.getownpropertydescriptors": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", + "dev": true, "requires": { - "hammerjs": "^2.0.8", - "resize-observer-polyfill": "^1.5.1", - "tslib": "^1.9.0" + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" } }, - "ngx-translate-messageformat-compiler": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/ngx-translate-messageformat-compiler/-/ngx-translate-messageformat-compiler-4.5.0.tgz", - "integrity": "sha512-FasG50bAcF39d+tRa4QRqxtSY9N2xfPelJHSn/OpZF4uU08ioC+eERo4x9HcOXZDtZH2vrbw9GNv/Xwzg/Irbw==", + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, "requires": { - "tslib": "^1.9.0" - } - }, - "ngx-window-token": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ngx-window-token/-/ngx-window-token-2.0.1.tgz", - "integrity": "sha512-rvqdqJEfnWXQFU5fyfYt06E10tR/UtFOYdF3QebfcOh5VIJhnTKiprX8e4B9OrX7WEVFm9BT8uV72xXcEgsaKA==", + "isobject": "^3.0.1" + } + }, + "object.values": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", + "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", + "dev": true, "requires": { - "tslib": "^1.9.0" + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" } }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "objectpath": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/objectpath/-/objectpath-1.2.2.tgz", + "integrity": "sha512-ie+GY5tJsKt7daHH6qGROf3JqxfD2XhfBPLY+HQrVuRY8MQE1ySKVSqQ/TQz/Dx7jDwuy3etQALDE1cRJAC0cg==" + }, + "obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", "dev": true }, - "node-fetch-npm": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node-fetch-npm/-/node-fetch-npm-2.0.2.tgz", - "integrity": "sha512-nJIxm1QmAj4v3nfCvEeCrYSoVwXyxLnaPBK5W1W5DGEJwjlKuC2VEUycGw5oxk+4zZahRrB84PUJJgEmhFTDFw==", + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", "dev": true, "requires": { - "encoding": "^0.1.11", - "json-parse-better-errors": "^1.0.0", - "safe-buffer": "^5.1.1" + "ee-first": "1.1.1" } }, - "node-forge": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.0.tgz", - "integrity": "sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==", + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", "dev": true }, - "node-libs-browser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", - "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", "dev": true, "requires": { - "assert": "^1.1.1", - "browserify-zlib": "^0.2.0", - "buffer": "^4.3.0", - "console-browserify": "^1.1.0", - "constants-browserify": "^1.0.0", - "crypto-browserify": "^3.11.0", - "domain-browser": "^1.1.1", - "events": "^3.0.0", - "https-browserify": "^1.0.0", - "os-browserify": "^0.3.0", - "path-browserify": "0.0.1", - "process": "^0.11.10", - "punycode": "^1.2.4", - "querystring-es3": "^0.2.0", - "readable-stream": "^2.3.3", - "stream-browserify": "^2.0.1", - "stream-http": "^2.7.2", - "string_decoder": "^1.0.0", - "timers-browserify": "^2.0.4", - "tty-browserify": "0.0.0", - "url": "^0.11.0", - "util": "^0.11.0", - "vm-browserify": "^1.0.1" + "mimic-fn": "^2.1.0" } }, - "node-releases": { - "version": "1.1.48", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.48.tgz", - "integrity": "sha512-Hr8BbmUl1ujAST0K0snItzEA5zkJTQup8VNTKNfT6Zw8vTJkIiagUPNfxHmgDOyfFYNfKAul40sD0UEYTvwebw==", + "open": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/open/-/open-7.0.0.tgz", + "integrity": "sha512-K6EKzYqnwQzk+/dzJAQSBORub3xlBTxMz+ntpZpH/LyCa1o6KjXhuN+2npAaI9jaSmU3R1Q8NWf4KUWcyytGsQ==", "dev": true, "requires": { - "semver": "^6.3.0" + "is-wsl": "^2.1.0" + } + }, + "opn": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", + "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==", + "dev": true, + "requires": { + "is-wsl": "^1.1.0" }, "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", "dev": true } } }, - "nopt": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", - "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, "requires": { - "abbrev": "1", - "osenv": "^0.1.4" + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" } }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "ora": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/ora/-/ora-4.0.2.tgz", + "integrity": "sha512-YUOZbamht5mfLxPmk4M35CD/5DuOkAacxlEUbStVXpBAt4fyhBf+vZHI/HRkI++QUp3sNoeA2Gw4C+hi4eGSig==", "dev": true, "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" + "chalk": "^2.4.2", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.2.0", + "is-interactive": "^1.0.0", + "log-symbols": "^3.0.0", + "strip-ansi": "^5.2.0", + "wcwidth": "^1.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } } }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true + "original": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", + "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", + "dev": true, + "requires": { + "url-parse": "^1.4.3" + } }, - "normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", + "os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", "dev": true }, - "normalize-scroll-left": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/normalize-scroll-left/-/normalize-scroll-left-0.2.0.tgz", - "integrity": "sha512-t5oCENZJl8TGusJKoCJm7+asaSsPuNmK6+iEjrZ5TyBj2f02brCRsd4c83hwtu+e5d4LCSBZ0uoDlMjBo+A8yA==" + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" }, - "normalize-url": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", - "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", "dev": true, "requires": { - "object-assign": "^4.0.1", - "prepend-http": "^1.0.0", - "query-string": "^4.1.0", - "sort-keys": "^1.0.0" + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" } }, - "npm-bundled": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", - "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", - "dev": true, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "requires": { - "npm-normalize-package-bin": "^1.0.1" + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" } }, - "npm-normalize-package-bin": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", - "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", "dev": true }, - "npm-package-arg": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.0.tgz", - "integrity": "sha512-zYbhP2k9DbJhA0Z3HKUePUgdB1x7MfIfKssC+WLPFMKTBZKpZh5m13PgexJjCq6KW7j17r0jHWcCpxEqnnncSA==", + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", + "dev": true + }, + "p-limit": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", + "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", "dev": true, "requires": { - "hosted-git-info": "^2.6.0", - "osenv": "^0.1.5", - "semver": "^5.5.0", - "validate-npm-package-name": "^3.0.0" + "p-try": "^2.0.0" } }, - "npm-packlist": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", - "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1", - "npm-normalize-package-bin": "^1.0.1" + "p-limit": "^2.0.0" } }, - "npm-pick-manifest": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-3.0.2.tgz", - "integrity": "sha512-wNprTNg+X5nf+tDi+hbjdHhM4bX+mKqv6XmPh7B5eG+QY9VARfQPfCEH013H5GqfNj6ee8Ij2fg8yk0mzps1Vw==", + "p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", "dev": true, "requires": { - "figgy-pudding": "^3.5.1", - "npm-package-arg": "^6.0.0", - "semver": "^5.4.1" + "aggregate-error": "^3.0.0" + } + }, + "p-retry": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-3.0.1.tgz", + "integrity": "sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w==", + "dev": true, + "requires": { + "retry": "^0.12.0" } }, - "npm-registry-fetch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-4.0.2.tgz", - "integrity": "sha512-Z0IFtPEozNdeZRPh3aHHxdG+ZRpzcbQaJLthsm3VhNf6DScicTFRHZzK82u8RsJUsUHkX+QH/zcB/5pmd20H4A==", + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "pacote": { + "version": "9.5.8", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-9.5.8.tgz", + "integrity": "sha512-0Tl8Oi/K0Lo4MZmH0/6IsT3gpGf9eEAznLXEQPKgPq7FscnbUOyopnVpwXlnQdIbCUaojWy1Wd7VMyqfVsRrIw==", "dev": true, "requires": { - "JSONStream": "^1.3.4", - "bluebird": "^3.5.1", - "figgy-pudding": "^3.4.1", + "bluebird": "^3.5.3", + "cacache": "^12.0.2", + "chownr": "^1.1.2", + "figgy-pudding": "^3.5.1", + "get-stream": "^4.1.0", + "glob": "^7.1.3", + "infer-owner": "^1.0.4", "lru-cache": "^5.1.1", "make-fetch-happen": "^5.0.0", + "minimatch": "^3.0.4", + "minipass": "^2.3.5", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "normalize-package-data": "^2.4.0", "npm-package-arg": "^6.1.0", - "safe-buffer": "^5.2.0" + "npm-packlist": "^1.1.12", + "npm-pick-manifest": "^3.0.0", + "npm-registry-fetch": "^4.0.0", + "osenv": "^0.1.5", + "promise-inflight": "^1.0.1", + "promise-retry": "^1.1.1", + "protoduck": "^5.0.1", + "rimraf": "^2.6.2", + "safe-buffer": "^5.1.2", + "semver": "^5.6.0", + "ssri": "^6.0.1", + "tar": "^4.4.10", + "unique-filename": "^1.1.1", + "which": "^1.3.1" }, "dependencies": { + "cacache": { + "version": "12.0.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.3.tgz", + "integrity": "sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw==", + "dev": true, + "requires": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, "lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -9987,11 +10224,24 @@ "yallist": "^3.0.2" } }, - "safe-buffer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", - "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", - "dev": true + "minipass": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "ssri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1" + } }, "yallist": { "version": "3.1.1", @@ -10001,634 +10251,735 @@ } } }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, + "parallel-transform": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz", + "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==", "dev": true, "requires": { - "path-key": "^2.0.0" + "cyclist": "^1.0.1", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" } }, - "num2fraction": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", - "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", + "parse-asn1": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz", + "integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==", + "dev": true, + "requires": { + "asn1.js": "^4.0.0", + "browserify-aes": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "optional": true + }, + "parseqs": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", + "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", + "dev": true, + "requires": { + "better-assert": "~1.0.0" + } + }, + "parseuri": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", + "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", + "dev": true, + "requires": { + "better-assert": "~1.0.0" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "dev": true }, - "number-is-nan": { + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", + "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", + "dev": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", "dev": true }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", "dev": true }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true }, - "object-component": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", - "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=", + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", "dev": true }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", "dev": true, "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" + "pify": "^3.0.0" }, "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true } } }, - "object-inspect": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", - "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "pbkdf2": { + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", + "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", + "dev": true, + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "picomatch": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.1.1.tgz", + "integrity": "sha512-OYMyqkKzK7blWO/+XZYP6w8hH0LDvkBvdvKukti+7kqYFCiEAk+gI3DWnryapc0Dau05ugGTy0foQ6mqn4AHYA==", "dev": true }, - "object-is": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.1.tgz", - "integrity": "sha1-CqYOyZiaCz7Xlc9NBvYs8a1lObY=" + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", "dev": true, "requires": { - "isobject": "^3.0.0" + "pinkie": "^2.0.0" } }, - "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", "dev": true, "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" + "find-up": "^3.0.0" } }, - "object.getownpropertydescriptors": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", - "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" - } + "popper.js": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", + "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==" }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "portfinder": { + "version": "1.0.25", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.25.tgz", + "integrity": "sha512-6ElJnHBbxVA1XSLgBp7G1FiCkQdlqGzuF7DswL5tcea+E8UpuvPU7beVAjjRwCioTS9ZluNbu+ZyRvgTsmqEBg==", "dev": true, "requires": { - "isobject": "^3.0.1" + "async": "^2.6.2", + "debug": "^3.1.1", + "mkdirp": "^0.5.1" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } } }, - "objectpath": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/objectpath/-/objectpath-1.2.2.tgz", - "integrity": "sha512-ie+GY5tJsKt7daHH6qGROf3JqxfD2XhfBPLY+HQrVuRY8MQE1ySKVSqQ/TQz/Dx7jDwuy3etQALDE1cRJAC0cg==" - }, - "obuf": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", "dev": true }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "postcss": { + "version": "7.0.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.21.tgz", + "integrity": "sha512-uIFtJElxJo29QC753JzhidoAhvp/e/Exezkdhfmt8AymWT6/5B7W1WmponYWkHk2eg6sONyTch0A3nkMPun3SQ==", "dev": true, "requires": { - "ee-first": "1.1.1" + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, - "on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "dev": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "postcss-calc": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.1.tgz", + "integrity": "sha512-oXqx0m6tb4N3JGdmeMSc/i91KppbYsFZKdH0xMOqK8V1rJlzrKlTdokz8ozUXLVejydRN6u2IddxpcijRj2FqQ==", + "dev": true, "requires": { - "wrappy": "1" + "css-unit-converter": "^1.1.1", + "postcss": "^7.0.5", + "postcss-selector-parser": "^5.0.0-rc.4", + "postcss-value-parser": "^3.3.1" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } } }, - "onetime": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", - "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "postcss-colormin": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-4.0.3.tgz", + "integrity": "sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw==", "dev": true, "requires": { - "mimic-fn": "^2.1.0" + "browserslist": "^4.0.0", + "color": "^3.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } } }, - "open": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/open/-/open-6.4.0.tgz", - "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", + "postcss-convert-values": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz", + "integrity": "sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ==", "dev": true, "requires": { - "is-wsl": "^1.1.0" + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } } }, - "opn": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", - "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==", + "postcss-discard-comments": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz", + "integrity": "sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg==", "dev": true, "requires": { - "is-wsl": "^1.1.0" + "postcss": "^7.0.0" } }, - "optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "postcss-discard-duplicates": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz", + "integrity": "sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ==", "dev": true, "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" + "postcss": "^7.0.0" } }, - "original": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", - "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", + "postcss-discard-empty": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz", + "integrity": "sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w==", "dev": true, "requires": { - "url-parse": "^1.4.3" + "postcss": "^7.0.0" } }, - "os-browserify": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", - "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", - "dev": true - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" - }, - "os-locale": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", - "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "postcss-discard-overridden": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz", + "integrity": "sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg==", "dev": true, "requires": { - "execa": "^1.0.0", - "lcid": "^2.0.0", - "mem": "^4.0.0" + "postcss": "^7.0.0" } }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" - }, - "osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "postcss-import": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-12.0.1.tgz", + "integrity": "sha512-3Gti33dmCjyKBgimqGxL3vcV8w9+bsHwO5UrBawp796+jdardbcFl4RP5w/76BwNL7aGzpKstIfF9I+kdE8pTw==", + "dev": true, "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" + "postcss": "^7.0.1", + "postcss-value-parser": "^3.2.3", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } } }, - "p-defer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", - "dev": true - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true - }, - "p-is-promise": { + "postcss-load-config": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", - "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", - "dev": true - }, - "p-limit": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", - "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.0.tgz", + "integrity": "sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q==", "dev": true, "requires": { - "p-try": "^2.0.0" + "cosmiconfig": "^5.0.0", + "import-cwd": "^2.0.0" } }, - "p-locate": { + "postcss-loader": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-3.0.0.tgz", + "integrity": "sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA==", "dev": true, "requires": { - "p-limit": "^2.0.0" + "loader-utils": "^1.1.0", + "postcss": "^7.0.0", + "postcss-load-config": "^2.0.0", + "schema-utils": "^1.0.0" } }, - "p-map": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", - "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", - "dev": true - }, - "p-retry": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-3.0.1.tgz", - "integrity": "sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w==", + "postcss-merge-longhand": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz", + "integrity": "sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw==", "dev": true, "requires": { - "retry": "^0.12.0" + "css-color-names": "0.0.4", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "stylehacks": "^4.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } } }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "pacote": { - "version": "9.5.5", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-9.5.5.tgz", - "integrity": "sha512-jAEP+Nqj4kyMWyNpfTU/Whx1jA7jEc5cCOlurm0/0oL+v8TAp1QSsK83N7bYe+2bEdFzMAtPG5TBebjzzGV0cA==", + "postcss-merge-rules": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz", + "integrity": "sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ==", "dev": true, "requires": { - "bluebird": "^3.5.3", - "cacache": "^12.0.2", - "figgy-pudding": "^3.5.1", - "get-stream": "^4.1.0", - "glob": "^7.1.3", - "infer-owner": "^1.0.4", - "lru-cache": "^5.1.1", - "make-fetch-happen": "^5.0.0", - "minimatch": "^3.0.4", - "minipass": "^2.3.5", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "normalize-package-data": "^2.4.0", - "npm-package-arg": "^6.1.0", - "npm-packlist": "^1.1.12", - "npm-pick-manifest": "^2.2.3", - "npm-registry-fetch": "^4.0.0", - "osenv": "^0.1.5", - "promise-inflight": "^1.0.1", - "promise-retry": "^1.1.1", - "protoduck": "^5.0.1", - "rimraf": "^2.6.2", - "safe-buffer": "^5.1.2", - "semver": "^5.6.0", - "ssri": "^6.0.1", - "tar": "^4.4.8", - "unique-filename": "^1.1.1", - "which": "^1.3.1" + "browserslist": "^4.0.0", + "caniuse-api": "^3.0.0", + "cssnano-util-same-parent": "^4.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0", + "vendors": "^1.0.0" }, "dependencies": { - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "requires": { - "yallist": "^3.0.2" - } - }, - "npm-pick-manifest": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-2.2.3.tgz", - "integrity": "sha512-+IluBC5K201+gRU85vFlUwX3PFShZAbAgDNp2ewJdWMVSppdo/Zih0ul2Ecky/X7b51J7LrrUAP+XOmOCvYZqA==", + "postcss-selector-parser": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz", + "integrity": "sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=", "dev": true, "requires": { - "figgy-pudding": "^3.5.1", - "npm-package-arg": "^6.0.0", - "semver": "^5.4.1" + "dot-prop": "^4.1.1", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" } - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true } } }, - "pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true - }, - "parallel-transform": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz", - "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==", + "postcss-minify-font-values": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz", + "integrity": "sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg==", "dev": true, "requires": { - "cyclist": "^1.0.1", - "inherits": "^2.0.3", - "readable-stream": "^2.1.5" + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } } }, - "parse-asn1": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz", - "integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==", + "postcss-minify-gradients": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz", + "integrity": "sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q==", "dev": true, "requires": { - "asn1.js": "^4.0.0", - "browserify-aes": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3", - "safe-buffer": "^5.1.1" + "cssnano-util-get-arguments": "^4.0.0", + "is-color-stop": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } } }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "postcss-minify-params": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz", + "integrity": "sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg==", "dev": true, "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" + "alphanum-sort": "^1.0.0", + "browserslist": "^4.0.0", + "cssnano-util-get-arguments": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "uniqs": "^2.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } } }, - "parse5": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", - "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", - "optional": true - }, - "parseqs": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", - "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", + "postcss-minify-selectors": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz", + "integrity": "sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g==", "dev": true, "requires": { - "better-assert": "~1.0.0" + "alphanum-sort": "^1.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0" + }, + "dependencies": { + "postcss-selector-parser": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz", + "integrity": "sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=", + "dev": true, + "requires": { + "dot-prop": "^4.1.1", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } } }, - "parseuri": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", - "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", + "postcss-normalize-charset": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz", + "integrity": "sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g==", "dev": true, "requires": { - "better-assert": "~1.0.0" + "postcss": "^7.0.0" } }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true - }, - "path-browserify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", - "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", - "dev": true - }, - "path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", - "dev": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" - }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", - "dev": true + "postcss-normalize-display-values": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz", + "integrity": "sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ==", + "dev": true, + "requires": { + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } }, - "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "postcss-normalize-positions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz", + "integrity": "sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA==", "dev": true, "requires": { - "pify": "^3.0.0" + "cssnano-util-get-arguments": "^4.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" }, "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", "dev": true } } }, - "pbkdf2": { - "version": "3.0.17", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", - "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", + "postcss-normalize-repeat-style": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz", + "integrity": "sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q==", "dev": true, "requires": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" + "cssnano-util-get-arguments": "^4.0.0", + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } } }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + "postcss-normalize-string": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz", + "integrity": "sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA==", + "dev": true, + "requires": { + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } }, - "picomatch": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.1.1.tgz", - "integrity": "sha512-OYMyqkKzK7blWO/+XZYP6w8hH0LDvkBvdvKukti+7kqYFCiEAk+gI3DWnryapc0Dau05ugGTy0foQ6mqn4AHYA==", - "dev": true + "postcss-normalize-timing-functions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz", + "integrity": "sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A==", + "dev": true, + "requires": { + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } }, - "pify": { + "postcss-normalize-unicode": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz", + "integrity": "sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg==", "dev": true, "requires": { - "pinkie": "^2.0.0" + "browserslist": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } } }, - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "postcss-normalize-url": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz", + "integrity": "sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA==", "dev": true, "requires": { - "find-up": "^3.0.0" + "is-absolute-url": "^2.0.0", + "normalize-url": "^3.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } } }, - "popper.js": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", - "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==" - }, - "portfinder": { - "version": "1.0.25", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.25.tgz", - "integrity": "sha512-6ElJnHBbxVA1XSLgBp7G1FiCkQdlqGzuF7DswL5tcea+E8UpuvPU7beVAjjRwCioTS9ZluNbu+ZyRvgTsmqEBg==", + "postcss-normalize-whitespace": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz", + "integrity": "sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA==", "dev": true, "requires": { - "async": "^2.6.2", - "debug": "^3.1.1", - "mkdirp": "^0.5.1" + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" }, "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", "dev": true } } }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true - }, - "postcss": { - "version": "7.0.17", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", - "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "postcss-ordered-values": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz", + "integrity": "sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw==", "dev": true, "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" + "cssnano-util-get-arguments": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" }, "dependencies": { - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true } } }, - "postcss-import": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-12.0.1.tgz", - "integrity": "sha512-3Gti33dmCjyKBgimqGxL3vcV8w9+bsHwO5UrBawp796+jdardbcFl4RP5w/76BwNL7aGzpKstIfF9I+kdE8pTw==", + "postcss-reduce-initial": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz", + "integrity": "sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA==", "dev": true, "requires": { - "postcss": "^7.0.1", - "postcss-value-parser": "^3.2.3", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" + "browserslist": "^4.0.0", + "caniuse-api": "^3.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0" + } + }, + "postcss-reduce-transforms": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz", + "integrity": "sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg==", + "dev": true, + "requires": { + "cssnano-util-get-match": "^4.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" }, "dependencies": { "postcss-value-parser": { @@ -10639,26 +10990,54 @@ } } }, - "postcss-load-config": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.0.tgz", - "integrity": "sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q==", + "postcss-selector-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", + "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", "dev": true, "requires": { - "cosmiconfig": "^5.0.0", - "import-cwd": "^2.0.0" + "cssesc": "^2.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + }, + "dependencies": { + "cssesc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", + "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==", + "dev": true + } } }, - "postcss-loader": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-3.0.0.tgz", - "integrity": "sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA==", + "postcss-svgo": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.2.tgz", + "integrity": "sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw==", "dev": true, "requires": { - "loader-utils": "^1.1.0", + "is-svg": "^3.0.0", "postcss": "^7.0.0", - "postcss-load-config": "^2.0.0", - "schema-utils": "^1.0.0" + "postcss-value-parser": "^3.0.0", + "svgo": "^1.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, + "postcss-unique-selectors": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz", + "integrity": "sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg==", + "dev": true, + "requires": { + "alphanum-sort": "^1.0.0", + "postcss": "^7.0.0", + "uniqs": "^2.0.0" } }, "postcss-value-parser": { @@ -11565,6 +11944,18 @@ "integrity": "sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug==", "dev": true }, + "rgb-regex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz", + "integrity": "sha1-wODWiC3w4jviVKR16O3UGRX+rrE=", + "dev": true + }, + "rgba-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz", + "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=", + "dev": true + }, "rifm": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/rifm/-/rifm-0.7.0.tgz", @@ -11592,6 +11983,17 @@ "inherits": "^2.0.1" } }, + "rollup": { + "version": "1.25.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.25.2.tgz", + "integrity": "sha512-+7z6Wab/L45QCPcfpuTZKwKiB0tynj05s/+s2U3F2Bi7rOLPr9UcjUwO7/xpjlPNXA/hwnth6jBExFRGyf3tMg==", + "dev": true, + "requires": { + "@types/estree": "*", + "@types/node": "*", + "acorn": "^7.1.0" + } + }, "run-async": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", @@ -11640,25 +12042,43 @@ "dev": true }, "sass": { - "version": "1.22.9", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.22.9.tgz", - "integrity": "sha512-FzU1X2V8DlnqabrL4u7OBwD2vcOzNMongEJEx3xMEhWY/v26FFR3aG0hyeu2T965sfR0E9ufJwmG+Qjz78vFPQ==", + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.23.3.tgz", + "integrity": "sha512-1DKRZxJMOh4Bme16AbWTyYeJAjTlrvw2+fWshHHaepeJfGq2soFZTnt0YhWit+bohtDu4LdyPoEj6VFD4APHog==", "dev": true, "requires": { "chokidar": ">=2.0.0 <4.0.0" } }, "sass-loader": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-7.2.0.tgz", - "integrity": "sha512-h8yUWaWtsbuIiOCgR9fd9c2lRXZ2uG+h8Dzg/AGNj+Hg/3TO8+BBAW9mEP+mh8ei+qBKqSJ0F1FLlYjNBc61OA==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-8.0.0.tgz", + "integrity": "sha512-+qeMu563PN7rPdit2+n5uuYVR0SSVwm0JsOUsaJXzgYcClWSlmX0iHDnmeOobPkf5kUglVot3QS6SyLyaQoJ4w==", "dev": true, "requires": { "clone-deep": "^4.0.1", - "loader-utils": "^1.0.1", - "neo-async": "^2.5.0", - "pify": "^4.0.1", - "semver": "^5.5.0" + "loader-utils": "^1.2.3", + "neo-async": "^2.6.1", + "schema-utils": "^2.1.0", + "semver": "^6.3.0" + }, + "dependencies": { + "schema-utils": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.4.tgz", + "integrity": "sha512-VNjcaUxVnEeun6B2fiiUDjXXBtD4ZSH7pdbfIu1pOFwgptDPLMo/z9jr4sUfsjFVPqDCEin/F7IYlq7/E6yDbQ==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "ajv-keywords": "^3.4.1" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } } }, "saucelabs": { @@ -11671,9 +12091,9 @@ } }, "sax": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/sax/-/sax-0.5.8.tgz", - "integrity": "sha1-1HLbIo6zMcJQaw6MFVJK25OdEsE=", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "dev": true }, "scheduler": { @@ -11968,6 +12388,23 @@ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true }, + "simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "dev": true, + "requires": { + "is-arrayish": "^0.3.1" + }, + "dependencies": { + "is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "dev": true + } + } + }, "slash": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", @@ -12310,12 +12747,12 @@ } }, "source-map-resolve": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", - "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", "dev": true, "requires": { - "atob": "^2.1.1", + "atob": "^2.1.2", "decode-uri-component": "^0.2.0", "resolve-url": "^0.2.1", "source-map-url": "^0.4.0", @@ -12339,9 +12776,9 @@ "dev": true }, "sourcemap-codec": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.6.tgz", - "integrity": "sha512-1ZooVLYFxC448piVLBbtOxFcXwnymH9oUF8nRd3CuYDVvkRBxRl6pB4Mtas5a4drtL+E8LDgFkQNcgIw6tc8Hg==", + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", "dev": true }, "spdx-correct": { @@ -12494,14 +12931,21 @@ } }, "ssri": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-7.1.0.tgz", + "integrity": "sha512-77/WrDZUWocK0mvA5NTRQyveUf+wsrIc6vyrxpS8tVvYBcX215QbafrJR3KtkpskIzoFLqqNuuYQvxaMjXJ/0g==", "dev": true, "requires": { - "figgy-pudding": "^3.5.1" + "figgy-pudding": "^3.5.1", + "minipass": "^3.1.1" } }, + "stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "dev": true + }, "static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", @@ -12712,42 +13156,66 @@ } } }, - "stylus": { - "version": "0.54.5", - "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.54.5.tgz", - "integrity": "sha1-QrlWCTHKcJDOhRWnmLqeaqPW3Hk=", + "stylehacks": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz", + "integrity": "sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g==", "dev": true, "requires": { - "css-parse": "1.7.x", - "debug": "*", - "glob": "7.0.x", - "mkdirp": "0.5.x", - "sax": "0.5.x", - "source-map": "0.1.x" + "browserslist": "^4.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0" }, "dependencies": { - "glob": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz", - "integrity": "sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo=", + "postcss-selector-parser": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz", + "integrity": "sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.2", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "dot-prop": "^4.1.1", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" } - }, - "source-map": { - "version": "0.1.43", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", - "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + } + } + }, + "stylus": { + "version": "0.54.7", + "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.54.7.tgz", + "integrity": "sha512-Yw3WMTzVwevT6ZTrLCYNHAFmanMxdylelL3hkWNgPMeTCpMwpV3nXjpOHuBXtFv7aiO2xRuQS6OoAdgkNcSNug==", + "dev": true, + "requires": { + "css-parse": "~2.0.0", + "debug": "~3.1.0", + "glob": "^7.1.3", + "mkdirp": "~0.5.x", + "safer-buffer": "^2.1.2", + "sax": "~1.2.4", + "semver": "^6.0.0", + "source-map": "^0.7.3" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, "requires": { - "amdefine": ">=0.0.4" + "ms": "2.0.0" } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true } } }, @@ -12771,6 +13239,27 @@ "has-flag": "^3.0.0" } }, + "svgo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", + "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "coa": "^2.0.2", + "css-select": "^2.0.0", + "css-select-base-adapter": "^0.1.1", + "css-tree": "1.0.0-alpha.37", + "csso": "^4.0.2", + "js-yaml": "^3.13.1", + "mkdirp": "~0.5.1", + "object.values": "^1.1.0", + "sax": "~1.2.4", + "stable": "^0.1.8", + "unquote": "~1.1.1", + "util.promisify": "~1.0.0" + } + }, "symbol-observable": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", @@ -12803,6 +13292,25 @@ "yallist": "^3.0.3" }, "dependencies": { + "fs-minipass": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", + "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "dev": true, + "requires": { + "minipass": "^2.6.0" + } + }, + "minipass": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, "yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -12812,9 +13320,9 @@ } }, "terser": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.6.3.tgz", - "integrity": "sha512-Lw+ieAXmY69d09IIc/yqeBqXpEQIpDGZqT34ui1QWXIUpR2RjbqEkT8X7Lgex19hslSqcWM5iMN2kM11eMsESQ==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.5.1.tgz", + "integrity": "sha512-lH9zLIbX8PRBEFCTvfHGCy0s9HEKnNso1Dx9swSopF3VUnFLB8DpQ61tHxoofovNC/sG0spajJM3EIIRSTByiQ==", "dev": true, "requires": { "commander": "^2.20.0", @@ -12823,31 +13331,133 @@ } }, "terser-webpack-plugin": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz", - "integrity": "sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-2.3.3.tgz", + "integrity": "sha512-gWHkaGzGYjmDoYxksFZynWTzvXOAjQ5dd7xuTMYlv4zpWlLSb6v0QLSZjELzP5dMs1ox30O1BIPs9dgqlMHuLQ==", "dev": true, "requires": { - "cacache": "^12.0.2", - "find-cache-dir": "^2.1.0", - "is-wsl": "^1.1.0", - "schema-utils": "^1.0.0", + "cacache": "^13.0.1", + "find-cache-dir": "^3.2.0", + "jest-worker": "^25.1.0", + "p-limit": "^2.2.2", + "schema-utils": "^2.6.4", "serialize-javascript": "^2.1.2", "source-map": "^0.6.1", - "terser": "^4.1.2", - "webpack-sources": "^1.4.0", - "worker-farm": "^1.7.0" + "terser": "^4.4.3", + "webpack-sources": "^1.4.3" }, "dependencies": { "find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.2.0.tgz", + "integrity": "sha512-1JKclkYYsf1q9WIJKLZa9S9muC+08RIjzAlLrK4QcYLJMS6mk9yombQ9qf+zJ7H9LS800k0s44L4sDq9VYzqyg==", "dev": true, "requires": { "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" + "make-dir": "^3.0.0", + "pkg-dir": "^4.1.0" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "jest-worker": { + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-25.1.0.tgz", + "integrity": "sha512-ZHhHtlxOWSxCoNOKHGbiLzXnl42ga9CxDr27H36Qn+15pQZd3R/F24jrmjDelw9j/iHUIWMWs08/u2QN50HHOg==", + "dev": true, + "requires": { + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "make-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.0.tgz", + "integrity": "sha512-grNJDhb8b1Jm1qeqW5R/O63wUo4UXo2v2HMic6YT9i/HBlF93S8jkMgH7yugvY9ABDShH4VZMn8I+U8+fCNegw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, + "p-limit": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", + "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "schema-utils": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.4.tgz", + "integrity": "sha512-VNjcaUxVnEeun6B2fiiUDjXXBtD4ZSH7pdbfIu1pOFwgptDPLMo/z9jr4sUfsjFVPqDCEin/F7IYlq7/E6yDbQ==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "ajv-keywords": "^3.4.1" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" } } } @@ -12883,6 +13493,12 @@ "setimmediate": "^1.0.4" } }, + "timsort": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", + "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", + "dev": true + }, "tiny-emitter": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", @@ -13104,9 +13720,9 @@ "integrity": "sha512-VrR/IiH00Z1tFP4vDGfwZ1esNqTiDMchBEXYY9kilT6wRGgFoCAlgkEUMHb1E3mB0FsfZhv756IF0+R+SFPfdg==" }, "typescript": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.3.tgz", - "integrity": "sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==", + "version": "3.7.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.5.tgz", + "integrity": "sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==", "dev": true }, "uglify-js": { @@ -13166,6 +13782,18 @@ "set-value": "^2.0.1" } }, + "uniq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", + "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", + "dev": true + }, + "uniqs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", + "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=", + "dev": true + }, "unique-filename": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", @@ -13224,6 +13852,12 @@ "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", "dev": true }, + "unquote": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", + "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=", + "dev": true + }, "unset-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", @@ -13360,6 +13994,18 @@ "object.getownpropertydescriptors": "^2.0.3" } }, + "util.promisify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", + "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.2", + "has-symbols": "^1.0.1", + "object.getownpropertydescriptors": "^2.1.0" + } + }, "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -13397,6 +14043,12 @@ "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", "dev": true }, + "vendors": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.4.tgz", + "integrity": "sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w==", + "dev": true + }, "verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", @@ -14161,9 +14813,9 @@ } }, "webpack": { - "version": "4.39.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.39.2.tgz", - "integrity": "sha512-AKgTfz3xPSsEibH00JfZ9sHXGUwIQ6eZ9tLN8+VLzachk1Cw2LVmy+4R7ZiwTa9cZZ15tzySjeMui/UnSCAZhA==", + "version": "4.41.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.41.2.tgz", + "integrity": "sha512-Zhw69edTGfbz9/8JJoyRQ/pq8FYUoY0diOXqW0T6yhgdhCv6wr0hra5DwwWexNRns2Z2+gsnrNcbe9hbGBgk/A==", "dev": true, "requires": { "@webassemblyjs/ast": "1.8.5", @@ -14189,32 +14841,104 @@ "terser-webpack-plugin": "^1.4.1", "watchpack": "^1.6.0", "webpack-sources": "^1.4.1" - } - }, - "webpack-core": { - "version": "0.6.9", - "resolved": "https://registry.npmjs.org/webpack-core/-/webpack-core-0.6.9.tgz", - "integrity": "sha1-/FcViMhVjad76e+23r3Fo7FyvcI=", - "dev": true, - "requires": { - "source-list-map": "~0.1.7", - "source-map": "~0.4.1" }, "dependencies": { - "source-list-map": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-0.1.8.tgz", - "integrity": "sha1-xVCyq1Qn9rPyH1r+rYjE9Vh7IQY=", + "acorn": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.0.tgz", + "integrity": "sha512-gac8OEcQ2Li1dxIEWGZzsp2BitJxwkwcOm0zHAJLcPJaVvm58FRnk6RkuLRpU1EujipU2ZFODv2P9DLMfnV8mw==", "dev": true }, - "source-map": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", - "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "cacache": { + "version": "12.0.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.3.tgz", + "integrity": "sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw==", + "dev": true, + "requires": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "dev": true + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, + "ssri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", "dev": true, "requires": { - "amdefine": ">=0.0.4" + "figgy-pudding": "^3.5.1" } + }, + "terser-webpack-plugin": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz", + "integrity": "sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==", + "dev": true, + "requires": { + "cacache": "^12.0.2", + "find-cache-dir": "^2.1.0", + "is-wsl": "^1.1.0", + "schema-utils": "^1.0.0", + "serialize-javascript": "^2.1.2", + "source-map": "^0.6.1", + "terser": "^4.1.2", + "webpack-sources": "^1.4.0", + "worker-farm": "^1.7.0" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true } } }, @@ -14231,6 +14955,16 @@ "webpack-log": "^2.0.0" }, "dependencies": { + "memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, "mime": { "version": "2.4.4", "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", @@ -14933,6 +15667,12 @@ } } }, + "is-absolute-url": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", + "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==", + "dev": true + }, "is-binary-path": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", @@ -15043,12 +15783,12 @@ } }, "webpack-subresource-integrity": { - "version": "1.1.0-rc.6", - "resolved": "https://registry.npmjs.org/webpack-subresource-integrity/-/webpack-subresource-integrity-1.1.0-rc.6.tgz", - "integrity": "sha512-Az7y8xTniNhaA0620AV1KPwWOqawurVVDzQSpPAeR5RwNbL91GoBSJAAo9cfd+GiFHwsS5bbHepBw1e6Hzxy4w==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/webpack-subresource-integrity/-/webpack-subresource-integrity-1.3.4.tgz", + "integrity": "sha512-6XbGYzjh30cGQT/NsC+9IAkJP8IL7/t47sbwR5DLSsamiD56Rwv4/+hsgEHsviPvrEFZ0JRAQtCRN3UsR2Pw9g==", "dev": true, "requires": { - "webpack-core": "^0.6.8" + "webpack-sources": "^1.3.0" } }, "websocket-driver": { @@ -15251,9 +15991,9 @@ "dev": true }, "zone.js": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.9.1.tgz", - "integrity": "sha512-GkPiJL8jifSrKReKaTZ5jkhrMEgXbXYC+IPo1iquBjayRa0q86w3Dipjn8b415jpitMExe9lV8iTsv8tk3DGag==" + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.10.2.tgz", + "integrity": "sha512-UAYfiuvxLN4oyuqhJwd21Uxb4CNawrq6fPS/05Su5L4G+1TN+HVDJMUHNMobVQDFJRir2cLAODXwluaOKB7HFg==" } } } diff --git a/ui-ngx/package.json b/ui-ngx/package.json index a8f073cfac..f552ad7e03 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -12,17 +12,17 @@ }, "private": true, "dependencies": { - "@angular/animations": "~8.2.14", + "@angular/animations": "~9.0.0", "@angular/cdk": "~8.2.3", - "@angular/common": "~8.2.14", - "@angular/compiler": "~8.2.14", - "@angular/core": "~8.2.14", + "@angular/common": "~9.0.0", + "@angular/compiler": "~9.0.0", + "@angular/core": "~9.0.0", "@angular/flex-layout": "^8.0.0-beta.27", - "@angular/forms": "~8.2.14", + "@angular/forms": "~9.0.0", "@angular/material": "^8.2.3", - "@angular/platform-browser": "~8.2.14", - "@angular/platform-browser-dynamic": "~8.2.14", - "@angular/router": "~8.2.14", + "@angular/platform-browser": "~9.0.0", + "@angular/platform-browser-dynamic": "~9.0.0", + "@angular/router": "~9.0.0", "@auth0/angular-jwt": "^3.0.1", "@date-io/date-fns": "^1.3.13", "@flowjs/flow.js": "^2.14.0", @@ -83,27 +83,27 @@ "tslib": "^1.10.0", "tv4": "^1.3.0", "typeface-roboto": "^0.0.75", - "zone.js": "~0.9.1" + "zone.js": "~0.10.2" }, "devDependencies": { "@angular-builders/custom-webpack": "^8.4.1", - "@angular-devkit/build-angular": "^0.803.25", - "@angular/cli": "^8.3.25", - "@angular/compiler-cli": "~8.2.14", - "@angular/language-service": "~8.2.14", + "@angular-devkit/build-angular": "~0.900.1", + "@angular/cli": "^9.0.1", + "@angular/compiler-cli": "~9.0.0", + "@angular/language-service": "~9.0.0", "@types/flot": "0.0.31", "@types/jasmine": "^3.5.2", "@types/jasminewd2": "~2.0.8", "@types/jquery": "^3.3.31", "@types/js-beautify": "^1.8.1", "@types/jstree": "^3.3.39", - "@types/node": "^12.12.26", + "@types/node": "^12.11.1", "@types/raphael": "^2.1.30", "@types/react": "^16.9.19", "@types/react-dom": "^16.9.5", "@types/tinycolor2": "^1.4.2", "@types/tooltipster": "0.0.29", - "codelyzer": "^5.2.1", + "codelyzer": "^5.1.2", "compression-webpack-plugin": "^3.1.0", "directory-tree": "^2.2.4", "jasmine-core": "~3.5.0", @@ -117,7 +117,7 @@ "protractor": "^5.4.3", "ts-node": "~8.5.4", "tslint": "~5.20.1", - "typescript": "~3.5.3" + "typescript": "~3.7.5" }, "resolutions": { "serialize-javascript": "^2.1.1" diff --git a/ui-ngx/src/app/core/services/window.service.ts b/ui-ngx/src/app/core/services/window.service.ts index 2309b4ec94..ae3d11b5d0 100644 --- a/ui-ngx/src/app/core/services/window.service.ts +++ b/ui-ngx/src/app/core/services/window.service.ts @@ -16,7 +16,7 @@ import { isPlatformBrowser } from '@angular/common'; -import { ClassProvider, FactoryProvider, InjectionToken, PLATFORM_ID } from '@angular/core'; +import { ClassProvider, FactoryProvider, InjectionToken, PLATFORM_ID, Injectable } from '@angular/core'; /* Create a new injection token for injecting the window into a component. */ export const WINDOW = new InjectionToken('WindowToken'); @@ -31,6 +31,7 @@ export abstract class WindowRef { } /* Define class that implements the abstract class and returns the native window object. */ +@Injectable() export class BrowserWindowRef extends WindowRef { constructor() { diff --git a/ui-ngx/src/app/modules/dashboard/dashboard-routing.module.ts b/ui-ngx/src/app/modules/dashboard/dashboard-routing.module.ts index 24228db507..d96cd9d9ba 100644 --- a/ui-ngx/src/app/modules/dashboard/dashboard-routing.module.ts +++ b/ui-ngx/src/app/modules/dashboard/dashboard-routing.module.ts @@ -30,7 +30,7 @@ const routes: Routes = [ }, canActivate: [AuthGuard], canActivateChild: [AuthGuard], - loadChildren: './dashboard-pages.module#DashboardPagesModule' + loadChildren: () => import('./dashboard-pages.module').then(m => m.DashboardPagesModule) } ]; diff --git a/ui-ngx/src/app/modules/home/components/alias/aliases-entity-select.component.ts b/ui-ngx/src/app/modules/home/components/alias/aliases-entity-select.component.ts index 1b4164be34..813240f3bb 100644 --- a/ui-ngx/src/app/modules/home/components/alias/aliases-entity-select.component.ts +++ b/ui-ngx/src/app/modules/home/components/alias/aliases-entity-select.component.ts @@ -46,7 +46,7 @@ export class AliasesEntitySelectComponent implements OnInit, OnDestroy { @Input() disabled: boolean; - @ViewChild('aliasEntitySelectPanelOrigin', {static: false}) aliasEntitySelectPanelOrigin: CdkOverlayOrigin; + @ViewChild('aliasEntitySelectPanelOrigin') aliasEntitySelectPanelOrigin: CdkOverlayOrigin; displayValue: string; diff --git a/ui-ngx/src/app/modules/home/components/alias/entity-alias-select.component.ts b/ui-ngx/src/app/modules/home/components/alias/entity-alias-select.component.ts index ebb3cb301d..cf52018fcb 100644 --- a/ui-ngx/src/app/modules/home/components/alias/entity-alias-select.component.ts +++ b/ui-ngx/src/app/modules/home/components/alias/entity-alias-select.component.ts @@ -70,8 +70,8 @@ export class EntityAliasSelectComponent implements ControlValueAccessor, OnInit, @Input() callbacks: EntityAliasSelectCallbacks; - @ViewChild('entityAliasAutocomplete', {static: false}) entityAliasAutocomplete: MatAutocomplete; - @ViewChild('autocomplete', {static: false, read: MatAutocompleteTrigger}) autoCompleteTrigger: MatAutocompleteTrigger; + @ViewChild('entityAliasAutocomplete') entityAliasAutocomplete: MatAutocomplete; + @ViewChild('autocomplete', { read: MatAutocompleteTrigger }) autoCompleteTrigger: MatAutocompleteTrigger; private requiredValue: boolean; diff --git a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts index bad11b2b09..c9cb0dc225 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts @@ -161,10 +161,10 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI @Input() entityName: string; - @ViewChild('searchInput', {static: false}) searchInputField: ElementRef; + @ViewChild('searchInput') searchInputField: ElementRef; - @ViewChild(MatPaginator, {static: false}) paginator: MatPaginator; - @ViewChild(MatSort, {static: false}) sort: MatSort; + @ViewChild(MatPaginator) paginator: MatPaginator; + @ViewChild(MatSort) sort: MatSort; constructor(protected store: Store, private attributeService: AttributeService, 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 a6b4f2b379..c5c262ec07 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 @@ -94,10 +94,10 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn @ViewChild('entityTableHeader', {static: true}) entityTableHeaderAnchor: TbAnchorComponent; - @ViewChild('searchInput', {static: false}) searchInputField: ElementRef; + @ViewChild('searchInput') searchInputField: ElementRef; - @ViewChild(MatPaginator, {static: false}) paginator: MatPaginator; - @ViewChild(MatSort, {static: false}) sort: MatSort; + @ViewChild(MatPaginator) paginator: MatPaginator; + @ViewChild(MatSort) sort: MatSort; constructor(protected store: Store, private route: ActivatedRoute, diff --git a/ui-ngx/src/app/modules/home/components/entity/entity-table-header.component.ts b/ui-ngx/src/app/modules/home/components/entity/entity-table-header.component.ts index 966bd7d27b..f0f624b38c 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entity-table-header.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/entity-table-header.component.ts @@ -16,11 +16,12 @@ import { BaseData, HasId } from '@shared/models/base-data'; import { PageComponent } from '@shared/components/page.component'; -import { Input, OnInit } from '@angular/core'; +import { Input, OnInit, Directive } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; +@Directive() export abstract class EntityTableHeaderComponent> extends PageComponent implements OnInit { @Input() diff --git a/ui-ngx/src/app/modules/home/components/entity/entity-tabs.component.ts b/ui-ngx/src/app/modules/home/components/entity/entity-tabs.component.ts index bd37d8808a..ef873cf8b2 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entity-tabs.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/entity-tabs.component.ts @@ -16,16 +16,7 @@ import { BaseData, HasId } from '@shared/models/base-data'; import { PageComponent } from '@shared/components/page.component'; -import { - AfterViewInit, - ContentChildren, - EventEmitter, - Input, - OnInit, - Output, - QueryList, - ViewChildren -} from '@angular/core'; +import { AfterViewInit, ContentChildren, EventEmitter, Input, OnInit, Output, QueryList, ViewChildren, Directive } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; @@ -41,6 +32,7 @@ import { DebugEventType, EventType } from '@shared/models/event.models'; import { AttributeScope, LatestTelemetry } from '@shared/models/telemetry/telemetry.models'; import { NULL_UUID } from '@shared/models/id/has-uuid'; +@Directive() export abstract class EntityTabsComponent> extends PageComponent implements OnInit, AfterViewInit { attributeScopes = AttributeScope; diff --git a/ui-ngx/src/app/modules/home/components/entity/entity.component.ts b/ui-ngx/src/app/modules/home/components/entity/entity.component.ts index 0edf4a48d7..78d6eba7d0 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entity.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/entity.component.ts @@ -17,12 +17,13 @@ import { BaseData, HasId } from '@shared/models/base-data'; import { FormGroup, NgForm } from '@angular/forms'; import { PageComponent } from '@shared/components/page.component'; -import { EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'; +import { EventEmitter, Input, OnInit, Output, ViewChild, Directive } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { EntityAction } from '@home/models/entity/entity-component.models'; import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; +@Directive() export abstract class EntityComponent> extends PageComponent implements OnInit { entityValue: T; diff --git a/ui-ngx/src/app/modules/home/components/relation/relation-table.component.ts b/ui-ngx/src/app/modules/home/components/relation/relation-table.component.ts index 26f7deb804..254cac99fe 100644 --- a/ui-ngx/src/app/modules/home/components/relation/relation-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/relation/relation-table.component.ts @@ -91,10 +91,10 @@ export class RelationTableComponent extends PageComponent implements AfterViewIn } } - @ViewChild('searchInput', {static: false}) searchInputField: ElementRef; + @ViewChild('searchInput') searchInputField: ElementRef; - @ViewChild(MatPaginator, {static: false}) paginator: MatPaginator; - @ViewChild(MatSort, {static: false}) sort: MatSort; + @ViewChild(MatPaginator) paginator: MatPaginator; + @ViewChild(MatSort) sort: MatSort; constructor(protected store: Store, private entityRelationService: EntityRelationService, diff --git a/ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.ts b/ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.ts index f92a8c0c7d..40de233426 100644 --- a/ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.ts @@ -82,10 +82,10 @@ export class ManageWidgetActionsComponent extends PageComponent implements OnIni viewsInited = false; dirtyValue = false; - @ViewChild('searchInput', {static: false}) searchInputField: ElementRef; + @ViewChild('searchInput') searchInputField: ElementRef; - @ViewChild(MatPaginator, {static: false}) paginator: MatPaginator; - @ViewChild(MatSort, {static: false}) sort: MatSort; + @ViewChild(MatPaginator) paginator: MatPaginator; + @ViewChild(MatSort) sort: MatSort; private propagateChange = (_: any) => {}; diff --git a/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts b/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts index a3c85401be..53272e088d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts @@ -59,7 +59,7 @@ export interface WidgetActionDialogData { export class WidgetActionDialogComponent extends DialogComponent implements OnInit, ErrorStateMatcher { - @ViewChild('dashboardStateInput', {static: false}) dashboardStateInput: ElementRef; + @ViewChild('dashboardStateInput') dashboardStateInput: ElementRef; widgetActionFormGroup: FormGroup; actionTypeFormGroup: FormGroup; diff --git a/ui-ngx/src/app/modules/home/components/widget/data-key-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/data-key-config.component.ts index 9f85b13ff5..dbacb975e7 100644 --- a/ui-ngx/src/app/modules/home/components/widget/data-key-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/data-key-config.component.ts @@ -71,10 +71,10 @@ export class DataKeyConfigComponent extends PageComponent implements OnInit, Con @Input() dataKeySettingsSchema: any; - @ViewChild('keyInput', {static: false}) keyInput: ElementRef; + @ViewChild('keyInput') keyInput: ElementRef; - @ViewChild('funcBodyEdit', {static: false}) funcBodyEdit: JsFuncComponent; - @ViewChild('postFuncBodyEdit', {static: false}) postFuncBodyEdit: JsFuncComponent; + @ViewChild('funcBodyEdit') funcBodyEdit: JsFuncComponent; + @ViewChild('postFuncBodyEdit') postFuncBodyEdit: JsFuncComponent; displayAdvanced = false; diff --git a/ui-ngx/src/app/modules/home/components/widget/data-keys.component.ts b/ui-ngx/src/app/modules/home/components/widget/data-keys.component.ts index d204a9f232..f2bc966034 100644 --- a/ui-ngx/src/app/modules/home/components/widget/data-keys.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/data-keys.component.ts @@ -124,9 +124,9 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, AfterVie @Input() disabled: boolean; - @ViewChild('keyInput', {static: false}) keyInput: ElementRef; - @ViewChild('keyAutocomplete', {static: false}) matAutocomplete: MatAutocomplete; - @ViewChild('chipList', {static: false}) chipList: MatChipList; + @ViewChild('keyInput') keyInput: ElementRef; + @ViewChild('keyAutocomplete') matAutocomplete: MatAutocomplete; + @ViewChild('chipList') chipList: MatChipList; keys: Array = []; filteredKeys: Observable>; diff --git a/ui-ngx/src/app/modules/home/components/widget/legend-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/legend-config.component.ts index 956bab327d..556297118c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/legend-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/legend-config.component.ts @@ -76,7 +76,7 @@ export class LegendConfigComponent implements OnInit, OnDestroy, ControlValueAcc @Input() disabled: boolean; - @ViewChild('legendConfigPanelOrigin', {static: false}) legendConfigPanelOrigin: CdkOverlayOrigin; + @ViewChild('legendConfigPanelOrigin') legendConfigPanelOrigin: CdkOverlayOrigin; innerValue: LegendConfig; 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 2394a9a056..dfc098a007 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 @@ -116,9 +116,9 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, @Input() ctx: WidgetContext; - @ViewChild('searchInput', {static: false}) searchInputField: ElementRef; - @ViewChild(MatPaginator, {static: false}) paginator: MatPaginator; - @ViewChild(MatSort, {static: false}) sort: MatSort; + @ViewChild('searchInput') searchInputField: ElementRef; + @ViewChild(MatPaginator) paginator: MatPaginator; + @ViewChild(MatSort) sort: MatSort; public enableSelection = true; public displayPagination = true; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.component.ts index a0fc8f6ad3..509ec2c473 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.component.ts @@ -73,7 +73,7 @@ export class EntitiesHierarchyWidgetComponent extends PageComponent implements O @Input() ctx: WidgetContext; - @ViewChild('searchInput', {static: false}) searchInputField: ElementRef; + @ViewChild('searchInput') searchInputField: ElementRef; public toastTargetId = 'entities-hierarchy-' + this.utils.guid(); 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 63e337fc9a..f9d08887df 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 @@ -95,9 +95,9 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni @Input() ctx: WidgetContext; - @ViewChild('searchInput', {static: false}) searchInputField: ElementRef; - @ViewChild(MatPaginator, {static: false}) paginator: MatPaginator; - @ViewChild(MatSort, {static: false}) sort: MatSort; + @ViewChild('searchInput') searchInputField: ElementRef; + @ViewChild(MatPaginator) paginator: MatPaginator; + @ViewChild(MatSort) sort: MatSort; public displayPagination = true; public pageSizeOptions; 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 4c1a4ae336..859d9b7f49 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 @@ -107,7 +107,7 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI @Input() ctx: WidgetContext; - @ViewChild('searchInput', {static: false}) searchInputField: ElementRef; + @ViewChild('searchInput') searchInputField: ElementRef; @ViewChildren(MatPaginator) paginators: QueryList; @ViewChildren(MatSort) sorts: QueryList; diff --git a/ui-ngx/src/app/modules/home/home-routing.module.ts b/ui-ngx/src/app/modules/home/home-routing.module.ts index d2a6332c8c..e27bf3fe08 100644 --- a/ui-ngx/src/app/modules/home/home-routing.module.ts +++ b/ui-ngx/src/app/modules/home/home-routing.module.ts @@ -32,7 +32,7 @@ const routes: Routes = [ }, canActivate: [AuthGuard], canActivateChild: [AuthGuard], - loadChildren: './pages/home-pages.module#HomePagesModule' + loadChildren: () => import('./pages/home-pages.module').then(m => m.HomePagesModule) } ]; diff --git a/ui-ngx/src/app/modules/home/home.component.ts b/ui-ngx/src/app/modules/home/home.component.ts index 2d0632bba5..b25d82f7db 100644 --- a/ui-ngx/src/app/modules/home/home.component.ts +++ b/ui-ngx/src/app/modules/home/home.component.ts @@ -55,10 +55,10 @@ export class HomeComponent extends PageComponent implements AfterViewInit, OnIni logo = require('../../../assets/logo_title_white.svg'); - @ViewChild('sidenav', {static: false}) + @ViewChild('sidenav') sidenav: MatSidenav; - @ViewChild('searchInput', {static: false}) searchInputField: ElementRef; + @ViewChild('searchInput') searchInputField: ElementRef; // @ts-ignore fullscreenEnabled = screenfull.enabled; diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.ts index 0b41ca71d0..54ce1417c5 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.ts @@ -212,7 +212,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC set rightLayoutOpened(rightLayoutOpened: boolean) { } - @ViewChild('tbEditWidget', {static: false}) editWidgetComponent: EditWidgetComponent; + @ViewChild('tbEditWidget') editWidgetComponent: EditWidgetComponent; constructor(protected store: Store, @Inject(WINDOW) private window: Window, diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.ts index b20b47c662..e6fb2baffe 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.ts @@ -74,10 +74,10 @@ export class ManageDashboardStatesDialogComponent extends submitted = false; - @ViewChild('searchInput', {static: false}) searchInputField: ElementRef; + @ViewChild('searchInput') searchInputField: ElementRef; - @ViewChild(MatPaginator, {static: false}) paginator: MatPaginator; - @ViewChild(MatSort, {static: false}) sort: MatSort; + @ViewChild(MatPaginator) paginator: MatPaginator; + @ViewChild(MatSort) sort: MatSort; constructor(protected store: Store, protected router: Router, diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/link-labels.component.ts b/ui-ngx/src/app/modules/home/pages/rulechain/link-labels.component.ts index cb8bd71383..c55e569079 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/link-labels.component.ts +++ b/ui-ngx/src/app/modules/home/pages/rulechain/link-labels.component.ts @@ -38,9 +38,9 @@ import { map, mergeMap, share, startWith } from 'rxjs/operators'; }) export class LinkLabelsComponent implements ControlValueAccessor, OnInit, OnChanges { - @ViewChild('chipList', {static: false}) chipList: MatChipList; - @ViewChild('labelAutocomplete', {static: false}) matAutocomplete: MatAutocomplete; - @ViewChild('labelInput', {static: false}) labelInput: ElementRef; + @ViewChild('chipList') chipList: MatChipList; + @ViewChild('labelAutocomplete') matAutocomplete: MatAutocomplete; + @ViewChild('labelInput') labelInput: ElementRef; private requiredValue: boolean; get required(): boolean { 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 d3d4fba033..aa2b73a458 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 @@ -52,7 +52,7 @@ export class RuleNodeConfigComponent implements ControlValueAccessor, OnInit, On @ViewChild('definedConfigContent', {read: ViewContainerRef, static: true}) definedConfigContainer: ViewContainerRef; - @ViewChild('jsonObjectEditComponent', {static: false}) jsonObjectEditComponent: JsonObjectEditComponent; + @ViewChild('jsonObjectEditComponent') jsonObjectEditComponent: JsonObjectEditComponent; private requiredValue: boolean; get required(): boolean { 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 1182ad925b..bf54edd149 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 @@ -34,7 +34,7 @@ export class RuleNodeDetailsComponent extends PageComponent implements OnInit, O @ViewChild('ruleNodeForm', {static: true}) ruleNodeForm: NgForm; - @ViewChild('ruleNodeConfigComponent', {static: false}) ruleNodeConfigComponent: RuleNodeConfigComponent; + @ViewChild('ruleNodeConfigComponent') ruleNodeConfigComponent: RuleNodeConfigComponent; @Input() ruleNode: FcRuleNode; 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 3f9c2a988f..46e4b75c92 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 @@ -90,7 +90,7 @@ export class RuleChainPageComponent extends PageComponent @HostBinding('style.width') width = '100%'; @HostBinding('style.height') height = '100%'; - @ViewChild('ruleNodeSearchInput', {static: false}) ruleNodeSearchInputField: ElementRef; + @ViewChild('ruleNodeSearchInput') ruleNodeSearchInputField: ElementRef; @ViewChild('ruleChainCanvas', {static: true}) ruleChainCanvas: NgxFlowchartComponent; @@ -124,8 +124,8 @@ export class RuleChainPageComponent extends PageComponent editingRuleNodeAllowCustomLabels = false; editingRuleNodeLinkLabels: {[label: string]: LinkLabel}; - @ViewChild('tbRuleNode', {static: false}) ruleNodeComponent: RuleNodeDetailsComponent; - @ViewChild('tbRuleNodeLink', {static: false}) ruleNodeLinkComponent: RuleNodeLinkComponent; + @ViewChild('tbRuleNode') ruleNodeComponent: RuleNodeDetailsComponent; + @ViewChild('tbRuleNodeLink') ruleNodeLinkComponent: RuleNodeLinkComponent; editingRuleNodeLink: FcRuleEdge = null; isEditingRuleNodeLink = false; diff --git a/ui-ngx/src/app/shared/components/dashboard-select.component.ts b/ui-ngx/src/app/shared/components/dashboard-select.component.ts index 73652174e7..1e27d82e2c 100644 --- a/ui-ngx/src/app/shared/components/dashboard-select.component.ts +++ b/ui-ngx/src/app/shared/components/dashboard-select.component.ts @@ -78,7 +78,7 @@ export class DashboardSelectComponent implements ControlValueAccessor, OnInit { dashboardId: string | null; - @ViewChild('dashboardSelectPanelOrigin', {static: false}) dashboardSelectPanelOrigin: CdkOverlayOrigin; + @ViewChild('dashboardSelectPanelOrigin') dashboardSelectPanelOrigin: CdkOverlayOrigin; private propagateChange = (v: any) => { }; diff --git a/ui-ngx/src/app/shared/components/entity/entity-keys-list.component.ts b/ui-ngx/src/app/shared/components/entity/entity-keys-list.component.ts index de5e0ffcda..909fd08be6 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-keys-list.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-keys-list.component.ts @@ -84,9 +84,9 @@ export class EntityKeysListComponent implements ControlValueAccessor, OnInit, Af @Input() disabled: boolean; - @ViewChild('keyInput', {static: false}) keyInput: ElementRef; - @ViewChild('keyAutocomplete', {static: false}) matAutocomplete: MatAutocomplete; - @ViewChild('chipList', {static: false}) chipList: MatChipList; + @ViewChild('keyInput') keyInput: ElementRef; + @ViewChild('keyAutocomplete') matAutocomplete: MatAutocomplete; + @ViewChild('chipList') chipList: MatChipList; filteredKeys: Observable>; diff --git a/ui-ngx/src/app/shared/components/entity/entity-list.component.ts b/ui-ngx/src/app/shared/components/entity/entity-list.component.ts index af40b416b8..2d0b4437de 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-list.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-list.component.ts @@ -83,8 +83,8 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV @Input() disabled: boolean; - @ViewChild('entityInput', {static: false}) entityInput: ElementRef; - @ViewChild('entityAutocomplete', {static: false}) matAutocomplete: MatAutocomplete; + @ViewChild('entityInput') entityInput: ElementRef; + @ViewChild('entityAutocomplete') matAutocomplete: MatAutocomplete; @ViewChild('chipList', {static: true}) chipList: MatChipList; entities: Array> = []; diff --git a/ui-ngx/src/app/shared/components/entity/entity-subtype-list.component.ts b/ui-ngx/src/app/shared/components/entity/entity-subtype-list.component.ts index 20a9658c7f..77df064e7d 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-subtype-list.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-subtype-list.component.ts @@ -89,8 +89,8 @@ export class EntitySubTypeListComponent implements ControlValueAccessor, OnInit, @Input() entityType: EntityType; - @ViewChild('entitySubtypeInput', {static: false}) entitySubtypeInput: ElementRef; - @ViewChild('entitySubtypeAutocomplete', {static: false}) entitySubtypeAutocomplete: MatAutocomplete; + @ViewChild('entitySubtypeInput') entitySubtypeInput: ElementRef; + @ViewChild('entitySubtypeAutocomplete') entitySubtypeAutocomplete: MatAutocomplete; @ViewChild('chipList', {static: true}) chipList: MatChipList; entitySubtypeList: Array = []; diff --git a/ui-ngx/src/app/shared/components/entity/entity-type-list.component.ts b/ui-ngx/src/app/shared/components/entity/entity-type-list.component.ts index 829f066c02..10a64715e8 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-type-list.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-type-list.component.ts @@ -91,8 +91,8 @@ export class EntityTypeListComponent implements ControlValueAccessor, OnInit, Af @Input() ignoreAuthorityFilter: boolean; - @ViewChild('entityTypeInput', {static: false}) entityTypeInput: ElementRef; - @ViewChild('entityTypeAutocomplete', {static: false}) entityTypeAutocomplete: MatAutocomplete; + @ViewChild('entityTypeInput') entityTypeInput: ElementRef; + @ViewChild('entityTypeAutocomplete') entityTypeAutocomplete: MatAutocomplete; @ViewChild('chipList', {static: true}) chipList: MatChipList; allEntityTypeList: Array = []; diff --git a/ui-ngx/src/app/shared/components/mat-chip-draggable.directive.ts b/ui-ngx/src/app/shared/components/mat-chip-draggable.directive.ts index 32e0dc61ac..568f42fed0 100644 --- a/ui-ngx/src/app/shared/components/mat-chip-draggable.directive.ts +++ b/ui-ngx/src/app/shared/components/mat-chip-draggable.directive.ts @@ -14,18 +14,7 @@ /// limitations under the License. /// -import { - AfterViewInit, - Directive, - ElementRef, - OnDestroy, - Renderer, - Renderer2, - HostListener, - QueryList, - Output, - EventEmitter -} from '@angular/core'; +import { AfterViewInit, Directive, ElementRef, OnDestroy, Renderer, Renderer2, HostListener, QueryList, Output, EventEmitter } from '@angular/core'; import { MatChip, MatChipList } from '@angular/material/chips'; import DragStartEvent = JQuery.DragStartEvent; import Timeout = NodeJS.Timeout; 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 266015fcec..8defe0e114 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow.component.ts @@ -121,7 +121,7 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces @Input() disabled: boolean; - @ViewChild('timewindowPanelOrigin', {static: false}) timewindowPanelOrigin: CdkOverlayOrigin; + @ViewChild('timewindowPanelOrigin') timewindowPanelOrigin: CdkOverlayOrigin; innerValue: Timewindow; diff --git a/ui-ngx/tsconfig.json b/ui-ngx/tsconfig.json index 911c6a31dc..a35b8a413f 100644 --- a/ui-ngx/tsconfig.json +++ b/ui-ngx/tsconfig.json @@ -5,7 +5,7 @@ "outDir": "./dist/out-tsc", "sourceMap": true, "declaration": false, - "module": "es2015", + "module": "esnext", "moduleResolution": "node", "emitDecoratorMetadata": true, "experimentalDecorators": true, From 3dd0ec0399bf2f43355c3ea1d3fbaf1ed5896223 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 10 Feb 2020 13:11:00 +0200 Subject: [PATCH 096/133] Migrate to Angular 9 --- ui-ngx/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/package.json b/ui-ngx/package.json index f552ad7e03..f58929decc 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -17,7 +17,7 @@ "@angular/common": "~9.0.0", "@angular/compiler": "~9.0.0", "@angular/core": "~9.0.0", - "@angular/flex-layout": "^8.0.0-beta.27", + "@angular/flex-layout": "^9.0.0-beta.29", "@angular/forms": "~9.0.0", "@angular/material": "^8.2.3", "@angular/platform-browser": "~9.0.0", From 7f87b2e8fc3145acee167ff58bdb69e0fa1e6d9d Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 10 Feb 2020 13:11:40 +0200 Subject: [PATCH 097/133] Migrate to Angular 9 --- ui-ngx/package-lock.json | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/ui-ngx/package-lock.json b/ui-ngx/package-lock.json index 839fe9a570..41a07a5a00 100644 --- a/ui-ngx/package-lock.json +++ b/ui-ngx/package-lock.json @@ -508,12 +508,9 @@ "integrity": "sha512-6Pxgsrf0qF9iFFqmIcWmjJGkkCaCm6V5QNnxMy2KloO3SDq6QuMVRbN9RtC8Urmo25LP+eZ6ZgYqFYpdD8Hd9w==" }, "@angular/flex-layout": { - "version": "8.0.0-beta.27", - "resolved": "https://registry.npmjs.org/@angular/flex-layout/-/flex-layout-8.0.0-beta.27.tgz", - "integrity": "sha512-qmpvQPesU4ZQ56IscwgmVRpK2UnyV+gwvXUql7TMv0QV215hLcHczjGsrKkLfW2By5E7XEyDat9br72uVXcPMw==", - "requires": { - "tslib": "^1.7.1" - } + "version": "9.0.0-beta.29", + "resolved": "https://registry.npmjs.org/@angular/flex-layout/-/flex-layout-9.0.0-beta.29.tgz", + "integrity": "sha512-93sxR+kYfYMOdnlWL0Q77FZ428gg8XnBu0YZm6GsCdkw/vLggIT/G1ZAqHlCPIODt6pxmCJ5KXh4ShvniIYDsA==" }, "@angular/forms": { "version": "9.0.0", From 96c485006c547f0e3c9d5dce18eb0256b491765a Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 10 Feb 2020 13:15:29 +0200 Subject: [PATCH 098/133] Update angular material --- ui-ngx/package-lock.json | 18 +++--- ui-ngx/package.json | 5 +- ui-ngx/src/app/app.component.ts | 2 +- ui-ngx/src/app/core/core.module.ts | 4 +- .../src/app/core/services/dialog.service.ts | 2 +- .../services/dialog/alert-dialog.component.ts | 2 +- .../dialog/confirm-dialog.component.ts | 2 +- .../services/dialog/todo-dialog.component.ts | 2 +- .../script/node-script-test.service.ts | 2 +- .../alarm/alarm-details-dialog.component.ts | 2 +- .../components/alarm/alarm-table-config.ts | 2 +- .../components/alarm/alarm-table.component.ts | 2 +- .../alias/aliases-entity-select.component.ts | 2 +- .../alias/entity-alias-dialog.component.ts | 3 +- .../alias/entity-aliases-dialog.component.ts | 3 +- .../add-attribute-dialog.component.ts | 3 +- ...dd-widget-to-dashboard-dialog.component.ts | 3 +- .../edit-attribute-value-panel.component.ts | 3 +- .../audit-log-details-dialog.component.ts | 2 +- .../audit-log/audit-log-table-config.ts | 2 +- .../audit-log/audit-log-table.component.ts | 2 +- .../dashboard/dashboard.component.ts | 2 +- .../entity/add-entity-dialog.component.ts | 3 +- .../entity/entities-table.component.ts | 4 +- .../entity/entity-details-panel.component.ts | 2 +- .../entity/entity-tabs.component.ts | 2 +- .../event/event-content-dialog.component.ts | 2 +- .../components/event/event-table.component.ts | 2 +- .../import-dialog-csv.component.ts | 2 +- .../import-export/import-dialog.component.ts | 3 +- .../relation/relation-dialog.component.ts | 3 +- .../action/widget-action-dialog.component.ts | 3 +- .../data-key-config-dialog.component.ts | 3 +- .../components/widget/data-keys.component.ts | 3 +- .../widget/dialog/custom-dialog.service.ts | 2 +- ...d-entities-to-customer-dialog.component.ts | 3 +- .../assign-to-customer-dialog.component.ts | 3 +- ui-ngx/src/app/modules/home/home.component.ts | 2 +- .../asset/assets-table-config.resolver.ts | 2 +- .../dashboard/add-widget-dialog.component.ts | 3 +- .../dashboard-settings-dialog.component.ts | 3 +- .../dashboards-table-config.resolver.ts | 2 +- ...nage-dashboard-layouts-dialog.component.ts | 3 +- .../select-target-layout-dialog.component.ts | 2 +- .../make-dashboard-public-dialog.component.ts | 3 +- ...ge-dashboard-customers-dialog.component.ts | 3 +- .../dashboard-state-dialog.component.ts | 3 +- ...anage-dashboard-states-dialog.component.ts | 3 +- .../select-target-state-dialog.component.ts | 3 +- .../device-credentials-dialog.component.ts | 3 +- .../device/devices-table-config.resolver.ts | 2 +- .../entity-views-table-config.resolver.ts | 2 +- .../change-password-dialog.component.ts | 2 +- .../home/pages/profile/profile.component.ts | 2 +- .../pages/rulechain/link-labels.component.ts | 3 +- .../rulechain/rulechain-page.component.ts | 4 +- .../user/activation-link-dialog.component.ts | 2 +- .../pages/user/add-user-dialog.component.ts | 2 +- .../pages/user/users-table-config.resolver.ts | 2 +- .../save-widget-type-as-dialog.component.ts | 2 +- .../select-widget-type-dialog.component.ts | 2 +- .../components/circular-progress.directive.ts | 3 +- .../dialog/color-picker-dialog.component.ts | 3 +- .../dialog/material-icons-dialog.component.ts | 2 +- .../node-script-test-dialog.component.ts | 3 +- .../entity/entity-keys-list.component.ts | 4 +- .../entity/entity-list.component.ts | 4 +- .../entity/entity-subtype-list.component.ts | 4 +- .../entity/entity-type-list.component.ts | 4 +- .../components/fab-toolbar.component.ts | 2 +- .../app/shared/components/toast.directive.ts | 7 +-- ui-ngx/src/app/shared/shared.module.ts | 58 +++++++++---------- ui-ngx/src/main.ts | 16 ----- 73 files changed, 146 insertions(+), 134 deletions(-) diff --git a/ui-ngx/package-lock.json b/ui-ngx/package-lock.json index 41a07a5a00..b17f22e943 100644 --- a/ui-ngx/package-lock.json +++ b/ui-ngx/package-lock.json @@ -301,12 +301,11 @@ "integrity": "sha512-jB8+SC3vMztW5zt5UYVmtVwqIWE33UyEjbP5JPba3I3bLRK5E059LcJmN1rSdJHItgIAdG9Y1I0WJ6aiSFyp4Q==" }, "@angular/cdk": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-8.2.3.tgz", - "integrity": "sha512-ZwO5Sn720RA2YvBqud0JAHkZXjmjxM0yNzCO8RVtRE9i8Gl26Wk0j0nQeJkVm4zwv2QO8MwbKUKGTMt8evsokA==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-9.0.0.tgz", + "integrity": "sha512-2kYpyYbewIB6fubSIDMvSprJLNplRZoL/AtXW3od4dLyRxtzX+7iWTAtzUG/dhq8CKev0lpd1HENh5lLR/Lhjw==", "requires": { - "parse5": "^5.0.0", - "tslib": "^1.7.1" + "parse5": "^5.0.0" } }, "@angular/cli": { @@ -524,12 +523,9 @@ "dev": true }, "@angular/material": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-8.2.3.tgz", - "integrity": "sha512-SOczkIaqes+r+9XF/UUiokidfFKBpHkOPIaFK857sFD0FBNPvPEpOr5oHKCG3feERRwAFqHS7Wo2ohVEWypb5A==", - "requires": { - "tslib": "^1.7.1" - } + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-9.0.0.tgz", + "integrity": "sha512-QxN2rmR5mvg2YE1NoIGWLpbnmcJq0iFidzy6odzvN17+XkoCJBZ65IdYsHrJgfwGpoIy6bywuixrDHHcSh9I5w==" }, "@angular/platform-browser": { "version": "9.0.0", diff --git a/ui-ngx/package.json b/ui-ngx/package.json index f58929decc..ad4ea9bafc 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -13,13 +13,13 @@ "private": true, "dependencies": { "@angular/animations": "~9.0.0", - "@angular/cdk": "~8.2.3", + "@angular/cdk": "~9.0.0", "@angular/common": "~9.0.0", "@angular/compiler": "~9.0.0", "@angular/core": "~9.0.0", "@angular/flex-layout": "^9.0.0-beta.29", "@angular/forms": "~9.0.0", - "@angular/material": "^8.2.3", + "@angular/material": "^9.0.0", "@angular/platform-browser": "~9.0.0", "@angular/platform-browser-dynamic": "~9.0.0", "@angular/router": "~9.0.0", @@ -48,7 +48,6 @@ "flot": "git://github.com/thingsboard/flot.git#0.9-work", "flot.curvedlines": "git://github.com/MichaelZinsmaier/CurvedLines.git#master", "font-awesome": "^4.7.0", - "hammerjs": "^2.0.8", "javascript-detect-element-resize": "^0.5.3", "jquery": "^3.4.1", "jquery.terminal": "^1.5.0", diff --git a/ui-ngx/src/app/app.component.ts b/ui-ngx/src/app/app.component.ts index 6bd4f2e6d6..86b9ba8d14 100644 --- a/ui-ngx/src/app/app.component.ts +++ b/ui-ngx/src/app/app.component.ts @@ -23,7 +23,7 @@ import { select, Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { LocalStorageService } from '@core/local-storage/local-storage.service'; import { DomSanitizer } from '@angular/platform-browser'; -import { MatIconRegistry } from '@angular/material'; +import { MatIconRegistry } from '@angular/material/icon'; import { combineLatest } from 'rxjs'; import { selectIsAuthenticated, selectIsUserLoaded } from '@core/auth/auth.selectors'; import { distinctUntilChanged, filter, map, skip } from 'rxjs/operators'; diff --git a/ui-ngx/src/app/core/core.module.ts b/ui-ngx/src/app/core/core.module.ts index 6e202cbef0..eb9bc8daf5 100644 --- a/ui-ngx/src/app/core/core.module.ts +++ b/ui-ngx/src/app/core/core.module.ts @@ -32,7 +32,9 @@ import { } from '@ngx-translate/core'; import { TranslateHttpLoader } from '@ngx-translate/http-loader'; import { TbMissingTranslationHandler } from './translate/missing-translate-handler'; -import { MatButtonModule, MatDialogModule, MatSnackBarModule } from '@angular/material'; +import { MatButtonModule } from '@angular/material/button'; +import { MatDialogModule } from '@angular/material/dialog'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; import { ConfirmDialogComponent } from '@core/services/dialog/confirm-dialog.component'; import { FlexLayoutModule } from '@angular/flex-layout'; import { TranslateDefaultCompiler } from '@core/translate/translate-default-compiler'; diff --git a/ui-ngx/src/app/core/services/dialog.service.ts b/ui-ngx/src/app/core/services/dialog.service.ts index 4f627e3a6e..5fc7d8293f 100644 --- a/ui-ngx/src/app/core/services/dialog.service.ts +++ b/ui-ngx/src/app/core/services/dialog.service.ts @@ -16,7 +16,7 @@ import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; -import { MatDialog, MatDialogConfig } from '@angular/material'; +import { MatDialog, MatDialogConfig } from '@angular/material/dialog'; import { ConfirmDialogComponent } from '@core/services/dialog/confirm-dialog.component'; import { TranslateService } from '@ngx-translate/core'; import { AlertDialogComponent } from '@core/services/dialog/alert-dialog.component'; diff --git a/ui-ngx/src/app/core/services/dialog/alert-dialog.component.ts b/ui-ngx/src/app/core/services/dialog/alert-dialog.component.ts index b005609bfd..4e0efcacaa 100644 --- a/ui-ngx/src/app/core/services/dialog/alert-dialog.component.ts +++ b/ui-ngx/src/app/core/services/dialog/alert-dialog.component.ts @@ -15,7 +15,7 @@ /// import { Component, Inject } from '@angular/core'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; export interface AlertDialogData { title: string; diff --git a/ui-ngx/src/app/core/services/dialog/confirm-dialog.component.ts b/ui-ngx/src/app/core/services/dialog/confirm-dialog.component.ts index e5665ed2d1..acc9628095 100644 --- a/ui-ngx/src/app/core/services/dialog/confirm-dialog.component.ts +++ b/ui-ngx/src/app/core/services/dialog/confirm-dialog.component.ts @@ -15,7 +15,7 @@ /// import { Component, Inject } from '@angular/core'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; export interface ConfirmDialogData { title: string; diff --git a/ui-ngx/src/app/core/services/dialog/todo-dialog.component.ts b/ui-ngx/src/app/core/services/dialog/todo-dialog.component.ts index 54e365eb84..5bda2f3961 100644 --- a/ui-ngx/src/app/core/services/dialog/todo-dialog.component.ts +++ b/ui-ngx/src/app/core/services/dialog/todo-dialog.component.ts @@ -15,7 +15,7 @@ /// import {Component} from '@angular/core'; -import {MatDialogRef} from '@angular/material'; +import { MatDialogRef } from '@angular/material/dialog'; @Component({ selector: 'tb-todo-dialog', 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 86213c717f..daaab9fa25 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 @@ -18,7 +18,7 @@ import { Injectable } from '@angular/core'; import { Observable, of } from 'rxjs'; import { RuleChainService } from '@core/http/rule-chain.service'; import { map, switchMap } from 'rxjs/operators'; -import { MatDialog } from '@angular/material'; +import { MatDialog } from '@angular/material/dialog'; import { NodeScriptTestDialogComponent, NodeScriptTestDialogData } from '@shared/components/dialog/node-script-test-dialog.component'; @Injectable({ diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts index 8d8efcd472..41ca351154 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts @@ -15,7 +15,7 @@ /// import { Component, Inject, OnInit } from '@angular/core'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { FormBuilder, FormGroup } from '@angular/forms'; diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts index 3a1ad3762b..3845a986a2 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts @@ -23,7 +23,7 @@ import { EntityType, EntityTypeResource, entityTypeTranslations } from '@shared/ import { TranslateService } from '@ngx-translate/core'; import { DatePipe } from '@angular/common'; import { Direction } from '@shared/models/page/sort-order'; -import { MatDialog } from '@angular/material'; +import { MatDialog } from '@angular/material/dialog'; import { TimePageLink } from '@shared/models/page/page-link'; import { Observable } from 'rxjs'; import { PageData } from '@shared/models/page/page-data'; diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.ts index cbd749631e..fd1becbf7d 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.ts @@ -17,7 +17,7 @@ import { Component, Input, OnInit, ViewChild } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { DatePipe } from '@angular/common'; -import { MatDialog } from '@angular/material'; +import { MatDialog } from '@angular/material/dialog'; import { EntityId } from '@shared/models/id/entity-id'; import { EntitiesTableComponent } from '@home/components/entity/entities-table.component'; import { Store } from '@ngrx/store'; diff --git a/ui-ngx/src/app/modules/home/components/alias/aliases-entity-select.component.ts b/ui-ngx/src/app/modules/home/components/alias/aliases-entity-select.component.ts index 813240f3bb..3cb67d2619 100644 --- a/ui-ngx/src/app/modules/home/components/alias/aliases-entity-select.component.ts +++ b/ui-ngx/src/app/modules/home/components/alias/aliases-entity-select.component.ts @@ -15,7 +15,7 @@ /// import { Component, Inject, Input, OnDestroy, OnInit, ViewChild, ViewContainerRef } from '@angular/core'; -import { TooltipPosition } from '@angular/material'; +import { TooltipPosition } from '@angular/material/tooltip'; import { IAliasController } from '@core/api/widget-api.models'; import { CdkOverlayOrigin, ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; import { TranslateService } from '@ngx-translate/core'; diff --git a/ui-ngx/src/app/modules/home/components/alias/entity-alias-dialog.component.ts b/ui-ngx/src/app/modules/home/components/alias/entity-alias-dialog.component.ts index dd869f16b7..03adbd4ec9 100644 --- a/ui-ngx/src/app/modules/home/components/alias/entity-alias-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/alias/entity-alias-dialog.component.ts @@ -15,7 +15,8 @@ /// import { Component, Inject, OnInit, SkipSelf } from '@angular/core'; -import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +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 { diff --git a/ui-ngx/src/app/modules/home/components/alias/entity-aliases-dialog.component.ts b/ui-ngx/src/app/modules/home/components/alias/entity-aliases-dialog.component.ts index 5041c24dee..ac2a633de2 100644 --- a/ui-ngx/src/app/modules/home/components/alias/entity-aliases-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/alias/entity-aliases-dialog.component.ts @@ -15,7 +15,8 @@ /// import { Component, Inject, OnInit, SkipSelf } from '@angular/core'; -import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +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 { diff --git a/ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.ts b/ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.ts index 7b13618063..88607d55c2 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.ts @@ -15,7 +15,8 @@ /// import { Component, Inject, OnInit, SkipSelf, ViewChild } from '@angular/core'; -import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +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 { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; diff --git a/ui-ngx/src/app/modules/home/components/attribute/add-widget-to-dashboard-dialog.component.ts b/ui-ngx/src/app/modules/home/components/attribute/add-widget-to-dashboard-dialog.component.ts index 519bb48a58..bf96840d5c 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/add-widget-to-dashboard-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/attribute/add-widget-to-dashboard-dialog.component.ts @@ -15,7 +15,8 @@ /// import { Component, Inject, OnInit, SkipSelf } from '@angular/core'; -import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +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 { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; diff --git a/ui-ngx/src/app/modules/home/components/attribute/edit-attribute-value-panel.component.ts b/ui-ngx/src/app/modules/home/components/attribute/edit-attribute-value-panel.component.ts index d2912dda2e..ef3e185479 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/edit-attribute-value-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/attribute/edit-attribute-value-panel.component.ts @@ -15,7 +15,8 @@ /// import { Component, Inject, InjectionToken, OnInit, SkipSelf, ViewChild } from '@angular/core'; -import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +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 { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; diff --git a/ui-ngx/src/app/modules/home/components/audit-log/audit-log-details-dialog.component.ts b/ui-ngx/src/app/modules/home/components/audit-log/audit-log-details-dialog.component.ts index 7ca9bec6cd..ae0b83f666 100644 --- a/ui-ngx/src/app/modules/home/components/audit-log/audit-log-details-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/audit-log/audit-log-details-dialog.component.ts @@ -15,7 +15,7 @@ /// import { Component, ElementRef, Inject, OnInit, Renderer2, ViewChild } from '@angular/core'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { ActionStatus, AuditLog } from '@shared/models/audit-log.models'; diff --git a/ui-ngx/src/app/modules/home/components/audit-log/audit-log-table-config.ts b/ui-ngx/src/app/modules/home/components/audit-log/audit-log-table-config.ts index 8f09a3e12c..39f3c16e72 100644 --- a/ui-ngx/src/app/modules/home/components/audit-log/audit-log-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/audit-log/audit-log-table-config.ts @@ -34,7 +34,7 @@ import { AuditLogService } from '@core/http/audit-log.service'; import { TranslateService } from '@ngx-translate/core'; import { DatePipe } from '@angular/common'; import { Direction } from '@shared/models/page/sort-order'; -import { MatDialog } from '@angular/material'; +import { MatDialog } from '@angular/material/dialog'; import { PageLink, TimePageLink } from '@shared/models/page/page-link'; import { Observable } from 'rxjs'; import { PageData } from '@shared/models/page/page-data'; diff --git a/ui-ngx/src/app/modules/home/components/audit-log/audit-log-table.component.ts b/ui-ngx/src/app/modules/home/components/audit-log/audit-log-table.component.ts index 9913395e00..fd217c7a8f 100644 --- a/ui-ngx/src/app/modules/home/components/audit-log/audit-log-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/audit-log/audit-log-table.component.ts @@ -18,7 +18,7 @@ import { Component, Input, OnInit, ViewChild } from '@angular/core'; import { AuditLogService } from '@core/http/audit-log.service'; import { TranslateService } from '@ngx-translate/core'; import { DatePipe } from '@angular/common'; -import { MatDialog } from '@angular/material'; +import { MatDialog } from '@angular/material/dialog'; import { AuditLogMode } from '@shared/models/audit-log.models'; import { EntityId } from '@shared/models/id/entity-id'; import { UserId } from '@shared/models/id/user-id'; diff --git a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts index 9e16757b66..5f2a7962b3 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts @@ -49,7 +49,7 @@ import { BreakpointObserver } from '@angular/cdk/layout'; import { MediaBreakpoints } from '@shared/models/constants'; import { IAliasController, IStateController } from '@app/core/api/widget-api.models'; import { Widget, WidgetPosition } from '@app/shared/models/widget.models'; -import { MatMenuTrigger } from '@angular/material'; +import { MatMenuTrigger } from '@angular/material/menu'; import { SafeStyle } from '@angular/platform-browser'; @Component({ diff --git a/ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.ts b/ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.ts index 375fd147ae..c4c0aeab0c 100644 --- a/ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.ts @@ -15,7 +15,8 @@ /// import {Component, ComponentFactoryResolver, Inject, OnInit, SkipSelf, ViewChild} from '@angular/core'; -import {ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef} from '@angular/material'; +import { ErrorStateMatcher } from '@angular/material/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import {PageComponent} from '@shared/components/page.component'; import {Store} from '@ngrx/store'; import {AppState} from '@core/core.state'; 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 c5c262ec07..9f56cee910 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 @@ -28,7 +28,9 @@ import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { PageLink, TimePageLink } from '@shared/models/page/page-link'; -import { MatDialog, MatPaginator, MatSort } from '@angular/material'; +import { MatDialog } from '@angular/material/dialog'; +import { MatPaginator } from '@angular/material/paginator'; +import { MatSort } from '@angular/material/sort'; import { EntitiesDataSource } from '@home/models/datasource/entity-datasource'; import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators'; import { Direction, SortOrder } from '@shared/models/page/sort-order'; diff --git a/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts b/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts index b791d0a8f5..c78260e9f2 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts @@ -43,7 +43,7 @@ import { EntityComponent } from './entity.component'; import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; import { EntityAction } from '@home/models/entity/entity-component.models'; import { Subscription } from 'rxjs'; -import { MatTabGroup, MatTab } from '@angular/material'; +import { MatTabGroup, MatTab } from '@angular/material/tabs'; import { EntityTabsComponent } from '@home/components/entity/entity-tabs.component'; @Component({ diff --git a/ui-ngx/src/app/modules/home/components/entity/entity-tabs.component.ts b/ui-ngx/src/app/modules/home/components/entity/entity-tabs.component.ts index ef873cf8b2..fa8551664b 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entity-tabs.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/entity-tabs.component.ts @@ -20,7 +20,7 @@ import { AfterViewInit, ContentChildren, EventEmitter, Input, OnInit, Output, Qu import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; -import { MatTab } from '@angular/material'; +import { MatTab } from '@angular/material/tabs'; import { EntityAction } from '@home/models/entity/entity-component.models'; import { BehaviorSubject } from 'rxjs'; import { Authority } from '@app/shared/models/authority.enum'; diff --git a/ui-ngx/src/app/modules/home/components/event/event-content-dialog.component.ts b/ui-ngx/src/app/modules/home/components/event/event-content-dialog.component.ts index 4a9c58826b..34d4d350d1 100644 --- a/ui-ngx/src/app/modules/home/components/event/event-content-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/event/event-content-dialog.component.ts @@ -15,7 +15,7 @@ /// import { Component, ElementRef, Inject, OnInit, Renderer2, ViewChild } from '@angular/core'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; 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 c881686a6e..9ffed65fb6 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 @@ -18,7 +18,7 @@ import { Component, Input, OnInit, ViewChild } from '@angular/core'; import { AuditLogService } from '@core/http/audit-log.service'; import { TranslateService } from '@ngx-translate/core'; import { DatePipe } from '@angular/common'; -import { MatDialog } from '@angular/material'; +import { MatDialog } from '@angular/material/dialog'; import { AuditLogMode } from '@shared/models/audit-log.models'; import { EntityId } from '@shared/models/id/entity-id'; import { UserId } from '@shared/models/id/user-id'; diff --git a/ui-ngx/src/app/modules/home/components/import-export/import-dialog-csv.component.ts b/ui-ngx/src/app/modules/home/components/import-export/import-dialog-csv.component.ts index b140733e83..8d0638f61c 100644 --- a/ui-ngx/src/app/modules/home/components/import-export/import-dialog-csv.component.ts +++ b/ui-ngx/src/app/modules/home/components/import-export/import-dialog-csv.component.ts @@ -15,7 +15,7 @@ /// import { Component, Inject, OnInit, ViewChild } from '@angular/core'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +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'; diff --git a/ui-ngx/src/app/modules/home/components/import-export/import-dialog.component.ts b/ui-ngx/src/app/modules/home/components/import-export/import-dialog.component.ts index 530ebd109d..ee0a4d0844 100644 --- a/ui-ngx/src/app/modules/home/components/import-export/import-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/import-export/import-dialog.component.ts @@ -15,7 +15,8 @@ /// import { Component, Inject, OnInit, SkipSelf } from '@angular/core'; -import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +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 { diff --git a/ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.ts b/ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.ts index 91d3236b23..91a6356146 100644 --- a/ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.ts @@ -15,7 +15,8 @@ /// import { Component, Inject, OnInit, SkipSelf, ViewChild } from '@angular/core'; -import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +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 { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; diff --git a/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts b/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts index 53272e088d..ad99bc8946 100644 --- a/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts @@ -15,7 +15,8 @@ /// import { Component, ElementRef, Inject, OnInit, SkipSelf, ViewChild } from '@angular/core'; -import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +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 { diff --git a/ui-ngx/src/app/modules/home/components/widget/data-key-config-dialog.component.ts b/ui-ngx/src/app/modules/home/components/widget/data-key-config-dialog.component.ts index 14e2c8a387..00280e6b12 100644 --- a/ui-ngx/src/app/modules/home/components/widget/data-key-config-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/data-key-config-dialog.component.ts @@ -15,7 +15,8 @@ /// import { Component, Inject, OnInit, SkipSelf, ViewChild } from '@angular/core'; -import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +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 { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; diff --git a/ui-ngx/src/app/modules/home/components/widget/data-keys.component.ts b/ui-ngx/src/app/modules/home/components/widget/data-keys.component.ts index f2bc966034..4c6951fe9f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/data-keys.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/data-keys.component.ts @@ -39,7 +39,8 @@ import { filter, map, mergeMap, share, tap } from 'rxjs/operators'; import { Store } from '@ngrx/store'; import { AppState } from '@app/core/core.state'; import { TranslateService } from '@ngx-translate/core'; -import { MatAutocomplete, MatChipInputEvent, MatChipList } from '@angular/material'; +import { MatAutocomplete } from '@angular/material/autocomplete'; +import { MatChipInputEvent, MatChipList } from '@angular/material/chips'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { DataKey, DatasourceType, widgetType } from '@shared/models/widget.models'; diff --git a/ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog.service.ts b/ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog.service.ts index b20213c764..2fbde80090 100644 --- a/ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog.service.ts +++ b/ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog.service.ts @@ -16,7 +16,7 @@ import { Injectable, Injector, NgModule } from '@angular/core'; import { Observable } from 'rxjs'; -import { MatDialog } from '@angular/material'; +import { MatDialog } from '@angular/material/dialog'; import { TranslateService } from '@ngx-translate/core'; import { AuthService } from '@core/auth/auth.service'; import { DynamicComponentFactoryService } from '@core/services/dynamic-component-factory.service'; diff --git a/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.ts b/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.ts index 18f535d2d5..51a71c7859 100644 --- a/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.ts @@ -15,7 +15,8 @@ /// import {Component, Inject, OnInit, SkipSelf} from '@angular/core'; -import {ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef} from '@angular/material'; +import { ErrorStateMatcher } from '@angular/material/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import {PageComponent} from '@shared/components/page.component'; import {Store} from '@ngrx/store'; import {AppState} from '@core/core.state'; diff --git a/ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.ts b/ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.ts index ba376b2190..f57e3c1954 100644 --- a/ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.ts @@ -15,7 +15,8 @@ /// import {Component, Inject, OnInit, SkipSelf} from '@angular/core'; -import {ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef} from '@angular/material'; +import { ErrorStateMatcher } from '@angular/material/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import {PageComponent} from '@shared/components/page.component'; import {Store} from '@ngrx/store'; import {AppState} from '@core/core.state'; diff --git a/ui-ngx/src/app/modules/home/home.component.ts b/ui-ngx/src/app/modules/home/home.component.ts index b25d82f7db..0e89d632d0 100644 --- a/ui-ngx/src/app/modules/home/home.component.ts +++ b/ui-ngx/src/app/modules/home/home.component.ts @@ -31,7 +31,7 @@ import { MediaBreakpoints } from '@shared/models/constants'; import { ActionNotificationShow } from '@core/notification/notification.actions'; import { Router } from '@angular/router'; import * as screenfull from 'screenfull'; -import { MatSidenav } from '@angular/material'; +import { MatSidenav } from '@angular/material/sidenav'; import { AuthState } from '@core/auth/auth.models'; import { WINDOW } from '@core/services/window.service'; import { ISearchableComponent, instanceOfSearchableComponent } from '@home/models/searchable-component.models'; diff --git a/ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts index 23e7d3ef98..3c100f254a 100644 --- a/ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts @@ -40,7 +40,7 @@ import { CustomerService } from '@core/http/customer.service'; import { Customer } from '@app/shared/models/customer.model'; import { NULL_UUID } from '@shared/models/id/has-uuid'; import { BroadcastService } from '@core/services/broadcast.service'; -import { MatDialog } from '@angular/material'; +import { MatDialog } from '@angular/material/dialog'; import { DialogService } from '@core/services/dialog.service'; import { AssignToCustomerDialogComponent, diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/add-widget-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/add-widget-dialog.component.ts index 7614f13a60..0423cef206 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/add-widget-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/add-widget-dialog.component.ts @@ -15,7 +15,8 @@ /// import { Component, Inject, OnInit, SkipSelf } from '@angular/core'; -import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +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 { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm } from '@angular/forms'; diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-settings-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-settings-dialog.component.ts index c6b90373c1..8f0d533721 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-settings-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-settings-dialog.component.ts @@ -15,7 +15,8 @@ /// import { Component, Inject, OnInit, SkipSelf } from '@angular/core'; -import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +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 { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts index a684829ce2..69af000a14 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts @@ -38,7 +38,7 @@ import {AppState} from '@core/core.state'; import {Authority} from '@app/shared/models/authority.enum'; import {CustomerService} from '@core/http/customer.service'; import {Customer} from '@app/shared/models/customer.model'; -import {MatDialog} from '@angular/material'; +import { MatDialog } from '@angular/material/dialog'; import {DialogService} from '@core/services/dialog.service'; import { AddEntitiesToCustomerDialogComponent, diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/layout/manage-dashboard-layouts-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/layout/manage-dashboard-layouts-dialog.component.ts index 15f38c0905..16b965f3dc 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/layout/manage-dashboard-layouts-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/layout/manage-dashboard-layouts-dialog.component.ts @@ -15,7 +15,8 @@ /// import { Component, Inject, OnInit, SkipSelf } from '@angular/core'; -import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +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 { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm } from '@angular/forms'; diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/layout/select-target-layout-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/layout/select-target-layout-dialog.component.ts index 29a4b28729..04d47a7ed0 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/layout/select-target-layout-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/layout/select-target-layout-dialog.component.ts @@ -15,7 +15,7 @@ /// import { Component, OnInit } from '@angular/core'; -import { MatDialogRef } from '@angular/material'; +import { MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { Router } from '@angular/router'; diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/make-dashboard-public-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/make-dashboard-public-dialog.component.ts index 55f258eb42..d39b2da812 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/make-dashboard-public-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/make-dashboard-public-dialog.component.ts @@ -15,7 +15,8 @@ /// import {Component, Inject, OnInit, SkipSelf} from '@angular/core'; -import {ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef} from '@angular/material'; +import { ErrorStateMatcher } from '@angular/material/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import {PageComponent} from '@shared/components/page.component'; import {Store} from '@ngrx/store'; import {AppState} from '@core/core.state'; diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/manage-dashboard-customers-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/manage-dashboard-customers-dialog.component.ts index 4d9f2c074a..c0dbbfb72e 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/manage-dashboard-customers-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/manage-dashboard-customers-dialog.component.ts @@ -15,7 +15,8 @@ /// import {Component, Inject, OnInit, SkipSelf} from '@angular/core'; -import {ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef} from '@angular/material'; +import { ErrorStateMatcher } from '@angular/material/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import {PageComponent} from '@shared/components/page.component'; import {Store} from '@ngrx/store'; import {AppState} from '@core/core.state'; diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/dashboard-state-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/states/dashboard-state-dialog.component.ts index 973b3e40e3..f384ea2602 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/states/dashboard-state-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/dashboard-state-dialog.component.ts @@ -15,7 +15,8 @@ /// import { Component, Inject, OnInit, SkipSelf } from '@angular/core'; -import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +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 { diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.ts index e6fb2baffe..4d8e925988 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.ts @@ -15,7 +15,8 @@ /// import { AfterViewInit, Component, ElementRef, Inject, OnInit, SkipSelf, ViewChild } from '@angular/core'; -import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +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 { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm } from '@angular/forms'; diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/select-target-state-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/states/select-target-state-dialog.component.ts index 0c5b6842ff..b1ac4e78fc 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/states/select-target-state-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/select-target-state-dialog.component.ts @@ -15,7 +15,8 @@ /// import { Component, Inject, OnInit, SkipSelf } from '@angular/core'; -import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +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 { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; diff --git a/ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.ts index f649b88cb8..f1b79fd09d 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.ts @@ -15,7 +15,8 @@ /// import {Component, OnInit, SkipSelf, Inject} from '@angular/core'; -import {ErrorStateMatcher, MatDialogRef, MAT_DIALOG_DATA} from '@angular/material'; +import { ErrorStateMatcher } from '@angular/material/core'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; 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 c60ff5e59f..062153a71f 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 @@ -44,7 +44,7 @@ import { Customer } from '@app/shared/models/customer.model'; import { NULL_UUID } from '@shared/models/id/has-uuid'; import { BroadcastService } from '@core/services/broadcast.service'; import { DeviceTableHeaderComponent } from '@modules/home/pages/device/device-table-header.component'; -import { MatDialog } from '@angular/material'; +import { MatDialog } from '@angular/material/dialog'; import { DeviceCredentialsDialogComponent, DeviceCredentialsDialogData diff --git a/ui-ngx/src/app/modules/home/pages/entity-view/entity-views-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/entity-view/entity-views-table-config.resolver.ts index 94ea10a623..22288d34d3 100644 --- a/ui-ngx/src/app/modules/home/pages/entity-view/entity-views-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/entity-view/entity-views-table-config.resolver.ts @@ -40,7 +40,7 @@ import {CustomerService} from '@core/http/customer.service'; import {Customer} from '@app/shared/models/customer.model'; import {NULL_UUID} from '@shared/models/id/has-uuid'; import {BroadcastService} from '@core/services/broadcast.service'; -import {MatDialog} from '@angular/material'; +import { MatDialog } from '@angular/material/dialog'; import {DialogService} from '@core/services/dialog.service'; import { AssignToCustomerDialogComponent, diff --git a/ui-ngx/src/app/modules/home/pages/profile/change-password-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/profile/change-password-dialog.component.ts index 01f01d1249..65a3532a0f 100644 --- a/ui-ngx/src/app/modules/home/pages/profile/change-password-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/profile/change-password-dialog.component.ts @@ -15,7 +15,7 @@ /// import { Component, OnInit } from '@angular/core'; -import { MatDialogRef } from '@angular/material'; +import { MatDialogRef } from '@angular/material/dialog'; import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; diff --git a/ui-ngx/src/app/modules/home/pages/profile/profile.component.ts b/ui-ngx/src/app/modules/home/pages/profile/profile.component.ts index 7bb273ae0b..9a36d61f11 100644 --- a/ui-ngx/src/app/modules/home/pages/profile/profile.component.ts +++ b/ui-ngx/src/app/modules/home/pages/profile/profile.component.ts @@ -28,7 +28,7 @@ import {environment as env} from '@env/environment'; import {TranslateService} from '@ngx-translate/core'; import {ActionSettingsChangeLanguage} from '@core/settings/settings.actions'; import {ChangePasswordDialogComponent} from '@modules/home/pages/profile/change-password-dialog.component'; -import {MatDialog} from '@angular/material'; +import { MatDialog } from '@angular/material/dialog'; import {DialogService} from '@core/services/dialog.service'; import {AuthService} from '@core/auth/auth.service'; import {ActivatedRoute} from '@angular/router'; diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/link-labels.component.ts b/ui-ngx/src/app/modules/home/pages/rulechain/link-labels.component.ts index c55e569079..ca895e3a96 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/link-labels.component.ts +++ b/ui-ngx/src/app/modules/home/pages/rulechain/link-labels.component.ts @@ -21,7 +21,8 @@ import { Observable, of } from 'rxjs'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { deepClone } from '@core/utils'; import { TruncatePipe } from '@shared/pipe/truncate.pipe'; -import { MatAutocomplete, MatAutocompleteSelectedEvent, MatChipInputEvent, MatChipList } from '@angular/material'; +import { MatAutocomplete, MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; +import { MatChipInputEvent, MatChipList } from '@angular/material/chips'; import { TranslateService } from '@ngx-translate/core'; import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes'; import { map, mergeMap, share, startWith } from 'rxjs/operators'; 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 46e4b75c92..31a12c84e6 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 @@ -33,7 +33,9 @@ import { AppState } from '@core/core.state'; import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; import { HasDirtyFlag } from '@core/guards/confirm-on-exit.guard'; import { TranslateService } from '@ngx-translate/core'; -import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialog, MatDialogRef, MatExpansionPanel } from '@angular/material'; +import { ErrorStateMatcher } from '@angular/material/core'; +import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog'; +import { MatExpansionPanel } from '@angular/material/expansion'; import { DialogService } from '@core/services/dialog.service'; import { AuthService } from '@core/auth/auth.service'; import { ActivatedRoute, Router } from '@angular/router'; diff --git a/ui-ngx/src/app/modules/home/pages/user/activation-link-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/user/activation-link-dialog.component.ts index 969a427895..bc17f6e701 100644 --- a/ui-ngx/src/app/modules/home/pages/user/activation-link-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/user/activation-link-dialog.component.ts @@ -15,7 +15,7 @@ /// import { Component, Inject, OnInit } from '@angular/core'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; diff --git a/ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.ts index fbbd7cf6aa..e0a4631a68 100644 --- a/ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.ts @@ -15,7 +15,7 @@ /// import { Component, Inject, OnInit, ViewChild } from '@angular/core'; -import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material'; +import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog'; import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; diff --git a/ui-ngx/src/app/modules/home/pages/user/users-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/user/users-table-config.resolver.ts index 1361eb2776..d2c2592773 100644 --- a/ui-ngx/src/app/modules/home/pages/user/users-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/user/users-table-config.resolver.ts @@ -37,7 +37,7 @@ import { map, mergeMap, take, tap } from 'rxjs/operators'; import { forkJoin, noop, Observable, of } from 'rxjs'; import { Authority } from '@shared/models/authority.enum'; import { CustomerId } from '@shared/models/id/customer-id'; -import { MatDialog } from '@angular/material'; +import { MatDialog } from '@angular/material/dialog'; import { EntityAction } from '@home/models/entity/entity-component.models'; import { AddUserDialogComponent, diff --git a/ui-ngx/src/app/modules/home/pages/widget/save-widget-type-as-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/widget/save-widget-type-as-dialog.component.ts index e7a874e7cc..fef5db8a49 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/save-widget-type-as-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/widget/save-widget-type-as-dialog.component.ts @@ -15,7 +15,7 @@ /// import { Component, OnInit } from '@angular/core'; -import { MatDialogRef } from '@angular/material'; +import { MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; diff --git a/ui-ngx/src/app/modules/home/pages/widget/select-widget-type-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/widget/select-widget-type-dialog.component.ts index 6e854cb96a..5a235ce262 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/select-widget-type-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/widget/select-widget-type-dialog.component.ts @@ -15,7 +15,7 @@ /// import { Component } from '@angular/core'; -import { MatDialogRef } from '@angular/material'; +import { MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { DialogComponent } from '@shared/components/dialog.component'; diff --git a/ui-ngx/src/app/shared/components/circular-progress.directive.ts b/ui-ngx/src/app/shared/components/circular-progress.directive.ts index 38166e629f..97a38f114d 100644 --- a/ui-ngx/src/app/shared/components/circular-progress.directive.ts +++ b/ui-ngx/src/app/shared/components/circular-progress.directive.ts @@ -16,7 +16,8 @@ import { Directive, ElementRef, ViewContainerRef, ComponentFactoryResolver, ComponentRef, Input } from '@angular/core'; import { Overlay } from '@angular/cdk/overlay'; -import { MatProgressBar, MatSpinner } from '@angular/material'; +import { MatProgressBar } from '@angular/material/progress-bar'; +import { MatSpinner } from '@angular/material/progress-spinner'; @Directive({ selector: '[tb-circular-progress]' 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 b66537aa41..23b89e8536 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 @@ -15,7 +15,8 @@ /// import { Component, Inject, OnInit, SkipSelf, ViewChild } from '@angular/core'; -import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +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 { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; 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 643fc11d4b..8b755df513 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 @@ -15,7 +15,7 @@ /// import { Component, Inject, OnInit, QueryList, ViewChildren, TemplateRef, AfterViewInit } from '@angular/core'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +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'; diff --git a/ui-ngx/src/app/shared/components/dialog/node-script-test-dialog.component.ts b/ui-ngx/src/app/shared/components/dialog/node-script-test-dialog.component.ts index 4a3c31bd81..5359bf4d19 100644 --- a/ui-ngx/src/app/shared/components/dialog/node-script-test-dialog.component.ts +++ b/ui-ngx/src/app/shared/components/dialog/node-script-test-dialog.component.ts @@ -25,7 +25,8 @@ import { ViewChildren, ViewEncapsulation } from '@angular/core'; -import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +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 { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; diff --git a/ui-ngx/src/app/shared/components/entity/entity-keys-list.component.ts b/ui-ngx/src/app/shared/components/entity/entity-keys-list.component.ts index 909fd08be6..a1a88f49a7 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-keys-list.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-keys-list.component.ts @@ -33,7 +33,9 @@ import {AliasEntityType, EntityType} from '@shared/models/entity-type.models'; import {BaseData} from '@shared/models/base-data'; import {EntityId} from '@shared/models/id/entity-id'; import {EntityService} from '@core/http/entity.service'; -import {ErrorStateMatcher, MatAutocomplete, MatAutocompleteSelectedEvent, MatChipList, MatChipInputEvent} from '@angular/material'; +import { MatAutocomplete, MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; +import { MatChipList, MatChipInputEvent } from '@angular/material/chips'; +import { ErrorStateMatcher } from '@angular/material/core'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import {DataKeyType} from '@shared/models/telemetry/telemetry.models'; import * as equal from 'deep-equal'; diff --git a/ui-ngx/src/app/shared/components/entity/entity-list.component.ts b/ui-ngx/src/app/shared/components/entity/entity-list.component.ts index 2d0b4437de..72cf8412aa 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-list.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-list.component.ts @@ -42,7 +42,9 @@ import {AliasEntityType, EntityType} from '@shared/models/entity-type.models'; import {BaseData} from '@shared/models/base-data'; import {EntityId} from '@shared/models/id/entity-id'; import {EntityService} from '@core/http/entity.service'; -import {ErrorStateMatcher, MatAutocomplete, MatAutocompleteSelectedEvent, MatChipList} from '@angular/material'; +import { MatAutocomplete, MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; +import { MatChipList } from '@angular/material/chips'; +import { ErrorStateMatcher } from '@angular/material/core'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { emptyPageData } from '@shared/models/page/page-data'; diff --git a/ui-ngx/src/app/shared/components/entity/entity-subtype-list.component.ts b/ui-ngx/src/app/shared/components/entity/entity-subtype-list.component.ts index 77df064e7d..ae27c245b1 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-subtype-list.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-subtype-list.component.ts @@ -42,7 +42,9 @@ import { AliasEntityType, EntitySubtype, EntityType, entityTypeTranslations } fr import {BaseData} from '@shared/models/base-data'; import {EntityId} from '@shared/models/id/entity-id'; import {EntityService} from '@core/http/entity.service'; -import {ErrorStateMatcher, MatAutocomplete, MatAutocompleteSelectedEvent, MatChipList} from '@angular/material'; +import { MatAutocomplete, MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; +import { MatChipList } from '@angular/material/chips'; +import { ErrorStateMatcher } from '@angular/material/core'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { emptyPageData } from '@shared/models/page/page-data'; import { AssetService } from '@core/http/asset.service'; diff --git a/ui-ngx/src/app/shared/components/entity/entity-type-list.component.ts b/ui-ngx/src/app/shared/components/entity/entity-type-list.component.ts index 10a64715e8..2c107e4898 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-type-list.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-type-list.component.ts @@ -42,7 +42,9 @@ import { AliasEntityType, EntityType, entityTypeTranslations } from '@shared/mod import {BaseData} from '@shared/models/base-data'; import {EntityId} from '@shared/models/id/entity-id'; import {EntityService} from '@core/http/entity.service'; -import {ErrorStateMatcher, MatAutocomplete, MatAutocompleteSelectedEvent, MatChipList} from '@angular/material'; +import { MatAutocomplete, MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; +import { MatChipList } from '@angular/material/chips'; +import { ErrorStateMatcher } from '@angular/material/core'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { emptyPageData } from '@shared/models/page/page-data'; diff --git a/ui-ngx/src/app/shared/components/fab-toolbar.component.ts b/ui-ngx/src/app/shared/components/fab-toolbar.component.ts index f6b813c3da..535769d8f0 100644 --- a/ui-ngx/src/app/shared/components/fab-toolbar.component.ts +++ b/ui-ngx/src/app/shared/components/fab-toolbar.component.ts @@ -27,7 +27,7 @@ import { } from '@angular/core'; import { PageComponent } from '@shared/components/page.component'; import { WINDOW } from '@core/services/window.service'; -import { mixinColor, CanColorCtor } from '@angular/material'; +import { mixinColor, CanColorCtor } from '@angular/material/core'; export declare type FabToolbarDirection = 'left' | 'right'; diff --git a/ui-ngx/src/app/shared/components/toast.directive.ts b/ui-ngx/src/app/shared/components/toast.directive.ts index 3d62912b17..50fda9ee3d 100644 --- a/ui-ngx/src/app/shared/components/toast.directive.ts +++ b/ui-ngx/src/app/shared/components/toast.directive.ts @@ -23,12 +23,7 @@ import { OnDestroy, ViewChild, ViewContainerRef } from '@angular/core'; -import { - MAT_SNACK_BAR_DATA, - MatSnackBar, - MatSnackBarConfig, - MatSnackBarRef -} from '@angular/material'; +import { MAT_SNACK_BAR_DATA, MatSnackBar, MatSnackBarConfig, MatSnackBarRef } from '@angular/material/snack-bar'; import { NotificationMessage } from '@app/core/notification/notification.models'; import { onParentScrollOrWindowResize } from '@app/core/utils'; import { Subscription } from 'rxjs'; diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index 84ee7e7631..c2f1d7fe96 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -24,36 +24,34 @@ import { NgxFlowModule, FlowInjectionToken } from '@flowjs/ngx-flow'; import { NgxFlowchartModule } from 'ngx-flowchart/dist/ngx-flowchart'; import Flow from '@flowjs/flow.js'; -import { - MatAutocompleteModule, - MatButtonModule, - MatCardModule, - MatCheckboxModule, - MatChipsModule, - MatDatepickerModule, - MatDialogModule, - MatDividerModule, - MatExpansionModule, - MatGridListModule, - MatIconModule, - MatInputModule, - MatMenuModule, - MatPaginatorModule, - MatProgressBarModule, - MatProgressSpinnerModule, - MatRadioModule, - MatSelectModule, - MatSidenavModule, - MatSliderModule, - MatSlideToggleModule, - MatSnackBarModule, - MatSortModule, - MatStepperModule, - MatTableModule, - MatTabsModule, - MatToolbarModule, - MatTooltipModule -} from '@angular/material'; +import { MatAutocompleteModule } from '@angular/material/autocomplete'; +import { MatButtonModule } from '@angular/material/button'; +import { MatCardModule } from '@angular/material/card'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatChipsModule } from '@angular/material/chips'; +import { MatDatepickerModule } from '@angular/material/datepicker'; +import { MatDialogModule } from '@angular/material/dialog'; +import { MatDividerModule } from '@angular/material/divider'; +import { MatExpansionModule } from '@angular/material/expansion'; +import { MatGridListModule } from '@angular/material/grid-list'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatPaginatorModule } from '@angular/material/paginator'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { MatRadioModule } from '@angular/material/radio'; +import { MatSelectModule } from '@angular/material/select'; +import { MatSidenavModule } from '@angular/material/sidenav'; +import { MatSlideToggleModule } from '@angular/material/slide-toggle'; +import { MatSliderModule } from '@angular/material/slider'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { MatSortModule } from '@angular/material/sort'; +import { MatStepperModule } from '@angular/material/stepper'; +import { MatTableModule } from '@angular/material/table'; +import { MatTabsModule } from '@angular/material/tabs'; +import { MatToolbarModule } from '@angular/material/toolbar'; +import { MatTooltipModule } from '@angular/material/tooltip'; import { MatDatetimepickerModule, MatNativeDatetimeModule } from '@mat-datetimepicker/core'; import { GridsterModule } from 'angular-gridster2'; import { FlexLayoutModule } from '@angular/flex-layout'; diff --git a/ui-ngx/src/main.ts b/ui-ngx/src/main.ts index efc77ee56d..22da21b395 100644 --- a/ui-ngx/src/main.ts +++ b/ui-ngx/src/main.ts @@ -1,20 +1,4 @@ -/// -/// Copyright © 2016-2019 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 'hammerjs'; import { enableProdMode } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; From 8bbee948fbcfdf76df1599eb0a842ae48aaa4ceb Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 10 Feb 2020 19:06:15 +0200 Subject: [PATCH 099/133] Angular 9 migration --- ui-ngx/angular.json | 3 +- ui-ngx/package-lock.json | 413 +++++++++++------- ui-ngx/package.json | 39 +- ui-ngx/src/app/core/core.module.ts | 5 - .../notification/notification.selectors.ts | 29 -- .../dynamic-component-factory.service.ts | 8 +- .../translate/translate-default-compiler.ts | 3 +- .../dashboard/dashboard-pages.module.ts | 1 - .../home/components/home-components.module.ts | 20 - .../import-export/import-export.service.ts | 2 +- .../table-columns-assignment.component.ts | 2 +- .../shared-home-components.module.ts | 3 - .../widget/action/custom-sample-js.raw | 2 +- .../manage-widget-actions.component.models.ts | 2 +- .../custom-dialog-container.component.ts | 14 +- .../widget/dialog/custom-dialog.component.ts | 2 +- .../widget/dialog/custom-dialog.service.ts | 2 - .../widget/legend-config.component.ts | 35 +- .../lib/alarms-table-widget.component.ts | 2 +- .../lib/entities-table-widget.component.ts | 2 +- .../lib/timeseries-table-widget.component.ts | 2 +- .../widget/widget-components.module.ts | 8 +- .../components/widget/widget.component.ts | 1 + .../home/dialogs/home-dialogs.module.ts | 4 - .../models/datasource/attribute-datasource.ts | 2 +- .../models/datasource/entity-datasource.ts | 6 +- .../models/datasource/relation-datasource.ts | 3 +- .../modules/home/pages/asset/asset.module.ts | 5 - .../home/pages/customer/customer.module.ts | 4 - .../home/pages/dashboard/dashboard.module.ts | 13 - ...ashboard-states-dialog.component.models.ts | 3 +- .../states/states-controller.module.ts | 4 - .../home/pages/device/device.module.ts | 6 - .../pages/entity-view/entity-view.module.ts | 5 - .../home/pages/profile/profile.module.ts | 3 - .../rulechain/rulechain-routing.module.ts | 2 +- .../home/pages/rulechain/rulechain.module.ts | 7 - .../home/pages/tenant/tenant.module.ts | 4 - .../modules/home/pages/user/user.module.ts | 6 - .../pages/widget/widget-library.module.ts | 5 - .../json-form/react/json-form-rc-select.tsx | 16 +- .../mat-chip-draggable.directive.ts | 3 +- .../shared/components/nav-tree.component.scss | 27 +- .../components/time/timewindow.component.ts | 2 +- ui-ngx/src/app/shared/shared.module.ts | 10 - ui-ngx/src/main.ts | 16 + ui-ngx/src/polyfills.ts | 2 + ui-ngx/src/styles.scss | 2 + ui-ngx/src/theme.scss | 2 + ui-ngx/src/tsconfig.app.json | 3 + 50 files changed, 343 insertions(+), 422 deletions(-) delete mode 100644 ui-ngx/src/app/core/notification/notification.selectors.ts diff --git a/ui-ngx/angular.json b/ui-ngx/angular.json index e1106a7c7e..2031867edc 100644 --- a/ui-ngx/angular.json +++ b/ui-ngx/angular.json @@ -36,7 +36,7 @@ "node_modules/tooltipster/dist/css/tooltipster.bundle.min.css", "node_modules/tooltipster/dist/css/plugins/tooltipster/sideTip/themes/tooltipster-sideTip-shadow.min.css", "src/app/shared/components/json-form/react/json-form.scss", - "node_modules/rc-select/assets/index.css", + "node_modules/rc-select/assets/index.less", "node_modules/jstree-bootstrap-theme/dist/themes/proton/style.min.css" ], "stylePreprocessorOptions": { @@ -101,7 +101,6 @@ "sourceMap": false, "extractCss": true, "namedChunks": false, - "aot": false, "extractLicenses": true, "vendorChunk": false, "buildOptimizer": false, diff --git a/ui-ngx/package-lock.json b/ui-ngx/package-lock.json index b17f22e943..4370c4fa7e 100644 --- a/ui-ngx/package-lock.json +++ b/ui-ngx/package-lock.json @@ -5,11 +5,14 @@ "requires": true, "dependencies": { "@angular-builders/custom-webpack": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/@angular-builders/custom-webpack/-/custom-webpack-8.4.1.tgz", - "integrity": "sha512-FbBt4mFbAxETdYLb6tTX869pIpm8nMiCpT34jROejuqLtsljymdqXhSCEWogWlel8ULAYus6BNdzZyRLyAkfqQ==", + "version": "9.0.0-beta.7", + "resolved": "https://registry.npmjs.org/@angular-builders/custom-webpack/-/custom-webpack-9.0.0-beta.7.tgz", + "integrity": "sha512-4TkeHIJIczuikLWpVu9MJ449zSCge6qC4sWV9vWwisD/Wr6YmkF4iNSeq7vmBkxHDbLsiZm5mZsbR0ItHZO/OA==", "dev": true, "requires": { + "@angular-devkit/architect": "^0.900.0-rc.2", + "@angular-devkit/build-angular": "^0.900.0-rc.2", + "@angular-devkit/core": "^9.0.0-rc.2", "lodash": "^4.17.10", "ts-node": "^8.5.2", "webpack-merge": "^4.2.1" @@ -543,9 +546,9 @@ "integrity": "sha512-yyOcStpgN5t8wGRNO85mo0jplXkntP+v2tmSxNx45pahqmofSFm+QCEFa2zHQuMr7NoiGERhd0Tae7NDCCjtjA==" }, "@auth0/angular-jwt": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@auth0/angular-jwt/-/angular-jwt-3.0.1.tgz", - "integrity": "sha512-hfWfgbpgtcvyU/agNxQ6cBk81mmASiNxQeZ6xn/3zJo8uLFHk2eQIy2yt2ztktcOQ6V2uc6GlKLRKjVIgyc1Sw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@auth0/angular-jwt/-/angular-jwt-4.0.0.tgz", + "integrity": "sha512-CHvk1zJ9jpQupl0f5y7EmTvYAwugyFvC4ztLsZKr7ZC7anNVaDd1+pDFJYS+ZEU9jLWzE74+AfVKfigImADJuw==", "requires": { "url": "^0.11.0" } @@ -2774,11 +2777,18 @@ "integrity": "sha512-AlEKV7TxjeK+jxWVKcCFrfYAk8spX9aCyiToFIiLPtfQbsjmRGLIhb5VZgptQcJdHtLXo7+m0DuurwFgUToQuA==" }, "@date-io/date-fns": { - "version": "1.3.13", - "resolved": "https://registry.npmjs.org/@date-io/date-fns/-/date-fns-1.3.13.tgz", - "integrity": "sha512-yXxGzcRUPcogiMj58wVgFjc9qUYrCnnU9eLcyNbsQCmae4jPuZCDoIBR21j8ZURsM7GRtU62VOw5yNd4dDHunA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@date-io/date-fns/-/date-fns-2.3.0.tgz", + "integrity": "sha512-WlDNt3xPjhK/7pcnDU0En9qk6dF0yu2ve7qiDT466YHHxm++C3jsaJgODZ+avLdnhwxNJPnp8LwABsh2vtYcLQ==", "requires": { - "@date-io/core": "^1.3.13" + "@date-io/core": "^2.3.0" + }, + "dependencies": { + "@date-io/core": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@date-io/core/-/core-2.3.0.tgz", + "integrity": "sha512-aOmiF9tDtRCGuzGfBILKsd+zigEnGMBqXWNlr4ZDA6Y8r4JxTjIpvlCZrmtDP/0x2T+og28uhwBjJbCGFzdiCA==" + } } }, "@emotion/hash": { @@ -2807,17 +2817,14 @@ "dev": true }, "@mat-datetimepicker/core": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@mat-datetimepicker/core/-/core-2.0.1.tgz", - "integrity": "sha1-4NsdtdTPe6Vrck7AQIF8totXdfI=", - "requires": { - "tslib": "^1.9.0" - } + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@mat-datetimepicker/core/-/core-4.0.0.tgz", + "integrity": "sha512-tOjRezVBZ1Jo1xw9L3oIZveVutvopoEfgKHeEfeazNJYdz4RWJ4Lanw4xqFhh811Hi5n5NXN7eRDcB86AJZcxw==" }, "@material-ui/core": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.9.1.tgz", - "integrity": "sha512-wehQI0ahHDsZjK+uA8Q5Cs1K1/1HXYe2icwTqARaRCt7d9bTp0bJN/C9TLe/+sRWfRIkx6OIk7ABSJT1jBqxRg==", + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.9.2.tgz", + "integrity": "sha512-fSf/yBuE5GR7dA+FiQAAGY7HrCN/8RaYApi9tx3IKMiJIJkRCHk+E2lktgJZ+QRsaqCACLo2lwhU2CW5aeO0UQ==", "requires": { "@babel/runtime": "^7.4.4", "@material-ui/styles": "^4.9.0", @@ -2951,9 +2958,9 @@ } }, "@ngx-translate/core": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/@ngx-translate/core/-/core-11.0.1.tgz", - "integrity": "sha512-nBCa1ZD9fAUY/3eskP3Lql2fNg8OMrYIej1/5GRsfcutx9tG/5fZLCv9m6UCw1aS+u4uK/vXjv1ctG/FdMvaWg==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@ngx-translate/core/-/core-12.0.0.tgz", + "integrity": "sha512-hxuaLEqxlZ3IWBupyAoRXAhMZHCmaCg58XpY5+vevJmDhMEFJUEKdQyWVOKcf3+6PkoIFcuKJCeHa5C3Hb65gA==", "requires": { "tslib": "^1.9.0" } @@ -3068,9 +3075,9 @@ } }, "@types/jasmine": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.5.2.tgz", - "integrity": "sha512-7hrdBDFWlTb4EhrXYRyC7i3L2kKCV0TqYbzuV+gwyPNF2V4SSHw2Vs223ai26W4tEg+t4e9Wfi1vW6JLubYPiw==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.5.3.tgz", + "integrity": "sha512-LRJ21f/BO4QNZ3YDaMP0OEurOfE77x8mi8MfEnUsei5IKfmZL0GKl7juhABMdUIJHhVS9OCLotKHfsFNAuJ+DA==", "dev": true }, "@types/jasminewd2": { @@ -3083,9 +3090,9 @@ } }, "@types/jquery": { - "version": "3.3.31", - "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.3.31.tgz", - "integrity": "sha512-Lz4BAJihoFw5nRzKvg4nawXPzutkv7wmfQ5121avptaSIXlDNJCUuxZxX/G+9EVidZGuO0UBlk+YjKbwRKJigg==", + "version": "3.3.32", + "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.3.32.tgz", + "integrity": "sha512-UKoof2mnV/X1/Ix2g+V2Ny5sgHjV8nK/UJbiYxuo4zPwzGyFlZ/mp4KaePb2VqQrqJctmcDQNA57buU84/2uIw==", "requires": { "@types/sizzle": "*" } @@ -3117,9 +3124,9 @@ "integrity": "sha512-13gmo3M2qVvjQrWNseqM3+cR6S2Ss3grbR2NZltgMq94wOwqJYQdgn8qzwDshzgXqMlSUtyPZjysImmktu22ew==" }, "@types/node": { - "version": "12.12.26", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.26.tgz", - "integrity": "sha512-UmUm94/QZvU5xLcUlNR8hA7Ac+fGpO1EG/a8bcWVz0P0LqtxFmun9Y2bbtuckwGboWJIT70DoWq1r3hb56n3DA==", + "version": "13.7.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.7.0.tgz", + "integrity": "sha512-GnZbirvmqZUzMgkFn70c74OQpTTUcCzlhQliTzYjQMqg+hVKcDnxdL19Ne3UdYzdMA/+W3eb646FWn/ZaT1NfQ==", "dev": true }, "@types/prop-types": { @@ -3525,12 +3532,9 @@ "dev": true }, "angular-gridster2": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/angular-gridster2/-/angular-gridster2-8.3.0.tgz", - "integrity": "sha512-uQ7StNeF2SWxYA099MPifnqoOe/16rAu6KN00/sQ6kJ3umLDkPYRA61z3iPAdrxEkYMdXmtoMbFkm4igWLA3Ww==", - "requires": { - "tslib": "^1.9.0" - } + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/angular-gridster2/-/angular-gridster2-9.0.0.tgz", + "integrity": "sha512-0/nREskZEZMzSS9AdQmPHdncb4txIXzHnSgylIJ5esEPJI4dkB4A5bd9shUdwH6u4kPZInkHO3W0hHJOTSilUQ==" }, "angular2-hotkeys": { "version": "2.1.5", @@ -3609,9 +3613,9 @@ "dev": true }, "arg": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.2.tgz", - "integrity": "sha512-+ytCkGcBtHZ3V2r2Z06AncYO8jz46UEamcspGoU8lHcEbpn6J77QK0vdWvChsclg/tM5XIJC5tnjmPp7Eq6Obg==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", "dev": true }, "argparse": { @@ -4097,6 +4101,22 @@ "dns-txt": "^2.0.2", "multicast-dns": "^6.0.1", "multicast-dns-service-types": "^1.1.0" + }, + "dependencies": { + "deep-equal": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", + "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", + "dev": true, + "requires": { + "is-arguments": "^1.0.4", + "is-date-object": "^1.0.1", + "is-regex": "^1.0.4", + "object-is": "^1.0.1", + "object-keys": "^1.1.1", + "regexp.prototype.flags": "^1.2.0" + } + } } }, "boolbase": { @@ -5531,9 +5551,9 @@ } }, "date-fns": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.8.1.tgz", - "integrity": "sha512-EL/C8IHvYRwAHYgFRse4MGAPSqlJVlOrhVYZ75iQBKrnv+ZedmYsgwH3t+BCDuZDXpoo07+q9j4qgSSOa7irJg==" + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.9.0.tgz", + "integrity": "sha512-khbFLu/MlzLjEzy9Gh8oY1hNt/Dvxw3J6Rbc28cVoYWQaC1S3YI4xwkF9ZWcjDLscbZlY9hISMr66RFzZagLsA==" }, "date-format": { "version": "2.1.0", @@ -5569,16 +5589,29 @@ "dev": true }, "deep-equal": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", - "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.0.1.tgz", + "integrity": "sha512-7Et6r6XfNW61CPPCIYfm1YPGSmh6+CliYeL4km7GWJcpX5LTAflGF8drLLR+MZX+2P3NZfAfSduutBbSWqER4g==", "requires": { + "es-abstract": "^1.16.3", + "es-get-iterator": "^1.0.1", "is-arguments": "^1.0.4", "is-date-object": "^1.0.1", "is-regex": "^1.0.4", + "isarray": "^2.0.5", "object-is": "^1.0.1", "object-keys": "^1.1.1", - "regexp.prototype.flags": "^1.2.0" + "regexp.prototype.flags": "^1.2.0", + "side-channel": "^1.0.1", + "which-boxed-primitive": "^1.0.1", + "which-collection": "^1.0.0" + }, + "dependencies": { + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + } } }, "deep-freeze-strict": { @@ -5777,9 +5810,9 @@ "dev": true }, "diff": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz", - "integrity": "sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true }, "diff-match-patch": { @@ -5852,11 +5885,6 @@ "csstype": "^2.6.7" } }, - "dom-scroll-into-view": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/dom-scroll-into-view/-/dom-scroll-into-view-1.2.1.tgz", - "integrity": "sha1-6PNnMt0ImwIBqI14Fdw/iObWbH4=" - }, "dom-serialize": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", @@ -6145,7 +6173,6 @@ "version": "1.17.4", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz", "integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==", - "dev": true, "requires": { "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", @@ -6164,18 +6191,37 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", - "dev": true, "requires": { "has": "^1.0.3" } } } }, + "es-get-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.0.tgz", + "integrity": "sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ==", + "requires": { + "es-abstract": "^1.17.4", + "has-symbols": "^1.0.1", + "is-arguments": "^1.0.4", + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-string": "^1.0.5", + "isarray": "^2.0.5" + }, + "dependencies": { + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + } + } + }, "es-to-primitive": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, "requires": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", @@ -7057,8 +7103,7 @@ "has-symbols": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" }, "has-value": { "version": "1.0.0", @@ -7650,6 +7695,11 @@ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", "dev": true }, + "is-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.0.tgz", + "integrity": "sha512-t5mGUXC/xRheCK431ylNiSkGGpBp8bHENBcENTkDT6ppwPzEVxNGZRvgvmOEfbWkFhA7D2GEuE2mmQTr78sl2g==" + }, "is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -7659,6 +7709,11 @@ "binary-extensions": "^2.0.0" } }, + "is-boolean-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.0.1.tgz", + "integrity": "sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ==" + }, "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", @@ -7668,8 +7723,7 @@ "is-callable": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", - "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", - "dev": true + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==" }, "is-color-stop": { "version": "1.1.0", @@ -7773,12 +7827,22 @@ "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", "dev": true }, + "is-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.1.tgz", + "integrity": "sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw==" + }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, + "is-number-object": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", + "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==" + }, "is-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", @@ -7831,11 +7895,11 @@ "dev": true }, "is-regex": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", - "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", "requires": { - "has": "^1.0.1" + "has": "^1.0.3" } }, "is-resolvable": { @@ -7844,12 +7908,22 @@ "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", "dev": true }, + "is-set": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.1.tgz", + "integrity": "sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA==" + }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", "dev": true }, + "is-string": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", + "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==" + }, "is-svg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz", @@ -7863,7 +7937,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "dev": true, "requires": { "has-symbols": "^1.0.1" } @@ -7874,6 +7947,16 @@ "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", "dev": true }, + "is-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==" + }, + "is-weakset": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.1.tgz", + "integrity": "sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw==" + }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", @@ -8288,13 +8371,13 @@ "integrity": "sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw==" }, "jquery.terminal": { - "version": "1.23.2", - "resolved": "https://registry.npmjs.org/jquery.terminal/-/jquery.terminal-1.23.2.tgz", - "integrity": "sha512-ULKxZNzL8W4CoeAx5CJZTVY80SrNoeetA4lhnBeHd792uaLAkfRXMeJeARLWhBOrzDWo1yqn2nm4td0a+EU0dg==", + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/jquery.terminal/-/jquery.terminal-2.12.0.tgz", + "integrity": "sha512-y6uBR6OB0cViNH/AkEJRhr56H8db8MlTO7Vn0TuDZhl78R5+DnT5jMlQLKxZEQVhUWtZQMXZIlQt0Xr6Zf63BQ==", "requires": { - "@types/jquery": "^3.3.6", + "@types/jquery": "^3.3.29", "jquery": "~3", - "prismjs": "^1.15.0", + "prismjs": "^1.16.0", "wcwidth": "^1.0.1" } }, @@ -8592,12 +8675,12 @@ } }, "karma-jasmine": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-2.0.1.tgz", - "integrity": "sha512-iuC0hmr9b+SNn1DaUD2QEYtUxkS1J+bSJSn7ejdEexs7P8EYvA1CWkEdrDQ+8jVH3AgWlCNwjYsT1chjcNW9lA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-3.1.0.tgz", + "integrity": "sha512-IVGbC8gap5x5NNCEOsAE77ic8rZtHDt6wmO0fFC5yT5FeB8qKnGTeud2mtKyQ41xl7vZkZ7ZxKr4wMGR6tWN+A==", "dev": true, "requires": { - "jasmine-core": "^3.3" + "jasmine-core": "^3.5.0" } }, "karma-jasmine-html-reporter": { @@ -9208,24 +9291,6 @@ } } }, - "mini-store": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mini-store/-/mini-store-2.0.0.tgz", - "integrity": "sha512-EG0CuwpQmX+XL4QVS0kxNwHW5ftSbhygu1qxQH0pipugjnPkbvkalCdQbEihMwtQY6d3MTN+MS0q+aurs+RfLQ==", - "requires": { - "hoist-non-react-statics": "^2.3.1", - "prop-types": "^15.6.0", - "react-lifecycles-compat": "^3.0.4", - "shallowequal": "^1.0.2" - }, - "dependencies": { - "hoist-non-react-statics": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", - "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==" - } - } - }, "minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -9466,11 +9531,6 @@ "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", "dev": true }, - "mutationobserver-shim": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/mutationobserver-shim/-/mutationobserver-shim-0.3.3.tgz", - "integrity": "sha512-gciOLNN8Vsf7YzcqRjKzlAJ6y7e+B86u7i3KXes0xfxx/nfLmozlW1Vn+Sc9x3tPIePFgc1AeIFhtRgkqTjzDQ==" - }, "mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", @@ -9534,9 +9594,9 @@ } }, "ngx-color-picker": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/ngx-color-picker/-/ngx-color-picker-8.2.0.tgz", - "integrity": "sha512-rzR+cByjNG9M/UskU5vNoH7cUc6oM8STTDFKOZmnlX4ALOuM1+61CBjsNTGETWfo9a/h5mbGX02oh5/iNAa7vA==" + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/ngx-color-picker/-/ngx-color-picker-9.0.0.tgz", + "integrity": "sha512-fe5KS89gzzBBB+keugAQtneBOGso44EW8cxJa8j/4c7pELBGw1Xgu/duINuwlwVDalN9R5GIFWeMdNaBvBPHyA==" }, "ngx-flowchart": { "version": "git://github.com/thingsboard/ngx-flowchart.git#671b505b2484806a4a1c376344d0a12e5716679a", @@ -9867,13 +9927,12 @@ "object-inspect": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", - "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", - "dev": true + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==" }, "object-is": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.1.tgz", - "integrity": "sha1-CqYOyZiaCz7Xlc9NBvYs8a1lObY=" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.2.tgz", + "integrity": "sha512-Epah+btZd5wrrfjkJZq1AOB9O6OxUQto45hzFd7lXGrpHPGE0W1k+426yrZV+k6NJOzLNNW/nVsmZdIWsAqoOQ==" }, "object-keys": { "version": "1.1.1", @@ -9893,7 +9952,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "dev": true, "requires": { "define-properties": "^1.1.2", "function-bind": "^1.1.1", @@ -9933,9 +9991,9 @@ } }, "objectpath": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/objectpath/-/objectpath-1.2.2.tgz", - "integrity": "sha512-ie+GY5tJsKt7daHH6qGROf3JqxfD2XhfBPLY+HQrVuRY8MQE1ySKVSqQ/TQz/Dx7jDwuy3etQALDE1cRJAC0cg==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/objectpath/-/objectpath-2.0.0.tgz", + "integrity": "sha512-IWH9JOBUJz4HHBtXm1qqwoPiDAB8Qp+ZBE4PpXsOlXVEnxGa+fAgfAZFwN6L1cUYvzPpFeJ1HsY1WAhoOqQq7Q==" }, "obuf": { "version": "1.1.2", @@ -11486,14 +11544,14 @@ } }, "rc-align": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/rc-align/-/rc-align-2.4.5.tgz", - "integrity": "sha512-nv9wYUYdfyfK+qskThf4BQUSIadeI/dCsfaMZfNEoxm9HwOIioQ+LyqmMK6jWHAZQgOzMLaqawhuBXlF63vgjw==", + "version": "3.0.0-rc.1", + "resolved": "https://registry.npmjs.org/rc-align/-/rc-align-3.0.0-rc.1.tgz", + "integrity": "sha512-GbofumhCUb7SxP410j/fbtR2M9Zml+eoZSdaliZh6R3NhfEj5zP4jcO3HG3S9C9KIcXQQtd/cwVHkb9Y0KU7Hg==", "requires": { - "babel-runtime": "^6.26.0", + "classnames": "2.x", "dom-align": "^1.7.0", - "prop-types": "^15.5.8", - "rc-util": "^4.0.4" + "rc-util": "^4.12.0", + "resize-observer-polyfill": "^1.5.1" } }, "rc-animate": { @@ -11510,53 +11568,30 @@ "react-lifecycles-compat": "^3.0.4" } }, - "rc-menu": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-7.5.5.tgz", - "integrity": "sha512-4YJXJgrpUGEA1rMftXN7bDhrV5rPB8oBJoHqT+GVXtIWCanfQxEnM3fmhHQhatL59JoAFMZhJaNzhJIk4FUWCQ==", - "requires": { - "classnames": "2.x", - "dom-scroll-into-view": "1.x", - "mini-store": "^2.0.0", - "mutationobserver-shim": "^0.3.2", - "rc-animate": "^2.10.1", - "rc-trigger": "^2.3.0", - "rc-util": "^4.13.0", - "resize-observer-polyfill": "^1.5.0", - "shallowequal": "^1.1.0" - } - }, "rc-select": { - "version": "9.2.3", - "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-9.2.3.tgz", - "integrity": "sha512-WhswxOMWiNnkXRbxyrj0kiIvyCfo/BaRPaYbsDetSIAU2yEDwKHF798blCP5u86KLOBKBvtxWLFCkSsQw1so5w==", + "version": "10.0.0-rc.6", + "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-10.0.0-rc.6.tgz", + "integrity": "sha512-eGQZBk2agq6eqVlF01kgT7i6hJzbWLWP4zC8qYp6sjuzBvDtFo8IRiHVPQk9ZHYJC+ZW8S9atD+ZzndCM1DcvQ==", "requires": { - "babel-runtime": "^6.23.0", "classnames": "2.x", - "component-classes": "1.x", - "dom-scroll-into-view": "1.x", - "prop-types": "^15.5.8", - "raf": "^3.4.0", - "rc-animate": "2.x", - "rc-menu": "^7.3.0", - "rc-trigger": "^2.5.4", - "rc-util": "^4.0.4", - "react-lifecycles-compat": "^3.0.2", - "warning": "^4.0.2" + "rc-animate": "^2.10.0", + "rc-trigger": "^4.0.0-alpha.4", + "rc-util": "^4.17.0", + "rc-virtual-list": "^0.0.0-alpha.25", + "warning": "^4.0.3" } }, "rc-trigger": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/rc-trigger/-/rc-trigger-2.6.5.tgz", - "integrity": "sha512-m6Cts9hLeZWsTvWnuMm7oElhf+03GOjOLfTuU0QmdB9ZrW7jR2IpI5rpNM7i9MvAAlMAmTx5Zr7g3uu/aMvZAw==", + "version": "4.0.0-rc.5", + "resolved": "https://registry.npmjs.org/rc-trigger/-/rc-trigger-4.0.0-rc.5.tgz", + "integrity": "sha512-CUimlGKduOzBJxMEib3MzWhFpSq0wFk/iXxonaIIth/zVMZ4at+Wf3yxh8apY6UuYZlrJiA8S58/BfePpT+g0Q==", "requires": { - "babel-runtime": "6.x", "classnames": "^2.2.6", "prop-types": "15.x", - "rc-align": "^2.4.0", - "rc-animate": "2.x", - "rc-util": "^4.4.0", - "react-lifecycles-compat": "^3.0.4" + "raf": "^3.4.1", + "rc-align": "^3.0.0-rc.0", + "rc-animate": "^2.10.2", + "rc-util": "^4.15.2" } }, "rc-util": { @@ -11571,6 +11606,15 @@ "shallowequal": "^1.1.0" } }, + "rc-virtual-list": { + "version": "0.0.0-rc.1", + "resolved": "https://registry.npmjs.org/rc-virtual-list/-/rc-virtual-list-0.0.0-rc.1.tgz", + "integrity": "sha512-bd4Wj7XVG+fioBUW1tvQUdV9yrByf9Hc2MGnrhzxpGvSAYOoR9LqDxi2ahHOD8rzlwxsDUm1H5bvb89cZ/7baw==", + "requires": { + "classnames": "^2.2.6", + "rc-util": "^4.8.0" + } + }, "react": { "version": "16.12.0", "resolved": "https://registry.npmjs.org/react/-/react-16.12.0.tgz", @@ -11766,11 +11810,12 @@ } }, "regexp.prototype.flags": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.2.0.tgz", - "integrity": "sha512-ztaw4M1VqgMwl9HlPpOuiYgItcHlunW0He2fE6eNfT6E/CF2FtYi9ofOYe4mKntstYk0Fyh/rDRBdS3AnxjlrA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", + "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", "requires": { - "define-properties": "^1.1.2" + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" } }, "regexpu-core": { @@ -12370,6 +12415,15 @@ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", "dev": true }, + "side-channel": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.2.tgz", + "integrity": "sha512-7rL9YlPHg7Ancea1S96Pa8/QWb4BtXL/TZvS6B8XFetGBeuhAsfmUspK6DokBeZ64+Kj9TCNRD/30pVz1BvQNA==", + "requires": { + "es-abstract": "^1.17.0-next.1", + "object-inspect": "^1.7.0" + } + }, "sigmund": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", @@ -13081,7 +13135,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", - "dev": true, "requires": { "define-properties": "^1.1.3", "function-bind": "^1.1.1" @@ -13091,7 +13144,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", - "dev": true, "requires": { "define-properties": "^1.1.3", "function-bind": "^1.1.1" @@ -13612,16 +13664,16 @@ "dev": true }, "ts-node": { - "version": "8.5.4", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.5.4.tgz", - "integrity": "sha512-izbVCRV68EasEPQ8MSIGBNK9dc/4sYJJKYA+IarMQct1RtEot6Xp0bXuClsbUSnKpg50ho+aOAx8en5c+y4OFw==", + "version": "8.6.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.6.2.tgz", + "integrity": "sha512-4mZEbofxGqLL2RImpe3zMJukvEvcO1XP8bj8ozBPySdCUXEcU5cIRwR0aM3R+VoZq7iXc8N86NC0FspGRqP4gg==", "dev": true, "requires": { "arg": "^4.1.0", "diff": "^4.0.1", "make-error": "^1.1.1", "source-map-support": "^0.5.6", - "yn": "^3.0.0" + "yn": "3.1.1" } }, "tslib": { @@ -13630,9 +13682,9 @@ "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" }, "tslint": { - "version": "5.20.1", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.20.1.tgz", - "integrity": "sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.0.0.tgz", + "integrity": "sha512-9nLya8GBtlFmmFMW7oXXwoXS1NkrccqTqAtwXzdPV9e2mqSEvCki6iHL/Fbzi5oqbugshzgGPk7KBb2qNP1DSA==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -13646,7 +13698,7 @@ "mkdirp": "^0.5.1", "resolve": "^1.3.2", "semver": "^5.3.0", - "tslib": "^1.8.0", + "tslib": "^1.10.0", "tsutils": "^2.29.0" } }, @@ -15816,6 +15868,29 @@ "isexe": "^2.0.0" } }, + "which-boxed-primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.1.tgz", + "integrity": "sha512-7BT4TwISdDGBgaemWU0N0OU7FeAEJ9Oo2P1PHRm/FCWoEi2VLWC9b6xvxAA3C/NMpxg3HXVgi0sMmGbNUbNepQ==", + "requires": { + "is-bigint": "^1.0.0", + "is-boolean-object": "^1.0.0", + "is-number-object": "^1.0.3", + "is-string": "^1.0.4", + "is-symbol": "^1.0.2" + } + }, + "which-collection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "requires": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + } + }, "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", diff --git a/ui-ngx/package.json b/ui-ngx/package.json index ad4ea9bafc..c5b7284a83 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -2,6 +2,7 @@ "name": "thingsboard", "version": "3.0.0", "scripts": { + "postinstall": "ngcc --properties es2015 browser module main --first-only --create-ivy-entry-points", "ng": "ng", "start": "ng serve --host 0.0.0.0 --open", "build": "ng build", @@ -23,34 +24,34 @@ "@angular/platform-browser": "~9.0.0", "@angular/platform-browser-dynamic": "~9.0.0", "@angular/router": "~9.0.0", - "@auth0/angular-jwt": "^3.0.1", - "@date-io/date-fns": "^1.3.13", + "@auth0/angular-jwt": "^4.0.0", + "@date-io/date-fns": "^2.3.0", "@flowjs/flow.js": "^2.14.0", "@flowjs/ngx-flow": "^0.4.3", - "@mat-datetimepicker/core": "^2.0.1", - "@material-ui/core": "^4.9.1", + "@mat-datetimepicker/core": "^4.0.0", + "@material-ui/core": "^4.9.2", "@material-ui/icons": "^4.9.1", "@material-ui/pickers": "^3.2.10", "@ngrx/effects": "^8.6.0", "@ngrx/store": "^8.6.0", "@ngrx/store-devtools": "^8.6.0", "@ngx-share/core": "^7.1.4", - "@ngx-translate/core": "^11.0.1", + "@ngx-translate/core": "^12.0.0", "@ngx-translate/http-loader": "^4.0.0", "ace-builds": "^1.4.8", - "angular-gridster2": "^8.3.0", + "angular-gridster2": "^9.0.0", "angular2-hotkeys": "^2.1.5", "base64-js": "^1.3.1", "compass-sass-mixins": "^0.12.7", "core-js": "^3.6.4", - "date-fns": "2.8.1", - "deep-equal": "^1.1.1", + "date-fns": "^2.9.0", + "deep-equal": "^2.0.1", "flot": "git://github.com/thingsboard/flot.git#0.9-work", "flot.curvedlines": "git://github.com/MichaelZinsmaier/CurvedLines.git#master", "font-awesome": "^4.7.0", "javascript-detect-element-resize": "^0.5.3", "jquery": "^3.4.1", - "jquery.terminal": "^1.5.0", + "jquery.terminal": "^2.12.0", "js-beautify": "^1.10.3", "json-schema-defaults": "^0.4.0", "jstree": "^3.3.8", @@ -59,14 +60,14 @@ "messageformat": "^2.3.0", "moment": "^2.24.0", "ngx-clipboard": "^12.3.0", - "ngx-color-picker": "^8.2.0", + "ngx-color-picker": "^9.0.0", "ngx-flowchart": "git://github.com/thingsboard/ngx-flowchart.git#master", "ngx-hm-carousel": "^1.7.2", "ngx-translate-messageformat-compiler": "^4.5.0", - "objectpath": "^1.2.2", + "objectpath": "^2.0.0", "prop-types": "^15.7.2", "raphael": "^2.3.0", - "rc-select": "^9.2.3", + "rc-select": "^10.0.0-rc.6", "react": "^16.12.0", "react-ace": "^8.0.0", "react-dom": "^16.12.0", @@ -85,18 +86,18 @@ "zone.js": "~0.10.2" }, "devDependencies": { - "@angular-builders/custom-webpack": "^8.4.1", + "@angular-builders/custom-webpack": "^9.0.0-beta.7", "@angular-devkit/build-angular": "~0.900.1", "@angular/cli": "^9.0.1", "@angular/compiler-cli": "~9.0.0", "@angular/language-service": "~9.0.0", "@types/flot": "0.0.31", - "@types/jasmine": "^3.5.2", + "@types/jasmine": "^3.5.3", "@types/jasminewd2": "~2.0.8", - "@types/jquery": "^3.3.31", + "@types/jquery": "^3.3.32", "@types/js-beautify": "^1.8.1", "@types/jstree": "^3.3.39", - "@types/node": "^12.11.1", + "@types/node": "^13.7.0", "@types/raphael": "^2.1.30", "@types/react": "^16.9.19", "@types/react-dom": "^16.9.5", @@ -110,12 +111,12 @@ "karma": "~4.4.1", "karma-chrome-launcher": "~3.1.0", "karma-coverage-istanbul-reporter": "~2.1.1", - "karma-jasmine": "~2.0.1", + "karma-jasmine": "~3.1.0", "karma-jasmine-html-reporter": "^1.5.2", "ngrx-store-freeze": "^0.2.4", "protractor": "^5.4.3", - "ts-node": "~8.5.4", - "tslint": "~5.20.1", + "ts-node": "~8.6.2", + "tslint": "~6.0.0", "typescript": "~3.7.5" }, "resolutions": { diff --git a/ui-ngx/src/app/core/core.module.ts b/ui-ngx/src/app/core/core.module.ts index eb9bc8daf5..69d8b9b19e 100644 --- a/ui-ngx/src/app/core/core.module.ts +++ b/ui-ngx/src/app/core/core.module.ts @@ -48,11 +48,6 @@ export function HttpLoaderFactory(http: HttpClient) { } @NgModule({ - entryComponents: [ - ConfirmDialogComponent, - AlertDialogComponent, - TodoDialogComponent - ], declarations: [ ConfirmDialogComponent, AlertDialogComponent, diff --git a/ui-ngx/src/app/core/notification/notification.selectors.ts b/ui-ngx/src/app/core/notification/notification.selectors.ts deleted file mode 100644 index b075ae3b39..0000000000 --- a/ui-ngx/src/app/core/notification/notification.selectors.ts +++ /dev/null @@ -1,29 +0,0 @@ -/// -/// Copyright © 2016-2019 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 { createFeatureSelector, createSelector } from '@ngrx/store'; - -import { NotificationState } from './notification.models'; -import { AppState } from '@app/core/core.state'; - -export const selectNotificationState = createFeatureSelector( - 'notification' -); - -export const selectNotification = createSelector( - selectNotificationState, - (state: NotificationState) => state -); diff --git a/ui-ngx/src/app/core/services/dynamic-component-factory.service.ts b/ui-ngx/src/app/core/services/dynamic-component-factory.service.ts index f169193122..dd65b94897 100644 --- a/ui-ngx/src/app/core/services/dynamic-component-factory.service.ts +++ b/ui-ngx/src/app/core/services/dynamic-component-factory.service.ts @@ -59,14 +59,13 @@ export class DynamicComponentFactoryService { modules?: Type[]): Observable> { const dymamicComponentFactorySubject = new ReplaySubject>(); const comp = this.createDynamicComponent(componentType, template); - let moduleImports = [CommonModule]; + let moduleImports: Type[] = [CommonModule]; if (modules) { moduleImports = [...moduleImports, ...modules]; } - // noinspection AngularInvalidImportedOrDeclaredSymbol,AngularInvalidEntryComponent + // noinspection AngularInvalidImportedOrDeclaredSymbol @NgModule({ declarations: [comp], - entryComponents: [comp], imports: moduleImports }) class DynamicComponentInstanceModule extends DynamicComponentModule {} @@ -105,8 +104,7 @@ export class DynamicComponentFactoryService { private createDynamicComponent(componentType: Type, template: string): Type { // noinspection AngularMissingOrInvalidDeclarationInModule return Component({ - template, - jit: true + template })(componentType); } diff --git a/ui-ngx/src/app/core/translate/translate-default-compiler.ts b/ui-ngx/src/app/core/translate/translate-default-compiler.ts index 5e30a0b588..15639b6b5d 100644 --- a/ui-ngx/src/app/core/translate/translate-default-compiler.ts +++ b/ui-ngx/src/app/core/translate/translate-default-compiler.ts @@ -18,9 +18,10 @@ import { MESSAGE_FORMAT_CONFIG, MessageFormatConfig, TranslateMessageFormatCompiler } from 'ngx-translate-messageformat-compiler'; -import { Inject, Optional } from '@angular/core'; +import { Inject, Injectable, Optional } from '@angular/core'; const parse = require('messageformat-parser').parse; +@Injectable({ providedIn: 'root' }) export class TranslateDefaultCompiler extends TranslateMessageFormatCompiler { constructor( diff --git a/ui-ngx/src/app/modules/dashboard/dashboard-pages.module.ts b/ui-ngx/src/app/modules/dashboard/dashboard-pages.module.ts index 752857bf5d..052a6f6c65 100644 --- a/ui-ngx/src/app/modules/dashboard/dashboard-pages.module.ts +++ b/ui-ngx/src/app/modules/dashboard/dashboard-pages.module.ts @@ -23,7 +23,6 @@ import { DashboardModule } from '@home/pages/dashboard/dashboard.module'; import { DashboardPagesRoutingModule } from './dashboard-pages.routing.module'; @NgModule({ - entryComponents: [], imports: [ CommonModule, SharedModule, diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index cdbd9d7763..9a51aa26a1 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -66,26 +66,6 @@ import { EventContentDialogComponent } from '@home/components/event/event-conten import { SharedHomeComponentsModule } from '@home/components/shared-home-components.module'; @NgModule({ - entryComponents: [ - AddEntityDialogComponent, - AuditLogDetailsDialogComponent, - EventContentDialogComponent, - EventTableHeaderComponent, - RelationDialogComponent, - AlarmTableHeaderComponent, - AddAttributeDialogComponent, - EditAttributeValuePanelComponent, - AliasesEntitySelectPanelComponent, - EntityAliasesDialogComponent, - EntityAliasDialogComponent, - DataKeyConfigDialogComponent, - LegendConfigPanelComponent, - WidgetActionDialogComponent, - CustomDialogContainerComponent, - ImportDialogComponent, - ImportDialogCsvComponent, - AddWidgetToDashboardDialogComponent - ], declarations: [ EntitiesTableComponent, diff --git a/ui-ngx/src/app/modules/home/components/import-export/import-export.service.ts b/ui-ngx/src/app/modules/home/components/import-export/import-export.service.ts index e67d43c783..6b22358947 100644 --- a/ui-ngx/src/app/modules/home/components/import-export/import-export.service.ts +++ b/ui-ngx/src/app/modules/home/components/import-export/import-export.service.ts @@ -691,7 +691,7 @@ export class ImportExportService { const e = this.document.createEvent('MouseEvents'); const a = this.document.createElement('a'); a.download = filename; - a.href = this.window.URL.createObjectURL(blob); + a.href = URL.createObjectURL(blob); a.dataset.downloadurl = ['text/json', a.download, a.href].join(':'); // @ts-ignore e.initEvent('click', true, false, this.window, diff --git a/ui-ngx/src/app/modules/home/components/import-export/table-columns-assignment.component.ts b/ui-ngx/src/app/modules/home/components/import-export/table-columns-assignment.component.ts index 2f420d2d7c..0f98db20a0 100644 --- a/ui-ngx/src/app/modules/home/components/import-export/table-columns-assignment.component.ts +++ b/ui-ngx/src/app/modules/home/components/import-export/table-columns-assignment.component.ts @@ -21,8 +21,8 @@ import { AppState } from '@core/core.state'; import { EntityType } from '@shared/models/entity-type.models'; import { CsvColumnParam, ImportEntityColumnType, importEntityColumnTypeTranslations, importEntityObjectColumns } from '@home/components/import-export/import-export.models'; -import { CollectionViewer, DataSource } from '@angular/cdk/typings/collections'; import { BehaviorSubject, Observable } from 'rxjs'; +import { CollectionViewer, DataSource } from '@angular/cdk/collections'; @Component({ selector: 'tb-table-columns-assignment', diff --git a/ui-ngx/src/app/modules/home/components/shared-home-components.module.ts b/ui-ngx/src/app/modules/home/components/shared-home-components.module.ts index 30616276c5..2247c55f15 100644 --- a/ui-ngx/src/app/modules/home/components/shared-home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/shared-home-components.module.ts @@ -20,9 +20,6 @@ import { SharedModule } from '@app/shared/shared.module'; import { AlarmDetailsDialogComponent } from '@home/components/alarm/alarm-details-dialog.component'; @NgModule({ - entryComponents: [ - AlarmDetailsDialogComponent - ], declarations: [ AlarmDetailsDialogComponent diff --git a/ui-ngx/src/app/modules/home/components/widget/action/custom-sample-js.raw b/ui-ngx/src/app/modules/home/components/widget/action/custom-sample-js.raw index 960594c76b..02a97e7d6e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/action/custom-sample-js.raw +++ b/ui-ngx/src/app/modules/home/components/widget/action/custom-sample-js.raw @@ -25,7 +25,7 @@ // vm.entityType = entityId.entityType; // // vm.editEntityFormGroup = vm.fb.group({ -// entityName: [vm.entityName, [vm.Validators.required]] +// entityName: [vm.entityName, [vm.validators.required]] // }); // // vm.cancel = function() { diff --git a/ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.models.ts b/ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.models.ts index 27a7d4346a..e8e5238bf2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.models.ts @@ -16,7 +16,7 @@ import { WidgetActionDescriptor, WidgetActionSource, widgetActionTypeTranslationMap, CustomActionDescriptor } from '@app/shared/models/widget.models'; -import { CollectionViewer, DataSource } from '@angular/cdk/typings/collections'; +import { CollectionViewer, DataSource } from '@angular/cdk/collections'; import { EntityRelationInfo, EntitySearchDirection } from '@shared/models/relation.models'; import { BehaviorSubject, Observable, of, ReplaySubject } from 'rxjs'; import { emptyPageData, PageData } from '@shared/models/page/page-data'; diff --git a/ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog-container.component.ts b/ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog-container.component.ts index 312215030a..c1cfb7d85a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog-container.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/dialog/custom-dialog-container.component.ts @@ -62,16 +62,16 @@ export class CustomDialogContainerComponent extends DialogComponent, P extends PageLink> = (pageLink: P) => Observable>; diff --git a/ui-ngx/src/app/modules/home/models/datasource/relation-datasource.ts b/ui-ngx/src/app/modules/home/models/datasource/relation-datasource.ts index 12a5c5252d..9df0d573cd 100644 --- a/ui-ngx/src/app/modules/home/models/datasource/relation-datasource.ts +++ b/ui-ngx/src/app/modules/home/models/datasource/relation-datasource.ts @@ -14,11 +14,10 @@ /// limitations under the License. /// -import { CollectionViewer, DataSource } from '@angular/cdk/typings/collections'; +import { CollectionViewer, DataSource, SelectionModel } from '@angular/cdk/collections'; import { EntityRelationInfo, EntitySearchDirection } from '@shared/models/relation.models'; import { BehaviorSubject, Observable, of, ReplaySubject } from 'rxjs'; import { emptyPageData, PageData } from '@shared/models/page/page-data'; -import { SelectionModel } from '@angular/cdk/collections'; import { EntityRelationService } from '@core/http/entity-relation.service'; import { PageLink } from '@shared/models/page/page-link'; import { catchError, map, publishReplay, refCount, take, tap } from 'rxjs/operators'; diff --git a/ui-ngx/src/app/modules/home/pages/asset/asset.module.ts b/ui-ngx/src/app/modules/home/pages/asset/asset.module.ts index 2a453abf18..a6c5301fe3 100644 --- a/ui-ngx/src/app/modules/home/pages/asset/asset.module.ts +++ b/ui-ngx/src/app/modules/home/pages/asset/asset.module.ts @@ -25,11 +25,6 @@ import {HomeComponentsModule} from '@modules/home/components/home-components.mod import { AssetTabsComponent } from '@home/pages/asset/asset-tabs.component'; @NgModule({ - entryComponents: [ - AssetComponent, - AssetTabsComponent, - AssetTableHeaderComponent - ], declarations: [ AssetComponent, AssetTabsComponent, diff --git a/ui-ngx/src/app/modules/home/pages/customer/customer.module.ts b/ui-ngx/src/app/modules/home/pages/customer/customer.module.ts index 6594a9240f..cc70707e8f 100644 --- a/ui-ngx/src/app/modules/home/pages/customer/customer.module.ts +++ b/ui-ngx/src/app/modules/home/pages/customer/customer.module.ts @@ -23,10 +23,6 @@ import {HomeComponentsModule} from '@modules/home/components/home-components.mod import { CustomerTabsComponent } from '@home/pages/customer/customer-tabs.component'; @NgModule({ - entryComponents: [ - CustomerComponent, - CustomerTabsComponent - ], declarations: [ CustomerComponent, CustomerTabsComponent diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard.module.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard.module.ts index 6af624a0e8..e5f1388c6a 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard.module.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard.module.ts @@ -39,19 +39,6 @@ import { DashboardStateDialogComponent } from './states/dashboard-state-dialog.c import { SelectTargetStateDialogComponent } from './states/select-target-state-dialog.component'; @NgModule({ - entryComponents: [ - DashboardFormComponent, - DashboardTabsComponent, - ManageDashboardCustomersDialogComponent, - MakeDashboardPublicDialogComponent, - AddWidgetDialogComponent, - ManageDashboardLayoutsDialogComponent, - SelectTargetLayoutDialogComponent, - DashboardSettingsDialogComponent, - ManageDashboardStatesDialogComponent, - DashboardStateDialogComponent, - SelectTargetStateDialogComponent - ], declarations: [ DashboardFormComponent, DashboardTabsComponent, diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.models.ts b/ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.models.ts index c33b85103a..8641b963da 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.models.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.models.ts @@ -15,8 +15,7 @@ /// import { DashboardState } from '@shared/models/dashboard.models'; -import { CollectionViewer, DataSource } from '@angular/cdk/typings/collections'; -import { WidgetActionDescriptorInfo } from '@home/components/widget/action/manage-widget-actions.component.models'; +import { CollectionViewer, DataSource } from '@angular/cdk/collections'; import { BehaviorSubject, Observable, of, ReplaySubject } from 'rxjs'; import { emptyPageData, PageData } from '@shared/models/page/page-data'; import { PageLink } from '@shared/models/page/page-link'; diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/states-controller.module.ts b/ui-ngx/src/app/modules/home/pages/dashboard/states/states-controller.module.ts index 55063eea2c..5a65cefe21 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/states/states-controller.module.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/states-controller.module.ts @@ -25,10 +25,6 @@ import { HomeDialogsModule } from '@app/modules/home/dialogs/home-dialogs.module import { DefaultStateControllerComponent } from '@home/pages/dashboard/states/default-state-controller.component'; @NgModule({ - entryComponents: [ - DefaultStateControllerComponent, - EntityStateControllerComponent - ], declarations: [ StatesComponentDirective, DefaultStateControllerComponent, diff --git a/ui-ngx/src/app/modules/home/pages/device/device.module.ts b/ui-ngx/src/app/modules/home/pages/device/device.module.ts index 2e3b659860..2bf5e641f8 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device.module.ts +++ b/ui-ngx/src/app/modules/home/pages/device/device.module.ts @@ -26,12 +26,6 @@ import {HomeComponentsModule} from '@modules/home/components/home-components.mod import { DeviceTabsComponent } from '@home/pages/device/device-tabs.component'; @NgModule({ - entryComponents: [ - DeviceComponent, - DeviceTabsComponent, - DeviceTableHeaderComponent, - DeviceCredentialsDialogComponent - ], declarations: [ DeviceComponent, DeviceTabsComponent, diff --git a/ui-ngx/src/app/modules/home/pages/entity-view/entity-view.module.ts b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view.module.ts index 7e9aeccf84..e9548d7f9f 100644 --- a/ui-ngx/src/app/modules/home/pages/entity-view/entity-view.module.ts +++ b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view.module.ts @@ -25,11 +25,6 @@ import {HomeComponentsModule} from '@modules/home/components/home-components.mod import { EntityViewTabsComponent } from '@home/pages/entity-view/entity-view-tabs.component'; @NgModule({ - entryComponents: [ - EntityViewComponent, - EntityViewTabsComponent, - EntityViewTableHeaderComponent - ], declarations: [ EntityViewComponent, EntityViewTabsComponent, diff --git a/ui-ngx/src/app/modules/home/pages/profile/profile.module.ts b/ui-ngx/src/app/modules/home/pages/profile/profile.module.ts index fad3304d5e..7c19c97a57 100644 --- a/ui-ngx/src/app/modules/home/pages/profile/profile.module.ts +++ b/ui-ngx/src/app/modules/home/pages/profile/profile.module.ts @@ -22,9 +22,6 @@ import { ProfileRoutingModule } from './profile-routing.module'; import { ChangePasswordDialogComponent } from '@modules/home/pages/profile/change-password-dialog.component'; @NgModule({ - entryComponents: [ - ChangePasswordDialogComponent - ], declarations: [ ProfileComponent, ChangePasswordDialogComponent diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts index cf768adebb..f2d22112e7 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts @@ -42,7 +42,7 @@ import * as AngularCommon from '@angular/common'; import * as AngularForms from '@angular/forms'; import * as AngularCdkCoercion from '@angular/cdk/coercion'; import * as AngularCdkKeycodes from '@angular/cdk/keycodes'; -import * as AngularMaterial from '@angular/material'; +import * as AngularMaterial from '@angular/material/esm5'; import * as NgrxStore from '@ngrx/store'; import * as TranslateCore from '@ngx-translate/core'; import * as TbCore from '@core/public-api'; diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.module.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.module.ts index 0012a06f44..e11a3d318a 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.module.ts +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.module.ts @@ -30,13 +30,6 @@ import { LinkLabelsComponent } from '@home/pages/rulechain/link-labels.component import { RuleNodeConfigComponent } from './rule-node-config.component'; @NgModule({ - entryComponents: [ - RuleChainComponent, - RuleChainTabsComponent, - RuleNodeComponent, - AddRuleNodeLinkDialogComponent, - AddRuleNodeDialogComponent - ], declarations: [ RuleChainComponent, RuleChainTabsComponent, diff --git a/ui-ngx/src/app/modules/home/pages/tenant/tenant.module.ts b/ui-ngx/src/app/modules/home/pages/tenant/tenant.module.ts index eacf54c4b5..cff20ac96c 100644 --- a/ui-ngx/src/app/modules/home/pages/tenant/tenant.module.ts +++ b/ui-ngx/src/app/modules/home/pages/tenant/tenant.module.ts @@ -23,10 +23,6 @@ import {HomeComponentsModule} from '@modules/home/components/home-components.mod import { TenantTabsComponent } from '@home/pages/tenant/tenant-tabs.component'; @NgModule({ - entryComponents: [ - TenantComponent, - TenantTabsComponent - ], declarations: [ TenantComponent, TenantTabsComponent diff --git a/ui-ngx/src/app/modules/home/pages/user/user.module.ts b/ui-ngx/src/app/modules/home/pages/user/user.module.ts index 5e198a651c..ce5bab2226 100644 --- a/ui-ngx/src/app/modules/home/pages/user/user.module.ts +++ b/ui-ngx/src/app/modules/home/pages/user/user.module.ts @@ -25,12 +25,6 @@ import {HomeComponentsModule} from '@modules/home/components/home-components.mod import { UserTabsComponent } from '@home/pages/user/user-tabs.component'; @NgModule({ - entryComponents: [ - UserComponent, - UserTabsComponent, - AddUserDialogComponent, - ActivationLinkDialogComponent - ], declarations: [ UserComponent, UserTabsComponent, diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-library.module.ts b/ui-ngx/src/app/modules/home/pages/widget/widget-library.module.ts index 46f082e455..73aac141d8 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widget-library.module.ts +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-library.module.ts @@ -26,11 +26,6 @@ import { SelectWidgetTypeDialogComponent } from '@home/pages/widget/select-widge import { SaveWidgetTypeAsDialogComponent } from './save-widget-type-as-dialog.component'; @NgModule({ - entryComponents: [ - WidgetsBundleComponent, - SelectWidgetTypeDialogComponent, - SaveWidgetTypeAsDialogComponent - ], declarations: [ WidgetsBundleComponent, WidgetLibraryComponent, diff --git a/ui-ngx/src/app/shared/components/json-form/react/json-form-rc-select.tsx b/ui-ngx/src/app/shared/components/json-form/react/json-form-rc-select.tsx index 02fedb6071..1747fe1499 100644 --- a/ui-ngx/src/app/shared/components/json-form/react/json-form-rc-select.tsx +++ b/ui-ngx/src/app/shared/components/json-form/react/json-form-rc-select.tsx @@ -16,12 +16,13 @@ import * as React from 'react'; import ThingsboardBaseComponent from './json-form-base-component'; -import Select, {Option} from 'rc-select/lib'; +import Select, {Option} from 'rc-select'; import { JsonFormFieldProps, JsonFormFieldState, KeyLabelItem } from '@shared/components/json-form/react/json-form.models'; +import { Mode } from 'rc-select/lib/interface'; interface ThingsboardRcSelectState extends JsonFormFieldState { currentValue: KeyLabelItem | KeyLabelItem[]; @@ -150,6 +151,14 @@ class ThingsboardRcSelect extends React.Component @@ -158,12 +167,9 @@ class ThingsboardRcSelect extends React.Component Date: Tue, 11 Feb 2020 10:35:05 +0200 Subject: [PATCH 100/133] Fix components path --- .../home/components/home-components.module.ts | 62 +++++++++---------- ui-ngx/src/app/shared/shared.module.ts | 62 +++++++++---------- 2 files changed, 62 insertions(+), 62 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index 9a51aa26a1..68cfa079ef 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -17,51 +17,51 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { SharedModule } from '@app/shared/shared.module'; -import { AddEntityDialogComponent } from './entity/add-entity-dialog.component'; -import { EntitiesTableComponent } from './entity/entities-table.component'; -import { DetailsPanelComponent } from './details-panel.component'; -import { EntityDetailsPanelComponent } from './entity/entity-details-panel.component'; -import { ContactComponent } from './contact.component'; -import { AuditLogDetailsDialogComponent } from './audit-log/audit-log-details-dialog.component'; -import { AuditLogTableComponent } from './audit-log/audit-log-table.component'; +import { AddEntityDialogComponent } from '@home/components/entity/add-entity-dialog.component'; +import { EntitiesTableComponent } from '@home/components/entity/entities-table.component'; +import { DetailsPanelComponent } from '@home/components/details-panel.component'; +import { EntityDetailsPanelComponent } from '@home/components/entity/entity-details-panel.component'; +import { ContactComponent } from '@home/components/contact.component'; +import { AuditLogDetailsDialogComponent } from '@home/components/audit-log/audit-log-details-dialog.component'; +import { AuditLogTableComponent } from '@home/components/audit-log/audit-log-table.component'; import { EventTableHeaderComponent } from '@home/components/event/event-table-header.component'; import { EventTableComponent } from '@home/components/event/event-table.component'; import { RelationTableComponent } from '@home/components/relation/relation-table.component'; -import { RelationDialogComponent } from './relation/relation-dialog.component'; +import { RelationDialogComponent } from '@home/components/relation/relation-dialog.component'; import { AlarmTableHeaderComponent } from '@home/components/alarm/alarm-table-header.component'; import { AlarmTableComponent } from '@home/components/alarm/alarm-table.component'; import { AttributeTableComponent } from '@home/components/attribute/attribute-table.component'; -import { AddAttributeDialogComponent } from './attribute/add-attribute-dialog.component'; -import { EditAttributeValuePanelComponent } from './attribute/edit-attribute-value-panel.component'; +import { AddAttributeDialogComponent } from '@home/components/attribute/add-attribute-dialog.component'; +import { EditAttributeValuePanelComponent } from '@home/components/attribute/edit-attribute-value-panel.component'; import { DashboardComponent } from '@home/components/dashboard/dashboard.component'; import { WidgetComponent } from '@home/components/widget/widget.component'; -import { WidgetComponentService } from './widget/widget-component.service'; +import { WidgetComponentService } from '@home/components/widget/widget-component.service'; import { LegendComponent } from '@home/components/widget/legend.component'; import { AliasesEntitySelectPanelComponent } from '@home/components/alias/aliases-entity-select-panel.component'; import { AliasesEntitySelectComponent } from '@home/components/alias/aliases-entity-select.component'; import { WidgetConfigComponent } from '@home/components/widget/widget-config.component'; import { EntityAliasesDialogComponent } from '@home/components/alias/entity-aliases-dialog.component'; -import { EntityFilterViewComponent } from './entity/entity-filter-view.component'; -import { EntityAliasDialogComponent } from './alias/entity-alias-dialog.component'; -import { EntityFilterComponent } from './entity/entity-filter.component'; -import { RelationFiltersComponent } from './relation/relation-filters.component'; -import { EntityAliasSelectComponent } from './alias/entity-alias-select.component'; +import { EntityFilterViewComponent } from '@home/components/entity/entity-filter-view.component'; +import { EntityAliasDialogComponent } from '@home/components/alias/entity-alias-dialog.component'; +import { EntityFilterComponent } from '@home/components/entity/entity-filter.component'; +import { RelationFiltersComponent } from '@home/components/relation/relation-filters.component'; +import { EntityAliasSelectComponent } from '@home/components/alias/entity-alias-select.component'; import { DataKeysComponent } from '@home/components/widget/data-keys.component'; -import { DataKeyConfigDialogComponent } from './widget/data-key-config-dialog.component'; -import { DataKeyConfigComponent } from './widget/data-key-config.component'; -import { LegendConfigPanelComponent } from './widget/legend-config-panel.component'; -import { LegendConfigComponent } from './widget/legend-config.component'; -import { ManageWidgetActionsComponent } from './widget/action/manage-widget-actions.component'; -import { WidgetActionDialogComponent } from './widget/action/widget-action-dialog.component'; -import { CustomActionPrettyResourcesTabsComponent } from './widget/action/custom-action-pretty-resources-tabs.component'; -import { CustomActionPrettyEditorComponent } from './widget/action/custom-action-pretty-editor.component'; -import { CustomDialogService } from './widget/dialog/custom-dialog.service'; -import { CustomDialogContainerComponent } from './widget/dialog/custom-dialog-container.component'; -import { ImportExportService } from './import-export/import-export.service'; -import { ImportDialogComponent } from './import-export/import-dialog.component'; -import { AddWidgetToDashboardDialogComponent } from './attribute/add-widget-to-dashboard-dialog.component'; -import { ImportDialogCsvComponent } from './import-export/import-dialog-csv.component'; -import { TableColumnsAssignmentComponent } from './import-export/table-columns-assignment.component'; +import { DataKeyConfigDialogComponent } from '@home/components/widget/data-key-config-dialog.component'; +import { DataKeyConfigComponent } from '@home/components/widget/data-key-config.component'; +import { LegendConfigPanelComponent } from '@home/components/widget/legend-config-panel.component'; +import { LegendConfigComponent } from '@home/components/widget/legend-config.component'; +import { ManageWidgetActionsComponent } from '@home/components/widget/action/manage-widget-actions.component'; +import { WidgetActionDialogComponent } from '@home/components/widget/action/widget-action-dialog.component'; +import { CustomActionPrettyResourcesTabsComponent } from '@home/components/widget/action/custom-action-pretty-resources-tabs.component'; +import { CustomActionPrettyEditorComponent } from '@home/components/widget/action/custom-action-pretty-editor.component'; +import { CustomDialogService } from '@home/components/widget/dialog/custom-dialog.service'; +import { CustomDialogContainerComponent } from '@home/components/widget/dialog/custom-dialog-container.component'; +import { ImportExportService } from '@home/components/import-export/import-export.service'; +import { ImportDialogComponent } from '@home/components/import-export/import-dialog.component'; +import { AddWidgetToDashboardDialogComponent } from '@home/components/attribute/add-widget-to-dashboard-dialog.component'; +import { ImportDialogCsvComponent } from '@home/components/import-export/import-dialog-csv.component'; +import { TableColumnsAssignmentComponent } from '@home/components/import-export/table-columns-assignment.component'; import { EventContentDialogComponent } from '@home/components/event/event-content-dialog.component'; import { SharedHomeComponentsModule } from '@home/components/shared-home-components.module'; diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index 8135d33587..45c980e986 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -16,10 +16,10 @@ import { NgModule } from '@angular/core'; import { CommonModule, DatePipe } from '@angular/common'; -import { FooterComponent } from './components/footer.component'; -import { LogoComponent } from './components/logo.component'; -import { TbSnackBarComponent, ToastDirective } from './components/toast.directive'; -import { BreadcrumbComponent } from '@app/shared/components/breadcrumb.component'; +import { FooterComponent } from '@shared/components/footer.component'; +import { LogoComponent } from '@shared/components/logo.component'; +import { TbSnackBarComponent, ToastDirective } from '@shared/components/toast.directive'; +import { BreadcrumbComponent } from '@shared/components/breadcrumb.component'; import { NgxFlowModule, FlowInjectionToken } from '@flowjs/ngx-flow'; import { NgxFlowchartModule } from 'ngx-flowchart/dist/ngx-flowchart'; import Flow from '@flowjs/flow.js'; @@ -62,7 +62,7 @@ import { HotkeyModule } from 'angular2-hotkeys'; import { ColorPickerModule } from 'ngx-color-picker'; import { NgxHmCarouselModule } from 'ngx-hm-carousel'; import { UserMenuComponent } from '@shared/components/user-menu.component'; -import { NospacePipe } from './pipe/nospace.pipe'; +import { NospacePipe } from '@shared/pipe/nospace.pipe'; import { TranslateModule } from '@ngx-translate/core'; import { TbCheckboxComponent } from '@shared/components/tb-checkbox.component'; import { HelpComponent } from '@shared/components/help.component'; @@ -80,42 +80,42 @@ import { FullscreenDirective } from '@shared/components/fullscreen.directive'; import { HighlightPipe } from '@shared/pipe/highlight.pipe'; import { DashboardAutocompleteComponent } from '@shared/components/dashboard-autocomplete.component'; import { EntitySubTypeAutocompleteComponent } from '@shared/components/entity/entity-subtype-autocomplete.component'; -import { EntitySubTypeSelectComponent } from './components/entity/entity-subtype-select.component'; -import { EntityAutocompleteComponent } from './components/entity/entity-autocomplete.component'; +import { EntitySubTypeSelectComponent } from '@shared/components/entity/entity-subtype-select.component'; +import { EntityAutocompleteComponent } from '@shared/components/entity/entity-autocomplete.component'; import { EntityListComponent } from '@shared/components/entity/entity-list.component'; -import { EntityTypeSelectComponent } from './components/entity/entity-type-select.component'; -import { EntitySelectComponent } from './components/entity/entity-select.component'; +import { EntityTypeSelectComponent } from '@shared/components/entity/entity-type-select.component'; +import { EntitySelectComponent } from '@shared/components/entity/entity-select.component'; import { DatetimeComponent } from '@shared/components/time/datetime.component'; -import { EntityKeysListComponent } from './components/entity/entity-keys-list.component'; -import { SocialSharePanelComponent } from './components/socialshare-panel.component'; +import { EntityKeysListComponent } from '@shared/components/entity/entity-keys-list.component'; +import { SocialSharePanelComponent } from '@shared/components/socialshare-panel.component'; import { RelationTypeAutocompleteComponent } from '@shared/components/relation/relation-type-autocomplete.component'; -import { EntityListSelectComponent } from './components/entity/entity-list-select.component'; -import { JsonObjectEditComponent } from './components/json-object-edit.component'; +import { EntityListSelectComponent } from '@shared/components/entity/entity-list-select.component'; +import { JsonObjectEditComponent } from '@shared/components/json-object-edit.component'; import { FooterFabButtonsComponent } from '@shared/components/footer-fab-buttons.component'; -import { CircularProgressDirective } from './components/circular-progress.directive'; +import { CircularProgressDirective } from '@shared/components/circular-progress.directive'; import { MatSpinner } from '@angular/material/progress-spinner'; -import { FabToolbarComponent, FabActionsDirective, FabTriggerDirective } from './components/fab-toolbar.component'; +import { FabToolbarComponent, FabActionsDirective, FabTriggerDirective } from '@shared/components/fab-toolbar.component'; import { DashboardSelectPanelComponent } from '@shared/components/dashboard-select-panel.component'; import { DashboardSelectComponent } from '@shared/components/dashboard-select.component'; -import { WidgetsBundleSelectComponent } from './components/widgets-bundle-select.component'; -import { KeyboardShortcutPipe } from './pipe/keyboard-shortcut.pipe'; -import { TbErrorComponent } from './components/tb-error.component'; -import { EntityTypeListComponent } from './components/entity/entity-type-list.component'; -import { EntitySubTypeListComponent } from './components/entity/entity-subtype-list.component'; -import { TruncatePipe } from './pipe/truncate.pipe'; -import { ColorPickerDialogComponent } from './components/dialog/color-picker-dialog.component'; -import { MatChipDraggableDirective } from './components/mat-chip-draggable.directive'; -import { ColorInputComponent } from './components/color-input.component'; -import { JsFuncComponent } from './components/js-func.component'; -import { JsonFormComponent } from './components/json-form/json-form.component'; +import { WidgetsBundleSelectComponent } from '@shared/components/widgets-bundle-select.component'; +import { KeyboardShortcutPipe } from '@shared/pipe/keyboard-shortcut.pipe'; +import { TbErrorComponent } from '@shared/components/tb-error.component'; +import { EntityTypeListComponent } from '@shared/components/entity/entity-type-list.component'; +import { EntitySubTypeListComponent } from '@shared/components/entity/entity-subtype-list.component'; +import { TruncatePipe } from '@shared/pipe/truncate.pipe'; +import { ColorPickerDialogComponent } from '@shared/components/dialog/color-picker-dialog.component'; +import { MatChipDraggableDirective } from '@shared/components/mat-chip-draggable.directive'; +import { ColorInputComponent } from '@shared/components/color-input.component'; +import { JsFuncComponent } from '@shared/components/js-func.component'; +import { JsonFormComponent } from '@shared/components/json-form/json-form.component'; import { MaterialIconsDialogComponent } from '@shared/components/dialog/material-icons-dialog.component'; import { MaterialIconSelectComponent } from '@shared/components/material-icon-select.component'; -import { ImageInputComponent } from './components/image-input.component'; -import { FileInputComponent } from './components/file-input.component'; +import { ImageInputComponent } from '@shared/components/image-input.component'; +import { FileInputComponent } from '@shared/components/file-input.component'; import { NodeScriptTestDialogComponent } from '@shared/components/dialog/node-script-test-dialog.component'; -import { MessageTypeAutocompleteComponent } from './components/message-type-autocomplete.component'; -import { JsonContentComponent } from './components/json-content.component'; -import { KeyValMapComponent } from './components/kv-map.component'; +import { MessageTypeAutocompleteComponent } from '@shared/components/message-type-autocomplete.component'; +import { JsonContentComponent } from '@shared/components/json-content.component'; +import { KeyValMapComponent } from '@shared/components/kv-map.component'; import { TbCheatSheetComponent } from '@shared/components/cheatsheet.component'; import { TbHotkeysDirective } from '@shared/components/hotkeys.directive'; import { NavTreeComponent } from '@shared/components/nav-tree.component'; From 1edf87336d7237c6ff03aad6dd06293b03c57684 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 11 Feb 2020 10:56:43 +0200 Subject: [PATCH 101/133] UI Dialog service refactoring --- ui-ngx/src/app/core/core.module.ts | 8 -------- ui-ngx/src/app/core/services/dialog.service.ts | 6 +++--- .../components}/dialog/alert-dialog.component.html | 0 .../components}/dialog/alert-dialog.component.scss | 0 .../components}/dialog/alert-dialog.component.ts | 0 .../components}/dialog/confirm-dialog.component.html | 0 .../components}/dialog/confirm-dialog.component.scss | 0 .../components}/dialog/confirm-dialog.component.ts | 0 .../components}/dialog/todo-dialog.component.html | 0 .../components}/dialog/todo-dialog.component.scss | 0 .../components}/dialog/todo-dialog.component.ts | 0 ui-ngx/src/app/shared/shared.module.ts | 6 ++++++ 12 files changed, 9 insertions(+), 11 deletions(-) rename ui-ngx/src/app/{core/services => shared/components}/dialog/alert-dialog.component.html (100%) rename ui-ngx/src/app/{core/services => shared/components}/dialog/alert-dialog.component.scss (100%) rename ui-ngx/src/app/{core/services => shared/components}/dialog/alert-dialog.component.ts (100%) rename ui-ngx/src/app/{core/services => shared/components}/dialog/confirm-dialog.component.html (100%) rename ui-ngx/src/app/{core/services => shared/components}/dialog/confirm-dialog.component.scss (100%) rename ui-ngx/src/app/{core/services => shared/components}/dialog/confirm-dialog.component.ts (100%) rename ui-ngx/src/app/{core/services => shared/components}/dialog/todo-dialog.component.html (100%) rename ui-ngx/src/app/{core/services => shared/components}/dialog/todo-dialog.component.scss (100%) rename ui-ngx/src/app/{core/services => shared/components}/dialog/todo-dialog.component.ts (100%) diff --git a/ui-ngx/src/app/core/core.module.ts b/ui-ngx/src/app/core/core.module.ts index 69d8b9b19e..8e6f550172 100644 --- a/ui-ngx/src/app/core/core.module.ts +++ b/ui-ngx/src/app/core/core.module.ts @@ -35,12 +35,9 @@ import { TbMissingTranslationHandler } from './translate/missing-translate-handl import { MatButtonModule } from '@angular/material/button'; import { MatDialogModule } from '@angular/material/dialog'; import { MatSnackBarModule } from '@angular/material/snack-bar'; -import { ConfirmDialogComponent } from '@core/services/dialog/confirm-dialog.component'; import { FlexLayoutModule } from '@angular/flex-layout'; import { TranslateDefaultCompiler } from '@core/translate/translate-default-compiler'; -import { AlertDialogComponent } from '@core/services/dialog/alert-dialog.component'; import { WINDOW_PROVIDERS } from '@core/services/window.service'; -import {TodoDialogComponent} from '@core/services/dialog/todo-dialog.component'; import { HotkeyModule } from 'angular2-hotkeys'; export function HttpLoaderFactory(http: HttpClient) { @@ -48,11 +45,6 @@ export function HttpLoaderFactory(http: HttpClient) { } @NgModule({ - declarations: [ - ConfirmDialogComponent, - AlertDialogComponent, - TodoDialogComponent - ], imports: [ CommonModule, HttpClientModule, diff --git a/ui-ngx/src/app/core/services/dialog.service.ts b/ui-ngx/src/app/core/services/dialog.service.ts index 5fc7d8293f..3b52e6fec6 100644 --- a/ui-ngx/src/app/core/services/dialog.service.ts +++ b/ui-ngx/src/app/core/services/dialog.service.ts @@ -17,10 +17,7 @@ import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import { MatDialog, MatDialogConfig } from '@angular/material/dialog'; -import { ConfirmDialogComponent } from '@core/services/dialog/confirm-dialog.component'; import { TranslateService } from '@ngx-translate/core'; -import { AlertDialogComponent } from '@core/services/dialog/alert-dialog.component'; -import { TodoDialogComponent } from '@core/services/dialog/todo-dialog.component'; import { AuthService } from '@core/auth/auth.service'; import { ColorPickerDialogComponent, @@ -30,6 +27,9 @@ import { MaterialIconsDialogComponent, MaterialIconsDialogData } from '@shared/components/dialog/material-icons-dialog.component'; +import { ConfirmDialogComponent } from '@shared/components/dialog/confirm-dialog.component'; +import { AlertDialogComponent } from '@shared/components/dialog/alert-dialog.component'; +import { TodoDialogComponent } from '@shared/components/dialog/todo-dialog.component'; @Injectable( { diff --git a/ui-ngx/src/app/core/services/dialog/alert-dialog.component.html b/ui-ngx/src/app/shared/components/dialog/alert-dialog.component.html similarity index 100% rename from ui-ngx/src/app/core/services/dialog/alert-dialog.component.html rename to ui-ngx/src/app/shared/components/dialog/alert-dialog.component.html diff --git a/ui-ngx/src/app/core/services/dialog/alert-dialog.component.scss b/ui-ngx/src/app/shared/components/dialog/alert-dialog.component.scss similarity index 100% rename from ui-ngx/src/app/core/services/dialog/alert-dialog.component.scss rename to ui-ngx/src/app/shared/components/dialog/alert-dialog.component.scss diff --git a/ui-ngx/src/app/core/services/dialog/alert-dialog.component.ts b/ui-ngx/src/app/shared/components/dialog/alert-dialog.component.ts similarity index 100% rename from ui-ngx/src/app/core/services/dialog/alert-dialog.component.ts rename to ui-ngx/src/app/shared/components/dialog/alert-dialog.component.ts diff --git a/ui-ngx/src/app/core/services/dialog/confirm-dialog.component.html b/ui-ngx/src/app/shared/components/dialog/confirm-dialog.component.html similarity index 100% rename from ui-ngx/src/app/core/services/dialog/confirm-dialog.component.html rename to ui-ngx/src/app/shared/components/dialog/confirm-dialog.component.html diff --git a/ui-ngx/src/app/core/services/dialog/confirm-dialog.component.scss b/ui-ngx/src/app/shared/components/dialog/confirm-dialog.component.scss similarity index 100% rename from ui-ngx/src/app/core/services/dialog/confirm-dialog.component.scss rename to ui-ngx/src/app/shared/components/dialog/confirm-dialog.component.scss diff --git a/ui-ngx/src/app/core/services/dialog/confirm-dialog.component.ts b/ui-ngx/src/app/shared/components/dialog/confirm-dialog.component.ts similarity index 100% rename from ui-ngx/src/app/core/services/dialog/confirm-dialog.component.ts rename to ui-ngx/src/app/shared/components/dialog/confirm-dialog.component.ts diff --git a/ui-ngx/src/app/core/services/dialog/todo-dialog.component.html b/ui-ngx/src/app/shared/components/dialog/todo-dialog.component.html similarity index 100% rename from ui-ngx/src/app/core/services/dialog/todo-dialog.component.html rename to ui-ngx/src/app/shared/components/dialog/todo-dialog.component.html diff --git a/ui-ngx/src/app/core/services/dialog/todo-dialog.component.scss b/ui-ngx/src/app/shared/components/dialog/todo-dialog.component.scss similarity index 100% rename from ui-ngx/src/app/core/services/dialog/todo-dialog.component.scss rename to ui-ngx/src/app/shared/components/dialog/todo-dialog.component.scss diff --git a/ui-ngx/src/app/core/services/dialog/todo-dialog.component.ts b/ui-ngx/src/app/shared/components/dialog/todo-dialog.component.ts similarity index 100% rename from ui-ngx/src/app/core/services/dialog/todo-dialog.component.ts rename to ui-ngx/src/app/shared/components/dialog/todo-dialog.component.ts diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index 45c980e986..a00b7c191d 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -108,6 +108,9 @@ import { MatChipDraggableDirective } from '@shared/components/mat-chip-draggable import { ColorInputComponent } from '@shared/components/color-input.component'; import { JsFuncComponent } from '@shared/components/js-func.component'; import { JsonFormComponent } from '@shared/components/json-form/json-form.component'; +import { ConfirmDialogComponent } from '@shared/components/dialog/confirm-dialog.component'; +import { AlertDialogComponent } from '@shared/components/dialog/alert-dialog.component'; +import { TodoDialogComponent } from '@shared/components/dialog/todo-dialog.component'; import { MaterialIconsDialogComponent } from '@shared/components/dialog/material-icons-dialog.component'; import { MaterialIconSelectComponent } from '@shared/components/material-icon-select.component'; import { ImageInputComponent } from '@shared/components/image-input.component'; @@ -178,6 +181,9 @@ import { LedLightComponent } from '@shared/components/led-light.component'; FabActionsDirective, FabToolbarComponent, WidgetsBundleSelectComponent, + ConfirmDialogComponent, + AlertDialogComponent, + TodoDialogComponent, ColorPickerDialogComponent, MaterialIconsDialogComponent, ColorInputComponent, From 654bd0296cb43af5da0bf0beecf3bdea032550cf Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 11 Feb 2020 11:01:18 +0200 Subject: [PATCH 102/133] Optimize imports --- .../add-attribute-dialog.component.ts | 11 +---------- .../edit-attribute-value-panel.component.ts | 17 +---------------- .../relation/relation-dialog.component.ts | 4 ++-- .../data-key-config-dialog.component.ts | 19 ++----------------- .../dialog/color-picker-dialog.component.ts | 16 ++-------------- .../node-script-test-dialog.component.ts | 13 +++++++------ 6 files changed, 15 insertions(+), 65 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.ts b/ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.ts index 88607d55c2..277c7fdbdc 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.ts @@ -14,22 +14,13 @@ /// limitations under the License. /// -import { Component, Inject, OnInit, SkipSelf, ViewChild } from '@angular/core'; +import { Component, Inject, OnInit, SkipSelf } 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 { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; -import { - CONTAINS_TYPE, - EntityRelation, - EntitySearchDirection, - RelationTypeGroup -} from '@shared/models/relation.models'; -import { EntityRelationService } from '@core/http/entity-relation.service'; import { EntityId } from '@shared/models/id/entity-id'; -import { forkJoin, Observable } from 'rxjs'; -import { JsonObjectEditComponent } from '@app/shared/components/json-object-edit.component'; import { Router } from '@angular/router'; import { DialogComponent } from '@app/shared/components/dialog.component'; import { AttributeData, AttributeScope } from '@shared/models/telemetry/telemetry.models'; diff --git a/ui-ngx/src/app/modules/home/components/attribute/edit-attribute-value-panel.component.ts b/ui-ngx/src/app/modules/home/components/attribute/edit-attribute-value-panel.component.ts index ef3e185479..1339382b83 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/edit-attribute-value-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/attribute/edit-attribute-value-panel.component.ts @@ -14,26 +14,11 @@ /// limitations under the License. /// -import { Component, Inject, InjectionToken, OnInit, SkipSelf, ViewChild } from '@angular/core'; +import { Component, Inject, InjectionToken, OnInit, SkipSelf } 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 { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; -import { - CONTAINS_TYPE, - EntityRelation, - EntitySearchDirection, - RelationTypeGroup -} from '@shared/models/relation.models'; -import { EntityRelationService } from '@core/http/entity-relation.service'; -import { EntityId } from '@shared/models/id/entity-id'; -import { forkJoin, Observable } from 'rxjs'; -import { JsonObjectEditComponent } from '@app/shared/components/json-object-edit.component'; -import { Router } from '@angular/router'; -import { DialogComponent } from '@app/shared/components/dialog.component'; -import { AttributeData, AttributeScope } from '@shared/models/telemetry/telemetry.models'; -import { AttributeService } from '@core/http/attribute.service'; import { PageComponent } from '@shared/components/page.component'; import { OverlayRef } from '@angular/cdk/overlay'; diff --git a/ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.ts b/ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.ts index 91a6356146..d96c0770e0 100644 --- a/ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.ts @@ -29,9 +29,9 @@ import { import { EntityRelationService } from '@core/http/entity-relation.service'; import { EntityId } from '@shared/models/id/entity-id'; import { forkJoin, Observable } from 'rxjs'; -import { JsonObjectEditComponent } from '@app/shared/components/json-object-edit.component'; +import { JsonObjectEditComponent } from '@shared/components/json-object-edit.component'; import { Router } from '@angular/router'; -import { DialogComponent } from '@app/shared/components/dialog.component'; +import { DialogComponent } from '@shared/components/dialog.component'; export interface RelationDialogData { isAdd: boolean; diff --git a/ui-ngx/src/app/modules/home/components/widget/data-key-config-dialog.component.ts b/ui-ngx/src/app/modules/home/components/widget/data-key-config-dialog.component.ts index 00280e6b12..7fbe987d01 100644 --- a/ui-ngx/src/app/modules/home/components/widget/data-key-config-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/data-key-config-dialog.component.ts @@ -20,25 +20,10 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; -import { - CONTAINS_TYPE, - EntityRelation, - EntitySearchDirection, - RelationTypeGroup -} from '@shared/models/relation.models'; -import { EntityRelationService } from '@core/http/entity-relation.service'; -import { EntityId } from '@shared/models/id/entity-id'; -import { forkJoin, Observable } from 'rxjs'; -import { JsonObjectEditComponent } from '@app/shared/components/json-object-edit.component'; import { Router } from '@angular/router'; -import { DialogComponent } from '@app/shared/components/dialog.component'; -import { AttributeData, AttributeScope } from '@shared/models/telemetry/telemetry.models'; -import { AttributeService } from '@core/http/attribute.service'; -import { DataKey } from '@app/shared/models/widget.models'; -import { EntityAlias } from '@shared/models/alias.models'; -import { IAliasController } from '@core/api/widget-api.models'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { DataKey } from '@shared/models/widget.models'; import { DataKeysCallbacks } from './data-keys.component.models'; -import { JsFuncComponent } from '@shared/components/js-func.component'; import { DataKeyConfigComponent } from '@home/components/widget/data-key-config.component'; export interface DataKeyConfigDialogData { 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 23b89e8536..dce9b69bdf 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,26 +14,14 @@ /// limitations under the License. /// -import { Component, Inject, OnInit, SkipSelf, ViewChild } from '@angular/core'; +import { Component, Inject, OnInit, SkipSelf } 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 { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; -import { - CONTAINS_TYPE, - EntityRelation, - EntitySearchDirection, - RelationTypeGroup -} from '@shared/models/relation.models'; -import { EntityRelationService } from '@core/http/entity-relation.service'; -import { EntityId } from '@shared/models/id/entity-id'; -import { forkJoin, Observable } from 'rxjs'; -import { JsonObjectEditComponent } from '@app/shared/components/json-object-edit.component'; import { Router } from '@angular/router'; -import { DialogComponent } from '@app/shared/components/dialog.component'; -import { AttributeData, AttributeScope } from '@shared/models/telemetry/telemetry.models'; -import { AttributeService } from '@core/http/attribute.service'; +import { DialogComponent } from '@shared/components/dialog.component'; export interface ColorPickerDialogData { color: string; diff --git a/ui-ngx/src/app/shared/components/dialog/node-script-test-dialog.component.ts b/ui-ngx/src/app/shared/components/dialog/node-script-test-dialog.component.ts index 5359bf4d19..87610b7f5f 100644 --- a/ui-ngx/src/app/shared/components/dialog/node-script-test-dialog.component.ts +++ b/ui-ngx/src/app/shared/components/dialog/node-script-test-dialog.component.ts @@ -17,11 +17,13 @@ import { AfterViewInit, Component, - ElementRef, HostBinding, + ElementRef, + HostBinding, Inject, OnInit, QueryList, - SkipSelf, ViewChild, + SkipSelf, + ViewChild, ViewChildren, ViewEncapsulation } from '@angular/core'; @@ -30,15 +32,14 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; -import { combineLatest, never, Observable, of, throwError, NEVER } from 'rxjs'; +import { NEVER, Observable, of } from 'rxjs'; import { Router } from '@angular/router'; -import { DialogComponent } from '@app/shared/components/dialog.component'; +import { DialogComponent } from '@shared/components/dialog.component'; import { ContentType } from '@shared/models/constants'; -import { JsonObjectEditComponent } from '@shared/components/json-object-edit.component'; import { JsonContentComponent } from '@shared/components/json-content.component'; import { TestScriptInputParams } from '@shared/models/rule-node.models'; import { RuleChainService } from '@core/http/rule-chain.service'; -import { map, mergeMap } from 'rxjs/operators'; +import { mergeMap } from 'rxjs/operators'; import { ActionNotificationShow } from '@core/notification/notification.actions'; export interface NodeScriptTestDialogData { From 9e561eaf05985bcbe8a724b631edd369f1a7f064 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 11 Feb 2020 11:15:06 +0200 Subject: [PATCH 103/133] Optimize imports --- .../home/components/widget/data-keys.component.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/data-keys.component.ts b/ui-ngx/src/app/modules/home/components/widget/data-keys.component.ts index 4c6951fe9f..beafc9d1e8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/data-keys.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/data-keys.component.ts @@ -16,22 +16,25 @@ import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes'; import { - AfterViewInit, ChangeDetectionStrategy, + AfterViewInit, Component, ElementRef, forwardRef, Input, OnChanges, OnInit, - SimpleChanges, SkipSelf, + SimpleChanges, + SkipSelf, ViewChild } from '@angular/core'; import { ControlValueAccessor, FormBuilder, FormControl, - FormGroup, FormGroupDirective, - NG_VALUE_ACCESSOR, NgForm, + FormGroup, + FormGroupDirective, + NG_VALUE_ACCESSOR, + NgForm, Validators } from '@angular/forms'; import { Observable, of } from 'rxjs'; @@ -51,10 +54,6 @@ 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 { - ColorPickerDialogComponent, - ColorPickerDialogData -} from '@shared/components/dialog/color-picker-dialog.component'; import { MatDialog } from '@angular/material/dialog'; import { DataKeyConfigDialogComponent, From 75d7042d600f4fc096660bf545b8315e815f1e46 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 11 Feb 2020 11:20:08 +0200 Subject: [PATCH 104/133] Optimize imports --- ui-ngx/src/app/shared/shared.module.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index a00b7c191d..9bf97a2707 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -334,6 +334,9 @@ import { LedLightComponent } from '@shared/components/led-light.component'; ColorPickerModule, NgxHmCarouselModule, NgxFlowchartModule, + ConfirmDialogComponent, + AlertDialogComponent, + TodoDialogComponent, ColorPickerDialogComponent, MaterialIconsDialogComponent, ColorInputComponent, From 3a95d9acdfbb62a41aa6ecc79c49c72d728a052f Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 11 Feb 2020 11:24:32 +0200 Subject: [PATCH 105/133] Optimize imports --- ui-ngx/src/app/shared/components/dialog.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/shared/components/dialog.component.ts b/ui-ngx/src/app/shared/components/dialog.component.ts index c244d48803..82473e0c0c 100644 --- a/ui-ngx/src/app/shared/components/dialog.component.ts +++ b/ui-ngx/src/app/shared/components/dialog.component.ts @@ -15,7 +15,7 @@ /// import { OnDestroy } from '@angular/core'; -import { PageComponent } from './page.component'; +import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { MatDialogRef } from '@angular/material/dialog'; From 50f6377ee56b99c45b1e83accef065d91a0ba384 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 11 Feb 2020 12:06:51 +0200 Subject: [PATCH 106/133] Optimize imports --- .../attribute/add-widget-to-dashboard-dialog.component.ts | 2 +- .../dashboard}/select-target-layout-dialog.component.html | 0 .../dashboard}/select-target-layout-dialog.component.ts | 0 .../app/modules/home/components/home-components.module.ts | 5 ++++- .../modules/home/pages/dashboard/dashboard-page.component.ts | 2 +- .../src/app/modules/home/pages/dashboard/dashboard.module.ts | 2 -- 6 files changed, 6 insertions(+), 5 deletions(-) rename ui-ngx/src/app/modules/home/{pages/dashboard/layout => components/dashboard}/select-target-layout-dialog.component.html (100%) rename ui-ngx/src/app/modules/home/{pages/dashboard/layout => components/dashboard}/select-target-layout-dialog.component.ts (100%) diff --git a/ui-ngx/src/app/modules/home/components/attribute/add-widget-to-dashboard-dialog.component.ts b/ui-ngx/src/app/modules/home/components/attribute/add-widget-to-dashboard-dialog.component.ts index bf96840d5c..237482dda5 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/add-widget-to-dashboard-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/attribute/add-widget-to-dashboard-dialog.component.ts @@ -37,7 +37,7 @@ import { EntityId } from '@app/shared/models/id/entity-id'; import { Widget } from '@app/shared/models/widget.models'; import { DashboardService } from '@core/http/dashboard.service'; import { forkJoin, Observable, of } from 'rxjs'; -import { SelectTargetLayoutDialogComponent } from '@home/pages/dashboard/layout/select-target-layout-dialog.component'; +import { SelectTargetLayoutDialogComponent } from '@home/components/dashboard/select-target-layout-dialog.component'; import { MatDialog } from '@angular/material/dialog'; import { SelectTargetStateDialogComponent, diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/layout/select-target-layout-dialog.component.html b/ui-ngx/src/app/modules/home/components/dashboard/select-target-layout-dialog.component.html similarity index 100% rename from ui-ngx/src/app/modules/home/pages/dashboard/layout/select-target-layout-dialog.component.html rename to ui-ngx/src/app/modules/home/components/dashboard/select-target-layout-dialog.component.html diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/layout/select-target-layout-dialog.component.ts b/ui-ngx/src/app/modules/home/components/dashboard/select-target-layout-dialog.component.ts similarity index 100% rename from ui-ngx/src/app/modules/home/pages/dashboard/layout/select-target-layout-dialog.component.ts rename to ui-ngx/src/app/modules/home/components/dashboard/select-target-layout-dialog.component.ts diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index 68cfa079ef..6403c3b12b 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -64,6 +64,7 @@ import { ImportDialogCsvComponent } from '@home/components/import-export/import- import { TableColumnsAssignmentComponent } from '@home/components/import-export/table-columns-assignment.component'; import { EventContentDialogComponent } from '@home/components/event/event-content-dialog.component'; import { SharedHomeComponentsModule } from '@home/components/shared-home-components.module'; +import { SelectTargetLayoutDialogComponent } from '@home/components/dashboard/select-target-layout-dialog.component'; @NgModule({ declarations: @@ -109,6 +110,7 @@ import { SharedHomeComponentsModule } from '@home/components/shared-home-compone CustomDialogContainerComponent, ImportDialogComponent, ImportDialogCsvComponent, + SelectTargetLayoutDialogComponent, AddWidgetToDashboardDialogComponent, TableColumnsAssignmentComponent ], @@ -150,7 +152,8 @@ import { SharedHomeComponentsModule } from '@home/components/shared-home-compone CustomDialogContainerComponent, ImportDialogComponent, ImportDialogCsvComponent, - TableColumnsAssignmentComponent + TableColumnsAssignmentComponent, + SelectTargetLayoutDialogComponent ], providers: [ WidgetComponentService, diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.ts index 54ce1417c5..258c7b767f 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.ts @@ -74,7 +74,7 @@ import { ManageDashboardLayoutsDialogComponent, ManageDashboardLayoutsDialogData } from '@home/pages/dashboard/layout/manage-dashboard-layouts-dialog.component'; -import { SelectTargetLayoutDialogComponent } from '@home/pages/dashboard/layout/select-target-layout-dialog.component'; +import { SelectTargetLayoutDialogComponent } from '@home/components/dashboard/select-target-layout-dialog.component'; import { DashboardSettingsDialogComponent, DashboardSettingsDialogData diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard.module.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard.module.ts index e5f1388c6a..37797282a6 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard.module.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard.module.ts @@ -32,7 +32,6 @@ import { EditWidgetComponent } from './edit-widget.component'; import { DashboardWidgetSelectComponent } from './dashboard-widget-select.component'; import { AddWidgetDialogComponent } from './add-widget-dialog.component'; import { ManageDashboardLayoutsDialogComponent } from './layout/manage-dashboard-layouts-dialog.component'; -import { SelectTargetLayoutDialogComponent } from './layout/select-target-layout-dialog.component'; import { DashboardSettingsDialogComponent } from './dashboard-settings-dialog.component'; import { ManageDashboardStatesDialogComponent } from './states/manage-dashboard-states-dialog.component'; import { DashboardStateDialogComponent } from './states/dashboard-state-dialog.component'; @@ -51,7 +50,6 @@ import { SelectTargetStateDialogComponent } from './states/select-target-state-d DashboardWidgetSelectComponent, AddWidgetDialogComponent, ManageDashboardLayoutsDialogComponent, - SelectTargetLayoutDialogComponent, DashboardSettingsDialogComponent, ManageDashboardStatesDialogComponent, DashboardStateDialogComponent, From a1f157bcded9bbf4fb9529035f2d359eb5370df8 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 11 Feb 2020 12:10:08 +0200 Subject: [PATCH 107/133] Optimize imports --- .../layout => components/dashboard}/layout-button.scss | 0 .../layout/manage-dashboard-layouts-dialog.component.ts | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename ui-ngx/src/app/modules/home/{pages/dashboard/layout => components/dashboard}/layout-button.scss (100%) diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/layout/layout-button.scss b/ui-ngx/src/app/modules/home/components/dashboard/layout-button.scss similarity index 100% rename from ui-ngx/src/app/modules/home/pages/dashboard/layout/layout-button.scss rename to ui-ngx/src/app/modules/home/components/dashboard/layout-button.scss diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/layout/manage-dashboard-layouts-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/layout/manage-dashboard-layouts-dialog.component.ts index 16b965f3dc..43435da249 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/layout/manage-dashboard-layouts-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/layout/manage-dashboard-layouts-dialog.component.ts @@ -45,7 +45,7 @@ export interface ManageDashboardLayoutsDialogData { selector: 'tb-manage-dashboard-layouts-dialog', templateUrl: './manage-dashboard-layouts-dialog.component.html', providers: [{provide: ErrorStateMatcher, useExisting: ManageDashboardLayoutsDialogComponent}], - styleUrls: ['./layout-button.scss'] + styleUrls: ['../../../components/dashboard/layout-button.scss'] }) export class ManageDashboardLayoutsDialogComponent extends DialogComponent implements OnInit, ErrorStateMatcher { From 90047abc3332594643921edf8c6e63f7887f5f56 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 11 Feb 2020 12:14:44 +0200 Subject: [PATCH 108/133] Optimize imports --- .../attribute/add-widget-to-dashboard-dialog.component.ts | 2 +- .../dashboard}/select-target-state-dialog.component.html | 0 .../dashboard}/select-target-state-dialog.component.ts | 0 .../app/modules/home/components/home-components.module.ts | 5 ++++- .../src/app/modules/home/pages/dashboard/dashboard.module.ts | 4 +--- 5 files changed, 6 insertions(+), 5 deletions(-) rename ui-ngx/src/app/modules/home/{pages/dashboard/states => components/dashboard}/select-target-state-dialog.component.html (100%) rename ui-ngx/src/app/modules/home/{pages/dashboard/states => components/dashboard}/select-target-state-dialog.component.ts (100%) diff --git a/ui-ngx/src/app/modules/home/components/attribute/add-widget-to-dashboard-dialog.component.ts b/ui-ngx/src/app/modules/home/components/attribute/add-widget-to-dashboard-dialog.component.ts index 237482dda5..e219cf5a1a 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/add-widget-to-dashboard-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/attribute/add-widget-to-dashboard-dialog.component.ts @@ -42,7 +42,7 @@ import { MatDialog } from '@angular/material/dialog'; import { SelectTargetStateDialogComponent, SelectTargetStateDialogData -} from '@home/pages/dashboard/states/select-target-state-dialog.component'; +} from '@home/components/dashboard/select-target-state-dialog.component'; import { mergeMap, map } from 'rxjs/operators'; import { AliasesInfo } from '@shared/models/alias.models'; import { ItemBufferService } from '@core/services/item-buffer.service'; diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/select-target-state-dialog.component.html b/ui-ngx/src/app/modules/home/components/dashboard/select-target-state-dialog.component.html similarity index 100% rename from ui-ngx/src/app/modules/home/pages/dashboard/states/select-target-state-dialog.component.html rename to ui-ngx/src/app/modules/home/components/dashboard/select-target-state-dialog.component.html diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/select-target-state-dialog.component.ts b/ui-ngx/src/app/modules/home/components/dashboard/select-target-state-dialog.component.ts similarity index 100% rename from ui-ngx/src/app/modules/home/pages/dashboard/states/select-target-state-dialog.component.ts rename to ui-ngx/src/app/modules/home/components/dashboard/select-target-state-dialog.component.ts diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index 6403c3b12b..23ae8c7b29 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -65,6 +65,7 @@ import { TableColumnsAssignmentComponent } from '@home/components/import-export/ import { EventContentDialogComponent } from '@home/components/event/event-content-dialog.component'; import { SharedHomeComponentsModule } from '@home/components/shared-home-components.module'; import { SelectTargetLayoutDialogComponent } from '@home/components/dashboard/select-target-layout-dialog.component'; +import { SelectTargetStateDialogComponent } from '@home/components/dashboard/select-target-state-dialog.component'; @NgModule({ declarations: @@ -111,6 +112,7 @@ import { SelectTargetLayoutDialogComponent } from '@home/components/dashboard/se ImportDialogComponent, ImportDialogCsvComponent, SelectTargetLayoutDialogComponent, + SelectTargetStateDialogComponent, AddWidgetToDashboardDialogComponent, TableColumnsAssignmentComponent ], @@ -153,7 +155,8 @@ import { SelectTargetLayoutDialogComponent } from '@home/components/dashboard/se ImportDialogComponent, ImportDialogCsvComponent, TableColumnsAssignmentComponent, - SelectTargetLayoutDialogComponent + SelectTargetLayoutDialogComponent, + SelectTargetStateDialogComponent ], providers: [ WidgetComponentService, diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard.module.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard.module.ts index 37797282a6..985456bfc8 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard.module.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard.module.ts @@ -35,7 +35,6 @@ import { ManageDashboardLayoutsDialogComponent } from './layout/manage-dashboard import { DashboardSettingsDialogComponent } from './dashboard-settings-dialog.component'; import { ManageDashboardStatesDialogComponent } from './states/manage-dashboard-states-dialog.component'; import { DashboardStateDialogComponent } from './states/dashboard-state-dialog.component'; -import { SelectTargetStateDialogComponent } from './states/select-target-state-dialog.component'; @NgModule({ declarations: [ @@ -52,8 +51,7 @@ import { SelectTargetStateDialogComponent } from './states/select-target-state-d ManageDashboardLayoutsDialogComponent, DashboardSettingsDialogComponent, ManageDashboardStatesDialogComponent, - DashboardStateDialogComponent, - SelectTargetStateDialogComponent + DashboardStateDialogComponent ], imports: [ CommonModule, From 0c3feb4ecdba7fb19ee4f37c146c26bcaefceb22 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 11 Feb 2020 12:52:51 +0200 Subject: [PATCH 109/133] Optimize imports --- .../dialog/material-icons-dialog.component.ts | 10 ++++------ ui-ngx/src/app/shared/components/public-api.ts | 2 ++ 2 files changed, 6 insertions(+), 6 deletions(-) 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 8b755df513..5cb0ce20c0 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,16 @@ /// limitations under the License. /// -import { Component, Inject, OnInit, QueryList, ViewChildren, TemplateRef, AfterViewInit } from '@angular/core'; +import { AfterViewInit, Component, Inject, OnInit, QueryList, ViewChildren } 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 '@app/shared/components/dialog.component'; +import { DialogComponent } from '@shared/components/dialog.component'; import { UtilsService } from '@core/services/utils.service'; import { FormControl } from '@angular/forms'; -import { Observable, of, merge, noop } from 'rxjs'; -import { delay, map, mergeMap, share, startWith, tap, mapTo } from 'rxjs/operators'; -import { DashboardInfo } from '@shared/models/dashboard.models'; -import { MatTab } from '@angular/material/tabs'; +import { merge, Observable, of } from 'rxjs'; +import { delay, map, mapTo, mergeMap, share, startWith, tap } from 'rxjs/operators'; export interface MaterialIconsDialogData { icon: string; diff --git a/ui-ngx/src/app/shared/components/public-api.ts b/ui-ngx/src/app/shared/components/public-api.ts index 1672fb40e1..72c2e790dd 100644 --- a/ui-ngx/src/app/shared/components/public-api.ts +++ b/ui-ngx/src/app/shared/components/public-api.ts @@ -14,5 +14,7 @@ /// limitations under the License. /// +export * from './dialog/color-picker-dialog.component'; +export * from './dialog/material-icons-dialog.component'; export * from './page.component'; export * from './js-func.component'; From 4c5d82968eac02056bce8bd5167ae373d27ccc67 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 11 Feb 2020 20:03:37 +0200 Subject: [PATCH 110/133] Rule nodes UI migration --- .../static/rulenode/rulenode-core-config.js | 4 +- .../src/app/core/http/rule-chain.service.ts | 4 +- .../app/core/services/resources.service.ts | 64 +++++++++++++------ .../rulechain/rulechain-routing.module.ts | 6 +- 4 files changed, 53 insertions(+), 25 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js index 91cf4cdfaa..4133c3c8f8 100644 --- a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js +++ b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js @@ -1,4 +1,4 @@ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@angular/core"),require("@angular/common"),require("@ngx-translate/core"),require("@shared/public-api"),require("@ngrx/store"),require("@angular/forms"),require("@core/public-api"),require("@angular/cdk/keycodes"),require("@home/components/public-api"),require("@angular/cdk/coercion"),require("@angular/material"),require("rxjs"),require("rxjs/operators")):"function"==typeof define&&define.amd?define("rulenode-core-config",["exports","@angular/core","@angular/common","@ngx-translate/core","@shared/public-api","@ngrx/store","@angular/forms","@core/public-api","@angular/cdk/keycodes","@home/components/public-api","@angular/cdk/coercion","@angular/material","rxjs","rxjs/operators"],t):t((e=e||self)["rulenode-core-config"]={},e.ng.core,e.ng.common,e["ngx-translate"],e.shared,e["ngrx-store"],e.ng.forms,e.core,e.ng.cdk.keycodes,e.publicApi$2,e.ng.cdk.coercion,e.ng.material,e.rxjs,e.rxjs.operators)}(this,(function(e,t,r,n,a,o,i,l,s,m,u,d,p,c){"use strict"; +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@angular/core"),require("@angular/common"),require("@ngx-translate/core"),require("@shared/public-api"),require("@ngrx/store"),require("@angular/forms"),require("@core/public-api"),require("@angular/cdk/keycodes"),require("@home/components/public-api"),require("@angular/cdk/coercion"),require("@angular/material/chips"),require("@angular/material/autocomplete"),require("rxjs"),require("rxjs/operators")):"function"==typeof define&&define.amd?define("rulenode-core-config",["exports","@angular/core","@angular/common","@ngx-translate/core","@shared/public-api","@ngrx/store","@angular/forms","@core/public-api","@angular/cdk/keycodes","@home/components/public-api","@angular/cdk/coercion","@angular/material/chips","@angular/material/autocomplete","rxjs","rxjs/operators"],t):t((e=e||self)["rulenode-core-config"]={},e.ng.core,e.ng.common,e["ngx-translate"],e.shared,e["ngrx-store"],e.ng.forms,e.core,e.ng.cdk.keycodes,e.publicApi$2,e.ng.cdk.coercion,e.ng.material.chips,e.ng.material.autocomplete,e.rxjs,e.rxjs.operators)}(this,(function(e,t,r,n,a,o,i,l,s,m,u,d,p,c,f){"use strict"; /*! ***************************************************************************** Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use @@ -12,5 +12,5 @@ See the Apache Version 2.0 License for specific language governing permissions and limitations under the License. - ***************************************************************************** */var f=function(e,t){return(f=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)t.hasOwnProperty(r)&&(e[r]=t[r])})(e,t)};function g(e,t){function r(){this.constructor=e}f(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)}function y(e){var t="function"==typeof Symbol&&e[Symbol.iterator],r=0;return t?t.call(e):{next:function(){return e&&r>=e.length&&(e=void 0),{value:e&&e[r++],done:!e}}}}var b=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.emptyConfigForm},r.prototype.onConfigurationSet=function(e){this.emptyConfigForm=this.fb.group({})},r.decorators=[{type:t.Component,args:[{selector:"tb-node-empty-config",template:"
"}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var h=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.attributeScopes=Object.keys(a.AttributeScope),n.telemetryTypeTranslationsMap=a.telemetryTypeTranslations,n}return g(r,e),r.prototype.configForm=function(){return this.attributesConfigForm},r.prototype.onConfigurationSet=function(e){this.attributesConfigForm=this.fb.group({scope:[e?e.scope:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var C=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.timeseriesConfigForm},r.prototype.onConfigurationSet=function(e){this.timeseriesConfigForm=this.fb.group({defaultTTL:[e?e.defaultTTL:null,[i.Validators.required,i.Validators.min(0)]]})},r.decorators=[{type:t.Component,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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var v=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.rpcRequestConfigForm},r.prototype.onConfigurationSet=function(e){this.rpcRequestConfigForm=this.fb.group({timeoutInSeconds:[e?e.timeoutInSeconds:null,[i.Validators.required,i.Validators.min(0)]]})},r.decorators=[{type:t.Component,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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var F=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return g(r,e),r.prototype.configForm=function(){return this.logConfigForm},r.prototype.onConfigurationSet=function(e){this.logConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.logConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"string",this.translate.instant("tb.rulenode.to-string"),"ToString",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.logConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.decorators=[{type:t.Component,args:[{selector:"tb-action-node-log-config",template:'
\n \n \n \n
\n \n
\n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},r.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},r}(a.RuleNodeConfigurationComponent);var T=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.assignCustomerConfigForm},r.prototype.onConfigurationSet=function(e){this.assignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[i.Validators.required]],createCustomerIfNotExists:[!!e&&e.createCustomerIfNotExists,[]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.decorators=[{type:t.Component,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 \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var q=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return g(r,e),r.prototype.configForm=function(){return this.clearAlarmConfigForm},r.prototype.onConfigurationSet=function(e){this.clearAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[i.Validators.required]],alarmType:[e?e.alarmType:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.clearAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.clearAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.decorators=[{type:t.Component,args:[{selector:"tb-action-node-clear-alarm-config",template:'
\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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},r.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},r}(a.RuleNodeConfigurationComponent);var x=function(e){function r(t,r,n,o){var i=e.call(this,t)||this;return i.store=t,i.fb=r,i.nodeScriptTestService=n,i.translate=o,i.alarmSeverities=Object.keys(a.AlarmSeverity),i.alarmSeverityTranslationMap=a.alarmSeverityTranslations,i.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],i}return g(r,e),r.prototype.configForm=function(){return this.createAlarmConfigForm},r.prototype.onConfigurationSet=function(e){this.createAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[i.Validators.required]],useMessageAlarmData:[!!e&&e.useMessageAlarmData,[]],alarmType:[e?e.alarmType:null,[]],severity:[e?e.severity:null,[]],propagate:[!!e&&e.propagate,[]],relationTypes:[e?e.relationTypes:null,[]]})},r.prototype.validatorTriggers=function(){return["useMessageAlarmData"]},r.prototype.updateValidators=function(e){this.createAlarmConfigForm.get("useMessageAlarmData").value?(this.createAlarmConfigForm.get("alarmType").setValidators([]),this.createAlarmConfigForm.get("severity").setValidators([])):(this.createAlarmConfigForm.get("alarmType").setValidators([i.Validators.required]),this.createAlarmConfigForm.get("severity").setValidators([i.Validators.required])),this.createAlarmConfigForm.get("alarmType").updateValueAndValidity({emitEvent:e}),this.createAlarmConfigForm.get("severity").updateValueAndValidity({emitEvent:e})},r.prototype.testScript=function(){var e=this,t=this.createAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.createAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},r.prototype.removeKey=function(e,t){var r=this.createAlarmConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.createAlarmConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.createAlarmConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.createAlarmConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.decorators=[{type:t.Component,args:[{selector:"tb-action-node-create-alarm-config",template:'
\n \n \n \n
\n \n
\n \n {{ \'tb.rulenode.use-message-alarm-data\' | translate }}\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.alarm-severity\n \n \n {{ alarmSeverityTranslationMap.get(severity) | translate }}\n \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 \n \n
\n
\n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},r.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},r}(a.RuleNodeConfigurationComponent);var I=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return g(r,e),r.prototype.configForm=function(){return this.createRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.createRelationConfigForm=this.fb.group({direction:[e?e.direction:null,[i.Validators.required]],entityType:[e?e.entityType:null,[i.Validators.required]],entityNamePattern:[e?e.entityNamePattern:null,[]],entityTypePattern:[e?e.entityTypePattern:null,[]],relationType:[e?e.relationType:null,[i.Validators.required]],createEntityIfNotExists:[!!e&&e.createEntityIfNotExists,[]],removeCurrentRelations:[!!e&&e.removeCurrentRelations,[]],changeOriginatorToRelatedEntity:[!!e&&e.changeOriginatorToRelatedEntity,[]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.validatorTriggers=function(){return["entityType"]},r.prototype.updateValidators=function(e){var t=this.createRelationConfigForm.get("entityType").value;t?this.createRelationConfigForm.get("entityNamePattern").setValidators([i.Validators.required]):this.createRelationConfigForm.get("entityNamePattern").setValidators([]),!t||t!==a.EntityType.DEVICE&&t!==a.EntityType.ASSET?this.createRelationConfigForm.get("entityTypePattern").setValidators([]):this.createRelationConfigForm.get("entityTypePattern").setValidators([i.Validators.required]),this.createRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e}),this.createRelationConfigForm.get("entityTypePattern").updateValueAndValidity({emitEvent:e})},r.decorators=[{type:t.Component,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 \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var k=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.msgDelayConfigForm},r.prototype.onConfigurationSet=function(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,[i.Validators.required,i.Validators.min(1),i.Validators.max(1e5)]]})},r.prototype.validatorTriggers=function(){return["useMetadataPeriodInSecondsPatterns"]},r.prototype.updateValidators=function(e){this.msgDelayConfigForm.get("useMetadataPeriodInSecondsPatterns").value?(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([i.Validators.required]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([])):(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([i.Validators.required,i.Validators.min(0)])),this.msgDelayConfigForm.get("periodInSecondsPattern").updateValueAndValidity({emitEvent:e}),this.msgDelayConfigForm.get("periodInSeconds").updateValueAndValidity({emitEvent:e})},r.decorators=[{type:t.Component,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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var N=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return g(r,e),r.prototype.configForm=function(){return this.deleteRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.deleteRelationConfigForm=this.fb.group({deleteForSingleEntity:[!!e&&e.deleteForSingleEntity,[]],direction:[e?e.direction:null,[i.Validators.required]],entityType:[e?e.entityType:null,[]],entityNamePattern:[e?e.entityNamePattern:null,[]],relationType:[e?e.relationType:null,[i.Validators.required]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.validatorTriggers=function(){return["deleteForSingleEntity","entityType"]},r.prototype.updateValidators=function(e){var t=this.deleteRelationConfigForm.get("deleteForSingleEntity").value,r=this.deleteRelationConfigForm.get("entityType").value;t?this.deleteRelationConfigForm.get("entityType").setValidators([i.Validators.required]):this.deleteRelationConfigForm.get("entityType").setValidators([]),t&&r?this.deleteRelationConfigForm.get("entityNamePattern").setValidators([i.Validators.required]):this.deleteRelationConfigForm.get("entityNamePattern").setValidators([]),this.deleteRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:!1}),this.deleteRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e})},r.decorators=[{type:t.Component,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 \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var S=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return g(r,e),r.prototype.configForm=function(){return this.generatorConfigForm},r.prototype.onConfigurationSet=function(e){this.generatorConfigForm=this.fb.group({msgCount:[e?e.msgCount:null,[i.Validators.required,i.Validators.min(0)]],periodInSeconds:[e?e.periodInSeconds:null,[i.Validators.required,i.Validators.min(1)]],originator:[e?e.originator:null,[]],jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.prepareInputConfig=function(e){return e&&(e.originatorId&&e.originatorType?e.originator={id:e.originatorId,entityType:e.originatorType}:e.originator=null,delete e.originatorId,delete e.originatorType),e},r.prototype.prepareOutputConfig=function(e){return e.originator?(e.originatorId=e.originator.id,e.originatorType=e.originator.entityType):(e.originatorId=null,e.originatorType=null),delete e.originator,e},r.prototype.testScript=function(){var e=this,t=this.generatorConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"generate",this.translate.instant("tb.rulenode.generator"),"Generate",["prevMsg","prevMetadata","prevMsgType"],this.ruleNodeId).subscribe((function(t){t&&e.generatorConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.decorators=[{type:t.Component,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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},r.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},r}(a.RuleNodeConfigurationComponent);var E={CUSTOMER:"CUSTOMER",TENANT:"TENANT",RELATED:"RELATED",ALARM_ORIGINATOR:"ALARM_ORIGINATOR"},V=new Map([[E.CUSTOMER,"tb.rulenode.originator-customer"],[E.TENANT,"tb.rulenode.originator-tenant"],[E.RELATED,"tb.rulenode.originator-related"],[E.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator"]]),A={CIRCLE:"CIRCLE",POLYGON:"POLYGON"},L=new Map([[A.CIRCLE,"tb.rulenode.perimeter-circle"],[A.POLYGON,"tb.rulenode.perimeter-polygon"]]),M={MILLISECONDS:"MILLISECONDS",SECONDS:"SECONDS",MINUTES:"MINUTES",HOURS:"HOURS",DAYS:"DAYS"},P=new Map([[M.MILLISECONDS,"tb.rulenode.time-unit-milliseconds"],[M.SECONDS,"tb.rulenode.time-unit-seconds"],[M.MINUTES,"tb.rulenode.time-unit-minutes"],[M.HOURS,"tb.rulenode.time-unit-hours"],[M.DAYS,"tb.rulenode.time-unit-days"]]),R={METER:"METER",KILOMETER:"KILOMETER",FOOT:"FOOT",MILE:"MILE",NAUTICAL_MILE:"NAUTICAL_MILE"},w=new Map([[R.METER,"tb.rulenode.range-unit-meter"],[R.KILOMETER,"tb.rulenode.range-unit-kilometer"],[R.FOOT,"tb.rulenode.range-unit-foot"],[R.MILE,"tb.rulenode.range-unit-mile"],[R.NAUTICAL_MILE,"tb.rulenode.range-unit-nautical-mile"]]),D={TITLE:"TITLE",COUNTRY:"COUNTRY",STATE:"STATE",ZIP:"ZIP",ADDRESS:"ADDRESS",ADDRESS2:"ADDRESS2",PHONE:"PHONE",EMAIL:"EMAIL",ADDITIONAL_INFO:"ADDITIONAL_INFO"},O=new Map([[D.TITLE,"tb.rulenode.entity-details-title"],[D.COUNTRY,"tb.rulenode.entity-details-country"],[D.STATE,"tb.rulenode.entity-details-state"],[D.ZIP,"tb.rulenode.entity-details-zip"],[D.ADDRESS,"tb.rulenode.entity-details-address"],[D.ADDRESS2,"tb.rulenode.entity-details-address2"],[D.PHONE,"tb.rulenode.entity-details-phone"],[D.EMAIL,"tb.rulenode.entity-details-email"],[D.ADDITIONAL_INFO,"tb.rulenode.entity-details-additional_info"]]),K={FIRST:"FIRST",LAST:"LAST",ALL:"ALL"},j={ASC:"ASC",DESC:"DESC"},U={STANDARD:"STANDARD",FIFO:"FIFO"},G=new Map([[U.STANDARD,"tb.rulenode.sqs-queue-standard"],[U.FIFO,"tb.rulenode.sqs-queue-fifo"]]),B=["anonymous","basic","cert.PEM"],H=new Map([["anonymous","tb.rulenode.credentials-anonymous"],["basic","tb.rulenode.credentials-basic"],["cert.PEM","tb.rulenode.credentials-pem"]]),Q={GET:"GET",POST:"POST",PUT:"PUT",DELETE:"DELETE"},z=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.perimeterType=A,n.perimeterTypes=Object.keys(A),n.perimeterTypeTranslationMap=L,n.rangeUnits=Object.keys(R),n.rangeUnitTranslationMap=w,n.timeUnits=Object.keys(M),n.timeUnitsTranslationMap=P,n}return g(r,e),r.prototype.configForm=function(){return this.geoActionConfigForm},r.prototype.onConfigurationSet=function(e){this.geoActionConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[i.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[i.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType: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,[i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]],minInsideDurationTimeUnit:[e?e.minInsideDurationTimeUnit:null,[i.Validators.required]],minOutsideDuration:[e?e.minOutsideDuration:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]],minOutsideDurationTimeUnit:[e?e.minOutsideDurationTimeUnit:null,[i.Validators.required]]})},r.prototype.validatorTriggers=function(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]},r.prototype.updateValidators=function(e){var t=this.geoActionConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,r=this.geoActionConfigForm.get("perimeterType").value;t?this.geoActionConfigForm.get("perimeterType").setValidators([]):this.geoActionConfigForm.get("perimeterType").setValidators([i.Validators.required]),t||r!==A.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([i.Validators.required,i.Validators.min(-90),i.Validators.max(90)]),this.geoActionConfigForm.get("centerLongitude").setValidators([i.Validators.required,i.Validators.min(-180),i.Validators.max(180)]),this.geoActionConfigForm.get("range").setValidators([i.Validators.required,i.Validators.min(0)]),this.geoActionConfigForm.get("rangeUnit").setValidators([i.Validators.required])),t||r!==A.POLYGON?this.geoActionConfigForm.get("polygonsDefinition").setValidators([]):this.geoActionConfigForm.get("polygonsDefinition").setValidators([i.Validators.required]),this.geoActionConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),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})},r.decorators=[{type:t.Component,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.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var $=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.msgCountConfigForm},r.prototype.onConfigurationSet=function(e){this.msgCountConfigForm=this.fb.group({interval:[e?e.interval:null,[i.Validators.required,i.Validators.min(1)]],telemetryPrefix:[e?e.telemetryPrefix:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var _=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.rpcReplyConfigForm},r.prototype.onConfigurationSet=function(e){this.rpcReplyConfigForm=this.fb.group({requestIdMetaDataAttribute:[e?e.requestIdMetaDataAttribute:null,[]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-action-node-rpc-reply-config",template:'
\n \n tb.rulenode.request-id-metadata-attribute\n \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var W=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.saveToCustomTableConfigForm},r.prototype.onConfigurationSet=function(e){this.saveToCustomTableConfigForm=this.fb.group({tableName:[e?e.tableName:null,[i.Validators.required]],fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,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 \n \n \n \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var Y=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.translate=r,o.injector=n,o.fb=a,o.propagateChange=null,o.valueChangeSubscription=null,o}return g(r,e),Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){this.ngControl=this.injector.get(i.NgControl),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.kvListFormGroup=this.fb.group({}),this.kvListFormGroup.addControl("keyVals",this.fb.array([]))},r.prototype.keyValsFormArray=function(){return this.kvListFormGroup.get("keyVals")},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.kvListFormGroup.disable({emitEvent:!1}):this.kvListFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){var t,r,n=this;this.valueChangeSubscription&&this.valueChangeSubscription.unsubscribe();var a=[];if(e)try{for(var o=y(Object.keys(e)),l=o.next();!l.done;l=o.next()){var s=l.value;Object.prototype.hasOwnProperty.call(e,s)&&a.push(this.fb.group({key:[s,[i.Validators.required]],value:[e[s],[i.Validators.required]]}))}}catch(e){t={error:e}}finally{try{l&&!l.done&&(r=o.return)&&r.call(o)}finally{if(t)throw t.error}}this.kvListFormGroup.setControl("keyVals",this.fb.array(a)),this.valueChangeSubscription=this.kvListFormGroup.valueChanges.subscribe((function(){n.updateModel()}))},r.prototype.removeKeyVal=function(e){this.kvListFormGroup.get("keyVals").removeAt(e)},r.prototype.addKeyVal=function(){this.kvListFormGroup.get("keyVals").push(this.fb.group({key:["",[i.Validators.required]],value:["",[i.Validators.required]]}))},r.prototype.validate=function(e){return!this.kvListFormGroup.get("keyVals").value.length&&this.required?{kvMapRequired:!0}:this.kvListFormGroup.valid?null:{kvFieldsRequired:!0}},r.prototype.updateModel=function(){var e=this.kvListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.kvListFormGroup.valid)this.propagateChange(null);else{var t={};e.forEach((function(e){t[e.key]=e.value})),this.propagateChange(t)}},r.decorators=[{type:t.Component,args:[{selector:"tb-kv-map-config",template:'
\n
\n {{ keyText }}\n {{ valText }}\n \n
\n
\n
\n \n \n \n \n {{ keyRequiredText | translate }}\n \n \n \n \n \n \n {{ valRequiredText | translate }}\n \n \n \n
\n
\n \n
\n \n
\n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return r})),multi:!0},{provide:i.NG_VALIDATORS,useExisting:t.forwardRef((function(){return r})),multi:!0}],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:rgba(0,0,0,.54);font-size:12px;font-weight:700;white-space:nowrap}:host .tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:20px;max-height:300px;overflow:auto}:host .tb-kv-map-config .body .row{padding-top:5px;max-height:40px}:host .tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell{margin:0;max-height:40px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell .mat-form-field-infix{border-top:0}:host ::ng-deep .tb-kv-map-config .body button.mat-button{margin:0}"]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:t.Injector},{type:i.FormBuilder}]},r.propDecorators={disabled:[{type:t.Input}],requiredText:[{type:t.Input}],keyText:[{type:t.Input}],keyRequiredText:[{type:t.Input}],valText:[{type:t.Input}],valRequiredText:[{type:t.Input}],required:[{type:t.Input}]},r}(a.PageComponent);var J=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n.propagateChange=null,n}return g(r,e),Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.deviceRelationsQueryFormGroup=this.fb.group({direction:[null,[i.Validators.required]],maxLevel:[null,[]],relationType:[null],deviceTypes:[null,[i.Validators.required]]}),this.deviceRelationsQueryFormGroup.valueChanges.subscribe((function(t){e.deviceRelationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.deviceRelationsQueryFormGroup.disable({emitEvent:!1}):this.deviceRelationsQueryFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){this.deviceRelationsQueryFormGroup.reset(e,{emitEvent:!1})},r.decorators=[{type:t.Component,args:[{selector:"tb-device-relations-query-config",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
\n
relation.relation-type
\n \n \n
device.device-types
\n \n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return r})),multi:!0}]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r.propDecorators={disabled:[{type:t.Input}],required:[{type:t.Input}]},r}(a.PageComponent);var Z=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.propagateChange=null,n}return g(r,e),Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.relationsQueryFormGroup=this.fb.group({direction:[null,[i.Validators.required]],maxLevel:[null,[]],filters:[null]}),this.relationsQueryFormGroup.valueChanges.subscribe((function(t){e.relationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.relationsQueryFormGroup.disable({emitEvent:!1}):this.relationsQueryFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){this.relationsQueryFormGroup.reset(e,{emitEvent:!1})},r.decorators=[{type:t.Component,args:[{selector:"tb-relations-query-config",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
\n
relation.relation-filters
\n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return r})),multi:!0}]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r.propDecorators={disabled:[{type:t.Input}],required:[{type:t.Input}]},r}(a.PageComponent);var X=function(e){function r(t,r,n,o){var i,l,m=e.call(this,t)||this;m.store=t,m.translate=r,m.truncate=n,m.fb=o,m.placeholder="tb.rulenode.message-type",m.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],m.messageTypes=[],m.messageTypesList=[],m.searchText="",m.propagateChange=function(e){},m.messageTypeConfigForm=m.fb.group({messageType:[null]});try{for(var u=y(Object.keys(a.MessageType)),d=u.next();!d.done;d=u.next()){var p=d.value;m.messageTypesList.push({name:a.messageTypeNames.get(a.MessageType[p]),value:p})}}catch(e){i={error:e}}finally{try{d&&!d.done&&(l=u.return)&&l.call(u)}finally{if(i)throw i.error}}return m}return g(r,e),Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.ngOnInit=function(){var e=this;this.filteredMessageTypes=this.messageTypeConfigForm.get("messageType").valueChanges.pipe(c.startWith(""),c.map((function(e){return e||""})),c.mergeMap((function(t){return e.fetchMessageTypes(t)})),c.share())},r.prototype.ngAfterViewInit=function(){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.messageTypeConfigForm.disable({emitEvent:!1}):this.messageTypeConfigForm.enable({emitEvent:!1})},r.prototype.writeValue=function(e){var t=this;this.searchText="",this.messageTypes.length=0,e&&e.forEach((function(e){var r=t.messageTypesList.find((function(t){return t.value===e}));r?t.messageTypes.push({name:r.name,value:r.value}):t.messageTypes.push({name:e,value:e})}))},r.prototype.displayMessageTypeFn=function(e){return e?e.name:void 0},r.prototype.textIsNotEmpty=function(e){return!!(e&&null!=e&&e.length>0)},r.prototype.createMessageType=function(e,t){e.preventDefault(),this.transformMessageType(t)},r.prototype.add=function(e){this.transformMessageType(e.value)},r.prototype.fetchMessageTypes=function(e){if(this.searchText=e,this.searchText&&this.searchText.length){var t=this.searchText.toUpperCase();return p.of(this.messageTypesList.filter((function(e){return e.name.toUpperCase().includes(t)})))}return p.of(this.messageTypesList)},r.prototype.transformMessageType=function(e){if((e||"").trim()){var t=null,r=e.trim(),n=this.messageTypesList.find((function(e){return e.name===r}));(t=n?{name:n.name,value:n.value}:{name:r,value:r})&&this.addMessageType(t)}this.clear("")},r.prototype.remove=function(e){var t=this.messageTypes.indexOf(e);t>=0&&(this.messageTypes.splice(t,1),this.updateModel())},r.prototype.selected=function(e){this.addMessageType(e.option.value),this.clear("")},r.prototype.addMessageType=function(e){-1===this.messageTypes.findIndex((function(t){return t.value===e.value}))&&(this.messageTypes.push(e),this.updateModel())},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.messageTypeInput.nativeElement.value=e,this.messageTypeConfigForm.get("messageType").patchValue(null,{emitEvent:!0}),setTimeout((function(){t.messageTypeInput.nativeElement.blur(),t.messageTypeInput.nativeElement.focus()}),0)},r.prototype.updateModel=function(){var e=this.messageTypes.map((function(e){return e.value}));this.required?(this.chipList.errorState=!e.length,this.propagateChange(e.length>0?e:null)):(this.chipList.errorState=!1,this.propagateChange(e))},r.decorators=[{type:t.Component,args:[{selector:"tb-message-types-config",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 {{ translate.get(\'tb.rulenode.no-message-type-matching\',\n {messageType: truncate.transform(searchText, true, 6, '...')}) | async }}\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',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return r})),multi:!0}]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:a.TruncatePipe},{type:i.FormBuilder}]},r.propDecorators={required:[{type:t.Input}],label:[{type:t.Input}],placeholder:[{type:t.Input}],disabled:[{type:t.Input}],chipList:[{type:t.ViewChild,args:["chipList",{static:!1}]}],matAutocomplete:[{type:t.ViewChild,args:["messageTypeAutocomplete",{static:!1}]}],messageTypeInput:[{type:t.ViewChild,args:["messageTypeInput",{static:!1}]}]},r}(a.PageComponent);var ee=function(){function e(){}return e.decorators=[{type:t.NgModule,args:[{declarations:[Y,J,Z,X],imports:[r.CommonModule,a.SharedModule,m.HomeComponentsModule],exports:[Y,J,Z,X]}]}],e}(),te=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.unassignCustomerConfigForm},r.prototype.onConfigurationSet=function(e){this.unassignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[i.Validators.required]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.decorators=[{type:t.Component,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 \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var re=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.snsConfigForm},r.prototype.onConfigurationSet=function(e){this.snsConfigForm=this.fb.group({topicArnPattern:[e?e.topicArnPattern:null,[i.Validators.required]],accessKeyId:[e?e.accessKeyId:null,[i.Validators.required]],secretAccessKey:[e?e.secretAccessKey:null,[i.Validators.required]],region:[e?e.region:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-action-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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var ne=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.sqsQueueType=U,n.sqsQueueTypes=Object.keys(U),n.sqsQueueTypeTranslationsMap=G,n}return g(r,e),r.prototype.configForm=function(){return this.sqsConfigForm},r.prototype.onConfigurationSet=function(e){this.sqsConfigForm=this.fb.group({queueType:[e?e.queueType:null,[i.Validators.required]],queueUrlPattern:[e?e.queueUrlPattern:null,[i.Validators.required]],delaySeconds:[e?e.delaySeconds:null,[i.Validators.min(0),i.Validators.max(900)]],messageAttributes:[e?e.messageAttributes:null,[]],accessKeyId:[e?e.accessKeyId:null,[i.Validators.required]],secretAccessKey:[e?e.secretAccessKey:null,[i.Validators.required]],region:[e?e.region:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-action-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
tb.rulenode.message-attributes-hint
\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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var ae=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.pubSubConfigForm},r.prototype.onConfigurationSet=function(e){this.pubSubConfigForm=this.fb.group({projectId:[e?e.projectId:null,[i.Validators.required]],topicName:[e?e.topicName:null,[i.Validators.required]],serviceAccountKey:[e?e.serviceAccountKey:null,[i.Validators.required]],serviceAccountKeyFileName:[e?e.serviceAccountKeyFileName:null,[i.Validators.required]],messageAttributes:[e?e.messageAttributes:null,[]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-action-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
tb.rulenode.message-attributes-hint
\n \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var oe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.ackValues=["all","-1","0","1"],n}return g(r,e),r.prototype.configForm=function(){return this.kafkaConfigForm},r.prototype.onConfigurationSet=function(e){this.kafkaConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],bootstrapServers:[e?e.bootstrapServers:null,[i.Validators.required]],retries:[e?e.retries:null,[i.Validators.min(0)]],batchSize:[e?e.batchSize:null,[i.Validators.min(0)]],linger:[e?e.linger:null,[i.Validators.min(0)]],bufferMemory:[e?e.bufferMemory:null,[i.Validators.min(0)]],acks:[e?e.acks:null,[i.Validators.required]],keySerializer:[e?e.keySerializer:null,[i.Validators.required]],valueSerializer:[e?e.valueSerializer:null,[i.Validators.required]],otherProperties:[e?e.otherProperties:null,[]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-action-node-kafka-config",template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var ie=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allMqttCredentialsTypes=B,n.mqttCredentialsTypeTranslationsMap=H,n}return g(r,e),r.prototype.configForm=function(){return this.mqttConfigForm},r.prototype.onConfigurationSet=function(e){this.mqttConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(200)]],clientId:[e?e.clientId:null,[]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:this.fb.group({type:[e&&e.credentials?e.credentials.type:null,[i.Validators.required]],username:[e&&e.credentials?e.credentials.username:null,[]],password:[e&&e.credentials?e.credentials.password: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,[]]})})},r.prototype.prepareOutputConfig=function(e){var t=e.credentials.type;switch(t){case"anonymous":e.credentials={type:t};break;case"basic":e.credentials={type:t,username:e.credentials.username,password:e.credentials.password};break;case"cert.PEM":delete e.credentials.username}return e},r.prototype.validatorTriggers=function(){return["credentials.type"]},r.prototype.updateValidators=function(e){var t=this.mqttConfigForm.get("credentials"),r=t.get("type").value;switch(e&&t.reset({type:r},{emitEvent:!1}),t.get("username").setValidators([]),t.get("password").setValidators([]),t.get("caCert").setValidators([]),t.get("caCertFileName").setValidators([]),t.get("privateKey").setValidators([]),t.get("privateKeyFileName").setValidators([]),t.get("cert").setValidators([]),t.get("certFileName").setValidators([]),r){case"anonymous":break;case"basic":t.get("username").setValidators([i.Validators.required]),t.get("password").setValidators([i.Validators.required]);break;case"cert.PEM":t.get("caCert").setValidators([i.Validators.required]),t.get("caCertFileName").setValidators([i.Validators.required]),t.get("privateKey").setValidators([i.Validators.required]),t.get("privateKeyFileName").setValidators([i.Validators.required]),t.get("cert").setValidators([i.Validators.required]),t.get("certFileName").setValidators([i.Validators.required])}t.get("username").updateValueAndValidity({emitEvent:e}),t.get("password").updateValueAndValidity({emitEvent:e}),t.get("caCert").updateValueAndValidity({emitEvent:e}),t.get("caCertFileName").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})},r.decorators=[{type:t.Component,args:[{selector:"tb-action-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 \n \n {{ \'tb.rulenode.clean-session\' | translate }}\n \n \n {{ \'tb.rulenode.enable-ssl\' | translate }}\n \n \n \n tb.rulenode.credentials\n \n {{ mqttCredentialsTypeTranslationsMap.get(mqttConfigForm.get(\'credentials\').get(\'type\').value) | translate }}\n \n \n
\n \n tb.rulenode.credentials-type\n \n \n {{ mqttCredentialsTypeTranslationsMap.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 {{ \'tb.rulenode.password-required\' | translate }}\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',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}"]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var le=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],n}return g(r,e),r.prototype.configForm=function(){return this.rabbitMqConfigForm},r.prototype.onConfigurationSet=function(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,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.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,[i.Validators.min(0)]],handshakeTimeout:[e?e.handshakeTimeout:null,[i.Validators.min(0)]],clientProperties:[e?e.clientProperties:null,[]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-action-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 {{ \'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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.httpRequestTypes=Object.keys(Q),n}return g(r,e),r.prototype.configForm=function(){return this.restApiCallConfigForm},r.prototype.onConfigurationSet=function(e){this.restApiCallConfigForm=this.fb.group({restEndpointUrlPattern:[e?e.restEndpointUrlPattern:null,[i.Validators.required]],requestMethod:[e?e.requestMethod:null,[i.Validators.required]],useSimpleClientHttpFactory:[!!e&&e.useSimpleClientHttpFactory,[]],readTimeoutMs:[e?e.readTimeoutMs:null,[]],maxParallelRequestsCount:[e?e.maxParallelRequestsCount:null,[i.Validators.min(0)]],headers:[e?e.headers:null,[]],useRedisQueueForMsgPersistence:[!!e&&e.useRedisQueueForMsgPersistence,[]],trimQueue:[!!e&&e.trimQueue,[]],maxQueueSize:[e?e.maxQueueSize:null,[]]})},r.prototype.validatorTriggers=function(){return["useSimpleClientHttpFactory","useRedisQueueForMsgPersistence"]},r.prototype.updateValidators=function(e){var t=this.restApiCallConfigForm.get("useSimpleClientHttpFactory").value,r=this.restApiCallConfigForm.get("useRedisQueueForMsgPersistence").value;t?this.restApiCallConfigForm.get("readTimeoutMs").setValidators([]):this.restApiCallConfigForm.get("readTimeoutMs").setValidators([i.Validators.min(0)]),r?this.restApiCallConfigForm.get("maxQueueSize").setValidators([i.Validators.min(0)]):this.restApiCallConfigForm.get("maxQueueSize").setValidators([]),this.restApiCallConfigForm.get("readTimeoutMs").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("maxQueueSize").updateValueAndValidity({emitEvent:e})},r.decorators=[{type:t.Component,args:[{selector:"tb-action-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.use-simple-client-http-factory\' | translate }}\n \n \n tb.rulenode.read-timeout\n \n \n \n \n tb.rulenode.max-parallel-requests-count\n \n \n \n \n
tb.rulenode.headers-hint
\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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var me=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.smtpProtocols=["smtp","smtps"],n}return g(r,e),r.prototype.configForm=function(){return this.sendEmailConfigForm},r.prototype.onConfigurationSet=function(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,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]]})},r.prototype.validatorTriggers=function(){return["useSystemSmtpSettings"]},r.prototype.updateValidators=function(e){this.sendEmailConfigForm.get("useSystemSmtpSettings").value?(this.sendEmailConfigForm.get("smtpProtocol").setValidators([]),this.sendEmailConfigForm.get("smtpHost").setValidators([]),this.sendEmailConfigForm.get("smtpPort").setValidators([]),this.sendEmailConfigForm.get("timeout").setValidators([])):(this.sendEmailConfigForm.get("smtpProtocol").setValidators([i.Validators.required]),this.sendEmailConfigForm.get("smtpHost").setValidators([i.Validators.required]),this.sendEmailConfigForm.get("smtpPort").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]),this.sendEmailConfigForm.get("timeout").setValidators([i.Validators.required,i.Validators.min(0)])),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})},r.decorators=[{type:t.Component,args:[{selector:"tb-action-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.username\n \n \n \n tb.rulenode.password\n \n \n
\n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var ue=function(){function e(){}return e.decorators=[{type:t.NgModule,args:[{declarations:[h,C,v,F,T,q,x,I,k,N,S,z,$,_,W,te,re,ne,ae,oe,ie,le,se,me],imports:[r.CommonModule,a.SharedModule,ee],exports:[h,C,v,F,T,q,x,I,k,N,S,z,$,_,W,te,re,ne,ae,oe,ie,le,se,me]}]}],e}(),de=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n}return g(r,e),r.prototype.configForm=function(){return this.checkMessageConfigForm},r.prototype.onConfigurationSet=function(e){this.checkMessageConfigForm=this.fb.group({messageNames:[e?e.messageNames:null,[]],metadataNames:[e?e.metadataNames:null,[]],checkAllKeys:[!!e&&e.checkAllKeys,[]]})},r.prototype.validateConfig=function(){var e=this.checkMessageConfigForm.get("messageNames").value,t=this.checkMessageConfigForm.get("metadataNames").value;return e.length>0||t.length>0},r.prototype.removeMessageName=function(e){var t=this.checkMessageConfigForm.get("messageNames").value,r=t.indexOf(e);r>=0&&(t.splice(r,1),this.checkMessageConfigForm.get("messageNames").setValue(t,{emitEvent:!0}))},r.prototype.removeMetadataName=function(e){var t=this.checkMessageConfigForm.get("metadataNames").value,r=t.indexOf(e);r>=0&&(t.splice(r,1),this.checkMessageConfigForm.get("metadataNames").setValue(t,{emitEvent:!0}))},r.prototype.addMessageName=function(e){var t=e.input,r=e.value;if((r||"").trim()){r=r.trim();var n=this.checkMessageConfigForm.get("messageNames").value;n&&-1!==n.indexOf(r)||(n||(n=[]),n.push(r),this.checkMessageConfigForm.get("messageNames").setValue(n,{emitEvent:!0}))}t&&(t.value="")},r.prototype.addMetadataName=function(e){var t=e.input,r=e.value;if((r||"").trim()){r=r.trim();var n=this.checkMessageConfigForm.get("metadataNames").value;n&&-1!==n.indexOf(r)||(n||(n=[]),n.push(r),this.checkMessageConfigForm.get("metadataNames").setValue(n,{emitEvent:!0}))}t&&(t.value="")},r.decorators=[{type:t.Component,args:[{selector:"tb-filter-node-check-message-config",template:'
\n \n \n \n \n \n {{messageName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n \n \n \n \n {{metadataName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\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}"]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var pe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.entitySearchDirection=Object.keys(a.EntitySearchDirection),n.entitySearchDirectionTranslationsMap=a.entitySearchDirectionTranslations,n}return g(r,e),r.prototype.configForm=function(){return this.checkRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.checkRelationConfigForm=this.fb.group({checkForSingleEntity:[!!e&&e.checkForSingleEntity,[]],direction:[e?e.direction:null,[]],entityType:[e?e.entityType:null,e&&e.checkForSingleEntity?[i.Validators.required]:[]],entityId:[e?e.entityId:null,e&&e.checkForSingleEntity?[i.Validators.required]:[]],relationType:[e?e.relationType:null,[i.Validators.required]]})},r.prototype.validatorTriggers=function(){return["checkForSingleEntity"]},r.prototype.updateValidators=function(e){var t=this.checkRelationConfigForm.get("checkForSingleEntity").value;this.checkRelationConfigForm.get("entityType").setValidators(t?[i.Validators.required]:[]),this.checkRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:e}),this.checkRelationConfigForm.get("entityId").setValidators(t?[i.Validators.required]:[]),this.checkRelationConfigForm.get("entityId").updateValueAndValidity({emitEvent:e})},r.decorators=[{type:t.Component,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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var ce=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.perimeterType=A,n.perimeterTypes=Object.keys(A),n.perimeterTypeTranslationMap=L,n.rangeUnits=Object.keys(R),n.rangeUnitTranslationMap=w,n}return g(r,e),r.prototype.configForm=function(){return this.geoFilterConfigForm},r.prototype.onConfigurationSet=function(e){this.geoFilterConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[i.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[i.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType: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,[]]})},r.prototype.validatorTriggers=function(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]},r.prototype.updateValidators=function(e){var t=this.geoFilterConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,r=this.geoFilterConfigForm.get("perimeterType").value;t?this.geoFilterConfigForm.get("perimeterType").setValidators([]):this.geoFilterConfigForm.get("perimeterType").setValidators([i.Validators.required]),t||r!==A.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([i.Validators.required,i.Validators.min(-90),i.Validators.max(90)]),this.geoFilterConfigForm.get("centerLongitude").setValidators([i.Validators.required,i.Validators.min(-180),i.Validators.max(180)]),this.geoFilterConfigForm.get("range").setValidators([i.Validators.required,i.Validators.min(0)]),this.geoFilterConfigForm.get("rangeUnit").setValidators([i.Validators.required])),t||r!==A.POLYGON?this.geoFilterConfigForm.get("polygonsDefinition").setValidators([]):this.geoFilterConfigForm.get("polygonsDefinition").setValidators([i.Validators.required]),this.geoFilterConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),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})},r.decorators=[{type:t.Component,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.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \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'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var fe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.messageTypeConfigForm},r.prototype.onConfigurationSet=function(e){this.messageTypeConfigForm=this.fb.group({messageTypes:[e?e.messageTypes:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-filter-node-message-type-config",template:'
\n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var ge=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allowedEntityTypes=[a.EntityType.DEVICE,a.EntityType.ASSET,a.EntityType.ENTITY_VIEW,a.EntityType.TENANT,a.EntityType.CUSTOMER,a.EntityType.USER,a.EntityType.DASHBOARD,a.EntityType.RULE_CHAIN,a.EntityType.RULE_NODE],n}return g(r,e),r.prototype.configForm=function(){return this.originatorTypeConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorTypeConfigForm=this.fb.group({originatorTypes:[e?e.originatorTypes:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-filter-node-originator-type-config",template:'
\n \n \n \n
\n',styles:[":host ::ng-deep tb-entity-type-list .mat-form-field-flex{padding-top:0}:host ::ng-deep tb-entity-type-list .mat-form-field-infix{border-top:0}"]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var ye=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return g(r,e),r.prototype.configForm=function(){return this.scriptConfigForm},r.prototype.onConfigurationSet=function(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"filter",this.translate.instant("tb.rulenode.filter"),"Filter",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.decorators=[{type:t.Component,args:[{selector:"tb-filter-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},r.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},r}(a.RuleNodeConfigurationComponent);var be=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return g(r,e),r.prototype.configForm=function(){return this.switchConfigForm},r.prototype.onConfigurationSet=function(e){this.switchConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.switchConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"switch",this.translate.instant("tb.rulenode.switch"),"Switch",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.switchConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.decorators=[{type:t.Component,args:[{selector:"tb-filter-node-switch-config",template:'
\n \n \n \n
\n \n
\n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},r.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},r}(a.RuleNodeConfigurationComponent);var he=function(){function e(){}return e.decorators=[{type:t.NgModule,args:[{declarations:[de,pe,ce,fe,ge,ye,be],imports:[r.CommonModule,a.SharedModule,ee],exports:[de,pe,ce,fe,ge,ye,be]}]}],e}(),Ce=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.customerAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.customerAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-customer-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var ve=function(e){function r(t,r,n){var a,o,i=e.call(this,t)||this;i.store=t,i.translate=r,i.fb=n,i.entityDetailsTranslationsMap=O,i.entityDetailsList=[],i.searchText="",i.displayDetailsFn=i.displayDetails.bind(i);try{for(var l=y(Object.keys(D)),s=l.next();!s.done;s=l.next()){var m=s.value;i.entityDetailsList.push(D[m])}}catch(e){a={error:e}}finally{try{s&&!s.done&&(o=l.return)&&o.call(l)}finally{if(a)throw a.error}}return i}return g(r,e),r.prototype.ngOnInit=function(){var t=this;this.detailsFormControl=new i.FormControl(""),e.prototype.ngOnInit.call(this),this.filteredEntityDetails=this.detailsFormControl.valueChanges.pipe(c.startWith(""),c.map((function(e){return e||""})),c.mergeMap((function(e){return t.fetchEntityDetails(e)})),c.share())},r.prototype.configForm=function(){return this.entityDetailsConfigForm},r.prototype.prepareInputConfig=function(e){return this.searchText="",this.detailsFormControl.patchValue("",{emitEvent:!0}),e},r.prototype.onConfigurationSet=function(e){this.entityDetailsConfigForm=this.fb.group({detailsList:[e?e.detailsList:null,[i.Validators.required]],addToMetadata:[!!e&&e.addToMetadata,[]]})},r.prototype.displayDetails=function(e){return e?this.translate.instant(O.get(e)):void 0},r.prototype.fetchEntityDetails=function(e){var t=this;if(this.searchText=e,this.searchText&&this.searchText.length){var r=this.searchText.toUpperCase();return p.of(this.entityDetailsList.filter((function(e){return t.translate.instant(O.get(D[e])).toUpperCase().includes(r)})))}return p.of(this.entityDetailsList)},r.prototype.detailsFieldSelected=function(e){this.addDetailsField(e.option.value),this.clear("")},r.prototype.removeDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;if(t){var r=t.indexOf(e);r>=0&&(t.splice(r,1),this.entityDetailsConfigForm.get("detailsList").setValue(t))}},r.prototype.addDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;t||(t=[]),-1===t.indexOf(e)&&(t.push(e),this.entityDetailsConfigForm.get("detailsList").setValue(t))},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.detailsInput.nativeElement.value=e,this.detailsFormControl.patchValue(null,{emitEvent:!0}),setTimeout((function(){t.detailsInput.nativeElement.blur(),t.detailsInput.nativeElement.focus()}),0)},r.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-entity-details-config",template:'
\n \n \n \n \n \n {{entityDetailsTranslationsMap.get(details) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-entity-details-matching\n
\n
\n
\n
\n
\n \n \n {{ \'tb.rulenode.add-to-metadata\' | translate }}\n \n
tb.rulenode.add-to-metadata-hint
\n
\n',styles:[":host ::ng-deep mat-form-field.entity-fields-list .mat-form-field-wrapper{margin-bottom:-1.25em}"]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:i.FormBuilder}]},r.propDecorators={detailsInput:[{type:t.ViewChild,args:["detailsInput",{static:!1}]}]},r}(a.RuleNodeConfigurationComponent);var Fe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n}return g(r,e),r.prototype.configForm=function(){return this.deviceAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.deviceAttributesConfigForm=this.fb.group({deviceRelationsQuery:[e?e.deviceRelationsQuery:null,[i.Validators.required]],tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})},r.prototype.removeKey=function(e,t){var r=this.deviceAttributesConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.deviceAttributesConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.deviceAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.deviceAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-device-attributes-config",template:'
\n \n \n \n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var Te=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n}return g(r,e),r.prototype.configForm=function(){return this.originatorAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorAttributesConfigForm=this.fb.group({tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})},r.prototype.removeKey=function(e,t){var r=this.originatorAttributesConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.originatorAttributesConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.originatorAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.originatorAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-originator-attributes-config",template:'
\n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var qe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.originatorFieldsConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorFieldsConfigForm=this.fb.group({fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-originator-fields-config",template:'
\n \n \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var xe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n.fetchMode=K,n.fetchModes=Object.keys(K),n.samplingOrders=Object.keys(j),n.timeUnits=Object.keys(M),n.timeUnitsTranslationMap=P,n}return g(r,e),r.prototype.configForm=function(){return this.getTelemetryFromDatabaseConfigForm},r.prototype.onConfigurationSet=function(e){this.getTelemetryFromDatabaseConfigForm=this.fb.group({latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],fetchMode:[e?e.fetchMode:null,[i.Validators.required]],orderBy:[e?e.orderBy:null,[]],limit:[e?e.limit:null,[]],useMetadataIntervalPatterns:[!!e&&e.useMetadataIntervalPatterns,[]],startInterval:[e?e.startInterval:null,[]],startIntervalTimeUnit:[e?e.startIntervalTimeUnit:null,[]],endInterval:[e?e.endInterval:null,[]],endIntervalTimeUnit:[e?e.endIntervalTimeUnit:null,[]],startIntervalPattern:[e?e.startIntervalPattern:null,[]],endIntervalPattern:[e?e.endIntervalPattern:null,[]]})},r.prototype.validatorTriggers=function(){return["fetchMode","useMetadataIntervalPatterns"]},r.prototype.updateValidators=function(e){var t=this.getTelemetryFromDatabaseConfigForm.get("fetchMode").value,r=this.getTelemetryFromDatabaseConfigForm.get("useMetadataIntervalPatterns").value;t&&t===K.ALL?(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([i.Validators.required,i.Validators.min(2),i.Validators.max(1e3)])):(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([])),r?(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([i.Validators.required])):(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([])),this.getTelemetryFromDatabaseConfigForm.get("orderBy").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("limit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").updateValueAndValidity({emitEvent:e})},r.prototype.removeKey=function(e,t){var r=this.getTelemetryFromDatabaseConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.getTelemetryFromDatabaseConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-get-telemetry-from-database",template:'
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n tb.rulenode.fetch-mode\n \n \n {{ mode }}\n \n \n tb.rulenode.fetch-mode-hint\n \n
\n \n tb.rulenode.order-by\n \n \n {{ order }}\n \n \n tb.rulenode.order-by-hint\n \n \n tb.rulenode.limit\n \n tb.rulenode.limit-hint\n \n
\n \n {{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-interval-patterns-hint
\n
\n
\n \n tb.rulenode.start-interval\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.start-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.end-interval\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.end-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n \n tb.rulenode.start-interval-pattern\n \n \n {{ \'tb.rulenode.start-interval-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.end-interval-pattern\n \n \n {{ \'tb.rulenode.end-interval-pattern-required\' | translate }}\n \n \n \n \n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var Ie=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.relatedAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.relatedAttributesConfigForm=this.fb.group({relationsQuery:[e?e.relationsQuery:null,[i.Validators.required]],telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-related-attributes-config",template:'
\n \n \n \n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var ke=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.tenantAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.tenantAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,args:[{selector:"tb-enrichment-node-tenant-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var Ne=function(){function e(){}return e.decorators=[{type:t.NgModule,args:[{declarations:[Ce,ve,Fe,Te,qe,xe,Ie,ke],imports:[r.CommonModule,a.SharedModule,ee],exports:[Ce,ve,Fe,Te,qe,xe,Ie,ke]}]}],e}(),Se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.originatorSource=E,n.originatorSources=Object.keys(E),n.originatorSourceTranslationMap=V,n}return g(r,e),r.prototype.configForm=function(){return this.changeOriginatorConfigForm},r.prototype.onConfigurationSet=function(e){this.changeOriginatorConfigForm=this.fb.group({originatorSource:[e?e.originatorSource:null,[i.Validators.required]],relationsQuery:[e?e.relationsQuery:null,[]]})},r.prototype.validatorTriggers=function(){return["originatorSource"]},r.prototype.updateValidators=function(e){var t=this.changeOriginatorConfigForm.get("originatorSource").value;t&&t===E.RELATED?this.changeOriginatorConfigForm.get("relationsQuery").setValidators([i.Validators.required]):this.changeOriginatorConfigForm.get("relationsQuery").setValidators([]),this.changeOriginatorConfigForm.get("relationsQuery").updateValueAndValidity({emitEvent:e})},r.decorators=[{type:t.Component,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
\n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var Ee=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return g(r,e),r.prototype.configForm=function(){return this.scriptConfigForm},r.prototype.onConfigurationSet=function(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"update",this.translate.instant("tb.rulenode.transformer"),"Transform",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.decorators=[{type:t.Component,args:[{selector:"tb-transformation-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},r.propDecorators={jsFuncComponent:[{type:t.ViewChild,args:["jsFuncComponent",{static:!0}]}]},r}(a.RuleNodeConfigurationComponent);var Ve=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return g(r,e),r.prototype.configForm=function(){return this.toEmailConfigForm},r.prototype.onConfigurationSet=function(e){this.toEmailConfigForm=this.fb.group({fromTemplate:[e?e.fromTemplate:null,[i.Validators.required]],toTemplate:[e?e.toTemplate:null,[i.Validators.required]],ccTemplate:[e?e.ccTemplate:null,[]],bccTemplate:[e?e.bccTemplate:null,[]],subjectTemplate:[e?e.subjectTemplate:null,[i.Validators.required]],bodyTemplate:[e?e.bodyTemplate:null,[i.Validators.required]]})},r.decorators=[{type:t.Component,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.body-template\n \n \n {{ \'tb.rulenode.body-template-required\' | translate }}\n \n \n \n
\n'}]}],r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r}(a.RuleNodeConfigurationComponent);var Ae=function(){function e(){}return e.decorators=[{type:t.NgModule,args:[{declarations:[Se,Ee,Ve],imports:[r.CommonModule,a.SharedModule,ee],exports:[Se,Ee,Ve]}]}],e}(),Le=function(){function e(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-name-pattern-hint":"Name pattern, use ${metaKeyName} to substitute variables from metadata","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-type-pattern-hint":"Type pattern, use ${metaKeyName} to substitute variables from metadata","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-name-pattern-hint":"Customer name pattern, use ${metaKeyName} to substitute variables from metadata","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.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.",limit:"Limit","limit-hint":"Min limit value is 2, max - 1000. In case you want to fetch a single entry, select fetch mode 'FIRST' or 'LAST'.","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","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","latest-timeseries":"Latest timeseries","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-hint":"Relation type pattern, use ${metaKeyName} to substitute variables from metadata","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","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","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","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 metadata period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds metadata pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","period-in-seconds-pattern-hint":"Period in seconds pattern, use ${metaKeyName} to substitute variables from metadata","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",propagate:"Propagate",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","from-template-hint":"From address template, use ${metaKeyName} to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use ${metaKeyName} to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use ${metaKeyName} to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use ${metaKeyName} to substitute variables from metadata","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","endpoint-url-pattern-hint":"HTTP URL address pattern, use ${metaKeyName} to substitute variables from metadata","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory","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 ${metaKeyName} in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use ${metaKeyName} to substitute variables from metadata","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","topic-arn-pattern-hint":"Topic ARN pattern, use ${metaKeyName} to substitute variables from metadata","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","queue-url-pattern-hint":"Queue URL pattern, use ${metaKeyName} to substitute variables from metadata","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 ${metaKeyName} in name/value fields to substitute variables from metadata","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","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","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"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-interval-patterns":"Use metadata interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","check-all-keys":"Check that all selected keys 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-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","start-interval-pattern-hint":"Start interval pattern, use ${metaKeyName} to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use ${metaKeyName} to substitute variables from metadata","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","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":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","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","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","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":"You should 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-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 Latest telemetry with Timestamp","get-latest-value-with-ts-hint":'If selected, latest telemetry values will be added to the outbound message metadata with timestamp, e.g: "temp": "{\\"ts\\":1574329385897,\\"value\\":42}"',"use-redis-queue":"Use redis queue for message persistence","trim-redis-queue":"Trim redis queue","redis-queue-max-size":"Redis queue max size"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}},!0)}(e)}return e.decorators=[{type:t.NgModule,args:[{declarations:[b],imports:[r.CommonModule,a.SharedModule],exports:[ue,he,Ne,Ae,b]}]}],e.ctorParameters=function(){return[{type:n.TranslateService}]},e}();e.RuleNodeCoreConfigModule=Le,e.default=Le,e.ɵa=b,e.ɵb=ue,e.ɵba=ee,e.ɵbb=Y,e.ɵbc=J,e.ɵbd=Z,e.ɵbe=X,e.ɵbf=he,e.ɵbg=de,e.ɵbh=pe,e.ɵbi=ce,e.ɵbj=fe,e.ɵbk=ge,e.ɵbl=ye,e.ɵbm=be,e.ɵbn=Ne,e.ɵbo=Ce,e.ɵbp=ve,e.ɵbq=Fe,e.ɵbr=Te,e.ɵbs=qe,e.ɵbt=xe,e.ɵbu=Ie,e.ɵbv=ke,e.ɵbw=Ae,e.ɵbx=Se,e.ɵby=Ee,e.ɵbz=Ve,e.ɵc=h,e.ɵd=C,e.ɵe=v,e.ɵf=F,e.ɵg=T,e.ɵh=q,e.ɵi=x,e.ɵj=I,e.ɵk=k,e.ɵl=N,e.ɵm=S,e.ɵn=z,e.ɵo=$,e.ɵp=_,e.ɵq=W,e.ɵr=te,e.ɵs=re,e.ɵt=ne,e.ɵu=ae,e.ɵv=oe,e.ɵw=ie,e.ɵx=le,e.ɵy=se,e.ɵz=me,Object.defineProperty(e,"__esModule",{value:!0})})); + ***************************************************************************** */var g=function(e,t){return(g=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)t.hasOwnProperty(r)&&(e[r]=t[r])})(e,t)};function y(e,t){function r(){this.constructor=e}g(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)}function b(e,t,r,n){var a,o=arguments.length,i=o<3?t:null===n?n=Object.getOwnPropertyDescriptor(t,r):n;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)i=Reflect.decorate(e,t,r,n);else for(var l=e.length-1;l>=0;l--)(a=e[l])&&(i=(o<3?a(i):o>3?a(t,r,i):a(t,r))||i);return o>3&&i&&Object.defineProperty(t,r,i),i}function h(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)}function C(e){var t="function"==typeof Symbol&&e[Symbol.iterator],r=0;return t?t.call(e):{next:function(){return e&&r>=e.length&&(e=void 0),{value:e&&e[r++],done:!e}}}}var v,F=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.emptyConfigForm},r.prototype.onConfigurationSet=function(e){this.emptyConfigForm=this.fb.group({})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-node-empty-config",template:"
"}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),T=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.attributeScopes=Object.keys(a.AttributeScope),n.telemetryTypeTranslationsMap=a.telemetryTypeTranslations,n}return y(r,e),r.prototype.configForm=function(){return this.attributesConfigForm},r.prototype.onConfigurationSet=function(e){this.attributesConfigForm=this.fb.group({scope:[e?e.scope:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-attributes-config",template:'
\n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),q=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.timeseriesConfigForm},r.prototype.onConfigurationSet=function(e){this.timeseriesConfigForm=this.fb.group({defaultTTL:[e?e.defaultTTL:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({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'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),x=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.rpcRequestConfigForm},r.prototype.onConfigurationSet=function(e){this.rpcRequestConfigForm=this.fb.group({timeoutInSeconds:[e?e.timeoutInSeconds:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({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'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),I=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.logConfigForm},r.prototype.onConfigurationSet=function(e){this.logConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.logConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"string",this.translate.instant("tb.rulenode.to-string"),"ToString",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.logConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-log-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),S=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.assignCustomerConfigForm},r.prototype.onConfigurationSet=function(e){this.assignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[i.Validators.required]],createCustomerIfNotExists:[!!e&&e.createCustomerIfNotExists,[]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({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 \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),N=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.clearAlarmConfigForm},r.prototype.onConfigurationSet=function(e){this.clearAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[i.Validators.required]],alarmType:[e?e.alarmType:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.clearAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.clearAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-clear-alarm-config",template:'
\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'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),k=function(e){function r(t,r,n,o){var i=e.call(this,t)||this;return i.store=t,i.fb=r,i.nodeScriptTestService=n,i.translate=o,i.alarmSeverities=Object.keys(a.AlarmSeverity),i.alarmSeverityTranslationMap=a.alarmSeverityTranslations,i.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],i}return y(r,e),r.prototype.configForm=function(){return this.createAlarmConfigForm},r.prototype.onConfigurationSet=function(e){this.createAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[i.Validators.required]],useMessageAlarmData:[!!e&&e.useMessageAlarmData,[]],alarmType:[e?e.alarmType:null,[]],severity:[e?e.severity:null,[]],propagate:[!!e&&e.propagate,[]],relationTypes:[e?e.relationTypes:null,[]]})},r.prototype.validatorTriggers=function(){return["useMessageAlarmData"]},r.prototype.updateValidators=function(e){this.createAlarmConfigForm.get("useMessageAlarmData").value?(this.createAlarmConfigForm.get("alarmType").setValidators([]),this.createAlarmConfigForm.get("severity").setValidators([])):(this.createAlarmConfigForm.get("alarmType").setValidators([i.Validators.required]),this.createAlarmConfigForm.get("severity").setValidators([i.Validators.required])),this.createAlarmConfigForm.get("alarmType").updateValueAndValidity({emitEvent:e}),this.createAlarmConfigForm.get("severity").updateValueAndValidity({emitEvent:e})},r.prototype.testScript=function(){var e=this,t=this.createAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.createAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},r.prototype.removeKey=function(e,t){var r=this.createAlarmConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.createAlarmConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.createAlarmConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.createAlarmConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-create-alarm-config",template:'
\n \n \n \n
\n \n
\n \n {{ \'tb.rulenode.use-message-alarm-data\' | translate }}\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.alarm-severity\n \n \n {{ alarmSeverityTranslationMap.get(severity) | translate }}\n \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 \n \n
\n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),E=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return y(r,e),r.prototype.configForm=function(){return this.createRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.createRelationConfigForm=this.fb.group({direction:[e?e.direction:null,[i.Validators.required]],entityType:[e?e.entityType:null,[i.Validators.required]],entityNamePattern:[e?e.entityNamePattern:null,[]],entityTypePattern:[e?e.entityTypePattern:null,[]],relationType:[e?e.relationType:null,[i.Validators.required]],createEntityIfNotExists:[!!e&&e.createEntityIfNotExists,[]],removeCurrentRelations:[!!e&&e.removeCurrentRelations,[]],changeOriginatorToRelatedEntity:[!!e&&e.changeOriginatorToRelatedEntity,[]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.validatorTriggers=function(){return["entityType"]},r.prototype.updateValidators=function(e){var t=this.createRelationConfigForm.get("entityType").value;t?this.createRelationConfigForm.get("entityNamePattern").setValidators([i.Validators.required]):this.createRelationConfigForm.get("entityNamePattern").setValidators([]),!t||t!==a.EntityType.DEVICE&&t!==a.EntityType.ASSET?this.createRelationConfigForm.get("entityTypePattern").setValidators([]):this.createRelationConfigForm.get("entityTypePattern").setValidators([i.Validators.required]),this.createRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e}),this.createRelationConfigForm.get("entityTypePattern").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({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 \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),V=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.msgDelayConfigForm},r.prototype.onConfigurationSet=function(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,[i.Validators.required,i.Validators.min(1),i.Validators.max(1e5)]]})},r.prototype.validatorTriggers=function(){return["useMetadataPeriodInSecondsPatterns"]},r.prototype.updateValidators=function(e){this.msgDelayConfigForm.get("useMetadataPeriodInSecondsPatterns").value?(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([i.Validators.required]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([])):(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([i.Validators.required,i.Validators.min(0)])),this.msgDelayConfigForm.get("periodInSecondsPattern").updateValueAndValidity({emitEvent:e}),this.msgDelayConfigForm.get("periodInSeconds").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({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'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),A=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return y(r,e),r.prototype.configForm=function(){return this.deleteRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.deleteRelationConfigForm=this.fb.group({deleteForSingleEntity:[!!e&&e.deleteForSingleEntity,[]],direction:[e?e.direction:null,[i.Validators.required]],entityType:[e?e.entityType:null,[]],entityNamePattern:[e?e.entityNamePattern:null,[]],relationType:[e?e.relationType:null,[i.Validators.required]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.validatorTriggers=function(){return["deleteForSingleEntity","entityType"]},r.prototype.updateValidators=function(e){var t=this.deleteRelationConfigForm.get("deleteForSingleEntity").value,r=this.deleteRelationConfigForm.get("entityType").value;t?this.deleteRelationConfigForm.get("entityType").setValidators([i.Validators.required]):this.deleteRelationConfigForm.get("entityType").setValidators([]),t&&r?this.deleteRelationConfigForm.get("entityNamePattern").setValidators([i.Validators.required]):this.deleteRelationConfigForm.get("entityNamePattern").setValidators([]),this.deleteRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:!1}),this.deleteRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({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 \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),L=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.generatorConfigForm},r.prototype.onConfigurationSet=function(e){this.generatorConfigForm=this.fb.group({msgCount:[e?e.msgCount:null,[i.Validators.required,i.Validators.min(0)]],periodInSeconds:[e?e.periodInSeconds:null,[i.Validators.required,i.Validators.min(1)]],originator:[e?e.originator:null,[]],jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.prepareInputConfig=function(e){return e&&(e.originatorId&&e.originatorType?e.originator={id:e.originatorId,entityType:e.originatorType}:e.originator=null,delete e.originatorId,delete e.originatorType),e},r.prototype.prepareOutputConfig=function(e){return e.originator?(e.originatorId=e.originator.id,e.originatorType=e.originator.entityType):(e.originatorId=null,e.originatorType=null),delete e.originator,e},r.prototype.testScript=function(){var e=this,t=this.generatorConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"generate",this.translate.instant("tb.rulenode.generator"),"Generate",["prevMsg","prevMetadata","prevMsgType"],this.ruleNodeId).subscribe((function(t){t&&e.generatorConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({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'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent);!function(e){e.CUSTOMER="CUSTOMER",e.TENANT="TENANT",e.RELATED="RELATED",e.ALARM_ORIGINATOR="ALARM_ORIGINATOR"}(v||(v={}));var M,P=new Map([[v.CUSTOMER,"tb.rulenode.originator-customer"],[v.TENANT,"tb.rulenode.originator-tenant"],[v.RELATED,"tb.rulenode.originator-related"],[v.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator"]]);!function(e){e.CIRCLE="CIRCLE",e.POLYGON="POLYGON"}(M||(M={}));var R,w=new Map([[M.CIRCLE,"tb.rulenode.perimeter-circle"],[M.POLYGON,"tb.rulenode.perimeter-polygon"]]);!function(e){e.MILLISECONDS="MILLISECONDS",e.SECONDS="SECONDS",e.MINUTES="MINUTES",e.HOURS="HOURS",e.DAYS="DAYS"}(R||(R={}));var D,O=new Map([[R.MILLISECONDS,"tb.rulenode.time-unit-milliseconds"],[R.SECONDS,"tb.rulenode.time-unit-seconds"],[R.MINUTES,"tb.rulenode.time-unit-minutes"],[R.HOURS,"tb.rulenode.time-unit-hours"],[R.DAYS,"tb.rulenode.time-unit-days"]]);!function(e){e.METER="METER",e.KILOMETER="KILOMETER",e.FOOT="FOOT",e.MILE="MILE",e.NAUTICAL_MILE="NAUTICAL_MILE"}(D||(D={}));var K,B=new Map([[D.METER,"tb.rulenode.range-unit-meter"],[D.KILOMETER,"tb.rulenode.range-unit-kilometer"],[D.FOOT,"tb.rulenode.range-unit-foot"],[D.MILE,"tb.rulenode.range-unit-mile"],[D.NAUTICAL_MILE,"tb.rulenode.range-unit-nautical-mile"]]);!function(e){e.TITLE="TITLE",e.COUNTRY="COUNTRY",e.STATE="STATE",e.ZIP="ZIP",e.ADDRESS="ADDRESS",e.ADDRESS2="ADDRESS2",e.PHONE="PHONE",e.EMAIL="EMAIL",e.ADDITIONAL_INFO="ADDITIONAL_INFO"}(K||(K={}));var j,U,G,H=new Map([[K.TITLE,"tb.rulenode.entity-details-title"],[K.COUNTRY,"tb.rulenode.entity-details-country"],[K.STATE,"tb.rulenode.entity-details-state"],[K.ZIP,"tb.rulenode.entity-details-zip"],[K.ADDRESS,"tb.rulenode.entity-details-address"],[K.ADDRESS2,"tb.rulenode.entity-details-address2"],[K.PHONE,"tb.rulenode.entity-details-phone"],[K.EMAIL,"tb.rulenode.entity-details-email"],[K.ADDITIONAL_INFO,"tb.rulenode.entity-details-additional_info"]]);!function(e){e.FIRST="FIRST",e.LAST="LAST",e.ALL="ALL"}(j||(j={})),function(e){e.ASC="ASC",e.DESC="DESC"}(U||(U={})),function(e){e.STANDARD="STANDARD",e.FIFO="FIFO"}(G||(G={}));var Q,z=new Map([[G.STANDARD,"tb.rulenode.sqs-queue-standard"],[G.FIFO,"tb.rulenode.sqs-queue-fifo"]]),$=["anonymous","basic","cert.PEM"],_=new Map([["anonymous","tb.rulenode.credentials-anonymous"],["basic","tb.rulenode.credentials-basic"],["cert.PEM","tb.rulenode.credentials-pem"]]);!function(e){e.GET="GET",e.POST="POST",e.PUT="PUT",e.DELETE="DELETE"}(Q||(Q={}));var J=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.perimeterType=M,n.perimeterTypes=Object.keys(M),n.perimeterTypeTranslationMap=w,n.rangeUnits=Object.keys(D),n.rangeUnitTranslationMap=B,n.timeUnits=Object.keys(R),n.timeUnitsTranslationMap=O,n}return y(r,e),r.prototype.configForm=function(){return this.geoActionConfigForm},r.prototype.onConfigurationSet=function(e){this.geoActionConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[i.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[i.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType: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,[i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]],minInsideDurationTimeUnit:[e?e.minInsideDurationTimeUnit:null,[i.Validators.required]],minOutsideDuration:[e?e.minOutsideDuration:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]],minOutsideDurationTimeUnit:[e?e.minOutsideDurationTimeUnit:null,[i.Validators.required]]})},r.prototype.validatorTriggers=function(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]},r.prototype.updateValidators=function(e){var t=this.geoActionConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,r=this.geoActionConfigForm.get("perimeterType").value;t?this.geoActionConfigForm.get("perimeterType").setValidators([]):this.geoActionConfigForm.get("perimeterType").setValidators([i.Validators.required]),t||r!==M.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([i.Validators.required,i.Validators.min(-90),i.Validators.max(90)]),this.geoActionConfigForm.get("centerLongitude").setValidators([i.Validators.required,i.Validators.min(-180),i.Validators.max(180)]),this.geoActionConfigForm.get("range").setValidators([i.Validators.required,i.Validators.min(0)]),this.geoActionConfigForm.get("rangeUnit").setValidators([i.Validators.required])),t||r!==M.POLYGON?this.geoActionConfigForm.get("polygonsDefinition").setValidators([]):this.geoActionConfigForm.get("polygonsDefinition").setValidators([i.Validators.required]),this.geoActionConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),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})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({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.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \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'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),W=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.msgCountConfigForm},r.prototype.onConfigurationSet=function(e){this.msgCountConfigForm=this.fb.group({interval:[e?e.interval:null,[i.Validators.required,i.Validators.min(1)]],telemetryPrefix:[e?e.telemetryPrefix:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({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'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Y=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.rpcReplyConfigForm},r.prototype.onConfigurationSet=function(e){this.rpcReplyConfigForm=this.fb.group({requestIdMetaDataAttribute:[e?e.requestIdMetaDataAttribute:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rpc-reply-config",template:'
\n \n tb.rulenode.request-id-metadata-attribute\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Z=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.saveToCustomTableConfigForm},r.prototype.onConfigurationSet=function(e){this.saveToCustomTableConfigForm=this.fb.group({tableName:[e?e.tableName:null,[i.Validators.required]],fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({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 \n \n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),X=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.translate=r,o.injector=n,o.fb=a,o.propagateChange=null,o.valueChangeSubscription=null,o}var a;return y(r,e),a=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){this.ngControl=this.injector.get(i.NgControl),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.kvListFormGroup=this.fb.group({}),this.kvListFormGroup.addControl("keyVals",this.fb.array([]))},r.prototype.keyValsFormArray=function(){return this.kvListFormGroup.get("keyVals")},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.kvListFormGroup.disable({emitEvent:!1}):this.kvListFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){var t,r,n=this;this.valueChangeSubscription&&this.valueChangeSubscription.unsubscribe();var a=[];if(e)try{for(var o=C(Object.keys(e)),l=o.next();!l.done;l=o.next()){var s=l.value;Object.prototype.hasOwnProperty.call(e,s)&&a.push(this.fb.group({key:[s,[i.Validators.required]],value:[e[s],[i.Validators.required]]}))}}catch(e){t={error:e}}finally{try{l&&!l.done&&(r=o.return)&&r.call(o)}finally{if(t)throw t.error}}this.kvListFormGroup.setControl("keyVals",this.fb.array(a)),this.valueChangeSubscription=this.kvListFormGroup.valueChanges.subscribe((function(){n.updateModel()}))},r.prototype.removeKeyVal=function(e){this.kvListFormGroup.get("keyVals").removeAt(e)},r.prototype.addKeyVal=function(){this.kvListFormGroup.get("keyVals").push(this.fb.group({key:["",[i.Validators.required]],value:["",[i.Validators.required]]}))},r.prototype.validate=function(e){return!this.kvListFormGroup.get("keyVals").value.length&&this.required?{kvMapRequired:!0}:this.kvListFormGroup.valid?null:{kvFieldsRequired:!0}},r.prototype.updateModel=function(){var e=this.kvListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.kvListFormGroup.valid)this.propagateChange(null);else{var t={};e.forEach((function(e){t[e.key]=e.value})),this.propagateChange(t)}},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:t.Injector},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",String)],r.prototype,"requiredText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"keyText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"keyRequiredText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"valText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"valRequiredText",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=a=b([t.Component({selector:"tb-kv-map-config",template:'
\n
\n {{ keyText }}\n {{ valText }}\n \n
\n
\n
\n \n \n \n \n {{ keyRequiredText | translate }}\n \n \n \n \n \n \n {{ valRequiredText | translate }}\n \n \n \n
\n
\n \n
\n \n
\n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return a})),multi:!0},{provide:i.NG_VALIDATORS,useExisting:t.forwardRef((function(){return a})),multi:!0}],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:rgba(0,0,0,.54);font-size:12px;font-weight:700;white-space:nowrap}:host .tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:20px;max-height:300px;overflow:auto}:host .tb-kv-map-config .body .row{padding-top:5px;max-height:40px}:host .tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell{margin:0;max-height:40px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell .mat-form-field-infix{border-top:0}:host ::ng-deep .tb-kv-map-config .body button.mat-button{margin:0}"]}),h("design:paramtypes",[o.Store,n.TranslateService,t.Injector,i.FormBuilder])],r)}(a.PageComponent),ee=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n.propagateChange=null,n}var n;return y(r,e),n=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.deviceRelationsQueryFormGroup=this.fb.group({direction:[null,[i.Validators.required]],maxLevel:[null,[]],relationType:[null],deviceTypes:[null,[i.Validators.required]]}),this.deviceRelationsQueryFormGroup.valueChanges.subscribe((function(t){e.deviceRelationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.deviceRelationsQueryFormGroup.disable({emitEvent:!1}):this.deviceRelationsQueryFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){this.deviceRelationsQueryFormGroup.reset(e,{emitEvent:!1})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=n=b([t.Component({selector:"tb-device-relations-query-config",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
\n
relation.relation-type
\n \n \n
device.device-types
\n \n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0}]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),te=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.propagateChange=null,n}var n;return y(r,e),n=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.relationsQueryFormGroup=this.fb.group({direction:[null,[i.Validators.required]],maxLevel:[null,[]],filters:[null]}),this.relationsQueryFormGroup.valueChanges.subscribe((function(t){e.relationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.relationsQueryFormGroup.disable({emitEvent:!1}):this.relationsQueryFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){this.relationsQueryFormGroup.reset(e,{emitEvent:!1})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=n=b([t.Component({selector:"tb-relations-query-config",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
\n
relation.relation-filters
\n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0}]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),re=function(e){function r(t,r,n,o){var i,l,m=e.call(this,t)||this;m.store=t,m.translate=r,m.truncate=n,m.fb=o,m.placeholder="tb.rulenode.message-type",m.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],m.messageTypes=[],m.messageTypesList=[],m.searchText="",m.propagateChange=function(e){},m.messageTypeConfigForm=m.fb.group({messageType:[null]});try{for(var u=C(Object.keys(a.MessageType)),d=u.next();!d.done;d=u.next()){var p=d.value;m.messageTypesList.push({name:a.messageTypeNames.get(a.MessageType[p]),value:p})}}catch(e){i={error:e}}finally{try{d&&!d.done&&(l=u.return)&&l.call(u)}finally{if(i)throw i.error}}return m}var l;return y(r,e),l=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.ngOnInit=function(){var e=this;this.filteredMessageTypes=this.messageTypeConfigForm.get("messageType").valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(t){return e.fetchMessageTypes(t)})),f.share())},r.prototype.ngAfterViewInit=function(){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.messageTypeConfigForm.disable({emitEvent:!1}):this.messageTypeConfigForm.enable({emitEvent:!1})},r.prototype.writeValue=function(e){var t=this;this.searchText="",this.messageTypes.length=0,e&&e.forEach((function(e){var r=t.messageTypesList.find((function(t){return t.value===e}));r?t.messageTypes.push({name:r.name,value:r.value}):t.messageTypes.push({name:e,value:e})}))},r.prototype.displayMessageTypeFn=function(e){return e?e.name:void 0},r.prototype.textIsNotEmpty=function(e){return!!(e&&null!=e&&e.length>0)},r.prototype.createMessageType=function(e,t){e.preventDefault(),this.transformMessageType(t)},r.prototype.add=function(e){this.transformMessageType(e.value)},r.prototype.fetchMessageTypes=function(e){if(this.searchText=e,this.searchText&&this.searchText.length){var t=this.searchText.toUpperCase();return c.of(this.messageTypesList.filter((function(e){return e.name.toUpperCase().includes(t)})))}return c.of(this.messageTypesList)},r.prototype.transformMessageType=function(e){if((e||"").trim()){var t=null,r=e.trim(),n=this.messageTypesList.find((function(e){return e.name===r}));(t=n?{name:n.name,value:n.value}:{name:r,value:r})&&this.addMessageType(t)}this.clear("")},r.prototype.remove=function(e){var t=this.messageTypes.indexOf(e);t>=0&&(this.messageTypes.splice(t,1),this.updateModel())},r.prototype.selected=function(e){this.addMessageType(e.option.value),this.clear("")},r.prototype.addMessageType=function(e){-1===this.messageTypes.findIndex((function(t){return t.value===e.value}))&&(this.messageTypes.push(e),this.updateModel())},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.messageTypeInput.nativeElement.value=e,this.messageTypeConfigForm.get("messageType").patchValue(null,{emitEvent:!0}),setTimeout((function(){t.messageTypeInput.nativeElement.blur(),t.messageTypeInput.nativeElement.focus()}),0)},r.prototype.updateModel=function(){var e=this.messageTypes.map((function(e){return e.value}));this.required?(this.chipList.errorState=!e.length,this.propagateChange(e.length>0?e:null)):(this.chipList.errorState=!1,this.propagateChange(e))},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:a.TruncatePipe},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),b([t.Input(),h("design:type",String)],r.prototype,"label",void 0),b([t.Input(),h("design:type",Object)],r.prototype,"placeholder",void 0),b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.ViewChild("chipList",{static:!1}),h("design:type",d.MatChipList)],r.prototype,"chipList",void 0),b([t.ViewChild("messageTypeAutocomplete",{static:!1}),h("design:type",p.MatAutocomplete)],r.prototype,"matAutocomplete",void 0),b([t.ViewChild("messageTypeInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"messageTypeInput",void 0),r=l=b([t.Component({selector:"tb-message-types-config",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 {{ translate.get(\'tb.rulenode.no-message-type-matching\',\n {messageType: truncate.transform(searchText, true, 6, '...')}) | async }}\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',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return l})),multi:!0}]}),h("design:paramtypes",[o.Store,n.TranslateService,a.TruncatePipe,i.FormBuilder])],r)}(a.PageComponent),ne=function(){function e(){}return e=b([t.NgModule({declarations:[X,ee,te,re],imports:[r.CommonModule,a.SharedModule,m.HomeComponentsModule],exports:[X,ee,te,re]})],e)}(),ae=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.unassignCustomerConfigForm},r.prototype.onConfigurationSet=function(e){this.unassignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[i.Validators.required]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({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 \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),oe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.snsConfigForm},r.prototype.onConfigurationSet=function(e){this.snsConfigForm=this.fb.group({topicArnPattern:[e?e.topicArnPattern:null,[i.Validators.required]],accessKeyId:[e?e.accessKeyId:null,[i.Validators.required]],secretAccessKey:[e?e.secretAccessKey:null,[i.Validators.required]],region:[e?e.region:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-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'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ie=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.sqsQueueType=G,n.sqsQueueTypes=Object.keys(G),n.sqsQueueTypeTranslationsMap=z,n}return y(r,e),r.prototype.configForm=function(){return this.sqsConfigForm},r.prototype.onConfigurationSet=function(e){this.sqsConfigForm=this.fb.group({queueType:[e?e.queueType:null,[i.Validators.required]],queueUrlPattern:[e?e.queueUrlPattern:null,[i.Validators.required]],delaySeconds:[e?e.delaySeconds:null,[i.Validators.min(0),i.Validators.max(900)]],messageAttributes:[e?e.messageAttributes:null,[]],accessKeyId:[e?e.accessKeyId:null,[i.Validators.required]],secretAccessKey:[e?e.secretAccessKey:null,[i.Validators.required]],region:[e?e.region:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-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
tb.rulenode.message-attributes-hint
\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'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),le=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.pubSubConfigForm},r.prototype.onConfigurationSet=function(e){this.pubSubConfigForm=this.fb.group({projectId:[e?e.projectId:null,[i.Validators.required]],topicName:[e?e.topicName:null,[i.Validators.required]],serviceAccountKey:[e?e.serviceAccountKey:null,[i.Validators.required]],serviceAccountKeyFileName:[e?e.serviceAccountKeyFileName:null,[i.Validators.required]],messageAttributes:[e?e.messageAttributes:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-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
tb.rulenode.message-attributes-hint
\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.ackValues=["all","-1","0","1"],n}return y(r,e),r.prototype.configForm=function(){return this.kafkaConfigForm},r.prototype.onConfigurationSet=function(e){this.kafkaConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],bootstrapServers:[e?e.bootstrapServers:null,[i.Validators.required]],retries:[e?e.retries:null,[i.Validators.min(0)]],batchSize:[e?e.batchSize:null,[i.Validators.min(0)]],linger:[e?e.linger:null,[i.Validators.min(0)]],bufferMemory:[e?e.bufferMemory:null,[i.Validators.min(0)]],acks:[e?e.acks:null,[i.Validators.required]],keySerializer:[e?e.keySerializer:null,[i.Validators.required]],valueSerializer:[e?e.valueSerializer:null,[i.Validators.required]],otherProperties:[e?e.otherProperties:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-kafka-config",template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \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'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),me=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allMqttCredentialsTypes=$,n.mqttCredentialsTypeTranslationsMap=_,n}return y(r,e),r.prototype.configForm=function(){return this.mqttConfigForm},r.prototype.onConfigurationSet=function(e){this.mqttConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(200)]],clientId:[e?e.clientId:null,[]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:this.fb.group({type:[e&&e.credentials?e.credentials.type:null,[i.Validators.required]],username:[e&&e.credentials?e.credentials.username:null,[]],password:[e&&e.credentials?e.credentials.password: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,[]]})})},r.prototype.prepareOutputConfig=function(e){var t=e.credentials.type;switch(t){case"anonymous":e.credentials={type:t};break;case"basic":e.credentials={type:t,username:e.credentials.username,password:e.credentials.password};break;case"cert.PEM":delete e.credentials.username}return e},r.prototype.validatorTriggers=function(){return["credentials.type"]},r.prototype.updateValidators=function(e){var t=this.mqttConfigForm.get("credentials"),r=t.get("type").value;switch(e&&t.reset({type:r},{emitEvent:!1}),t.get("username").setValidators([]),t.get("password").setValidators([]),t.get("caCert").setValidators([]),t.get("caCertFileName").setValidators([]),t.get("privateKey").setValidators([]),t.get("privateKeyFileName").setValidators([]),t.get("cert").setValidators([]),t.get("certFileName").setValidators([]),r){case"anonymous":break;case"basic":t.get("username").setValidators([i.Validators.required]),t.get("password").setValidators([i.Validators.required]);break;case"cert.PEM":t.get("caCert").setValidators([i.Validators.required]),t.get("caCertFileName").setValidators([i.Validators.required]),t.get("privateKey").setValidators([i.Validators.required]),t.get("privateKeyFileName").setValidators([i.Validators.required]),t.get("cert").setValidators([i.Validators.required]),t.get("certFileName").setValidators([i.Validators.required])}t.get("username").updateValueAndValidity({emitEvent:e}),t.get("password").updateValueAndValidity({emitEvent:e}),t.get("caCert").updateValueAndValidity({emitEvent:e}),t.get("caCertFileName").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})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-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 \n \n {{ \'tb.rulenode.clean-session\' | translate }}\n \n \n {{ \'tb.rulenode.enable-ssl\' | translate }}\n \n \n \n tb.rulenode.credentials\n \n {{ mqttCredentialsTypeTranslationsMap.get(mqttConfigForm.get(\'credentials\').get(\'type\').value) | translate }}\n \n \n
\n \n tb.rulenode.credentials-type\n \n \n {{ mqttCredentialsTypeTranslationsMap.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 {{ \'tb.rulenode.password-required\' | translate }}\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',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ue=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],n}return y(r,e),r.prototype.configForm=function(){return this.rabbitMqConfigForm},r.prototype.onConfigurationSet=function(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,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.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,[i.Validators.min(0)]],handshakeTimeout:[e?e.handshakeTimeout:null,[i.Validators.min(0)]],clientProperties:[e?e.clientProperties:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-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 {{ \'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'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),de=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.httpRequestTypes=Object.keys(Q),n}return y(r,e),r.prototype.configForm=function(){return this.restApiCallConfigForm},r.prototype.onConfigurationSet=function(e){this.restApiCallConfigForm=this.fb.group({restEndpointUrlPattern:[e?e.restEndpointUrlPattern:null,[i.Validators.required]],requestMethod:[e?e.requestMethod:null,[i.Validators.required]],useSimpleClientHttpFactory:[!!e&&e.useSimpleClientHttpFactory,[]],readTimeoutMs:[e?e.readTimeoutMs:null,[]],maxParallelRequestsCount:[e?e.maxParallelRequestsCount:null,[i.Validators.min(0)]],headers:[e?e.headers:null,[]],useRedisQueueForMsgPersistence:[!!e&&e.useRedisQueueForMsgPersistence,[]],trimQueue:[!!e&&e.trimQueue,[]],maxQueueSize:[e?e.maxQueueSize:null,[]]})},r.prototype.validatorTriggers=function(){return["useSimpleClientHttpFactory","useRedisQueueForMsgPersistence"]},r.prototype.updateValidators=function(e){var t=this.restApiCallConfigForm.get("useSimpleClientHttpFactory").value,r=this.restApiCallConfigForm.get("useRedisQueueForMsgPersistence").value;t?this.restApiCallConfigForm.get("readTimeoutMs").setValidators([]):this.restApiCallConfigForm.get("readTimeoutMs").setValidators([i.Validators.min(0)]),r?this.restApiCallConfigForm.get("maxQueueSize").setValidators([i.Validators.min(0)]):this.restApiCallConfigForm.get("maxQueueSize").setValidators([]),this.restApiCallConfigForm.get("readTimeoutMs").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("maxQueueSize").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-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.use-simple-client-http-factory\' | translate }}\n \n \n tb.rulenode.read-timeout\n \n \n \n \n tb.rulenode.max-parallel-requests-count\n \n \n \n \n
tb.rulenode.headers-hint
\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'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),pe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.smtpProtocols=["smtp","smtps"],n}return y(r,e),r.prototype.configForm=function(){return this.sendEmailConfigForm},r.prototype.onConfigurationSet=function(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,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]]})},r.prototype.validatorTriggers=function(){return["useSystemSmtpSettings"]},r.prototype.updateValidators=function(e){this.sendEmailConfigForm.get("useSystemSmtpSettings").value?(this.sendEmailConfigForm.get("smtpProtocol").setValidators([]),this.sendEmailConfigForm.get("smtpHost").setValidators([]),this.sendEmailConfigForm.get("smtpPort").setValidators([]),this.sendEmailConfigForm.get("timeout").setValidators([])):(this.sendEmailConfigForm.get("smtpProtocol").setValidators([i.Validators.required]),this.sendEmailConfigForm.get("smtpHost").setValidators([i.Validators.required]),this.sendEmailConfigForm.get("smtpPort").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]),this.sendEmailConfigForm.get("timeout").setValidators([i.Validators.required,i.Validators.min(0)])),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})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-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.username\n \n \n \n tb.rulenode.password\n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ce=function(){function e(){}return e=b([t.NgModule({declarations:[T,q,x,I,S,N,k,E,V,A,L,J,W,Y,Z,ae,oe,ie,le,se,me,ue,de,pe],imports:[r.CommonModule,a.SharedModule,ne],exports:[T,q,x,I,S,N,k,E,V,A,L,J,W,Y,Z,ae,oe,ie,le,se,me,ue,de,pe]})],e)}(),fe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.checkMessageConfigForm},r.prototype.onConfigurationSet=function(e){this.checkMessageConfigForm=this.fb.group({messageNames:[e?e.messageNames:null,[]],metadataNames:[e?e.metadataNames:null,[]],checkAllKeys:[!!e&&e.checkAllKeys,[]]})},r.prototype.validateConfig=function(){var e=this.checkMessageConfigForm.get("messageNames").value,t=this.checkMessageConfigForm.get("metadataNames").value;return e.length>0||t.length>0},r.prototype.removeMessageName=function(e){var t=this.checkMessageConfigForm.get("messageNames").value,r=t.indexOf(e);r>=0&&(t.splice(r,1),this.checkMessageConfigForm.get("messageNames").setValue(t,{emitEvent:!0}))},r.prototype.removeMetadataName=function(e){var t=this.checkMessageConfigForm.get("metadataNames").value,r=t.indexOf(e);r>=0&&(t.splice(r,1),this.checkMessageConfigForm.get("metadataNames").setValue(t,{emitEvent:!0}))},r.prototype.addMessageName=function(e){var t=e.input,r=e.value;if((r||"").trim()){r=r.trim();var n=this.checkMessageConfigForm.get("messageNames").value;n&&-1!==n.indexOf(r)||(n||(n=[]),n.push(r),this.checkMessageConfigForm.get("messageNames").setValue(n,{emitEvent:!0}))}t&&(t.value="")},r.prototype.addMetadataName=function(e){var t=e.input,r=e.value;if((r||"").trim()){r=r.trim();var n=this.checkMessageConfigForm.get("metadataNames").value;n&&-1!==n.indexOf(r)||(n||(n=[]),n.push(r),this.checkMessageConfigForm.get("metadataNames").setValue(n,{emitEvent:!0}))}t&&(t.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-check-message-config",template:'
\n \n \n \n \n \n {{messageName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n \n \n \n \n {{metadataName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\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}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ge=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.entitySearchDirection=Object.keys(a.EntitySearchDirection),n.entitySearchDirectionTranslationsMap=a.entitySearchDirectionTranslations,n}return y(r,e),r.prototype.configForm=function(){return this.checkRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.checkRelationConfigForm=this.fb.group({checkForSingleEntity:[!!e&&e.checkForSingleEntity,[]],direction:[e?e.direction:null,[]],entityType:[e?e.entityType:null,e&&e.checkForSingleEntity?[i.Validators.required]:[]],entityId:[e?e.entityId:null,e&&e.checkForSingleEntity?[i.Validators.required]:[]],relationType:[e?e.relationType:null,[i.Validators.required]]})},r.prototype.validatorTriggers=function(){return["checkForSingleEntity"]},r.prototype.updateValidators=function(e){var t=this.checkRelationConfigForm.get("checkForSingleEntity").value;this.checkRelationConfigForm.get("entityType").setValidators(t?[i.Validators.required]:[]),this.checkRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:e}),this.checkRelationConfigForm.get("entityId").setValidators(t?[i.Validators.required]:[]),this.checkRelationConfigForm.get("entityId").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({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'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ye=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.perimeterType=M,n.perimeterTypes=Object.keys(M),n.perimeterTypeTranslationMap=w,n.rangeUnits=Object.keys(D),n.rangeUnitTranslationMap=B,n}return y(r,e),r.prototype.configForm=function(){return this.geoFilterConfigForm},r.prototype.onConfigurationSet=function(e){this.geoFilterConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[i.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[i.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType: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,[]]})},r.prototype.validatorTriggers=function(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]},r.prototype.updateValidators=function(e){var t=this.geoFilterConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,r=this.geoFilterConfigForm.get("perimeterType").value;t?this.geoFilterConfigForm.get("perimeterType").setValidators([]):this.geoFilterConfigForm.get("perimeterType").setValidators([i.Validators.required]),t||r!==M.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([i.Validators.required,i.Validators.min(-90),i.Validators.max(90)]),this.geoFilterConfigForm.get("centerLongitude").setValidators([i.Validators.required,i.Validators.min(-180),i.Validators.max(180)]),this.geoFilterConfigForm.get("range").setValidators([i.Validators.required,i.Validators.min(0)]),this.geoFilterConfigForm.get("rangeUnit").setValidators([i.Validators.required])),t||r!==M.POLYGON?this.geoFilterConfigForm.get("polygonsDefinition").setValidators([]):this.geoFilterConfigForm.get("polygonsDefinition").setValidators([i.Validators.required]),this.geoFilterConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),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})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({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.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \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'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),be=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.messageTypeConfigForm},r.prototype.onConfigurationSet=function(e){this.messageTypeConfigForm=this.fb.group({messageTypes:[e?e.messageTypes:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-message-type-config",template:'
\n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),he=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allowedEntityTypes=[a.EntityType.DEVICE,a.EntityType.ASSET,a.EntityType.ENTITY_VIEW,a.EntityType.TENANT,a.EntityType.CUSTOMER,a.EntityType.USER,a.EntityType.DASHBOARD,a.EntityType.RULE_CHAIN,a.EntityType.RULE_NODE],n}return y(r,e),r.prototype.configForm=function(){return this.originatorTypeConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorTypeConfigForm=this.fb.group({originatorTypes:[e?e.originatorTypes:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-originator-type-config",template:'
\n \n \n \n
\n',styles:[":host ::ng-deep tb-entity-type-list .mat-form-field-flex{padding-top:0}:host ::ng-deep tb-entity-type-list .mat-form-field-infix{border-top:0}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ce=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.scriptConfigForm},r.prototype.onConfigurationSet=function(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"filter",this.translate.instant("tb.rulenode.filter"),"Filter",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-filter-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),ve=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.switchConfigForm},r.prototype.onConfigurationSet=function(e){this.switchConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.switchConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"switch",this.translate.instant("tb.rulenode.switch"),"Switch",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.switchConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-filter-node-switch-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Fe=function(){function e(){}return e=b([t.NgModule({declarations:[fe,ge,ye,be,he,Ce,ve],imports:[r.CommonModule,a.SharedModule,ne],exports:[fe,ge,ye,be,he,Ce,ve]})],e)}(),Te=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.customerAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.customerAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-customer-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),qe=function(e){function r(t,r,n){var a,o,i=e.call(this,t)||this;i.store=t,i.translate=r,i.fb=n,i.entityDetailsTranslationsMap=H,i.entityDetailsList=[],i.searchText="",i.displayDetailsFn=i.displayDetails.bind(i);try{for(var l=C(Object.keys(K)),s=l.next();!s.done;s=l.next()){var m=s.value;i.entityDetailsList.push(K[m])}}catch(e){a={error:e}}finally{try{s&&!s.done&&(o=l.return)&&o.call(l)}finally{if(a)throw a.error}}return i}return y(r,e),r.prototype.ngOnInit=function(){var t=this;this.detailsFormControl=new i.FormControl(""),e.prototype.ngOnInit.call(this),this.filteredEntityDetails=this.detailsFormControl.valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(e){return t.fetchEntityDetails(e)})),f.share())},r.prototype.configForm=function(){return this.entityDetailsConfigForm},r.prototype.prepareInputConfig=function(e){return this.searchText="",this.detailsFormControl.patchValue("",{emitEvent:!0}),e},r.prototype.onConfigurationSet=function(e){this.entityDetailsConfigForm=this.fb.group({detailsList:[e?e.detailsList:null,[i.Validators.required]],addToMetadata:[!!e&&e.addToMetadata,[]]})},r.prototype.displayDetails=function(e){return e?this.translate.instant(H.get(e)):void 0},r.prototype.fetchEntityDetails=function(e){var t=this;if(this.searchText=e,this.searchText&&this.searchText.length){var r=this.searchText.toUpperCase();return c.of(this.entityDetailsList.filter((function(e){return t.translate.instant(H.get(K[e])).toUpperCase().includes(r)})))}return c.of(this.entityDetailsList)},r.prototype.detailsFieldSelected=function(e){this.addDetailsField(e.option.value),this.clear("")},r.prototype.removeDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;if(t){var r=t.indexOf(e);r>=0&&(t.splice(r,1),this.entityDetailsConfigForm.get("detailsList").setValue(t))}},r.prototype.addDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;t||(t=[]),-1===t.indexOf(e)&&(t.push(e),this.entityDetailsConfigForm.get("detailsList").setValue(t))},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.detailsInput.nativeElement.value=e,this.detailsFormControl.patchValue(null,{emitEvent:!0}),setTimeout((function(){t.detailsInput.nativeElement.blur(),t.detailsInput.nativeElement.focus()}),0)},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:i.FormBuilder}]},b([t.ViewChild("detailsInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"detailsInput",void 0),r=b([t.Component({selector:"tb-enrichment-node-entity-details-config",template:'
\n \n \n \n \n \n {{entityDetailsTranslationsMap.get(details) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-entity-details-matching\n
\n
\n
\n
\n
\n \n \n {{ \'tb.rulenode.add-to-metadata\' | translate }}\n \n
tb.rulenode.add-to-metadata-hint
\n
\n',styles:[":host ::ng-deep mat-form-field.entity-fields-list .mat-form-field-wrapper{margin-bottom:-1.25em}"]}),h("design:paramtypes",[o.Store,n.TranslateService,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),xe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.deviceAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.deviceAttributesConfigForm=this.fb.group({deviceRelationsQuery:[e?e.deviceRelationsQuery:null,[i.Validators.required]],tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})},r.prototype.removeKey=function(e,t){var r=this.deviceAttributesConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.deviceAttributesConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.deviceAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.deviceAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-device-attributes-config",template:'
\n \n \n \n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ie=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.originatorAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorAttributesConfigForm=this.fb.group({tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})},r.prototype.removeKey=function(e,t){var r=this.originatorAttributesConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.originatorAttributesConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.originatorAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.originatorAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-originator-attributes-config",template:'
\n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.originatorFieldsConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorFieldsConfigForm=this.fb.group({fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-originator-fields-config",template:'
\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ne=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n.fetchMode=j,n.fetchModes=Object.keys(j),n.samplingOrders=Object.keys(U),n.timeUnits=Object.keys(R),n.timeUnitsTranslationMap=O,n}return y(r,e),r.prototype.configForm=function(){return this.getTelemetryFromDatabaseConfigForm},r.prototype.onConfigurationSet=function(e){this.getTelemetryFromDatabaseConfigForm=this.fb.group({latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],fetchMode:[e?e.fetchMode:null,[i.Validators.required]],orderBy:[e?e.orderBy:null,[]],limit:[e?e.limit:null,[]],useMetadataIntervalPatterns:[!!e&&e.useMetadataIntervalPatterns,[]],startInterval:[e?e.startInterval:null,[]],startIntervalTimeUnit:[e?e.startIntervalTimeUnit:null,[]],endInterval:[e?e.endInterval:null,[]],endIntervalTimeUnit:[e?e.endIntervalTimeUnit:null,[]],startIntervalPattern:[e?e.startIntervalPattern:null,[]],endIntervalPattern:[e?e.endIntervalPattern:null,[]]})},r.prototype.validatorTriggers=function(){return["fetchMode","useMetadataIntervalPatterns"]},r.prototype.updateValidators=function(e){var t=this.getTelemetryFromDatabaseConfigForm.get("fetchMode").value,r=this.getTelemetryFromDatabaseConfigForm.get("useMetadataIntervalPatterns").value;t&&t===j.ALL?(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([i.Validators.required,i.Validators.min(2),i.Validators.max(1e3)])):(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([])),r?(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([i.Validators.required])):(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([])),this.getTelemetryFromDatabaseConfigForm.get("orderBy").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("limit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").updateValueAndValidity({emitEvent:e})},r.prototype.removeKey=function(e,t){var r=this.getTelemetryFromDatabaseConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.getTelemetryFromDatabaseConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-get-telemetry-from-database",template:'
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n tb.rulenode.fetch-mode\n \n \n {{ mode }}\n \n \n tb.rulenode.fetch-mode-hint\n \n
\n \n tb.rulenode.order-by\n \n \n {{ order }}\n \n \n tb.rulenode.order-by-hint\n \n \n tb.rulenode.limit\n \n tb.rulenode.limit-hint\n \n
\n \n {{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-interval-patterns-hint
\n
\n
\n \n tb.rulenode.start-interval\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.start-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.end-interval\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.end-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n \n tb.rulenode.start-interval-pattern\n \n \n {{ \'tb.rulenode.start-interval-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.end-interval-pattern\n \n \n {{ \'tb.rulenode.end-interval-pattern-required\' | translate }}\n \n \n \n \n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ke=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.relatedAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.relatedAttributesConfigForm=this.fb.group({relationsQuery:[e?e.relationsQuery:null,[i.Validators.required]],telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-related-attributes-config",template:'
\n \n \n \n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ee=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.tenantAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.tenantAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-tenant-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ve=function(){function e(){}return e=b([t.NgModule({declarations:[Te,qe,xe,Ie,Se,Ne,ke,Ee],imports:[r.CommonModule,a.SharedModule,ne],exports:[Te,qe,xe,Ie,Se,Ne,ke,Ee]})],e)}(),Ae=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.originatorSource=v,n.originatorSources=Object.keys(v),n.originatorSourceTranslationMap=P,n}return y(r,e),r.prototype.configForm=function(){return this.changeOriginatorConfigForm},r.prototype.onConfigurationSet=function(e){this.changeOriginatorConfigForm=this.fb.group({originatorSource:[e?e.originatorSource:null,[i.Validators.required]],relationsQuery:[e?e.relationsQuery:null,[]]})},r.prototype.validatorTriggers=function(){return["originatorSource"]},r.prototype.updateValidators=function(e){var t=this.changeOriginatorConfigForm.get("originatorSource").value;t&&t===v.RELATED?this.changeOriginatorConfigForm.get("relationsQuery").setValidators([i.Validators.required]):this.changeOriginatorConfigForm.get("relationsQuery").setValidators([]),this.changeOriginatorConfigForm.get("relationsQuery").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({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
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Le=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.scriptConfigForm},r.prototype.onConfigurationSet=function(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"update",this.translate.instant("tb.rulenode.transformer"),"Transform",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-transformation-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Me=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.toEmailConfigForm},r.prototype.onConfigurationSet=function(e){this.toEmailConfigForm=this.fb.group({fromTemplate:[e?e.fromTemplate:null,[i.Validators.required]],toTemplate:[e?e.toTemplate:null,[i.Validators.required]],ccTemplate:[e?e.ccTemplate:null,[]],bccTemplate:[e?e.bccTemplate:null,[]],subjectTemplate:[e?e.subjectTemplate:null,[i.Validators.required]],bodyTemplate:[e?e.bodyTemplate:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({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.body-template\n \n \n {{ \'tb.rulenode.body-template-required\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Pe=function(){function e(){}return e=b([t.NgModule({declarations:[Ae,Le,Me],imports:[r.CommonModule,a.SharedModule,ne],exports:[Ae,Le,Me]})],e)}(),Re=function(){function e(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-name-pattern-hint":"Name pattern, use ${metaKeyName} to substitute variables from metadata","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-type-pattern-hint":"Type pattern, use ${metaKeyName} to substitute variables from metadata","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-name-pattern-hint":"Customer name pattern, use ${metaKeyName} to substitute variables from metadata","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.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.",limit:"Limit","limit-hint":"Min limit value is 2, max - 1000. In case you want to fetch a single entry, select fetch mode 'FIRST' or 'LAST'.","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","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","latest-timeseries":"Latest timeseries","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-hint":"Relation type pattern, use ${metaKeyName} to substitute variables from metadata","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","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","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","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 metadata period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds metadata pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","period-in-seconds-pattern-hint":"Period in seconds pattern, use ${metaKeyName} to substitute variables from metadata","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",propagate:"Propagate",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","from-template-hint":"From address template, use ${metaKeyName} to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use ${metaKeyName} to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use ${metaKeyName} to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use ${metaKeyName} to substitute variables from metadata","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","endpoint-url-pattern-hint":"HTTP URL address pattern, use ${metaKeyName} to substitute variables from metadata","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory","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 ${metaKeyName} in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use ${metaKeyName} to substitute variables from metadata","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","topic-arn-pattern-hint":"Topic ARN pattern, use ${metaKeyName} to substitute variables from metadata","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","queue-url-pattern-hint":"Queue URL pattern, use ${metaKeyName} to substitute variables from metadata","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 ${metaKeyName} in name/value fields to substitute variables from metadata","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","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","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"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-interval-patterns":"Use metadata interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","check-all-keys":"Check that all selected keys 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-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","start-interval-pattern-hint":"Start interval pattern, use ${metaKeyName} to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use ${metaKeyName} to substitute variables from metadata","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","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":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","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","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","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":"You should 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-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 Latest telemetry with Timestamp","get-latest-value-with-ts-hint":'If selected, latest telemetry values will be added to the outbound message metadata with timestamp, e.g: "temp": "{\\"ts\\":1574329385897,\\"value\\":42}"',"use-redis-queue":"Use redis queue for message persistence","trim-redis-queue":"Trim redis queue","redis-queue-max-size":"Redis queue max size"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}},!0)}(e)}return e.ctorParameters=function(){return[{type:n.TranslateService}]},e=b([t.NgModule({declarations:[F],imports:[r.CommonModule,a.SharedModule],exports:[ce,Fe,Ve,Pe,F]}),h("design:paramtypes",[n.TranslateService])],e)}();e.RuleNodeCoreConfigModule=Re,e.ɵa=F,e.ɵb=ce,e.ɵba=ne,e.ɵbb=X,e.ɵbc=ee,e.ɵbd=te,e.ɵbe=re,e.ɵbf=Fe,e.ɵbg=fe,e.ɵbh=ge,e.ɵbi=ye,e.ɵbj=be,e.ɵbk=he,e.ɵbl=Ce,e.ɵbm=ve,e.ɵbn=Ve,e.ɵbo=Te,e.ɵbp=qe,e.ɵbq=xe,e.ɵbr=Ie,e.ɵbs=Se,e.ɵbt=Ne,e.ɵbu=ke,e.ɵbv=Ee,e.ɵbw=Pe,e.ɵbx=Ae,e.ɵby=Le,e.ɵbz=Me,e.ɵc=T,e.ɵd=q,e.ɵe=x,e.ɵf=I,e.ɵg=S,e.ɵh=N,e.ɵi=k,e.ɵj=E,e.ɵk=V,e.ɵl=A,e.ɵm=L,e.ɵn=J,e.ɵo=W,e.ɵp=Y,e.ɵq=Z,e.ɵr=ae,e.ɵs=oe,e.ɵt=ie,e.ɵu=le,e.ɵv=se,e.ɵw=me,e.ɵx=ue,e.ɵy=de,e.ɵz=pe,Object.defineProperty(e,"__esModule",{value:!0})})); //# sourceMappingURL=rulenode-core-config.umd.min.js.map \ No newline at end of file diff --git a/ui-ngx/src/app/core/http/rule-chain.service.ts b/ui-ngx/src/app/core/http/rule-chain.service.ts index 73e015ee65..28d8e5e122 100644 --- a/ui-ngx/src/app/core/http/rule-chain.service.ts +++ b/ui-ngx/src/app/core/http/rule-chain.service.ts @@ -243,8 +243,8 @@ export class RuleChainService { map((res) => { if (nodeDefinition.configDirective && nodeDefinition.configDirective.length) { const selector = snakeCase(nodeDefinition.configDirective, '-'); - const componentFactory = res.componentFactories.find((factory) => - factory.selector === selector); + const componentFactory = res.find((factory) => + factory.selector === selector); if (componentFactory) { this.ruleNodeConfigFactories[nodeDefinition.configDirective] = componentFactory; } else { diff --git a/ui-ngx/src/app/core/services/resources.service.ts b/ui-ngx/src/app/core/services/resources.service.ts index 757f9f3b96..0f9b8bdc4b 100644 --- a/ui-ngx/src/app/core/services/resources.service.ts +++ b/ui-ngx/src/app/core/services/resources.service.ts @@ -14,9 +14,17 @@ /// limitations under the License. /// -import { Injectable, Inject, ModuleWithComponentFactories, Compiler, Injector } from '@angular/core'; +import { + Compiler, + ComponentFactory, + Inject, + Injectable, + Injector, + ModuleWithComponentFactories, + Type +} from '@angular/core'; import { DOCUMENT } from '@angular/common'; -import { ReplaySubject, Observable, throwError } from 'rxjs'; +import { forkJoin, Observable, ReplaySubject, throwError } from 'rxjs'; declare const SystemJS; @@ -26,7 +34,7 @@ declare const SystemJS; export class ResourcesService { private loadedResources: { [url: string]: ReplaySubject } = {}; - private loadedModules: { [url: string]: ReplaySubject> } = {}; + private loadedModules: { [url: string]: ReplaySubject[]> } = {}; private anchor = this.document.getElementsByTagName('head')[0] || this.document.getElementsByTagName('body')[0]; @@ -52,11 +60,11 @@ export class ResourcesService { return this.loadResourceByType(fileType, url); } - public loadModule(url: string, modulesMap: {[key: string]: any}): Observable> { + public loadModule(url: string, modulesMap: {[key: string]: any}): Observable[]> { if (this.loadedModules[url]) { return this.loadedModules[url].asObservable(); } - const subject = new ReplaySubject>(); + const subject = new ReplaySubject[]>(); this.loadedModules[url] = subject; if (modulesMap) { for (const moduleId of Object.keys(modulesMap)) { @@ -65,23 +73,30 @@ export class ResourcesService { } SystemJS.import(url).then( (module) => { - if (module.default) { - this.compiler.compileModuleAndAllComponentsAsync(module.default).then( - (compiled) => { - try { - compiled.ngModuleFactory.create(this.injector); - this.loadedModules[url].next(compiled); - this.loadedModules[url].complete(); - } catch (e) { - this.loadedModules[url].error(new Error(`Unable to init module from url: ${url}`)); - delete this.loadedModules[url]; + const modules = this.extractNgModules(module); + if (modules.length) { + const tasks: Promise>[] = []; + for (const m of modules) { + tasks.push(this.compiler.compileModuleAndAllComponentsAsync(m)); + } + forkJoin(tasks).subscribe((compiled) => { + try { + const componentFactories: ComponentFactory[] = []; + for (const c of compiled) { + c.ngModuleFactory.create(this.injector); + componentFactories.push(...c.componentFactories); } - }, - (e) => { - this.loadedModules[url].error(new Error(`Unable to compile module from url: ${url}`)); + this.loadedModules[url].next(componentFactories); + this.loadedModules[url].complete(); + } catch (e) { + this.loadedModules[url].error(new Error(`Unable to init module from url: ${url}`)); delete this.loadedModules[url]; } - ); + }, + (e) => { + this.loadedModules[url].error(new Error(`Unable to compile module from url: ${url}`)); + delete this.loadedModules[url]; + }); } else { this.loadedModules[url].error(new Error(`Module '${url}' doesn't have default export!`)); delete this.loadedModules[url]; @@ -95,6 +110,17 @@ export class ResourcesService { return subject.asObservable(); } + private extractNgModules(module: any, modules: Type[] = [] ): Type[] { + if (module && 'ɵmod' in module) { + modules.push(module); + } else { + for (const k of Object.keys(module)) { + this.extractNgModules(module[k], modules); + } + } + return modules; + } + private loadResourceByType(type: 'css' | 'js', url: string): Observable { const subject = new ReplaySubject(); this.loadedResources[url] = subject; diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts index f2d22112e7..73584a47b4 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts @@ -42,7 +42,8 @@ import * as AngularCommon from '@angular/common'; import * as AngularForms from '@angular/forms'; import * as AngularCdkCoercion from '@angular/cdk/coercion'; import * as AngularCdkKeycodes from '@angular/cdk/keycodes'; -import * as AngularMaterial from '@angular/material/esm5'; +import * as AngularMaterialChips from '@angular/material/chips'; +import * as AngularMaterialAutocomplete from '@angular/material/autocomplete'; import * as NgrxStore from '@ngrx/store'; import * as TranslateCore from '@ngx-translate/core'; import * as TbCore from '@core/public-api'; @@ -58,7 +59,8 @@ const ruleNodeConfigResourcesModulesMap = { '@angular/forms': SystemJS.newModule(AngularForms), '@angular/cdk/keycodes': SystemJS.newModule(AngularCdkKeycodes), '@angular/cdk/coercion': SystemJS.newModule(AngularCdkCoercion), - '@angular/material': SystemJS.newModule(AngularMaterial), + '@angular/material/chips': SystemJS.newModule(AngularMaterialChips), + '@angular/material/autocomplete': SystemJS.newModule(AngularMaterialAutocomplete), '@ngrx/store': SystemJS.newModule(NgrxStore), rxjs: SystemJS.newModule(RxJs), 'rxjs/operators': SystemJS.newModule(RxJsOperators), From 3378aa1d77656856063fe35a9bded85074ac2924 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 12 Feb 2020 17:19:30 +0200 Subject: [PATCH 111/133] Fix angular 9 migration and style issues. --- .../system/widget_bundles/input_widgets.json | 6 +- .../static/rulenode/rulenode-core-config.js | 2 +- ui-ngx/package-lock.json | 5 +- .../rulechain/link-labels.component.html | 1 + .../pages/rulechain/link-labels.component.ts | 4 + .../rulechain/rulechain-page.component.scss | 1 + .../pages/rulechain/rulenode.component.scss | 2 + .../components/file-input.component.html | 2 +- .../components/image-input.component.html | 2 +- .../json-form/react/json-form-rc-select.tsx | 13 ++- .../components/json-form/react/json-form.scss | 82 +++++++++++++++++++ 11 files changed, 109 insertions(+), 11 deletions(-) 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 6dc7f2aad0..99824d1655 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 @@ -189,9 +189,9 @@ "sizeX": 7.5, "sizeY": 3, "resources": [], - "templateHtml": "\n
\n\n
\n
\n \n \n \n
\n
{{requiredErrorMessage}}
\n
\n
\n
\n\n
\n \n check\n Update server attribute\n \n \n close\n Discard changes\n \n
\n
\n\n
\n No entity selected\n
\n
\n No timeseries is selected\n
\n
\n Attribute parameter cannot be used in this widget\n
\n
\n", - "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.entity-title {\n font-weight: bold;\n font-size: 22px;\n padding-top: 12px;\n padding-bottom: 6px;\n color: #666;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .md-icon-button md-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.show-label label {\n display: block;\n}\n\nlabel {\n display: none;\n}\n\nmd-toast{\n min-width: 0;\n}\nmd-toast .md-toast-content {\n font-size: 14px!important;\n}", - "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet toast;\nlet utils;\nlet types;\nlet $q;\nlet $http;\n\nself.onInit = function() {\n\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get('attributeService');\n toast = $scope.$injector.get('toast');\n utils = $scope.$injector.get('utils');\n types = $scope.$injector.get('types');\n $q = $scope.$injector.get('$q');\n $http = $scope.$injector.get('$http');\n settings = angular.copy(self.ctx.settings) || {};\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n $scope.requiredErrorMessage = settings.requiredErrorMessage || \"Entity timeseries are required\";\n $scope.labelValue = settings.labelValue || \"Value\";\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type != \"timeseries\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n saveEntityTimeseries(\n datasource.entityType,\n datasource.entityId,\n [\n {\n key: $scope.currentKey,\n value: $scope.currentValue\n }\n ]\n ).then(\n function success() {\n $scope.originalValue = $scope.currentValue;\n if (settings.showResultMessage) {\n toast.showSuccess('Update successful', 1000, angular.element(self.ctx.$container), 'bottom left');\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n toast.showError('Update failed', angular.element(self.ctx.$container), 'bottom left');\n }\n }\n );\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.currentValue === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n\n function saveEntityTimeseries(entityType, entityId, telemetries) {\n var deferred = $q.defer();\n var telemetriesData = {};\n for (var a = 0; a < telemetries.length; a++) {\n if (angular.isDefined(telemetries[a].value) && telemetries[a].value !== null) {\n telemetriesData[telemetries[a].key] = telemetries[a].value;\n }\n }\n if (Object.keys(telemetriesData).length) {\n var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/timeseries/scope';\n $http.post(url, telemetriesData).then(\n function(response) {\n deferred.resolve(response.data);\n },\n function() {\n deferred.reject();\n }\n );\n }\n return deferred.promise;\n }\n}\n\nself.onDataUpdated = function() {\n\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.currentValue = $scope.originalValue = self.ctx.data[0].data[0][1];\n correctValue($scope.currentValue);\n $scope.$digest();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nfunction correctValue(value) {\n if (typeof value !== \"number\") {\n $scope.currentValue = 0;\n }\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n dataKeyOptional: true\n }\n}\n\nself.onDestroy = function() {\n\n}\n", + "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? labelValue : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n\n
\n \n \n
\n
\n\n
\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-timeseries-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.attribute-not-allowed' | translate }}\n
\n
\n
\n
", + "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}\n", + "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet utils;\nlet translate;\nlet http;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n http = $scope.$injector.get(self.ctx.servicesMap.get('http'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-timeseries-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || translate.instant('widgets.input-widgets.value');\n\n $scope.attributeUpdateFormGroup = $scope.fb.group(\n {currentValue: [undefined, [$scope.validators.required,\n $scope.validators.min(settings.minValue),\n $scope.validators.max(settings.maxValue)]]}\n );\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"timeseries\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n $scope.isFocused = false;\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n let observable = saveEntityTimeseries(\n datasource.entityType,\n datasource.entityId,\n [\n {\n key: $scope.currentKey,\n value: $scope.attributeUpdateFormGroup.get('currentValue').value\n }\n ]\n );\n if (observable) {\n observable.subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.attributeUpdateFormGroup.get('currentValue').value === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n\n function saveEntityTimeseries(entityType, entityId, telemetries) {\n var telemetriesData = {};\n for (var a = 0; a < telemetries.length; a++) {\n if (typeof telemetries[a].value !== 'undefined' && telemetries[a].value !== null) {\n telemetriesData[telemetries[a].key] = telemetries[a].value;\n }\n }\n if (Object.keys(telemetriesData).length) {\n var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/timeseries/scope';\n return http.post(url, telemetriesData);\n }\n return null;\n }\n}\n\nself.onDataUpdated = function() {\n\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue(correctValue($scope.originalValue));\n self.ctx.detectChanges();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nfunction correctValue(value) {\n if (typeof value !== \"number\") {\n return 0;\n }\n return value;\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n dataKeyOptional: true\n }\n}\n\nself.onDestroy = function() {\n\n}\n", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showLabel\":{\n \"title\":\"Show label\",\n \"type\":\"boolean\",\n \"default\":true\n },\n \"labelValue\": {\n \"title\": \"Label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"maxValue\": {\n \"title\": \"Max value\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"minValue\": {\n \"title\": \"Min value\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\":true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showResultMessage\",\n \"showLabel\",\n \"labelValue\",\n \"requiredErrorMessage\",\n \"maxValue\",\n \"minValue\"\n ]\n}", "dataKeySettingsSchema": "{}\n", "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\":{},\"title\":\"Update integer timeseries\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" diff --git a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js index 4133c3c8f8..11bbb085ef 100644 --- a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js +++ b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js @@ -12,5 +12,5 @@ See the Apache Version 2.0 License for specific language governing permissions and limitations under the License. - ***************************************************************************** */var g=function(e,t){return(g=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)t.hasOwnProperty(r)&&(e[r]=t[r])})(e,t)};function y(e,t){function r(){this.constructor=e}g(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)}function b(e,t,r,n){var a,o=arguments.length,i=o<3?t:null===n?n=Object.getOwnPropertyDescriptor(t,r):n;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)i=Reflect.decorate(e,t,r,n);else for(var l=e.length-1;l>=0;l--)(a=e[l])&&(i=(o<3?a(i):o>3?a(t,r,i):a(t,r))||i);return o>3&&i&&Object.defineProperty(t,r,i),i}function h(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)}function C(e){var t="function"==typeof Symbol&&e[Symbol.iterator],r=0;return t?t.call(e):{next:function(){return e&&r>=e.length&&(e=void 0),{value:e&&e[r++],done:!e}}}}var v,F=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.emptyConfigForm},r.prototype.onConfigurationSet=function(e){this.emptyConfigForm=this.fb.group({})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-node-empty-config",template:"
"}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),T=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.attributeScopes=Object.keys(a.AttributeScope),n.telemetryTypeTranslationsMap=a.telemetryTypeTranslations,n}return y(r,e),r.prototype.configForm=function(){return this.attributesConfigForm},r.prototype.onConfigurationSet=function(e){this.attributesConfigForm=this.fb.group({scope:[e?e.scope:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-attributes-config",template:'
\n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),q=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.timeseriesConfigForm},r.prototype.onConfigurationSet=function(e){this.timeseriesConfigForm=this.fb.group({defaultTTL:[e?e.defaultTTL:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({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'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),x=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.rpcRequestConfigForm},r.prototype.onConfigurationSet=function(e){this.rpcRequestConfigForm=this.fb.group({timeoutInSeconds:[e?e.timeoutInSeconds:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({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'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),I=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.logConfigForm},r.prototype.onConfigurationSet=function(e){this.logConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.logConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"string",this.translate.instant("tb.rulenode.to-string"),"ToString",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.logConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-log-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),S=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.assignCustomerConfigForm},r.prototype.onConfigurationSet=function(e){this.assignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[i.Validators.required]],createCustomerIfNotExists:[!!e&&e.createCustomerIfNotExists,[]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({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 \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),N=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.clearAlarmConfigForm},r.prototype.onConfigurationSet=function(e){this.clearAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[i.Validators.required]],alarmType:[e?e.alarmType:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.clearAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.clearAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-clear-alarm-config",template:'
\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'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),k=function(e){function r(t,r,n,o){var i=e.call(this,t)||this;return i.store=t,i.fb=r,i.nodeScriptTestService=n,i.translate=o,i.alarmSeverities=Object.keys(a.AlarmSeverity),i.alarmSeverityTranslationMap=a.alarmSeverityTranslations,i.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],i}return y(r,e),r.prototype.configForm=function(){return this.createAlarmConfigForm},r.prototype.onConfigurationSet=function(e){this.createAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[i.Validators.required]],useMessageAlarmData:[!!e&&e.useMessageAlarmData,[]],alarmType:[e?e.alarmType:null,[]],severity:[e?e.severity:null,[]],propagate:[!!e&&e.propagate,[]],relationTypes:[e?e.relationTypes:null,[]]})},r.prototype.validatorTriggers=function(){return["useMessageAlarmData"]},r.prototype.updateValidators=function(e){this.createAlarmConfigForm.get("useMessageAlarmData").value?(this.createAlarmConfigForm.get("alarmType").setValidators([]),this.createAlarmConfigForm.get("severity").setValidators([])):(this.createAlarmConfigForm.get("alarmType").setValidators([i.Validators.required]),this.createAlarmConfigForm.get("severity").setValidators([i.Validators.required])),this.createAlarmConfigForm.get("alarmType").updateValueAndValidity({emitEvent:e}),this.createAlarmConfigForm.get("severity").updateValueAndValidity({emitEvent:e})},r.prototype.testScript=function(){var e=this,t=this.createAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.createAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},r.prototype.removeKey=function(e,t){var r=this.createAlarmConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.createAlarmConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.createAlarmConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.createAlarmConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-create-alarm-config",template:'
\n \n \n \n
\n \n
\n \n {{ \'tb.rulenode.use-message-alarm-data\' | translate }}\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.alarm-severity\n \n \n {{ alarmSeverityTranslationMap.get(severity) | translate }}\n \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 \n \n
\n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),E=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return y(r,e),r.prototype.configForm=function(){return this.createRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.createRelationConfigForm=this.fb.group({direction:[e?e.direction:null,[i.Validators.required]],entityType:[e?e.entityType:null,[i.Validators.required]],entityNamePattern:[e?e.entityNamePattern:null,[]],entityTypePattern:[e?e.entityTypePattern:null,[]],relationType:[e?e.relationType:null,[i.Validators.required]],createEntityIfNotExists:[!!e&&e.createEntityIfNotExists,[]],removeCurrentRelations:[!!e&&e.removeCurrentRelations,[]],changeOriginatorToRelatedEntity:[!!e&&e.changeOriginatorToRelatedEntity,[]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.validatorTriggers=function(){return["entityType"]},r.prototype.updateValidators=function(e){var t=this.createRelationConfigForm.get("entityType").value;t?this.createRelationConfigForm.get("entityNamePattern").setValidators([i.Validators.required]):this.createRelationConfigForm.get("entityNamePattern").setValidators([]),!t||t!==a.EntityType.DEVICE&&t!==a.EntityType.ASSET?this.createRelationConfigForm.get("entityTypePattern").setValidators([]):this.createRelationConfigForm.get("entityTypePattern").setValidators([i.Validators.required]),this.createRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e}),this.createRelationConfigForm.get("entityTypePattern").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({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 \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),V=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.msgDelayConfigForm},r.prototype.onConfigurationSet=function(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,[i.Validators.required,i.Validators.min(1),i.Validators.max(1e5)]]})},r.prototype.validatorTriggers=function(){return["useMetadataPeriodInSecondsPatterns"]},r.prototype.updateValidators=function(e){this.msgDelayConfigForm.get("useMetadataPeriodInSecondsPatterns").value?(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([i.Validators.required]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([])):(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([i.Validators.required,i.Validators.min(0)])),this.msgDelayConfigForm.get("periodInSecondsPattern").updateValueAndValidity({emitEvent:e}),this.msgDelayConfigForm.get("periodInSeconds").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({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'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),A=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return y(r,e),r.prototype.configForm=function(){return this.deleteRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.deleteRelationConfigForm=this.fb.group({deleteForSingleEntity:[!!e&&e.deleteForSingleEntity,[]],direction:[e?e.direction:null,[i.Validators.required]],entityType:[e?e.entityType:null,[]],entityNamePattern:[e?e.entityNamePattern:null,[]],relationType:[e?e.relationType:null,[i.Validators.required]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.validatorTriggers=function(){return["deleteForSingleEntity","entityType"]},r.prototype.updateValidators=function(e){var t=this.deleteRelationConfigForm.get("deleteForSingleEntity").value,r=this.deleteRelationConfigForm.get("entityType").value;t?this.deleteRelationConfigForm.get("entityType").setValidators([i.Validators.required]):this.deleteRelationConfigForm.get("entityType").setValidators([]),t&&r?this.deleteRelationConfigForm.get("entityNamePattern").setValidators([i.Validators.required]):this.deleteRelationConfigForm.get("entityNamePattern").setValidators([]),this.deleteRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:!1}),this.deleteRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({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 \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),L=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.generatorConfigForm},r.prototype.onConfigurationSet=function(e){this.generatorConfigForm=this.fb.group({msgCount:[e?e.msgCount:null,[i.Validators.required,i.Validators.min(0)]],periodInSeconds:[e?e.periodInSeconds:null,[i.Validators.required,i.Validators.min(1)]],originator:[e?e.originator:null,[]],jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.prepareInputConfig=function(e){return e&&(e.originatorId&&e.originatorType?e.originator={id:e.originatorId,entityType:e.originatorType}:e.originator=null,delete e.originatorId,delete e.originatorType),e},r.prototype.prepareOutputConfig=function(e){return e.originator?(e.originatorId=e.originator.id,e.originatorType=e.originator.entityType):(e.originatorId=null,e.originatorType=null),delete e.originator,e},r.prototype.testScript=function(){var e=this,t=this.generatorConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"generate",this.translate.instant("tb.rulenode.generator"),"Generate",["prevMsg","prevMetadata","prevMsgType"],this.ruleNodeId).subscribe((function(t){t&&e.generatorConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({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'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent);!function(e){e.CUSTOMER="CUSTOMER",e.TENANT="TENANT",e.RELATED="RELATED",e.ALARM_ORIGINATOR="ALARM_ORIGINATOR"}(v||(v={}));var M,P=new Map([[v.CUSTOMER,"tb.rulenode.originator-customer"],[v.TENANT,"tb.rulenode.originator-tenant"],[v.RELATED,"tb.rulenode.originator-related"],[v.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator"]]);!function(e){e.CIRCLE="CIRCLE",e.POLYGON="POLYGON"}(M||(M={}));var R,w=new Map([[M.CIRCLE,"tb.rulenode.perimeter-circle"],[M.POLYGON,"tb.rulenode.perimeter-polygon"]]);!function(e){e.MILLISECONDS="MILLISECONDS",e.SECONDS="SECONDS",e.MINUTES="MINUTES",e.HOURS="HOURS",e.DAYS="DAYS"}(R||(R={}));var D,O=new Map([[R.MILLISECONDS,"tb.rulenode.time-unit-milliseconds"],[R.SECONDS,"tb.rulenode.time-unit-seconds"],[R.MINUTES,"tb.rulenode.time-unit-minutes"],[R.HOURS,"tb.rulenode.time-unit-hours"],[R.DAYS,"tb.rulenode.time-unit-days"]]);!function(e){e.METER="METER",e.KILOMETER="KILOMETER",e.FOOT="FOOT",e.MILE="MILE",e.NAUTICAL_MILE="NAUTICAL_MILE"}(D||(D={}));var K,B=new Map([[D.METER,"tb.rulenode.range-unit-meter"],[D.KILOMETER,"tb.rulenode.range-unit-kilometer"],[D.FOOT,"tb.rulenode.range-unit-foot"],[D.MILE,"tb.rulenode.range-unit-mile"],[D.NAUTICAL_MILE,"tb.rulenode.range-unit-nautical-mile"]]);!function(e){e.TITLE="TITLE",e.COUNTRY="COUNTRY",e.STATE="STATE",e.ZIP="ZIP",e.ADDRESS="ADDRESS",e.ADDRESS2="ADDRESS2",e.PHONE="PHONE",e.EMAIL="EMAIL",e.ADDITIONAL_INFO="ADDITIONAL_INFO"}(K||(K={}));var j,U,G,H=new Map([[K.TITLE,"tb.rulenode.entity-details-title"],[K.COUNTRY,"tb.rulenode.entity-details-country"],[K.STATE,"tb.rulenode.entity-details-state"],[K.ZIP,"tb.rulenode.entity-details-zip"],[K.ADDRESS,"tb.rulenode.entity-details-address"],[K.ADDRESS2,"tb.rulenode.entity-details-address2"],[K.PHONE,"tb.rulenode.entity-details-phone"],[K.EMAIL,"tb.rulenode.entity-details-email"],[K.ADDITIONAL_INFO,"tb.rulenode.entity-details-additional_info"]]);!function(e){e.FIRST="FIRST",e.LAST="LAST",e.ALL="ALL"}(j||(j={})),function(e){e.ASC="ASC",e.DESC="DESC"}(U||(U={})),function(e){e.STANDARD="STANDARD",e.FIFO="FIFO"}(G||(G={}));var Q,z=new Map([[G.STANDARD,"tb.rulenode.sqs-queue-standard"],[G.FIFO,"tb.rulenode.sqs-queue-fifo"]]),$=["anonymous","basic","cert.PEM"],_=new Map([["anonymous","tb.rulenode.credentials-anonymous"],["basic","tb.rulenode.credentials-basic"],["cert.PEM","tb.rulenode.credentials-pem"]]);!function(e){e.GET="GET",e.POST="POST",e.PUT="PUT",e.DELETE="DELETE"}(Q||(Q={}));var J=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.perimeterType=M,n.perimeterTypes=Object.keys(M),n.perimeterTypeTranslationMap=w,n.rangeUnits=Object.keys(D),n.rangeUnitTranslationMap=B,n.timeUnits=Object.keys(R),n.timeUnitsTranslationMap=O,n}return y(r,e),r.prototype.configForm=function(){return this.geoActionConfigForm},r.prototype.onConfigurationSet=function(e){this.geoActionConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[i.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[i.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType: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,[i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]],minInsideDurationTimeUnit:[e?e.minInsideDurationTimeUnit:null,[i.Validators.required]],minOutsideDuration:[e?e.minOutsideDuration:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]],minOutsideDurationTimeUnit:[e?e.minOutsideDurationTimeUnit:null,[i.Validators.required]]})},r.prototype.validatorTriggers=function(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]},r.prototype.updateValidators=function(e){var t=this.geoActionConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,r=this.geoActionConfigForm.get("perimeterType").value;t?this.geoActionConfigForm.get("perimeterType").setValidators([]):this.geoActionConfigForm.get("perimeterType").setValidators([i.Validators.required]),t||r!==M.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([i.Validators.required,i.Validators.min(-90),i.Validators.max(90)]),this.geoActionConfigForm.get("centerLongitude").setValidators([i.Validators.required,i.Validators.min(-180),i.Validators.max(180)]),this.geoActionConfigForm.get("range").setValidators([i.Validators.required,i.Validators.min(0)]),this.geoActionConfigForm.get("rangeUnit").setValidators([i.Validators.required])),t||r!==M.POLYGON?this.geoActionConfigForm.get("polygonsDefinition").setValidators([]):this.geoActionConfigForm.get("polygonsDefinition").setValidators([i.Validators.required]),this.geoActionConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),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})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({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.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \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'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),W=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.msgCountConfigForm},r.prototype.onConfigurationSet=function(e){this.msgCountConfigForm=this.fb.group({interval:[e?e.interval:null,[i.Validators.required,i.Validators.min(1)]],telemetryPrefix:[e?e.telemetryPrefix:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({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'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Y=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.rpcReplyConfigForm},r.prototype.onConfigurationSet=function(e){this.rpcReplyConfigForm=this.fb.group({requestIdMetaDataAttribute:[e?e.requestIdMetaDataAttribute:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rpc-reply-config",template:'
\n \n tb.rulenode.request-id-metadata-attribute\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Z=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.saveToCustomTableConfigForm},r.prototype.onConfigurationSet=function(e){this.saveToCustomTableConfigForm=this.fb.group({tableName:[e?e.tableName:null,[i.Validators.required]],fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({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 \n \n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),X=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.translate=r,o.injector=n,o.fb=a,o.propagateChange=null,o.valueChangeSubscription=null,o}var a;return y(r,e),a=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){this.ngControl=this.injector.get(i.NgControl),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.kvListFormGroup=this.fb.group({}),this.kvListFormGroup.addControl("keyVals",this.fb.array([]))},r.prototype.keyValsFormArray=function(){return this.kvListFormGroup.get("keyVals")},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.kvListFormGroup.disable({emitEvent:!1}):this.kvListFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){var t,r,n=this;this.valueChangeSubscription&&this.valueChangeSubscription.unsubscribe();var a=[];if(e)try{for(var o=C(Object.keys(e)),l=o.next();!l.done;l=o.next()){var s=l.value;Object.prototype.hasOwnProperty.call(e,s)&&a.push(this.fb.group({key:[s,[i.Validators.required]],value:[e[s],[i.Validators.required]]}))}}catch(e){t={error:e}}finally{try{l&&!l.done&&(r=o.return)&&r.call(o)}finally{if(t)throw t.error}}this.kvListFormGroup.setControl("keyVals",this.fb.array(a)),this.valueChangeSubscription=this.kvListFormGroup.valueChanges.subscribe((function(){n.updateModel()}))},r.prototype.removeKeyVal=function(e){this.kvListFormGroup.get("keyVals").removeAt(e)},r.prototype.addKeyVal=function(){this.kvListFormGroup.get("keyVals").push(this.fb.group({key:["",[i.Validators.required]],value:["",[i.Validators.required]]}))},r.prototype.validate=function(e){return!this.kvListFormGroup.get("keyVals").value.length&&this.required?{kvMapRequired:!0}:this.kvListFormGroup.valid?null:{kvFieldsRequired:!0}},r.prototype.updateModel=function(){var e=this.kvListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.kvListFormGroup.valid)this.propagateChange(null);else{var t={};e.forEach((function(e){t[e.key]=e.value})),this.propagateChange(t)}},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:t.Injector},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",String)],r.prototype,"requiredText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"keyText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"keyRequiredText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"valText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"valRequiredText",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=a=b([t.Component({selector:"tb-kv-map-config",template:'
\n
\n {{ keyText }}\n {{ valText }}\n \n
\n
\n
\n \n \n \n \n {{ keyRequiredText | translate }}\n \n \n \n \n \n \n {{ valRequiredText | translate }}\n \n \n \n
\n
\n \n
\n \n
\n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return a})),multi:!0},{provide:i.NG_VALIDATORS,useExisting:t.forwardRef((function(){return a})),multi:!0}],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:rgba(0,0,0,.54);font-size:12px;font-weight:700;white-space:nowrap}:host .tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:20px;max-height:300px;overflow:auto}:host .tb-kv-map-config .body .row{padding-top:5px;max-height:40px}:host .tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell{margin:0;max-height:40px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell .mat-form-field-infix{border-top:0}:host ::ng-deep .tb-kv-map-config .body button.mat-button{margin:0}"]}),h("design:paramtypes",[o.Store,n.TranslateService,t.Injector,i.FormBuilder])],r)}(a.PageComponent),ee=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n.propagateChange=null,n}var n;return y(r,e),n=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.deviceRelationsQueryFormGroup=this.fb.group({direction:[null,[i.Validators.required]],maxLevel:[null,[]],relationType:[null],deviceTypes:[null,[i.Validators.required]]}),this.deviceRelationsQueryFormGroup.valueChanges.subscribe((function(t){e.deviceRelationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.deviceRelationsQueryFormGroup.disable({emitEvent:!1}):this.deviceRelationsQueryFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){this.deviceRelationsQueryFormGroup.reset(e,{emitEvent:!1})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=n=b([t.Component({selector:"tb-device-relations-query-config",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
\n
relation.relation-type
\n \n \n
device.device-types
\n \n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0}]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),te=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.propagateChange=null,n}var n;return y(r,e),n=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.relationsQueryFormGroup=this.fb.group({direction:[null,[i.Validators.required]],maxLevel:[null,[]],filters:[null]}),this.relationsQueryFormGroup.valueChanges.subscribe((function(t){e.relationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.relationsQueryFormGroup.disable({emitEvent:!1}):this.relationsQueryFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){this.relationsQueryFormGroup.reset(e,{emitEvent:!1})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=n=b([t.Component({selector:"tb-relations-query-config",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
\n
relation.relation-filters
\n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0}]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),re=function(e){function r(t,r,n,o){var i,l,m=e.call(this,t)||this;m.store=t,m.translate=r,m.truncate=n,m.fb=o,m.placeholder="tb.rulenode.message-type",m.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],m.messageTypes=[],m.messageTypesList=[],m.searchText="",m.propagateChange=function(e){},m.messageTypeConfigForm=m.fb.group({messageType:[null]});try{for(var u=C(Object.keys(a.MessageType)),d=u.next();!d.done;d=u.next()){var p=d.value;m.messageTypesList.push({name:a.messageTypeNames.get(a.MessageType[p]),value:p})}}catch(e){i={error:e}}finally{try{d&&!d.done&&(l=u.return)&&l.call(u)}finally{if(i)throw i.error}}return m}var l;return y(r,e),l=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.ngOnInit=function(){var e=this;this.filteredMessageTypes=this.messageTypeConfigForm.get("messageType").valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(t){return e.fetchMessageTypes(t)})),f.share())},r.prototype.ngAfterViewInit=function(){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.messageTypeConfigForm.disable({emitEvent:!1}):this.messageTypeConfigForm.enable({emitEvent:!1})},r.prototype.writeValue=function(e){var t=this;this.searchText="",this.messageTypes.length=0,e&&e.forEach((function(e){var r=t.messageTypesList.find((function(t){return t.value===e}));r?t.messageTypes.push({name:r.name,value:r.value}):t.messageTypes.push({name:e,value:e})}))},r.prototype.displayMessageTypeFn=function(e){return e?e.name:void 0},r.prototype.textIsNotEmpty=function(e){return!!(e&&null!=e&&e.length>0)},r.prototype.createMessageType=function(e,t){e.preventDefault(),this.transformMessageType(t)},r.prototype.add=function(e){this.transformMessageType(e.value)},r.prototype.fetchMessageTypes=function(e){if(this.searchText=e,this.searchText&&this.searchText.length){var t=this.searchText.toUpperCase();return c.of(this.messageTypesList.filter((function(e){return e.name.toUpperCase().includes(t)})))}return c.of(this.messageTypesList)},r.prototype.transformMessageType=function(e){if((e||"").trim()){var t=null,r=e.trim(),n=this.messageTypesList.find((function(e){return e.name===r}));(t=n?{name:n.name,value:n.value}:{name:r,value:r})&&this.addMessageType(t)}this.clear("")},r.prototype.remove=function(e){var t=this.messageTypes.indexOf(e);t>=0&&(this.messageTypes.splice(t,1),this.updateModel())},r.prototype.selected=function(e){this.addMessageType(e.option.value),this.clear("")},r.prototype.addMessageType=function(e){-1===this.messageTypes.findIndex((function(t){return t.value===e.value}))&&(this.messageTypes.push(e),this.updateModel())},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.messageTypeInput.nativeElement.value=e,this.messageTypeConfigForm.get("messageType").patchValue(null,{emitEvent:!0}),setTimeout((function(){t.messageTypeInput.nativeElement.blur(),t.messageTypeInput.nativeElement.focus()}),0)},r.prototype.updateModel=function(){var e=this.messageTypes.map((function(e){return e.value}));this.required?(this.chipList.errorState=!e.length,this.propagateChange(e.length>0?e:null)):(this.chipList.errorState=!1,this.propagateChange(e))},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:a.TruncatePipe},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),b([t.Input(),h("design:type",String)],r.prototype,"label",void 0),b([t.Input(),h("design:type",Object)],r.prototype,"placeholder",void 0),b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.ViewChild("chipList",{static:!1}),h("design:type",d.MatChipList)],r.prototype,"chipList",void 0),b([t.ViewChild("messageTypeAutocomplete",{static:!1}),h("design:type",p.MatAutocomplete)],r.prototype,"matAutocomplete",void 0),b([t.ViewChild("messageTypeInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"messageTypeInput",void 0),r=l=b([t.Component({selector:"tb-message-types-config",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 {{ translate.get(\'tb.rulenode.no-message-type-matching\',\n {messageType: truncate.transform(searchText, true, 6, '...')}) | async }}\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',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return l})),multi:!0}]}),h("design:paramtypes",[o.Store,n.TranslateService,a.TruncatePipe,i.FormBuilder])],r)}(a.PageComponent),ne=function(){function e(){}return e=b([t.NgModule({declarations:[X,ee,te,re],imports:[r.CommonModule,a.SharedModule,m.HomeComponentsModule],exports:[X,ee,te,re]})],e)}(),ae=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.unassignCustomerConfigForm},r.prototype.onConfigurationSet=function(e){this.unassignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[i.Validators.required]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({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 \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),oe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.snsConfigForm},r.prototype.onConfigurationSet=function(e){this.snsConfigForm=this.fb.group({topicArnPattern:[e?e.topicArnPattern:null,[i.Validators.required]],accessKeyId:[e?e.accessKeyId:null,[i.Validators.required]],secretAccessKey:[e?e.secretAccessKey:null,[i.Validators.required]],region:[e?e.region:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-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'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ie=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.sqsQueueType=G,n.sqsQueueTypes=Object.keys(G),n.sqsQueueTypeTranslationsMap=z,n}return y(r,e),r.prototype.configForm=function(){return this.sqsConfigForm},r.prototype.onConfigurationSet=function(e){this.sqsConfigForm=this.fb.group({queueType:[e?e.queueType:null,[i.Validators.required]],queueUrlPattern:[e?e.queueUrlPattern:null,[i.Validators.required]],delaySeconds:[e?e.delaySeconds:null,[i.Validators.min(0),i.Validators.max(900)]],messageAttributes:[e?e.messageAttributes:null,[]],accessKeyId:[e?e.accessKeyId:null,[i.Validators.required]],secretAccessKey:[e?e.secretAccessKey:null,[i.Validators.required]],region:[e?e.region:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-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
tb.rulenode.message-attributes-hint
\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'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),le=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.pubSubConfigForm},r.prototype.onConfigurationSet=function(e){this.pubSubConfigForm=this.fb.group({projectId:[e?e.projectId:null,[i.Validators.required]],topicName:[e?e.topicName:null,[i.Validators.required]],serviceAccountKey:[e?e.serviceAccountKey:null,[i.Validators.required]],serviceAccountKeyFileName:[e?e.serviceAccountKeyFileName:null,[i.Validators.required]],messageAttributes:[e?e.messageAttributes:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-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
tb.rulenode.message-attributes-hint
\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.ackValues=["all","-1","0","1"],n}return y(r,e),r.prototype.configForm=function(){return this.kafkaConfigForm},r.prototype.onConfigurationSet=function(e){this.kafkaConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],bootstrapServers:[e?e.bootstrapServers:null,[i.Validators.required]],retries:[e?e.retries:null,[i.Validators.min(0)]],batchSize:[e?e.batchSize:null,[i.Validators.min(0)]],linger:[e?e.linger:null,[i.Validators.min(0)]],bufferMemory:[e?e.bufferMemory:null,[i.Validators.min(0)]],acks:[e?e.acks:null,[i.Validators.required]],keySerializer:[e?e.keySerializer:null,[i.Validators.required]],valueSerializer:[e?e.valueSerializer:null,[i.Validators.required]],otherProperties:[e?e.otherProperties:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-kafka-config",template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \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'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),me=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allMqttCredentialsTypes=$,n.mqttCredentialsTypeTranslationsMap=_,n}return y(r,e),r.prototype.configForm=function(){return this.mqttConfigForm},r.prototype.onConfigurationSet=function(e){this.mqttConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(200)]],clientId:[e?e.clientId:null,[]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:this.fb.group({type:[e&&e.credentials?e.credentials.type:null,[i.Validators.required]],username:[e&&e.credentials?e.credentials.username:null,[]],password:[e&&e.credentials?e.credentials.password: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,[]]})})},r.prototype.prepareOutputConfig=function(e){var t=e.credentials.type;switch(t){case"anonymous":e.credentials={type:t};break;case"basic":e.credentials={type:t,username:e.credentials.username,password:e.credentials.password};break;case"cert.PEM":delete e.credentials.username}return e},r.prototype.validatorTriggers=function(){return["credentials.type"]},r.prototype.updateValidators=function(e){var t=this.mqttConfigForm.get("credentials"),r=t.get("type").value;switch(e&&t.reset({type:r},{emitEvent:!1}),t.get("username").setValidators([]),t.get("password").setValidators([]),t.get("caCert").setValidators([]),t.get("caCertFileName").setValidators([]),t.get("privateKey").setValidators([]),t.get("privateKeyFileName").setValidators([]),t.get("cert").setValidators([]),t.get("certFileName").setValidators([]),r){case"anonymous":break;case"basic":t.get("username").setValidators([i.Validators.required]),t.get("password").setValidators([i.Validators.required]);break;case"cert.PEM":t.get("caCert").setValidators([i.Validators.required]),t.get("caCertFileName").setValidators([i.Validators.required]),t.get("privateKey").setValidators([i.Validators.required]),t.get("privateKeyFileName").setValidators([i.Validators.required]),t.get("cert").setValidators([i.Validators.required]),t.get("certFileName").setValidators([i.Validators.required])}t.get("username").updateValueAndValidity({emitEvent:e}),t.get("password").updateValueAndValidity({emitEvent:e}),t.get("caCert").updateValueAndValidity({emitEvent:e}),t.get("caCertFileName").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})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-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 \n \n {{ \'tb.rulenode.clean-session\' | translate }}\n \n \n {{ \'tb.rulenode.enable-ssl\' | translate }}\n \n \n \n tb.rulenode.credentials\n \n {{ mqttCredentialsTypeTranslationsMap.get(mqttConfigForm.get(\'credentials\').get(\'type\').value) | translate }}\n \n \n
\n \n tb.rulenode.credentials-type\n \n \n {{ mqttCredentialsTypeTranslationsMap.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 {{ \'tb.rulenode.password-required\' | translate }}\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',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ue=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],n}return y(r,e),r.prototype.configForm=function(){return this.rabbitMqConfigForm},r.prototype.onConfigurationSet=function(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,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.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,[i.Validators.min(0)]],handshakeTimeout:[e?e.handshakeTimeout:null,[i.Validators.min(0)]],clientProperties:[e?e.clientProperties:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-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 {{ \'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'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),de=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.httpRequestTypes=Object.keys(Q),n}return y(r,e),r.prototype.configForm=function(){return this.restApiCallConfigForm},r.prototype.onConfigurationSet=function(e){this.restApiCallConfigForm=this.fb.group({restEndpointUrlPattern:[e?e.restEndpointUrlPattern:null,[i.Validators.required]],requestMethod:[e?e.requestMethod:null,[i.Validators.required]],useSimpleClientHttpFactory:[!!e&&e.useSimpleClientHttpFactory,[]],readTimeoutMs:[e?e.readTimeoutMs:null,[]],maxParallelRequestsCount:[e?e.maxParallelRequestsCount:null,[i.Validators.min(0)]],headers:[e?e.headers:null,[]],useRedisQueueForMsgPersistence:[!!e&&e.useRedisQueueForMsgPersistence,[]],trimQueue:[!!e&&e.trimQueue,[]],maxQueueSize:[e?e.maxQueueSize:null,[]]})},r.prototype.validatorTriggers=function(){return["useSimpleClientHttpFactory","useRedisQueueForMsgPersistence"]},r.prototype.updateValidators=function(e){var t=this.restApiCallConfigForm.get("useSimpleClientHttpFactory").value,r=this.restApiCallConfigForm.get("useRedisQueueForMsgPersistence").value;t?this.restApiCallConfigForm.get("readTimeoutMs").setValidators([]):this.restApiCallConfigForm.get("readTimeoutMs").setValidators([i.Validators.min(0)]),r?this.restApiCallConfigForm.get("maxQueueSize").setValidators([i.Validators.min(0)]):this.restApiCallConfigForm.get("maxQueueSize").setValidators([]),this.restApiCallConfigForm.get("readTimeoutMs").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("maxQueueSize").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-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.use-simple-client-http-factory\' | translate }}\n \n \n tb.rulenode.read-timeout\n \n \n \n \n tb.rulenode.max-parallel-requests-count\n \n \n \n \n
tb.rulenode.headers-hint
\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'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),pe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.smtpProtocols=["smtp","smtps"],n}return y(r,e),r.prototype.configForm=function(){return this.sendEmailConfigForm},r.prototype.onConfigurationSet=function(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,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]]})},r.prototype.validatorTriggers=function(){return["useSystemSmtpSettings"]},r.prototype.updateValidators=function(e){this.sendEmailConfigForm.get("useSystemSmtpSettings").value?(this.sendEmailConfigForm.get("smtpProtocol").setValidators([]),this.sendEmailConfigForm.get("smtpHost").setValidators([]),this.sendEmailConfigForm.get("smtpPort").setValidators([]),this.sendEmailConfigForm.get("timeout").setValidators([])):(this.sendEmailConfigForm.get("smtpProtocol").setValidators([i.Validators.required]),this.sendEmailConfigForm.get("smtpHost").setValidators([i.Validators.required]),this.sendEmailConfigForm.get("smtpPort").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]),this.sendEmailConfigForm.get("timeout").setValidators([i.Validators.required,i.Validators.min(0)])),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})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-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.username\n \n \n \n tb.rulenode.password\n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ce=function(){function e(){}return e=b([t.NgModule({declarations:[T,q,x,I,S,N,k,E,V,A,L,J,W,Y,Z,ae,oe,ie,le,se,me,ue,de,pe],imports:[r.CommonModule,a.SharedModule,ne],exports:[T,q,x,I,S,N,k,E,V,A,L,J,W,Y,Z,ae,oe,ie,le,se,me,ue,de,pe]})],e)}(),fe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.checkMessageConfigForm},r.prototype.onConfigurationSet=function(e){this.checkMessageConfigForm=this.fb.group({messageNames:[e?e.messageNames:null,[]],metadataNames:[e?e.metadataNames:null,[]],checkAllKeys:[!!e&&e.checkAllKeys,[]]})},r.prototype.validateConfig=function(){var e=this.checkMessageConfigForm.get("messageNames").value,t=this.checkMessageConfigForm.get("metadataNames").value;return e.length>0||t.length>0},r.prototype.removeMessageName=function(e){var t=this.checkMessageConfigForm.get("messageNames").value,r=t.indexOf(e);r>=0&&(t.splice(r,1),this.checkMessageConfigForm.get("messageNames").setValue(t,{emitEvent:!0}))},r.prototype.removeMetadataName=function(e){var t=this.checkMessageConfigForm.get("metadataNames").value,r=t.indexOf(e);r>=0&&(t.splice(r,1),this.checkMessageConfigForm.get("metadataNames").setValue(t,{emitEvent:!0}))},r.prototype.addMessageName=function(e){var t=e.input,r=e.value;if((r||"").trim()){r=r.trim();var n=this.checkMessageConfigForm.get("messageNames").value;n&&-1!==n.indexOf(r)||(n||(n=[]),n.push(r),this.checkMessageConfigForm.get("messageNames").setValue(n,{emitEvent:!0}))}t&&(t.value="")},r.prototype.addMetadataName=function(e){var t=e.input,r=e.value;if((r||"").trim()){r=r.trim();var n=this.checkMessageConfigForm.get("metadataNames").value;n&&-1!==n.indexOf(r)||(n||(n=[]),n.push(r),this.checkMessageConfigForm.get("metadataNames").setValue(n,{emitEvent:!0}))}t&&(t.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-check-message-config",template:'
\n \n \n \n \n \n {{messageName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n \n \n \n \n {{metadataName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\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}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ge=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.entitySearchDirection=Object.keys(a.EntitySearchDirection),n.entitySearchDirectionTranslationsMap=a.entitySearchDirectionTranslations,n}return y(r,e),r.prototype.configForm=function(){return this.checkRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.checkRelationConfigForm=this.fb.group({checkForSingleEntity:[!!e&&e.checkForSingleEntity,[]],direction:[e?e.direction:null,[]],entityType:[e?e.entityType:null,e&&e.checkForSingleEntity?[i.Validators.required]:[]],entityId:[e?e.entityId:null,e&&e.checkForSingleEntity?[i.Validators.required]:[]],relationType:[e?e.relationType:null,[i.Validators.required]]})},r.prototype.validatorTriggers=function(){return["checkForSingleEntity"]},r.prototype.updateValidators=function(e){var t=this.checkRelationConfigForm.get("checkForSingleEntity").value;this.checkRelationConfigForm.get("entityType").setValidators(t?[i.Validators.required]:[]),this.checkRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:e}),this.checkRelationConfigForm.get("entityId").setValidators(t?[i.Validators.required]:[]),this.checkRelationConfigForm.get("entityId").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({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'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ye=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.perimeterType=M,n.perimeterTypes=Object.keys(M),n.perimeterTypeTranslationMap=w,n.rangeUnits=Object.keys(D),n.rangeUnitTranslationMap=B,n}return y(r,e),r.prototype.configForm=function(){return this.geoFilterConfigForm},r.prototype.onConfigurationSet=function(e){this.geoFilterConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[i.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[i.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType: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,[]]})},r.prototype.validatorTriggers=function(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]},r.prototype.updateValidators=function(e){var t=this.geoFilterConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,r=this.geoFilterConfigForm.get("perimeterType").value;t?this.geoFilterConfigForm.get("perimeterType").setValidators([]):this.geoFilterConfigForm.get("perimeterType").setValidators([i.Validators.required]),t||r!==M.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([i.Validators.required,i.Validators.min(-90),i.Validators.max(90)]),this.geoFilterConfigForm.get("centerLongitude").setValidators([i.Validators.required,i.Validators.min(-180),i.Validators.max(180)]),this.geoFilterConfigForm.get("range").setValidators([i.Validators.required,i.Validators.min(0)]),this.geoFilterConfigForm.get("rangeUnit").setValidators([i.Validators.required])),t||r!==M.POLYGON?this.geoFilterConfigForm.get("polygonsDefinition").setValidators([]):this.geoFilterConfigForm.get("polygonsDefinition").setValidators([i.Validators.required]),this.geoFilterConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),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})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({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.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \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'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),be=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.messageTypeConfigForm},r.prototype.onConfigurationSet=function(e){this.messageTypeConfigForm=this.fb.group({messageTypes:[e?e.messageTypes:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-message-type-config",template:'
\n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),he=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allowedEntityTypes=[a.EntityType.DEVICE,a.EntityType.ASSET,a.EntityType.ENTITY_VIEW,a.EntityType.TENANT,a.EntityType.CUSTOMER,a.EntityType.USER,a.EntityType.DASHBOARD,a.EntityType.RULE_CHAIN,a.EntityType.RULE_NODE],n}return y(r,e),r.prototype.configForm=function(){return this.originatorTypeConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorTypeConfigForm=this.fb.group({originatorTypes:[e?e.originatorTypes:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-originator-type-config",template:'
\n \n \n \n
\n',styles:[":host ::ng-deep tb-entity-type-list .mat-form-field-flex{padding-top:0}:host ::ng-deep tb-entity-type-list .mat-form-field-infix{border-top:0}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ce=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.scriptConfigForm},r.prototype.onConfigurationSet=function(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"filter",this.translate.instant("tb.rulenode.filter"),"Filter",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-filter-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),ve=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.switchConfigForm},r.prototype.onConfigurationSet=function(e){this.switchConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.switchConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"switch",this.translate.instant("tb.rulenode.switch"),"Switch",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.switchConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-filter-node-switch-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Fe=function(){function e(){}return e=b([t.NgModule({declarations:[fe,ge,ye,be,he,Ce,ve],imports:[r.CommonModule,a.SharedModule,ne],exports:[fe,ge,ye,be,he,Ce,ve]})],e)}(),Te=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.customerAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.customerAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-customer-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),qe=function(e){function r(t,r,n){var a,o,i=e.call(this,t)||this;i.store=t,i.translate=r,i.fb=n,i.entityDetailsTranslationsMap=H,i.entityDetailsList=[],i.searchText="",i.displayDetailsFn=i.displayDetails.bind(i);try{for(var l=C(Object.keys(K)),s=l.next();!s.done;s=l.next()){var m=s.value;i.entityDetailsList.push(K[m])}}catch(e){a={error:e}}finally{try{s&&!s.done&&(o=l.return)&&o.call(l)}finally{if(a)throw a.error}}return i}return y(r,e),r.prototype.ngOnInit=function(){var t=this;this.detailsFormControl=new i.FormControl(""),e.prototype.ngOnInit.call(this),this.filteredEntityDetails=this.detailsFormControl.valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(e){return t.fetchEntityDetails(e)})),f.share())},r.prototype.configForm=function(){return this.entityDetailsConfigForm},r.prototype.prepareInputConfig=function(e){return this.searchText="",this.detailsFormControl.patchValue("",{emitEvent:!0}),e},r.prototype.onConfigurationSet=function(e){this.entityDetailsConfigForm=this.fb.group({detailsList:[e?e.detailsList:null,[i.Validators.required]],addToMetadata:[!!e&&e.addToMetadata,[]]})},r.prototype.displayDetails=function(e){return e?this.translate.instant(H.get(e)):void 0},r.prototype.fetchEntityDetails=function(e){var t=this;if(this.searchText=e,this.searchText&&this.searchText.length){var r=this.searchText.toUpperCase();return c.of(this.entityDetailsList.filter((function(e){return t.translate.instant(H.get(K[e])).toUpperCase().includes(r)})))}return c.of(this.entityDetailsList)},r.prototype.detailsFieldSelected=function(e){this.addDetailsField(e.option.value),this.clear("")},r.prototype.removeDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;if(t){var r=t.indexOf(e);r>=0&&(t.splice(r,1),this.entityDetailsConfigForm.get("detailsList").setValue(t))}},r.prototype.addDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;t||(t=[]),-1===t.indexOf(e)&&(t.push(e),this.entityDetailsConfigForm.get("detailsList").setValue(t))},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.detailsInput.nativeElement.value=e,this.detailsFormControl.patchValue(null,{emitEvent:!0}),setTimeout((function(){t.detailsInput.nativeElement.blur(),t.detailsInput.nativeElement.focus()}),0)},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:i.FormBuilder}]},b([t.ViewChild("detailsInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"detailsInput",void 0),r=b([t.Component({selector:"tb-enrichment-node-entity-details-config",template:'
\n \n \n \n \n \n {{entityDetailsTranslationsMap.get(details) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-entity-details-matching\n
\n
\n
\n
\n
\n \n \n {{ \'tb.rulenode.add-to-metadata\' | translate }}\n \n
tb.rulenode.add-to-metadata-hint
\n
\n',styles:[":host ::ng-deep mat-form-field.entity-fields-list .mat-form-field-wrapper{margin-bottom:-1.25em}"]}),h("design:paramtypes",[o.Store,n.TranslateService,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),xe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.deviceAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.deviceAttributesConfigForm=this.fb.group({deviceRelationsQuery:[e?e.deviceRelationsQuery:null,[i.Validators.required]],tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})},r.prototype.removeKey=function(e,t){var r=this.deviceAttributesConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.deviceAttributesConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.deviceAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.deviceAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-device-attributes-config",template:'
\n \n \n \n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ie=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.originatorAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorAttributesConfigForm=this.fb.group({tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})},r.prototype.removeKey=function(e,t){var r=this.originatorAttributesConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.originatorAttributesConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.originatorAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.originatorAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-originator-attributes-config",template:'
\n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.originatorFieldsConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorFieldsConfigForm=this.fb.group({fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-originator-fields-config",template:'
\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ne=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n.fetchMode=j,n.fetchModes=Object.keys(j),n.samplingOrders=Object.keys(U),n.timeUnits=Object.keys(R),n.timeUnitsTranslationMap=O,n}return y(r,e),r.prototype.configForm=function(){return this.getTelemetryFromDatabaseConfigForm},r.prototype.onConfigurationSet=function(e){this.getTelemetryFromDatabaseConfigForm=this.fb.group({latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],fetchMode:[e?e.fetchMode:null,[i.Validators.required]],orderBy:[e?e.orderBy:null,[]],limit:[e?e.limit:null,[]],useMetadataIntervalPatterns:[!!e&&e.useMetadataIntervalPatterns,[]],startInterval:[e?e.startInterval:null,[]],startIntervalTimeUnit:[e?e.startIntervalTimeUnit:null,[]],endInterval:[e?e.endInterval:null,[]],endIntervalTimeUnit:[e?e.endIntervalTimeUnit:null,[]],startIntervalPattern:[e?e.startIntervalPattern:null,[]],endIntervalPattern:[e?e.endIntervalPattern:null,[]]})},r.prototype.validatorTriggers=function(){return["fetchMode","useMetadataIntervalPatterns"]},r.prototype.updateValidators=function(e){var t=this.getTelemetryFromDatabaseConfigForm.get("fetchMode").value,r=this.getTelemetryFromDatabaseConfigForm.get("useMetadataIntervalPatterns").value;t&&t===j.ALL?(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([i.Validators.required,i.Validators.min(2),i.Validators.max(1e3)])):(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([])),r?(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([i.Validators.required])):(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([])),this.getTelemetryFromDatabaseConfigForm.get("orderBy").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("limit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").updateValueAndValidity({emitEvent:e})},r.prototype.removeKey=function(e,t){var r=this.getTelemetryFromDatabaseConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.getTelemetryFromDatabaseConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-get-telemetry-from-database",template:'
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n tb.rulenode.fetch-mode\n \n \n {{ mode }}\n \n \n tb.rulenode.fetch-mode-hint\n \n
\n \n tb.rulenode.order-by\n \n \n {{ order }}\n \n \n tb.rulenode.order-by-hint\n \n \n tb.rulenode.limit\n \n tb.rulenode.limit-hint\n \n
\n \n {{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-interval-patterns-hint
\n
\n
\n \n tb.rulenode.start-interval\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.start-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.end-interval\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.end-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n \n tb.rulenode.start-interval-pattern\n \n \n {{ \'tb.rulenode.start-interval-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.end-interval-pattern\n \n \n {{ \'tb.rulenode.end-interval-pattern-required\' | translate }}\n \n \n \n \n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ke=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.relatedAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.relatedAttributesConfigForm=this.fb.group({relationsQuery:[e?e.relationsQuery:null,[i.Validators.required]],telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-related-attributes-config",template:'
\n \n \n \n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ee=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.tenantAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.tenantAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-tenant-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ve=function(){function e(){}return e=b([t.NgModule({declarations:[Te,qe,xe,Ie,Se,Ne,ke,Ee],imports:[r.CommonModule,a.SharedModule,ne],exports:[Te,qe,xe,Ie,Se,Ne,ke,Ee]})],e)}(),Ae=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.originatorSource=v,n.originatorSources=Object.keys(v),n.originatorSourceTranslationMap=P,n}return y(r,e),r.prototype.configForm=function(){return this.changeOriginatorConfigForm},r.prototype.onConfigurationSet=function(e){this.changeOriginatorConfigForm=this.fb.group({originatorSource:[e?e.originatorSource:null,[i.Validators.required]],relationsQuery:[e?e.relationsQuery:null,[]]})},r.prototype.validatorTriggers=function(){return["originatorSource"]},r.prototype.updateValidators=function(e){var t=this.changeOriginatorConfigForm.get("originatorSource").value;t&&t===v.RELATED?this.changeOriginatorConfigForm.get("relationsQuery").setValidators([i.Validators.required]):this.changeOriginatorConfigForm.get("relationsQuery").setValidators([]),this.changeOriginatorConfigForm.get("relationsQuery").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({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
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Le=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.scriptConfigForm},r.prototype.onConfigurationSet=function(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"update",this.translate.instant("tb.rulenode.transformer"),"Transform",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-transformation-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Me=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.toEmailConfigForm},r.prototype.onConfigurationSet=function(e){this.toEmailConfigForm=this.fb.group({fromTemplate:[e?e.fromTemplate:null,[i.Validators.required]],toTemplate:[e?e.toTemplate:null,[i.Validators.required]],ccTemplate:[e?e.ccTemplate:null,[]],bccTemplate:[e?e.bccTemplate:null,[]],subjectTemplate:[e?e.subjectTemplate:null,[i.Validators.required]],bodyTemplate:[e?e.bodyTemplate:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({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.body-template\n \n \n {{ \'tb.rulenode.body-template-required\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Pe=function(){function e(){}return e=b([t.NgModule({declarations:[Ae,Le,Me],imports:[r.CommonModule,a.SharedModule,ne],exports:[Ae,Le,Me]})],e)}(),Re=function(){function e(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-name-pattern-hint":"Name pattern, use ${metaKeyName} to substitute variables from metadata","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-type-pattern-hint":"Type pattern, use ${metaKeyName} to substitute variables from metadata","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-name-pattern-hint":"Customer name pattern, use ${metaKeyName} to substitute variables from metadata","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.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.",limit:"Limit","limit-hint":"Min limit value is 2, max - 1000. In case you want to fetch a single entry, select fetch mode 'FIRST' or 'LAST'.","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","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","latest-timeseries":"Latest timeseries","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-hint":"Relation type pattern, use ${metaKeyName} to substitute variables from metadata","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","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","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","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 metadata period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds metadata pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","period-in-seconds-pattern-hint":"Period in seconds pattern, use ${metaKeyName} to substitute variables from metadata","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",propagate:"Propagate",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","from-template-hint":"From address template, use ${metaKeyName} to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use ${metaKeyName} to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use ${metaKeyName} to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use ${metaKeyName} to substitute variables from metadata","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","endpoint-url-pattern-hint":"HTTP URL address pattern, use ${metaKeyName} to substitute variables from metadata","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory","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 ${metaKeyName} in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use ${metaKeyName} to substitute variables from metadata","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","topic-arn-pattern-hint":"Topic ARN pattern, use ${metaKeyName} to substitute variables from metadata","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","queue-url-pattern-hint":"Queue URL pattern, use ${metaKeyName} to substitute variables from metadata","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 ${metaKeyName} in name/value fields to substitute variables from metadata","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","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","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"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-interval-patterns":"Use metadata interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","check-all-keys":"Check that all selected keys 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-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","start-interval-pattern-hint":"Start interval pattern, use ${metaKeyName} to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use ${metaKeyName} to substitute variables from metadata","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","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":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","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","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","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":"You should 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-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 Latest telemetry with Timestamp","get-latest-value-with-ts-hint":'If selected, latest telemetry values will be added to the outbound message metadata with timestamp, e.g: "temp": "{\\"ts\\":1574329385897,\\"value\\":42}"',"use-redis-queue":"Use redis queue for message persistence","trim-redis-queue":"Trim redis queue","redis-queue-max-size":"Redis queue max size"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}},!0)}(e)}return e.ctorParameters=function(){return[{type:n.TranslateService}]},e=b([t.NgModule({declarations:[F],imports:[r.CommonModule,a.SharedModule],exports:[ce,Fe,Ve,Pe,F]}),h("design:paramtypes",[n.TranslateService])],e)}();e.RuleNodeCoreConfigModule=Re,e.ɵa=F,e.ɵb=ce,e.ɵba=ne,e.ɵbb=X,e.ɵbc=ee,e.ɵbd=te,e.ɵbe=re,e.ɵbf=Fe,e.ɵbg=fe,e.ɵbh=ge,e.ɵbi=ye,e.ɵbj=be,e.ɵbk=he,e.ɵbl=Ce,e.ɵbm=ve,e.ɵbn=Ve,e.ɵbo=Te,e.ɵbp=qe,e.ɵbq=xe,e.ɵbr=Ie,e.ɵbs=Se,e.ɵbt=Ne,e.ɵbu=ke,e.ɵbv=Ee,e.ɵbw=Pe,e.ɵbx=Ae,e.ɵby=Le,e.ɵbz=Me,e.ɵc=T,e.ɵd=q,e.ɵe=x,e.ɵf=I,e.ɵg=S,e.ɵh=N,e.ɵi=k,e.ɵj=E,e.ɵk=V,e.ɵl=A,e.ɵm=L,e.ɵn=J,e.ɵo=W,e.ɵp=Y,e.ɵq=Z,e.ɵr=ae,e.ɵs=oe,e.ɵt=ie,e.ɵu=le,e.ɵv=se,e.ɵw=me,e.ɵx=ue,e.ɵy=de,e.ɵz=pe,Object.defineProperty(e,"__esModule",{value:!0})})); + ***************************************************************************** */var g=function(e,t){return(g=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)t.hasOwnProperty(r)&&(e[r]=t[r])})(e,t)};function y(e,t){function r(){this.constructor=e}g(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)}function b(e,t,r,n){var a,o=arguments.length,i=o<3?t:null===n?n=Object.getOwnPropertyDescriptor(t,r):n;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)i=Reflect.decorate(e,t,r,n);else for(var l=e.length-1;l>=0;l--)(a=e[l])&&(i=(o<3?a(i):o>3?a(t,r,i):a(t,r))||i);return o>3&&i&&Object.defineProperty(t,r,i),i}function h(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)}function C(e){var t="function"==typeof Symbol&&e[Symbol.iterator],r=0;return t?t.call(e):{next:function(){return e&&r>=e.length&&(e=void 0),{value:e&&e[r++],done:!e}}}}var v,F=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.emptyConfigForm},r.prototype.onConfigurationSet=function(e){this.emptyConfigForm=this.fb.group({})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-node-empty-config",template:"
"}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),T=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.attributeScopes=Object.keys(a.AttributeScope),n.telemetryTypeTranslationsMap=a.telemetryTypeTranslations,n}return y(r,e),r.prototype.configForm=function(){return this.attributesConfigForm},r.prototype.onConfigurationSet=function(e){this.attributesConfigForm=this.fb.group({scope:[e?e.scope:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-attributes-config",template:'
\n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),q=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.timeseriesConfigForm},r.prototype.onConfigurationSet=function(e){this.timeseriesConfigForm=this.fb.group({defaultTTL:[e?e.defaultTTL:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({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'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),x=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.rpcRequestConfigForm},r.prototype.onConfigurationSet=function(e){this.rpcRequestConfigForm=this.fb.group({timeoutInSeconds:[e?e.timeoutInSeconds:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({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'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),I=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.logConfigForm},r.prototype.onConfigurationSet=function(e){this.logConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.logConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"string",this.translate.instant("tb.rulenode.to-string"),"ToString",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.logConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-log-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),S=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.assignCustomerConfigForm},r.prototype.onConfigurationSet=function(e){this.assignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[i.Validators.required]],createCustomerIfNotExists:[!!e&&e.createCustomerIfNotExists,[]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({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 \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),N=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.clearAlarmConfigForm},r.prototype.onConfigurationSet=function(e){this.clearAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[i.Validators.required]],alarmType:[e?e.alarmType:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.clearAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.clearAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-clear-alarm-config",template:'
\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'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),k=function(e){function r(t,r,n,o){var i=e.call(this,t)||this;return i.store=t,i.fb=r,i.nodeScriptTestService=n,i.translate=o,i.alarmSeverities=Object.keys(a.AlarmSeverity),i.alarmSeverityTranslationMap=a.alarmSeverityTranslations,i.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],i}return y(r,e),r.prototype.configForm=function(){return this.createAlarmConfigForm},r.prototype.onConfigurationSet=function(e){this.createAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[i.Validators.required]],useMessageAlarmData:[!!e&&e.useMessageAlarmData,[]],alarmType:[e?e.alarmType:null,[]],severity:[e?e.severity:null,[]],propagate:[!!e&&e.propagate,[]],relationTypes:[e?e.relationTypes:null,[]]})},r.prototype.validatorTriggers=function(){return["useMessageAlarmData"]},r.prototype.updateValidators=function(e){this.createAlarmConfigForm.get("useMessageAlarmData").value?(this.createAlarmConfigForm.get("alarmType").setValidators([]),this.createAlarmConfigForm.get("severity").setValidators([])):(this.createAlarmConfigForm.get("alarmType").setValidators([i.Validators.required]),this.createAlarmConfigForm.get("severity").setValidators([i.Validators.required])),this.createAlarmConfigForm.get("alarmType").updateValueAndValidity({emitEvent:e}),this.createAlarmConfigForm.get("severity").updateValueAndValidity({emitEvent:e})},r.prototype.testScript=function(){var e=this,t=this.createAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.createAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},r.prototype.removeKey=function(e,t){var r=this.createAlarmConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.createAlarmConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.createAlarmConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.createAlarmConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-create-alarm-config",template:'
\n \n \n \n
\n \n
\n \n {{ \'tb.rulenode.use-message-alarm-data\' | translate }}\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.alarm-severity\n \n \n {{ alarmSeverityTranslationMap.get(severity) | translate }}\n \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 \n \n
\n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),E=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return y(r,e),r.prototype.configForm=function(){return this.createRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.createRelationConfigForm=this.fb.group({direction:[e?e.direction:null,[i.Validators.required]],entityType:[e?e.entityType:null,[i.Validators.required]],entityNamePattern:[e?e.entityNamePattern:null,[]],entityTypePattern:[e?e.entityTypePattern:null,[]],relationType:[e?e.relationType:null,[i.Validators.required]],createEntityIfNotExists:[!!e&&e.createEntityIfNotExists,[]],removeCurrentRelations:[!!e&&e.removeCurrentRelations,[]],changeOriginatorToRelatedEntity:[!!e&&e.changeOriginatorToRelatedEntity,[]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.validatorTriggers=function(){return["entityType"]},r.prototype.updateValidators=function(e){var t=this.createRelationConfigForm.get("entityType").value;t?this.createRelationConfigForm.get("entityNamePattern").setValidators([i.Validators.required]):this.createRelationConfigForm.get("entityNamePattern").setValidators([]),!t||t!==a.EntityType.DEVICE&&t!==a.EntityType.ASSET?this.createRelationConfigForm.get("entityTypePattern").setValidators([]):this.createRelationConfigForm.get("entityTypePattern").setValidators([i.Validators.required]),this.createRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e}),this.createRelationConfigForm.get("entityTypePattern").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({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 \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),V=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.msgDelayConfigForm},r.prototype.onConfigurationSet=function(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,[i.Validators.required,i.Validators.min(1),i.Validators.max(1e5)]]})},r.prototype.validatorTriggers=function(){return["useMetadataPeriodInSecondsPatterns"]},r.prototype.updateValidators=function(e){this.msgDelayConfigForm.get("useMetadataPeriodInSecondsPatterns").value?(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([i.Validators.required]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([])):(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([i.Validators.required,i.Validators.min(0)])),this.msgDelayConfigForm.get("periodInSecondsPattern").updateValueAndValidity({emitEvent:e}),this.msgDelayConfigForm.get("periodInSeconds").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({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'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),A=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return y(r,e),r.prototype.configForm=function(){return this.deleteRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.deleteRelationConfigForm=this.fb.group({deleteForSingleEntity:[!!e&&e.deleteForSingleEntity,[]],direction:[e?e.direction:null,[i.Validators.required]],entityType:[e?e.entityType:null,[]],entityNamePattern:[e?e.entityNamePattern:null,[]],relationType:[e?e.relationType:null,[i.Validators.required]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.validatorTriggers=function(){return["deleteForSingleEntity","entityType"]},r.prototype.updateValidators=function(e){var t=this.deleteRelationConfigForm.get("deleteForSingleEntity").value,r=this.deleteRelationConfigForm.get("entityType").value;t?this.deleteRelationConfigForm.get("entityType").setValidators([i.Validators.required]):this.deleteRelationConfigForm.get("entityType").setValidators([]),t&&r?this.deleteRelationConfigForm.get("entityNamePattern").setValidators([i.Validators.required]):this.deleteRelationConfigForm.get("entityNamePattern").setValidators([]),this.deleteRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:!1}),this.deleteRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({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 \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),L=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.generatorConfigForm},r.prototype.onConfigurationSet=function(e){this.generatorConfigForm=this.fb.group({msgCount:[e?e.msgCount:null,[i.Validators.required,i.Validators.min(0)]],periodInSeconds:[e?e.periodInSeconds:null,[i.Validators.required,i.Validators.min(1)]],originator:[e?e.originator:null,[]],jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.prepareInputConfig=function(e){return e&&(e.originatorId&&e.originatorType?e.originator={id:e.originatorId,entityType:e.originatorType}:e.originator=null,delete e.originatorId,delete e.originatorType),e},r.prototype.prepareOutputConfig=function(e){return e.originator?(e.originatorId=e.originator.id,e.originatorType=e.originator.entityType):(e.originatorId=null,e.originatorType=null),delete e.originator,e},r.prototype.testScript=function(){var e=this,t=this.generatorConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"generate",this.translate.instant("tb.rulenode.generator"),"Generate",["prevMsg","prevMetadata","prevMsgType"],this.ruleNodeId).subscribe((function(t){t&&e.generatorConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({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'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent);!function(e){e.CUSTOMER="CUSTOMER",e.TENANT="TENANT",e.RELATED="RELATED",e.ALARM_ORIGINATOR="ALARM_ORIGINATOR"}(v||(v={}));var M,P=new Map([[v.CUSTOMER,"tb.rulenode.originator-customer"],[v.TENANT,"tb.rulenode.originator-tenant"],[v.RELATED,"tb.rulenode.originator-related"],[v.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator"]]);!function(e){e.CIRCLE="CIRCLE",e.POLYGON="POLYGON"}(M||(M={}));var R,w=new Map([[M.CIRCLE,"tb.rulenode.perimeter-circle"],[M.POLYGON,"tb.rulenode.perimeter-polygon"]]);!function(e){e.MILLISECONDS="MILLISECONDS",e.SECONDS="SECONDS",e.MINUTES="MINUTES",e.HOURS="HOURS",e.DAYS="DAYS"}(R||(R={}));var D,O=new Map([[R.MILLISECONDS,"tb.rulenode.time-unit-milliseconds"],[R.SECONDS,"tb.rulenode.time-unit-seconds"],[R.MINUTES,"tb.rulenode.time-unit-minutes"],[R.HOURS,"tb.rulenode.time-unit-hours"],[R.DAYS,"tb.rulenode.time-unit-days"]]);!function(e){e.METER="METER",e.KILOMETER="KILOMETER",e.FOOT="FOOT",e.MILE="MILE",e.NAUTICAL_MILE="NAUTICAL_MILE"}(D||(D={}));var K,B=new Map([[D.METER,"tb.rulenode.range-unit-meter"],[D.KILOMETER,"tb.rulenode.range-unit-kilometer"],[D.FOOT,"tb.rulenode.range-unit-foot"],[D.MILE,"tb.rulenode.range-unit-mile"],[D.NAUTICAL_MILE,"tb.rulenode.range-unit-nautical-mile"]]);!function(e){e.TITLE="TITLE",e.COUNTRY="COUNTRY",e.STATE="STATE",e.ZIP="ZIP",e.ADDRESS="ADDRESS",e.ADDRESS2="ADDRESS2",e.PHONE="PHONE",e.EMAIL="EMAIL",e.ADDITIONAL_INFO="ADDITIONAL_INFO"}(K||(K={}));var j,U,G,H=new Map([[K.TITLE,"tb.rulenode.entity-details-title"],[K.COUNTRY,"tb.rulenode.entity-details-country"],[K.STATE,"tb.rulenode.entity-details-state"],[K.ZIP,"tb.rulenode.entity-details-zip"],[K.ADDRESS,"tb.rulenode.entity-details-address"],[K.ADDRESS2,"tb.rulenode.entity-details-address2"],[K.PHONE,"tb.rulenode.entity-details-phone"],[K.EMAIL,"tb.rulenode.entity-details-email"],[K.ADDITIONAL_INFO,"tb.rulenode.entity-details-additional_info"]]);!function(e){e.FIRST="FIRST",e.LAST="LAST",e.ALL="ALL"}(j||(j={})),function(e){e.ASC="ASC",e.DESC="DESC"}(U||(U={})),function(e){e.STANDARD="STANDARD",e.FIFO="FIFO"}(G||(G={}));var Q,z=new Map([[G.STANDARD,"tb.rulenode.sqs-queue-standard"],[G.FIFO,"tb.rulenode.sqs-queue-fifo"]]),$=["anonymous","basic","cert.PEM"],_=new Map([["anonymous","tb.rulenode.credentials-anonymous"],["basic","tb.rulenode.credentials-basic"],["cert.PEM","tb.rulenode.credentials-pem"]]);!function(e){e.GET="GET",e.POST="POST",e.PUT="PUT",e.DELETE="DELETE"}(Q||(Q={}));var J=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.perimeterType=M,n.perimeterTypes=Object.keys(M),n.perimeterTypeTranslationMap=w,n.rangeUnits=Object.keys(D),n.rangeUnitTranslationMap=B,n.timeUnits=Object.keys(R),n.timeUnitsTranslationMap=O,n}return y(r,e),r.prototype.configForm=function(){return this.geoActionConfigForm},r.prototype.onConfigurationSet=function(e){this.geoActionConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[i.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[i.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType: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,[i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]],minInsideDurationTimeUnit:[e?e.minInsideDurationTimeUnit:null,[i.Validators.required]],minOutsideDuration:[e?e.minOutsideDuration:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]],minOutsideDurationTimeUnit:[e?e.minOutsideDurationTimeUnit:null,[i.Validators.required]]})},r.prototype.validatorTriggers=function(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]},r.prototype.updateValidators=function(e){var t=this.geoActionConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,r=this.geoActionConfigForm.get("perimeterType").value;t?this.geoActionConfigForm.get("perimeterType").setValidators([]):this.geoActionConfigForm.get("perimeterType").setValidators([i.Validators.required]),t||r!==M.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([i.Validators.required,i.Validators.min(-90),i.Validators.max(90)]),this.geoActionConfigForm.get("centerLongitude").setValidators([i.Validators.required,i.Validators.min(-180),i.Validators.max(180)]),this.geoActionConfigForm.get("range").setValidators([i.Validators.required,i.Validators.min(0)]),this.geoActionConfigForm.get("rangeUnit").setValidators([i.Validators.required])),t||r!==M.POLYGON?this.geoActionConfigForm.get("polygonsDefinition").setValidators([]):this.geoActionConfigForm.get("polygonsDefinition").setValidators([i.Validators.required]),this.geoActionConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),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})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({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.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \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'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),W=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.msgCountConfigForm},r.prototype.onConfigurationSet=function(e){this.msgCountConfigForm=this.fb.group({interval:[e?e.interval:null,[i.Validators.required,i.Validators.min(1)]],telemetryPrefix:[e?e.telemetryPrefix:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({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'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Y=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.rpcReplyConfigForm},r.prototype.onConfigurationSet=function(e){this.rpcReplyConfigForm=this.fb.group({requestIdMetaDataAttribute:[e?e.requestIdMetaDataAttribute:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rpc-reply-config",template:'
\n \n tb.rulenode.request-id-metadata-attribute\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Z=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.saveToCustomTableConfigForm},r.prototype.onConfigurationSet=function(e){this.saveToCustomTableConfigForm=this.fb.group({tableName:[e?e.tableName:null,[i.Validators.required]],fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({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 \n \n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),X=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.translate=r,o.injector=n,o.fb=a,o.propagateChange=null,o.valueChangeSubscription=null,o}var a;return y(r,e),a=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){this.ngControl=this.injector.get(i.NgControl),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.kvListFormGroup=this.fb.group({}),this.kvListFormGroup.addControl("keyVals",this.fb.array([]))},r.prototype.keyValsFormArray=function(){return this.kvListFormGroup.get("keyVals")},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.kvListFormGroup.disable({emitEvent:!1}):this.kvListFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){var t,r,n=this;this.valueChangeSubscription&&this.valueChangeSubscription.unsubscribe();var a=[];if(e)try{for(var o=C(Object.keys(e)),l=o.next();!l.done;l=o.next()){var s=l.value;Object.prototype.hasOwnProperty.call(e,s)&&a.push(this.fb.group({key:[s,[i.Validators.required]],value:[e[s],[i.Validators.required]]}))}}catch(e){t={error:e}}finally{try{l&&!l.done&&(r=o.return)&&r.call(o)}finally{if(t)throw t.error}}this.kvListFormGroup.setControl("keyVals",this.fb.array(a)),this.valueChangeSubscription=this.kvListFormGroup.valueChanges.subscribe((function(){n.updateModel()}))},r.prototype.removeKeyVal=function(e){this.kvListFormGroup.get("keyVals").removeAt(e)},r.prototype.addKeyVal=function(){this.kvListFormGroup.get("keyVals").push(this.fb.group({key:["",[i.Validators.required]],value:["",[i.Validators.required]]}))},r.prototype.validate=function(e){return!this.kvListFormGroup.get("keyVals").value.length&&this.required?{kvMapRequired:!0}:this.kvListFormGroup.valid?null:{kvFieldsRequired:!0}},r.prototype.updateModel=function(){var e=this.kvListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.kvListFormGroup.valid)this.propagateChange(null);else{var t={};e.forEach((function(e){t[e.key]=e.value})),this.propagateChange(t)}},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:t.Injector},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",String)],r.prototype,"requiredText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"keyText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"keyRequiredText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"valText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"valRequiredText",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=a=b([t.Component({selector:"tb-kv-map-config",template:'
\n
\n {{ keyText }}\n {{ valText }}\n \n
\n
\n
\n \n \n \n \n {{ keyRequiredText | translate }}\n \n \n \n \n \n \n {{ valRequiredText | translate }}\n \n \n \n
\n
\n \n
\n \n
\n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return a})),multi:!0},{provide:i.NG_VALIDATORS,useExisting:t.forwardRef((function(){return a})),multi:!0}],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:rgba(0,0,0,.54);font-size:12px;font-weight:700;white-space:nowrap}:host .tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:20px;max-height:300px;overflow:auto}:host .tb-kv-map-config .body .row{padding-top:5px;max-height:40px}:host .tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell{margin:0;max-height:40px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell .mat-form-field-infix{border-top:0}:host ::ng-deep .tb-kv-map-config .body button.mat-button{margin:0}"]}),h("design:paramtypes",[o.Store,n.TranslateService,t.Injector,i.FormBuilder])],r)}(a.PageComponent),ee=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n.propagateChange=null,n}var n;return y(r,e),n=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.deviceRelationsQueryFormGroup=this.fb.group({direction:[null,[i.Validators.required]],maxLevel:[null,[]],relationType:[null],deviceTypes:[null,[i.Validators.required]]}),this.deviceRelationsQueryFormGroup.valueChanges.subscribe((function(t){e.deviceRelationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.deviceRelationsQueryFormGroup.disable({emitEvent:!1}):this.deviceRelationsQueryFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){this.deviceRelationsQueryFormGroup.reset(e,{emitEvent:!1})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=n=b([t.Component({selector:"tb-device-relations-query-config",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
\n
relation.relation-type
\n \n \n
device.device-types
\n \n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0}]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),te=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.propagateChange=null,n}var n;return y(r,e),n=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.relationsQueryFormGroup=this.fb.group({direction:[null,[i.Validators.required]],maxLevel:[null,[]],filters:[null]}),this.relationsQueryFormGroup.valueChanges.subscribe((function(t){e.relationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.relationsQueryFormGroup.disable({emitEvent:!1}):this.relationsQueryFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){this.relationsQueryFormGroup.reset(e,{emitEvent:!1})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=n=b([t.Component({selector:"tb-relations-query-config",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
\n
relation.relation-filters
\n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0}]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),re=function(e){function r(t,r,n,o){var i,l,m=e.call(this,t)||this;m.store=t,m.translate=r,m.truncate=n,m.fb=o,m.placeholder="tb.rulenode.message-type",m.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],m.messageTypes=[],m.messageTypesList=[],m.searchText="",m.propagateChange=function(e){},m.messageTypeConfigForm=m.fb.group({messageType:[null]});try{for(var u=C(Object.keys(a.MessageType)),d=u.next();!d.done;d=u.next()){var p=d.value;m.messageTypesList.push({name:a.messageTypeNames.get(a.MessageType[p]),value:p})}}catch(e){i={error:e}}finally{try{d&&!d.done&&(l=u.return)&&l.call(u)}finally{if(i)throw i.error}}return m}var l;return y(r,e),l=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.ngOnInit=function(){var e=this;this.filteredMessageTypes=this.messageTypeConfigForm.get("messageType").valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(t){return e.fetchMessageTypes(t)})),f.share())},r.prototype.ngAfterViewInit=function(){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.messageTypeConfigForm.disable({emitEvent:!1}):this.messageTypeConfigForm.enable({emitEvent:!1})},r.prototype.writeValue=function(e){var t=this;this.searchText="",this.messageTypes.length=0,e&&e.forEach((function(e){var r=t.messageTypesList.find((function(t){return t.value===e}));r?t.messageTypes.push({name:r.name,value:r.value}):t.messageTypes.push({name:e,value:e})}))},r.prototype.displayMessageTypeFn=function(e){return e?e.name:void 0},r.prototype.textIsNotEmpty=function(e){return!!(e&&null!=e&&e.length>0)},r.prototype.createMessageType=function(e,t){e.preventDefault(),this.transformMessageType(t)},r.prototype.add=function(e){this.transformMessageType(e.value)},r.prototype.fetchMessageTypes=function(e){if(this.searchText=e,this.searchText&&this.searchText.length){var t=this.searchText.toUpperCase();return c.of(this.messageTypesList.filter((function(e){return e.name.toUpperCase().includes(t)})))}return c.of(this.messageTypesList)},r.prototype.transformMessageType=function(e){if((e||"").trim()){var t=null,r=e.trim(),n=this.messageTypesList.find((function(e){return e.name===r}));(t=n?{name:n.name,value:n.value}:{name:r,value:r})&&this.addMessageType(t)}this.clear("")},r.prototype.remove=function(e){var t=this.messageTypes.indexOf(e);t>=0&&(this.messageTypes.splice(t,1),this.updateModel())},r.prototype.selected=function(e){this.addMessageType(e.option.value),this.clear("")},r.prototype.addMessageType=function(e){-1===this.messageTypes.findIndex((function(t){return t.value===e.value}))&&(this.messageTypes.push(e),this.updateModel())},r.prototype.onFocus=function(){this.messageTypeConfigForm.get("messageType").updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.messageTypeInput.nativeElement.value=e,this.messageTypeConfigForm.get("messageType").patchValue(null,{emitEvent:!0}),setTimeout((function(){t.messageTypeInput.nativeElement.blur(),t.messageTypeInput.nativeElement.focus()}),0)},r.prototype.updateModel=function(){var e=this.messageTypes.map((function(e){return e.value}));this.required?(this.chipList.errorState=!e.length,this.propagateChange(e.length>0?e:null)):(this.chipList.errorState=!1,this.propagateChange(e))},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:a.TruncatePipe},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),b([t.Input(),h("design:type",String)],r.prototype,"label",void 0),b([t.Input(),h("design:type",Object)],r.prototype,"placeholder",void 0),b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.ViewChild("chipList",{static:!1}),h("design:type",d.MatChipList)],r.prototype,"chipList",void 0),b([t.ViewChild("messageTypeAutocomplete",{static:!1}),h("design:type",p.MatAutocomplete)],r.prototype,"matAutocomplete",void 0),b([t.ViewChild("messageTypeInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"messageTypeInput",void 0),r=l=b([t.Component({selector:"tb-message-types-config",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 {{ translate.get(\'tb.rulenode.no-message-type-matching\',\n {messageType: truncate.transform(searchText, true, 6, '...')}) | async }}\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',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return l})),multi:!0}]}),h("design:paramtypes",[o.Store,n.TranslateService,a.TruncatePipe,i.FormBuilder])],r)}(a.PageComponent),ne=function(){function e(){}return e=b([t.NgModule({declarations:[X,ee,te,re],imports:[r.CommonModule,a.SharedModule,m.HomeComponentsModule],exports:[X,ee,te,re]})],e)}(),ae=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.unassignCustomerConfigForm},r.prototype.onConfigurationSet=function(e){this.unassignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[i.Validators.required]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({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 \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),oe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.snsConfigForm},r.prototype.onConfigurationSet=function(e){this.snsConfigForm=this.fb.group({topicArnPattern:[e?e.topicArnPattern:null,[i.Validators.required]],accessKeyId:[e?e.accessKeyId:null,[i.Validators.required]],secretAccessKey:[e?e.secretAccessKey:null,[i.Validators.required]],region:[e?e.region:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-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'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ie=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.sqsQueueType=G,n.sqsQueueTypes=Object.keys(G),n.sqsQueueTypeTranslationsMap=z,n}return y(r,e),r.prototype.configForm=function(){return this.sqsConfigForm},r.prototype.onConfigurationSet=function(e){this.sqsConfigForm=this.fb.group({queueType:[e?e.queueType:null,[i.Validators.required]],queueUrlPattern:[e?e.queueUrlPattern:null,[i.Validators.required]],delaySeconds:[e?e.delaySeconds:null,[i.Validators.min(0),i.Validators.max(900)]],messageAttributes:[e?e.messageAttributes:null,[]],accessKeyId:[e?e.accessKeyId:null,[i.Validators.required]],secretAccessKey:[e?e.secretAccessKey:null,[i.Validators.required]],region:[e?e.region:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-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
tb.rulenode.message-attributes-hint
\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'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),le=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.pubSubConfigForm},r.prototype.onConfigurationSet=function(e){this.pubSubConfigForm=this.fb.group({projectId:[e?e.projectId:null,[i.Validators.required]],topicName:[e?e.topicName:null,[i.Validators.required]],serviceAccountKey:[e?e.serviceAccountKey:null,[i.Validators.required]],serviceAccountKeyFileName:[e?e.serviceAccountKeyFileName:null,[i.Validators.required]],messageAttributes:[e?e.messageAttributes:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-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
tb.rulenode.message-attributes-hint
\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.ackValues=["all","-1","0","1"],n}return y(r,e),r.prototype.configForm=function(){return this.kafkaConfigForm},r.prototype.onConfigurationSet=function(e){this.kafkaConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],bootstrapServers:[e?e.bootstrapServers:null,[i.Validators.required]],retries:[e?e.retries:null,[i.Validators.min(0)]],batchSize:[e?e.batchSize:null,[i.Validators.min(0)]],linger:[e?e.linger:null,[i.Validators.min(0)]],bufferMemory:[e?e.bufferMemory:null,[i.Validators.min(0)]],acks:[e?e.acks:null,[i.Validators.required]],keySerializer:[e?e.keySerializer:null,[i.Validators.required]],valueSerializer:[e?e.valueSerializer:null,[i.Validators.required]],otherProperties:[e?e.otherProperties:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-kafka-config",template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \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'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),me=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allMqttCredentialsTypes=$,n.mqttCredentialsTypeTranslationsMap=_,n}return y(r,e),r.prototype.configForm=function(){return this.mqttConfigForm},r.prototype.onConfigurationSet=function(e){this.mqttConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(200)]],clientId:[e?e.clientId:null,[]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:this.fb.group({type:[e&&e.credentials?e.credentials.type:null,[i.Validators.required]],username:[e&&e.credentials?e.credentials.username:null,[]],password:[e&&e.credentials?e.credentials.password: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,[]]})})},r.prototype.prepareOutputConfig=function(e){var t=e.credentials.type;switch(t){case"anonymous":e.credentials={type:t};break;case"basic":e.credentials={type:t,username:e.credentials.username,password:e.credentials.password};break;case"cert.PEM":delete e.credentials.username}return e},r.prototype.validatorTriggers=function(){return["credentials.type"]},r.prototype.updateValidators=function(e){var t=this.mqttConfigForm.get("credentials"),r=t.get("type").value;switch(e&&t.reset({type:r},{emitEvent:!1}),t.get("username").setValidators([]),t.get("password").setValidators([]),t.get("caCert").setValidators([]),t.get("caCertFileName").setValidators([]),t.get("privateKey").setValidators([]),t.get("privateKeyFileName").setValidators([]),t.get("cert").setValidators([]),t.get("certFileName").setValidators([]),r){case"anonymous":break;case"basic":t.get("username").setValidators([i.Validators.required]),t.get("password").setValidators([i.Validators.required]);break;case"cert.PEM":t.get("caCert").setValidators([i.Validators.required]),t.get("caCertFileName").setValidators([i.Validators.required]),t.get("privateKey").setValidators([i.Validators.required]),t.get("privateKeyFileName").setValidators([i.Validators.required]),t.get("cert").setValidators([i.Validators.required]),t.get("certFileName").setValidators([i.Validators.required])}t.get("username").updateValueAndValidity({emitEvent:e}),t.get("password").updateValueAndValidity({emitEvent:e}),t.get("caCert").updateValueAndValidity({emitEvent:e}),t.get("caCertFileName").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})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-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 \n \n {{ \'tb.rulenode.clean-session\' | translate }}\n \n \n {{ \'tb.rulenode.enable-ssl\' | translate }}\n \n \n \n tb.rulenode.credentials\n \n {{ mqttCredentialsTypeTranslationsMap.get(mqttConfigForm.get(\'credentials\').get(\'type\').value) | translate }}\n \n \n
\n \n tb.rulenode.credentials-type\n \n \n {{ mqttCredentialsTypeTranslationsMap.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 {{ \'tb.rulenode.password-required\' | translate }}\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',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ue=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],n}return y(r,e),r.prototype.configForm=function(){return this.rabbitMqConfigForm},r.prototype.onConfigurationSet=function(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,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.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,[i.Validators.min(0)]],handshakeTimeout:[e?e.handshakeTimeout:null,[i.Validators.min(0)]],clientProperties:[e?e.clientProperties:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-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 {{ \'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'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),de=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.httpRequestTypes=Object.keys(Q),n}return y(r,e),r.prototype.configForm=function(){return this.restApiCallConfigForm},r.prototype.onConfigurationSet=function(e){this.restApiCallConfigForm=this.fb.group({restEndpointUrlPattern:[e?e.restEndpointUrlPattern:null,[i.Validators.required]],requestMethod:[e?e.requestMethod:null,[i.Validators.required]],useSimpleClientHttpFactory:[!!e&&e.useSimpleClientHttpFactory,[]],readTimeoutMs:[e?e.readTimeoutMs:null,[]],maxParallelRequestsCount:[e?e.maxParallelRequestsCount:null,[i.Validators.min(0)]],headers:[e?e.headers:null,[]],useRedisQueueForMsgPersistence:[!!e&&e.useRedisQueueForMsgPersistence,[]],trimQueue:[!!e&&e.trimQueue,[]],maxQueueSize:[e?e.maxQueueSize:null,[]]})},r.prototype.validatorTriggers=function(){return["useSimpleClientHttpFactory","useRedisQueueForMsgPersistence"]},r.prototype.updateValidators=function(e){var t=this.restApiCallConfigForm.get("useSimpleClientHttpFactory").value,r=this.restApiCallConfigForm.get("useRedisQueueForMsgPersistence").value;t?this.restApiCallConfigForm.get("readTimeoutMs").setValidators([]):this.restApiCallConfigForm.get("readTimeoutMs").setValidators([i.Validators.min(0)]),r?this.restApiCallConfigForm.get("maxQueueSize").setValidators([i.Validators.min(0)]):this.restApiCallConfigForm.get("maxQueueSize").setValidators([]),this.restApiCallConfigForm.get("readTimeoutMs").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("maxQueueSize").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-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.use-simple-client-http-factory\' | translate }}\n \n \n tb.rulenode.read-timeout\n \n \n \n \n tb.rulenode.max-parallel-requests-count\n \n \n \n \n
tb.rulenode.headers-hint
\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'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),pe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.smtpProtocols=["smtp","smtps"],n}return y(r,e),r.prototype.configForm=function(){return this.sendEmailConfigForm},r.prototype.onConfigurationSet=function(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,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]]})},r.prototype.validatorTriggers=function(){return["useSystemSmtpSettings"]},r.prototype.updateValidators=function(e){this.sendEmailConfigForm.get("useSystemSmtpSettings").value?(this.sendEmailConfigForm.get("smtpProtocol").setValidators([]),this.sendEmailConfigForm.get("smtpHost").setValidators([]),this.sendEmailConfigForm.get("smtpPort").setValidators([]),this.sendEmailConfigForm.get("timeout").setValidators([])):(this.sendEmailConfigForm.get("smtpProtocol").setValidators([i.Validators.required]),this.sendEmailConfigForm.get("smtpHost").setValidators([i.Validators.required]),this.sendEmailConfigForm.get("smtpPort").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]),this.sendEmailConfigForm.get("timeout").setValidators([i.Validators.required,i.Validators.min(0)])),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})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-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.username\n \n \n \n tb.rulenode.password\n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ce=function(){function e(){}return e=b([t.NgModule({declarations:[T,q,x,I,S,N,k,E,V,A,L,J,W,Y,Z,ae,oe,ie,le,se,me,ue,de,pe],imports:[r.CommonModule,a.SharedModule,ne],exports:[T,q,x,I,S,N,k,E,V,A,L,J,W,Y,Z,ae,oe,ie,le,se,me,ue,de,pe]})],e)}(),fe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.checkMessageConfigForm},r.prototype.onConfigurationSet=function(e){this.checkMessageConfigForm=this.fb.group({messageNames:[e?e.messageNames:null,[]],metadataNames:[e?e.metadataNames:null,[]],checkAllKeys:[!!e&&e.checkAllKeys,[]]})},r.prototype.validateConfig=function(){var e=this.checkMessageConfigForm.get("messageNames").value,t=this.checkMessageConfigForm.get("metadataNames").value;return e.length>0||t.length>0},r.prototype.removeMessageName=function(e){var t=this.checkMessageConfigForm.get("messageNames").value,r=t.indexOf(e);r>=0&&(t.splice(r,1),this.checkMessageConfigForm.get("messageNames").setValue(t,{emitEvent:!0}))},r.prototype.removeMetadataName=function(e){var t=this.checkMessageConfigForm.get("metadataNames").value,r=t.indexOf(e);r>=0&&(t.splice(r,1),this.checkMessageConfigForm.get("metadataNames").setValue(t,{emitEvent:!0}))},r.prototype.addMessageName=function(e){var t=e.input,r=e.value;if((r||"").trim()){r=r.trim();var n=this.checkMessageConfigForm.get("messageNames").value;n&&-1!==n.indexOf(r)||(n||(n=[]),n.push(r),this.checkMessageConfigForm.get("messageNames").setValue(n,{emitEvent:!0}))}t&&(t.value="")},r.prototype.addMetadataName=function(e){var t=e.input,r=e.value;if((r||"").trim()){r=r.trim();var n=this.checkMessageConfigForm.get("metadataNames").value;n&&-1!==n.indexOf(r)||(n||(n=[]),n.push(r),this.checkMessageConfigForm.get("metadataNames").setValue(n,{emitEvent:!0}))}t&&(t.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-check-message-config",template:'
\n \n \n \n \n \n {{messageName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n \n \n \n \n {{metadataName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\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}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ge=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.entitySearchDirection=Object.keys(a.EntitySearchDirection),n.entitySearchDirectionTranslationsMap=a.entitySearchDirectionTranslations,n}return y(r,e),r.prototype.configForm=function(){return this.checkRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.checkRelationConfigForm=this.fb.group({checkForSingleEntity:[!!e&&e.checkForSingleEntity,[]],direction:[e?e.direction:null,[]],entityType:[e?e.entityType:null,e&&e.checkForSingleEntity?[i.Validators.required]:[]],entityId:[e?e.entityId:null,e&&e.checkForSingleEntity?[i.Validators.required]:[]],relationType:[e?e.relationType:null,[i.Validators.required]]})},r.prototype.validatorTriggers=function(){return["checkForSingleEntity"]},r.prototype.updateValidators=function(e){var t=this.checkRelationConfigForm.get("checkForSingleEntity").value;this.checkRelationConfigForm.get("entityType").setValidators(t?[i.Validators.required]:[]),this.checkRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:e}),this.checkRelationConfigForm.get("entityId").setValidators(t?[i.Validators.required]:[]),this.checkRelationConfigForm.get("entityId").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({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'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ye=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.perimeterType=M,n.perimeterTypes=Object.keys(M),n.perimeterTypeTranslationMap=w,n.rangeUnits=Object.keys(D),n.rangeUnitTranslationMap=B,n}return y(r,e),r.prototype.configForm=function(){return this.geoFilterConfigForm},r.prototype.onConfigurationSet=function(e){this.geoFilterConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[i.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[i.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType: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,[]]})},r.prototype.validatorTriggers=function(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]},r.prototype.updateValidators=function(e){var t=this.geoFilterConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,r=this.geoFilterConfigForm.get("perimeterType").value;t?this.geoFilterConfigForm.get("perimeterType").setValidators([]):this.geoFilterConfigForm.get("perimeterType").setValidators([i.Validators.required]),t||r!==M.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([i.Validators.required,i.Validators.min(-90),i.Validators.max(90)]),this.geoFilterConfigForm.get("centerLongitude").setValidators([i.Validators.required,i.Validators.min(-180),i.Validators.max(180)]),this.geoFilterConfigForm.get("range").setValidators([i.Validators.required,i.Validators.min(0)]),this.geoFilterConfigForm.get("rangeUnit").setValidators([i.Validators.required])),t||r!==M.POLYGON?this.geoFilterConfigForm.get("polygonsDefinition").setValidators([]):this.geoFilterConfigForm.get("polygonsDefinition").setValidators([i.Validators.required]),this.geoFilterConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),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})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({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.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \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'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),be=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.messageTypeConfigForm},r.prototype.onConfigurationSet=function(e){this.messageTypeConfigForm=this.fb.group({messageTypes:[e?e.messageTypes:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-message-type-config",template:'
\n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),he=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allowedEntityTypes=[a.EntityType.DEVICE,a.EntityType.ASSET,a.EntityType.ENTITY_VIEW,a.EntityType.TENANT,a.EntityType.CUSTOMER,a.EntityType.USER,a.EntityType.DASHBOARD,a.EntityType.RULE_CHAIN,a.EntityType.RULE_NODE],n}return y(r,e),r.prototype.configForm=function(){return this.originatorTypeConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorTypeConfigForm=this.fb.group({originatorTypes:[e?e.originatorTypes:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-originator-type-config",template:'
\n \n \n \n
\n',styles:[":host ::ng-deep tb-entity-type-list .mat-form-field-flex{padding-top:0}:host ::ng-deep tb-entity-type-list .mat-form-field-infix{border-top:0}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ce=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.scriptConfigForm},r.prototype.onConfigurationSet=function(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"filter",this.translate.instant("tb.rulenode.filter"),"Filter",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-filter-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),ve=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.switchConfigForm},r.prototype.onConfigurationSet=function(e){this.switchConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.switchConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"switch",this.translate.instant("tb.rulenode.switch"),"Switch",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.switchConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-filter-node-switch-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Fe=function(){function e(){}return e=b([t.NgModule({declarations:[fe,ge,ye,be,he,Ce,ve],imports:[r.CommonModule,a.SharedModule,ne],exports:[fe,ge,ye,be,he,Ce,ve]})],e)}(),Te=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.customerAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.customerAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-customer-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),qe=function(e){function r(t,r,n){var a,o,l=e.call(this,t)||this;l.store=t,l.translate=r,l.fb=n,l.entityDetailsTranslationsMap=H,l.entityDetailsList=[],l.searchText="",l.displayDetailsFn=l.displayDetails.bind(l);try{for(var s=C(Object.keys(K)),m=s.next();!m.done;m=s.next()){var u=m.value;l.entityDetailsList.push(K[u])}}catch(e){a={error:e}}finally{try{m&&!m.done&&(o=s.return)&&o.call(s)}finally{if(a)throw a.error}}return l.detailsFormControl=new i.FormControl(""),l.filteredEntityDetails=l.detailsFormControl.valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(e){return l.fetchEntityDetails(e)})),f.share()),l}return y(r,e),r.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},r.prototype.configForm=function(){return this.entityDetailsConfigForm},r.prototype.prepareInputConfig=function(e){return this.searchText="",this.detailsFormControl.patchValue("",{emitEvent:!0}),e},r.prototype.onConfigurationSet=function(e){this.entityDetailsConfigForm=this.fb.group({detailsList:[e?e.detailsList:null,[i.Validators.required]],addToMetadata:[!!e&&e.addToMetadata,[]]})},r.prototype.displayDetails=function(e){return e?this.translate.instant(H.get(e)):void 0},r.prototype.fetchEntityDetails=function(e){var t=this;if(this.searchText=e,this.searchText&&this.searchText.length){var r=this.searchText.toUpperCase();return c.of(this.entityDetailsList.filter((function(e){return t.translate.instant(H.get(K[e])).toUpperCase().includes(r)})))}return c.of(this.entityDetailsList)},r.prototype.detailsFieldSelected=function(e){this.addDetailsField(e.option.value),this.clear("")},r.prototype.removeDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;if(t){var r=t.indexOf(e);r>=0&&(t.splice(r,1),this.entityDetailsConfigForm.get("detailsList").setValue(t))}},r.prototype.addDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;t||(t=[]),-1===t.indexOf(e)&&(t.push(e),this.entityDetailsConfigForm.get("detailsList").setValue(t))},r.prototype.onEntityDetailsInputFocus=function(){this.detailsFormControl.updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.detailsInput.nativeElement.value=e,this.detailsFormControl.patchValue(null,{emitEvent:!0}),setTimeout((function(){t.detailsInput.nativeElement.blur(),t.detailsInput.nativeElement.focus()}),0)},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:i.FormBuilder}]},b([t.ViewChild("detailsInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"detailsInput",void 0),r=b([t.Component({selector:"tb-enrichment-node-entity-details-config",template:'
\n \n tb.rulenode.entity-details\n \n \n \n {{entityDetailsTranslationsMap.get(details) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-entity-details-matching\n
\n
\n
\n
\n
\n \n \n {{ \'tb.rulenode.add-to-metadata\' | translate }}\n \n
tb.rulenode.add-to-metadata-hint
\n
\n',styles:[":host ::ng-deep mat-form-field.entity-fields-list .mat-form-field-wrapper{margin-bottom:-1.25em}"]}),h("design:paramtypes",[o.Store,n.TranslateService,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),xe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.deviceAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.deviceAttributesConfigForm=this.fb.group({deviceRelationsQuery:[e?e.deviceRelationsQuery:null,[i.Validators.required]],tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})},r.prototype.removeKey=function(e,t){var r=this.deviceAttributesConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.deviceAttributesConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.deviceAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.deviceAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-device-attributes-config",template:'
\n \n \n \n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ie=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.originatorAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorAttributesConfigForm=this.fb.group({tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})},r.prototype.removeKey=function(e,t){var r=this.originatorAttributesConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.originatorAttributesConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.originatorAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.originatorAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-originator-attributes-config",template:'
\n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.originatorFieldsConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorFieldsConfigForm=this.fb.group({fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-originator-fields-config",template:'
\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ne=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n.fetchMode=j,n.fetchModes=Object.keys(j),n.samplingOrders=Object.keys(U),n.timeUnits=Object.keys(R),n.timeUnitsTranslationMap=O,n}return y(r,e),r.prototype.configForm=function(){return this.getTelemetryFromDatabaseConfigForm},r.prototype.onConfigurationSet=function(e){this.getTelemetryFromDatabaseConfigForm=this.fb.group({latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],fetchMode:[e?e.fetchMode:null,[i.Validators.required]],orderBy:[e?e.orderBy:null,[]],limit:[e?e.limit:null,[]],useMetadataIntervalPatterns:[!!e&&e.useMetadataIntervalPatterns,[]],startInterval:[e?e.startInterval:null,[]],startIntervalTimeUnit:[e?e.startIntervalTimeUnit:null,[]],endInterval:[e?e.endInterval:null,[]],endIntervalTimeUnit:[e?e.endIntervalTimeUnit:null,[]],startIntervalPattern:[e?e.startIntervalPattern:null,[]],endIntervalPattern:[e?e.endIntervalPattern:null,[]]})},r.prototype.validatorTriggers=function(){return["fetchMode","useMetadataIntervalPatterns"]},r.prototype.updateValidators=function(e){var t=this.getTelemetryFromDatabaseConfigForm.get("fetchMode").value,r=this.getTelemetryFromDatabaseConfigForm.get("useMetadataIntervalPatterns").value;t&&t===j.ALL?(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([i.Validators.required,i.Validators.min(2),i.Validators.max(1e3)])):(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([])),r?(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([i.Validators.required])):(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([])),this.getTelemetryFromDatabaseConfigForm.get("orderBy").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("limit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").updateValueAndValidity({emitEvent:e})},r.prototype.removeKey=function(e,t){var r=this.getTelemetryFromDatabaseConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.getTelemetryFromDatabaseConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-get-telemetry-from-database",template:'
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n tb.rulenode.fetch-mode\n \n \n {{ mode }}\n \n \n tb.rulenode.fetch-mode-hint\n \n
\n \n tb.rulenode.order-by\n \n \n {{ order }}\n \n \n tb.rulenode.order-by-hint\n \n \n tb.rulenode.limit\n \n tb.rulenode.limit-hint\n \n
\n \n {{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-interval-patterns-hint
\n
\n
\n \n tb.rulenode.start-interval\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.start-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.end-interval\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.end-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n \n tb.rulenode.start-interval-pattern\n \n \n {{ \'tb.rulenode.start-interval-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.end-interval-pattern\n \n \n {{ \'tb.rulenode.end-interval-pattern-required\' | translate }}\n \n \n \n \n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ke=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.relatedAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.relatedAttributesConfigForm=this.fb.group({relationsQuery:[e?e.relationsQuery:null,[i.Validators.required]],telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-related-attributes-config",template:'
\n \n \n \n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ee=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.tenantAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.tenantAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-tenant-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ve=function(){function e(){}return e=b([t.NgModule({declarations:[Te,qe,xe,Ie,Se,Ne,ke,Ee],imports:[r.CommonModule,a.SharedModule,ne],exports:[Te,qe,xe,Ie,Se,Ne,ke,Ee]})],e)}(),Ae=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.originatorSource=v,n.originatorSources=Object.keys(v),n.originatorSourceTranslationMap=P,n}return y(r,e),r.prototype.configForm=function(){return this.changeOriginatorConfigForm},r.prototype.onConfigurationSet=function(e){this.changeOriginatorConfigForm=this.fb.group({originatorSource:[e?e.originatorSource:null,[i.Validators.required]],relationsQuery:[e?e.relationsQuery:null,[]]})},r.prototype.validatorTriggers=function(){return["originatorSource"]},r.prototype.updateValidators=function(e){var t=this.changeOriginatorConfigForm.get("originatorSource").value;t&&t===v.RELATED?this.changeOriginatorConfigForm.get("relationsQuery").setValidators([i.Validators.required]):this.changeOriginatorConfigForm.get("relationsQuery").setValidators([]),this.changeOriginatorConfigForm.get("relationsQuery").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({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
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Le=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.scriptConfigForm},r.prototype.onConfigurationSet=function(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"update",this.translate.instant("tb.rulenode.transformer"),"Transform",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-transformation-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Me=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.toEmailConfigForm},r.prototype.onConfigurationSet=function(e){this.toEmailConfigForm=this.fb.group({fromTemplate:[e?e.fromTemplate:null,[i.Validators.required]],toTemplate:[e?e.toTemplate:null,[i.Validators.required]],ccTemplate:[e?e.ccTemplate:null,[]],bccTemplate:[e?e.bccTemplate:null,[]],subjectTemplate:[e?e.subjectTemplate:null,[i.Validators.required]],bodyTemplate:[e?e.bodyTemplate:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({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.body-template\n \n \n {{ \'tb.rulenode.body-template-required\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Pe=function(){function e(){}return e=b([t.NgModule({declarations:[Ae,Le,Me],imports:[r.CommonModule,a.SharedModule,ne],exports:[Ae,Le,Me]})],e)}(),Re=function(){function e(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-name-pattern-hint":"Name pattern, use ${metaKeyName} to substitute variables from metadata","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-type-pattern-hint":"Type pattern, use ${metaKeyName} to substitute variables from metadata","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-name-pattern-hint":"Customer name pattern, use ${metaKeyName} to substitute variables from metadata","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.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.",limit:"Limit","limit-hint":"Min limit value is 2, max - 1000. In case you want to fetch a single entry, select fetch mode 'FIRST' or 'LAST'.","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","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","latest-timeseries":"Latest timeseries","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-hint":"Relation type pattern, use ${metaKeyName} to substitute variables from metadata","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","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","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","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 metadata period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds metadata pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","period-in-seconds-pattern-hint":"Period in seconds pattern, use ${metaKeyName} to substitute variables from metadata","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",propagate:"Propagate",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","from-template-hint":"From address template, use ${metaKeyName} to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use ${metaKeyName} to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use ${metaKeyName} to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use ${metaKeyName} to substitute variables from metadata","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","endpoint-url-pattern-hint":"HTTP URL address pattern, use ${metaKeyName} to substitute variables from metadata","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory","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 ${metaKeyName} in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use ${metaKeyName} to substitute variables from metadata","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","topic-arn-pattern-hint":"Topic ARN pattern, use ${metaKeyName} to substitute variables from metadata","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","queue-url-pattern-hint":"Queue URL pattern, use ${metaKeyName} to substitute variables from metadata","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 ${metaKeyName} in name/value fields to substitute variables from metadata","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","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","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"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-interval-patterns":"Use metadata interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","check-all-keys":"Check that all selected keys 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-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","start-interval-pattern-hint":"Start interval pattern, use ${metaKeyName} to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use ${metaKeyName} to substitute variables from metadata","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","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":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","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","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","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":"You should 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-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 Latest telemetry with Timestamp","get-latest-value-with-ts-hint":'If selected, latest telemetry values will be added to the outbound message metadata with timestamp, e.g: "temp": "{\\"ts\\":1574329385897,\\"value\\":42}"',"use-redis-queue":"Use redis queue for message persistence","trim-redis-queue":"Trim redis queue","redis-queue-max-size":"Redis queue max size"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}},!0)}(e)}return e.ctorParameters=function(){return[{type:n.TranslateService}]},e=b([t.NgModule({declarations:[F],imports:[r.CommonModule,a.SharedModule],exports:[ce,Fe,Ve,Pe,F]}),h("design:paramtypes",[n.TranslateService])],e)}();e.RuleNodeCoreConfigModule=Re,e.ɵa=F,e.ɵb=ce,e.ɵba=ne,e.ɵbb=X,e.ɵbc=ee,e.ɵbd=te,e.ɵbe=re,e.ɵbf=Fe,e.ɵbg=fe,e.ɵbh=ge,e.ɵbi=ye,e.ɵbj=be,e.ɵbk=he,e.ɵbl=Ce,e.ɵbm=ve,e.ɵbn=Ve,e.ɵbo=Te,e.ɵbp=qe,e.ɵbq=xe,e.ɵbr=Ie,e.ɵbs=Se,e.ɵbt=Ne,e.ɵbu=ke,e.ɵbv=Ee,e.ɵbw=Pe,e.ɵbx=Ae,e.ɵby=Le,e.ɵbz=Me,e.ɵc=T,e.ɵd=q,e.ɵe=x,e.ɵf=I,e.ɵg=S,e.ɵh=N,e.ɵi=k,e.ɵj=E,e.ɵk=V,e.ɵl=A,e.ɵm=L,e.ɵn=J,e.ɵo=W,e.ɵp=Y,e.ɵq=Z,e.ɵr=ae,e.ɵs=oe,e.ɵt=ie,e.ɵu=le,e.ɵv=se,e.ɵw=me,e.ɵx=ue,e.ɵy=de,e.ɵz=pe,Object.defineProperty(e,"__esModule",{value:!0})})); //# sourceMappingURL=rulenode-core-config.umd.min.js.map \ No newline at end of file diff --git a/ui-ngx/package-lock.json b/ui-ngx/package-lock.json index 4370c4fa7e..8951973946 100644 --- a/ui-ngx/package-lock.json +++ b/ui-ngx/package-lock.json @@ -9600,7 +9600,10 @@ }, "ngx-flowchart": { "version": "git://github.com/thingsboard/ngx-flowchart.git#671b505b2484806a4a1c376344d0a12e5716679a", - "from": "git://github.com/thingsboard/ngx-flowchart.git#master" + "from": "git://github.com/thingsboard/ngx-flowchart.git#master", + "requires": { + "tslib": "^1.10.0" + } }, "ngx-hm-carousel": { "version": "1.7.2", diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/link-labels.component.html b/ui-ngx/src/app/modules/home/pages/rulechain/link-labels.component.html index 593613f143..60face16ef 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/link-labels.component.html +++ b/ui-ngx/src/app/modules/home/pages/rulechain/link-labels.component.html @@ -29,6 +29,7 @@ - +
diff --git a/ui-ngx/src/app/shared/components/image-input.component.html b/ui-ngx/src/app/shared/components/image-input.component.html index ab49bf213c..02e0a12139 100644 --- a/ui-ngx/src/app/shared/components/image-input.component.html +++ b/ui-ngx/src/app/shared/components/image-input.component.html @@ -38,7 +38,7 @@ flowDrop [flow]="flow.flowJs"> - +
diff --git a/ui-ngx/src/app/shared/components/json-form/react/json-form-rc-select.tsx b/ui-ngx/src/app/shared/components/json-form/react/json-form-rc-select.tsx index 1747fe1499..3fac3276f8 100644 --- a/ui-ngx/src/app/shared/components/json-form/react/json-form-rc-select.tsx +++ b/ui-ngx/src/app/shared/components/json-form/react/json-form-rc-select.tsx @@ -156,8 +156,12 @@ class ThingsboardRcSelect extends React.Component{this.props.form.title} +
+ +
+
+
+
+ {{ error }} +
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/round-switch.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/round-switch.component.scss new file mode 100644 index 0000000000..99cbe764eb --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/round-switch.component.scss @@ -0,0 +1,198 @@ +/** + * Copyright © 2016-2019 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 "~compass-sass-mixins/lib/compass"; + +$error-height: 14px !default; + +$background-color: #e6e7e8 !default; + +:host { + .tb-round-switch { + width: 100%; + height: 100%; + background: $background-color; + + .title-container { + .switch-title { + font-weight: 500; + color: #757575; + white-space: nowrap; + } + } + + .error-container { + position: absolute; + top: 1%; + right: 0; + left: 0; + z-index: 4; + height: $error-height; + + .switch-error { + color: #ff3315; + white-space: nowrap; + } + } + + #text-measure { + position: absolute; + width: auto; + height: auto; + white-space: nowrap; + visibility: hidden; + } + + #switch-container { + padding: 10px; + + .switch { + position: relative; + + box-sizing: border-box; + width: 260px; + min-width: 260px; + height: 260px; + min-height: 260px; + padding: 25px; + font-family: sans-serif; + font-size: 48px; + + color: #424242; + cursor: pointer; + background: #ddd; + background: linear-gradient(180deg, #bbb, #ddd); + border-radius: 130px; + + box-shadow: 0 0 0 8px rgba(0, 0, 0, .1), + 0 0 3px 1px rgba(0, 0, 0, .1), + inset 0 8px 3px -8px rgba(255, 255, 255, .4); + + input { + display: none; + } + + .on, + .off { + position: absolute; + width: 100%; + text-align: center; + + text-shadow: 1px 1px 4px #4a4a4a; + } + + .on { + top: 10px; + font-family: sans-serif; + color: #444; + + transition: all .1s; + } + + .off { + bottom: 5px; + + transition: all .1s; + + transform: scaleY(.85); + } + + .but { + position: relative; + display: block; + width: 200px; + height: 178px; + font-size: 48px; + cursor: pointer; + background-color: #d8d8d8; + border-bottom-width: 0; + border-radius: 400px 400px 400px 400px / 400px 400px 300px 300px; + + box-shadow: inset 8px 6px 5px -7px #a2a2a2, + inset -8px 6px 5px -7px #a2a2a2, + inset 0 -3px 2px -2px rgba(200, 200, 200, .5), + 0 3px 3px -2px #fff, + inset 0 -230px 60px -200px rgba(255, 255, 255, .2), + inset 0 220px 40px -200px rgba(0, 0, 0, .3); + + transition: all .2s; + } + + .back { + + box-sizing: border-box; + width: 210px; + height: 210px; + padding: 4px 4px; + cursor: pointer; + background-color: #888787; + background-image: linear-gradient(-90deg, transparent 30%, transparent 70%), linear-gradient(0deg, rgba(150, 150, 150, 0) 30%, rgba(150, 150, 150, .2) 50%, rgba(150, 150, 150, 0) 70%); + border-radius: 105px; + + box-shadow: 30px 30px 30px -20px rgba(58, 58, 58, .3), + -30px 30px 30px -20px rgba(58, 58, 58, .3), + 0 30px 30px 0 rgba(16, 16, 16, .3), + inset 0 -1px 0 0 #484848; + + transition: all .2s; + } + + + input:checked + .back .on, + input:checked + .back .off { + text-shadow: 1px 1px 4px #4a4a4a; + } + + input:checked + .back .on { + top: 10px; + color: #4c4c4c; + + transform: scaleY(.85); + } + + input:checked + .back .off { + bottom: 5px; + color: #444; + + transform: scaleY(1); + } + + input:checked + .back .but { + margin-top: 20px; + background: #dcdcdc radial-gradient(circle closest-corner at 50% 15%, rgba(0, 0, 0, .3), transparent); + border-radius: 400px 400px 400px 400px / 300px 300px 400px 400px; + + box-shadow: inset 8px -4px 5px -7px #a9a9a9, + inset -8px -4px 5px -7px #808080, + 0 -3px 8px -4px rgba(50, 50, 50, .4), + inset 0 3px 4px -2px #9c9c9c, + inset 0 280px 40px -200px rgba(0, 0, 0, .2), + inset 0 -200px 40px -200px rgba(180, 180, 180, .2); + } + + input:checked + .back { + padding: 2px 4px; + + background-image: linear-gradient(0deg, #868686 30%, transparent 70%), linear-gradient(90deg, rgba(115, 115, 115, 0) 0%, rgba(255, 255, 255, .74) 50%, rgba(105, 105, 105, 0) 100%); + + box-shadow: 30px 30px 30px -20px rgba(49, 49, 49, .1), + -30px 30px 30px -20px rgba(111, 111, 111, .1), + 0 30px 30px 0 rgba(0, 0, 0, .2), + inset 0 1px 2px 0 rgba(167, 167, 167, .6); + } + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/round-switch.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/round-switch.component.ts new file mode 100644 index 0000000000..3be714fd2b --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/round-switch.component.ts @@ -0,0 +1,338 @@ +/// +/// Copyright © 2016-2019 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { AfterViewInit, Component, ElementRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { WidgetContext } from '@home/models/widget-component.models'; +import { UtilsService } from '@core/services/utils.service'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { isDefined } from '@core/utils'; +import { IWidgetSubscription, SubscriptionInfo, WidgetSubscriptionOptions } from '@core/api/widget-api.models'; +import { DatasourceType, widgetType } from '@shared/models/widget.models'; +import { EntityType } from '@shared/models/entity-type.models'; + +type RetrieveValueMethod = 'rpc' | 'attribute' | 'timeseries'; + +interface RoundSwitchSettings { + initialValue: boolean; + title: string; + retrieveValueMethod: RetrieveValueMethod; + valueKey: string; + getValueMethod: string; + setValueMethod: string; + parseValueFunction: string; + convertValueFunction: string; + requestTimeout: number; +} + +@Component({ + selector: 'tb-round-switch', + templateUrl: './round-switch.component.html', + styleUrls: ['./round-switch.component.scss'] +}) +export class RoundSwitchComponent extends PageComponent implements OnInit, OnDestroy { + + @ViewChild('switch', {static: true}) switchElementRef: ElementRef; + @ViewChild('switchContainer', {static: true}) switchContainerRef: ElementRef; + @ViewChild('onoff', {static: true}) onoffRef: ElementRef; + @ViewChild('textMeasure', {static: true}) textMeasureRef: ElementRef; + @ViewChild('switchTitleContainer', {static: true}) switchTitleContainerRef: ElementRef; + @ViewChild('switchTitle', {static: true}) switchTitleRef: ElementRef; + @ViewChild('switchErrorContainer', {static: true}) switchErrorContainerRef: ElementRef; + @ViewChild('switchError', {static: true}) switchErrorRef: ElementRef; + + @Input() + ctx: WidgetContext; + + showTitle = false; + value = false; + error = ''; + title = ''; + checkboxId = 'onoff-' + this.utils.guid(); + + private isSimulated: boolean; + private requestTimeout: number; + private retrieveValueMethod: RetrieveValueMethod; + private valueKey: string; + private parseValueFunction: (data: any) => boolean; + private convertValueFunction: (value: any) => any; + private getValueMethod: string; + private setValueMethod: string; + + private valueSubscription: IWidgetSubscription; + + private executingUpdateValue: boolean; + private scheduledValue: boolean; + private rpcValue: boolean; + + private switchElement: JQuery; + private switchContainer: JQuery; + private onoff: JQuery; + private textMeasure: JQuery; + private switchTitleContainer: JQuery; + private switchTitle: JQuery; + private switchErrorContainer: JQuery; + private switchError: JQuery; + + private switchResizeListener: any; + + constructor(private utils: UtilsService, + protected store: Store) { + super(store); + } + + ngOnInit(): void { + this.switchElement = $(this.switchElementRef.nativeElement); + this.switchContainer = $(this.switchContainerRef.nativeElement); + this.onoff = $(this.onoffRef.nativeElement); + this.textMeasure = $(this.textMeasureRef.nativeElement); + this.switchTitleContainer = $(this.switchTitleContainerRef.nativeElement); + this.switchTitle = $(this.switchTitleRef.nativeElement); + this.switchErrorContainer = $(this.switchErrorContainerRef.nativeElement); + this.switchError = $(this.switchErrorRef.nativeElement); + + this.onoff.on('change', () => { + this.value = this.onoff.prop('checked') === false; + this.onValue(); + }); + + this.switchResizeListener = this.resize.bind(this); + // @ts-ignore + addResizeListener(this.switchContainerRef.nativeElement, this.switchResizeListener); + this.init(); + // this.ctx.resize = this.resize.bind(this); + } + + ngOnDestroy(): void { + if (this.valueSubscription) { + this.ctx.subscriptionApi.removeSubscription(this.valueSubscription.id); + } + if (this.switchResizeListener) { + // @ts-ignore + removeResizeListener(this.switchContainerRef.nativeElement, this.switchResizeListener); + } + } + + private init() { + const settings: RoundSwitchSettings = this.ctx.settings; + this.title = isDefined(settings.title) ? settings.title : ''; + this.showTitle = !!(this.title && this.title.length); + const initialValue = isDefined(settings.initialValue) ? settings.initialValue : false; + this.setValue(initialValue); + + const subscription = this.ctx.defaultSubscription; + const rpcEnabled = subscription.rpcEnabled; + + this.isSimulated = this.utils.widgetEditMode; + + this.requestTimeout = 500; + if (settings.requestTimeout) { + this.requestTimeout = settings.requestTimeout; + } + this.retrieveValueMethod = 'rpc'; + if (settings.retrieveValueMethod && settings.retrieveValueMethod.length) { + this.retrieveValueMethod = settings.retrieveValueMethod; + } + this.valueKey = 'value'; + if (settings.valueKey && settings.valueKey.length) { + this.valueKey = settings.valueKey; + } + this.parseValueFunction = (data) => !!data; + if (settings.parseValueFunction && settings.parseValueFunction.length) { + try { + this.parseValueFunction = new Function('data', settings.parseValueFunction) as (data: any) => boolean; + } catch (e) { + this.parseValueFunction = (data) => !!data; + } + } + this.convertValueFunction = (value) => value; + if (settings.convertValueFunction && settings.convertValueFunction.length) { + try { + this.convertValueFunction = new Function('value', settings.convertValueFunction) as (value: any) => any; + } catch (e) { + this.convertValueFunction = (value) => value; + } + } + this.getValueMethod = 'getValue'; + if (settings.getValueMethod && settings.getValueMethod.length) { + this.getValueMethod = settings.getValueMethod; + } + this.setValueMethod = 'setValue'; + if (settings.setValueMethod && settings.setValueMethod.length) { + this.setValueMethod = settings.setValueMethod; + } + if (!rpcEnabled) { + this.onError('Target device is not set!'); + } else { + if (!this.isSimulated) { + if (this.retrieveValueMethod === 'rpc') { + this.rpcRequestValue(); + } else if (this.retrieveValueMethod === 'attribute' || this.retrieveValueMethod === 'timeseries') { + this.subscribeForValue(); + } + } + } + + } + + private resize() { + const width = this.switchContainer.width(); + const height = this.switchContainer.height(); + const size = Math.min(width, height); + const scale = size/260; + this.switchElement.css({ + '-webkit-transform': `scale(${scale})`, + '-moz-transform': `scale(${scale})`, + '-ms-transform': `scale(${scale})`, + '-o-transform': `scale(${scale})`, + transform: `scale(${scale})` + }); + if (this.showTitle) { + this.setFontSize(this.switchTitle, this.title, this.switchTitleContainer.height() * 2 / 3, this.switchTitleContainer.width()); + } + this.setFontSize(this.switchError, this.error, this.switchErrorContainer.height(), this.switchErrorContainer.width()); + } + + private setFontSize(element: JQuery, text: string, fontSize: number, maxWidth: number) { + let textWidth = this.measureTextWidth(text, fontSize); + while (textWidth > maxWidth) { + fontSize--; + textWidth = this.measureTextWidth(text, fontSize); + } + element.css({fontSize: fontSize+'px', lineHeight: fontSize+'px'}); + } + + private measureTextWidth(text: string, fontSize: number): number { + this.textMeasure.css({fontSize: fontSize+'px', lineHeight: fontSize+'px'}); + this.textMeasure.text(text); + return this.textMeasure.width(); + } + + private onError(error: string) { + this.error = error; + this.setFontSize(this.switchError, this.error, this.switchErrorContainer.height(), this.switchErrorContainer.width()); + this.ctx.detectChanges(); + } + + private setValue(value: boolean) { + this.value = value ? true : false; + this.onoff.prop('checked', !this.value); + } + + private onValue() { + this.rpcUpdateValue(this.value); + } + + private rpcRequestValue() { + this.error = ''; + this.ctx.controlApi.sendTwoWayCommand(this.getValueMethod, null, this.requestTimeout).subscribe( + (responseBody) => { + this.setValue(this.parseValueFunction(responseBody)); + }, + () => { + const errorText = this.ctx.defaultSubscription.rpcErrorText; + this.onError(errorText); + } + ); + } + + private rpcUpdateValue(value) { + if (this.executingUpdateValue) { + this.scheduledValue = value; + return; + } else { + this.scheduledValue = null; + this.rpcValue = value; + this.executingUpdateValue = true; + } + this.error = ''; + this.ctx.controlApi.sendOneWayCommand(this.setValueMethod, this.convertValueFunction(value), this.requestTimeout).subscribe( + () => { + this.executingUpdateValue = false; + if (this.scheduledValue != null && this.scheduledValue !== this.rpcValue) { + this.rpcUpdateValue(this.scheduledValue); + } + }, + () => { + this.executingUpdateValue = false; + const errorText = this.ctx.defaultSubscription.rpcErrorText; + this.onError(errorText); + } + ); + } + + private subscribeForValue() { + const valueSubscriptionInfo: SubscriptionInfo[] = [{ + type: DatasourceType.entity, + entityType: EntityType.DEVICE, + entityId: this.ctx.defaultSubscription.targetDeviceId + }]; + if (this.retrieveValueMethod === 'attribute') { + valueSubscriptionInfo[0].attributes = [ + {name: this.valueKey} + ]; + } else { + valueSubscriptionInfo[0].timeseries = [ + {name: this.valueKey} + ]; + } + const subscriptionOptions: WidgetSubscriptionOptions = { + callbacks: { + onDataUpdated: this.onDataUpdated.bind(this), + onDataUpdateError: this.onDataUpdateError.bind(this) + } + }; + this.ctx.subscriptionApi.createSubscriptionFromInfo ( + widgetType.latest, valueSubscriptionInfo, subscriptionOptions, false, true).subscribe( + (subscription) => { + this.valueSubscription = subscription; + } + ); + } + + private onDataUpdated(subscription: IWidgetSubscription, detectChanges: boolean) { + let value = false; + const data = subscription.data; + if (data.length) { + const keyData = data[0]; + if (keyData && keyData.data && keyData.data[0]) { + const attrValue = keyData.data[0][1]; + if (attrValue) { + let parsed = null; + try { + parsed = this.parseValueFunction(JSON.parse(attrValue)); + } catch (e){/**/} + value = !!parsed; + } + } + } + this.setValue(value); + if (detectChanges) { + this.ctx.detectChanges(); + } + } + + private onDataUpdateError(subscription: IWidgetSubscription, e: any) { + const exceptionData = this.utils.parseException(e); + let errorText = exceptionData.name; + if (exceptionData.message) { + errorText += ': ' + exceptionData.message; + } + this.onError(errorText); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/rpc-widgets.module.ts b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/rpc-widgets.module.ts new file mode 100644 index 0000000000..3d50ef8b81 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/rpc-widgets.module.ts @@ -0,0 +1,44 @@ +/// +/// Copyright © 2016-2019 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 { CommonModule } from '@angular/common'; +import { SharedModule } from '@app/shared/shared.module'; +import { LedIndicatorComponent } from '@home/components/widget/lib/rpc/led-indicator.component'; +import { RoundSwitchComponent } from '@home/components/widget/lib/rpc/round-switch.component'; +import { SwitchComponent } from '@home/components/widget/lib/rpc/switch.component'; +import { KnobComponent } from '@home/components/widget/lib/rpc/knob.component'; + +@NgModule({ + declarations: + [ + LedIndicatorComponent, + RoundSwitchComponent, + SwitchComponent, + KnobComponent + ], + imports: [ + CommonModule, + SharedModule + ], + exports: [ + LedIndicatorComponent, + RoundSwitchComponent, + SwitchComponent, + KnobComponent + ] +}) +export class RpcWidgetsModule { } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/svg/knob.svg b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/svg/knob.svg new file mode 100644 index 0000000000..81fe9949b7 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/svg/knob.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/svg/thumb-bar-checked.svg b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/svg/thumb-bar-checked.svg new file mode 100644 index 0000000000..57fa2b423e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/svg/thumb-bar-checked.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/svg/thumb-bar.svg b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/svg/thumb-bar.svg new file mode 100644 index 0000000000..0b952933a4 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/svg/thumb-bar.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/svg/thumb-checked.svg b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/svg/thumb-checked.svg new file mode 100644 index 0000000000..6d59b94736 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/svg/thumb-checked.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/svg/thumb.svg b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/svg/thumb.svg new file mode 100644 index 0000000000..03e9ea0f77 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/svg/thumb.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/switch.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/switch.component.html new file mode 100644 index 0000000000..29747503d9 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/switch.component.html @@ -0,0 +1,39 @@ + +
+
+ {{ error }} +
+
+ {{title}} +
+
+
+ + +
+
+ OFF + + ON + +
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/switch.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/switch.component.scss new file mode 100644 index 0000000000..72edb278e8 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/switch.component.scss @@ -0,0 +1,147 @@ +/** + * Copyright © 2016-2019 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. + */ + + +$thumb-img: url("./svg/thumb.svg") !default; +$thumb-checked-img: url("./svg/thumb-checked.svg") !default; +$thumb-bar-img: url("./svg/thumb-bar.svg") !default; +$thumb-bar-checked-img: url("./svg/thumb-bar-checked.svg") !default; + +$background-color: #e6e7e8 !default; + +$error-height: 14px !default; + +:host { + + .tb-switch { + width: 100%; + height: 100%; + background: $background-color; + + .error-container { + position: absolute; + top: 1%; + right: 0; + left: 0; + z-index: 4; + height: $error-height; + + .switch-error { + color: #ff3315; + white-space: nowrap; + } + } + + .onoff-container { + height: 100%; + font-weight: 500; + color: #757575; + white-space: nowrap; + + .off-label { + color: #b7b5b5; + } + + .on-label { + color: #ff7e57; + text-shadow: #ff6e4a 1px 1px 10px, #ffd1c3 1px 1px 10px; + } + } + + .title-container { + .switch-title { + font-weight: 500; + color: #757575; + white-space: nowrap; + } + } + + #switch-container { + padding-right: 10px; + padding-left: 10px; + } + + .switch { + position: relative; + } + + #text-measure { + position: absolute; + width: auto; + height: auto; + white-space: nowrap; + visibility: hidden; + } + } +} + +:host ::ng-deep { + .tb-switch { + .switch { + mat-slide-toggle { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + margin: 0; + + .mat-slide-toggle-bar { + top: 0; + left: 0; + width: 100%; + height: 100%; + background: $thumb-bar-img no-repeat; + background-size: contain; + border-radius: 0; + } + + .mat-slide-toggle-thumb-container { + top: 5%; + left: .25%; + width: 50%; + height: 90%; + } + + .mat-slide-toggle-thumb { + top: 0; + left: 0; + width: 100%; + height: 100%; + background: $thumb-img no-repeat; + background-size: contain; + border-radius: 0; + box-shadow: none; + } + + &.mat-checked { + .mat-slide-toggle-thumb-container { + transform: translate3d(100%,0,0); + } + .mat-slide-toggle-bar { + background: $thumb-bar-checked-img no-repeat; + background-size: contain; + } + + .mat-slide-toggle-thumb { + background: $thumb-checked-img no-repeat; + background-size: contain; + } + } + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/switch.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/switch.component.ts new file mode 100644 index 0000000000..b91e32bf4d --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/switch.component.ts @@ -0,0 +1,354 @@ +/// +/// Copyright © 2016-2019 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, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { WidgetContext } from '@home/models/widget-component.models'; +import { UtilsService } from '@core/services/utils.service'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { isDefined } from '@core/utils'; +import { IWidgetSubscription, SubscriptionInfo, WidgetSubscriptionOptions } from '@core/api/widget-api.models'; +import { DatasourceType, widgetType } from '@shared/models/widget.models'; +import { EntityType } from '@shared/models/entity-type.models'; +import { MatSlideToggle } from '@angular/material/slide-toggle'; + +const switchAspectRation = 2.7893; + +type RetrieveValueMethod = 'rpc' | 'attribute' | 'timeseries'; + +interface SwitchSettings { + initialValue: boolean; + title: string; + showOnOffLabels: boolean; + retrieveValueMethod: RetrieveValueMethod; + valueKey: string; + getValueMethod: string; + setValueMethod: string; + parseValueFunction: string; + convertValueFunction: string; + requestTimeout: number; +} + +@Component({ + selector: 'tb-switch', + templateUrl: './switch.component.html', + styleUrls: ['./switch.component.scss'] +}) +export class SwitchComponent extends PageComponent implements OnInit, OnDestroy { + + @ViewChild('switch', {static: true}) switchElementRef: ElementRef; + @ViewChild('switchContainer', {static: true}) switchContainerRef: ElementRef; + @ViewChild('matSlideToggle', {static: true}) matSlideToggleRef: MatSlideToggle; + @ViewChild('onoffContainer', {static: true}) onoffContainerRef: ElementRef; + @ViewChild('onLabel', {static: true}) onLabelRef: ElementRef; + @ViewChild('offLabel', {static: true}) offLabelRef: ElementRef; + @ViewChild('switchTitleContainer', {static: true}) switchTitleContainerRef: ElementRef; + @ViewChild('switchTitle', {static: true}) switchTitleRef: ElementRef; + @ViewChild('textMeasure', {static: true}) textMeasureRef: ElementRef; + @ViewChild('switchErrorContainer', {static: true}) switchErrorContainerRef: ElementRef; + @ViewChild('switchError', {static: true}) switchErrorRef: ElementRef; + + @Input() + ctx: WidgetContext; + + showTitle = false; + value = false; + error = ''; + title = ''; + showOnOffLabels = false; + + private isSimulated: boolean; + private requestTimeout: number; + private retrieveValueMethod: RetrieveValueMethod; + private valueKey: string; + private parseValueFunction: (data: any) => boolean; + private convertValueFunction: (value: any) => any; + private getValueMethod: string; + private setValueMethod: string; + + private valueSubscription: IWidgetSubscription; + + private executingUpdateValue: boolean; + private scheduledValue: boolean; + private rpcValue: boolean; + + private switchElement: JQuery; + private switchContainer: JQuery; + private matSlideToggle: JQuery; + private onoffContainer: JQuery; + private onLabel: JQuery; + private offLabel: JQuery; + private switchTitleContainer: JQuery; + private switchTitle: JQuery; + private textMeasure: JQuery; + private switchErrorContainer: JQuery; + private switchError: JQuery; + + private switchResizeListener: any; + + constructor(private utils: UtilsService, + protected store: Store) { + super(store); + } + + ngOnInit(): void { + this.switchElement = $(this.switchElementRef.nativeElement); + this.switchContainer = $(this.switchContainerRef.nativeElement); + this.matSlideToggle = $(this.matSlideToggleRef._elementRef.nativeElement); + this.onoffContainer = $(this.onoffContainerRef.nativeElement); + this.onLabel = $(this.onLabelRef.nativeElement); + this.offLabel = $(this.offLabelRef.nativeElement); + this.switchTitleContainer = $(this.switchTitleContainerRef.nativeElement); + this.switchTitle = $(this.switchTitleRef.nativeElement); + this.textMeasure = $(this.textMeasureRef.nativeElement); + this.switchErrorContainer = $(this.switchErrorContainerRef.nativeElement); + this.switchError = $(this.switchErrorRef.nativeElement); + + this.switchResizeListener = this.resize.bind(this); + // @ts-ignore + addResizeListener(this.switchContainerRef.nativeElement, this.switchResizeListener); + this.init(); + } + + ngOnDestroy(): void { + if (this.valueSubscription) { + this.ctx.subscriptionApi.removeSubscription(this.valueSubscription.id); + } + if (this.switchResizeListener) { + // @ts-ignore + removeResizeListener(this.switchContainerRef.nativeElement, this.switchResizeListener); + } + } + + private init() { + const settings: SwitchSettings = this.ctx.settings; + this.title = isDefined(settings.title) ? settings.title : ''; + this.showTitle = !!(this.title && this.title.length); + this.showOnOffLabels = isDefined(settings.showOnOffLabels) ? settings.showOnOffLabels : true; + const initialValue = isDefined(settings.initialValue) ? settings.initialValue : false; + this.setValue(initialValue); + + const subscription = this.ctx.defaultSubscription; + const rpcEnabled = subscription.rpcEnabled; + + this.isSimulated = this.utils.widgetEditMode; + + this.requestTimeout = 500; + if (settings.requestTimeout) { + this.requestTimeout = settings.requestTimeout; + } + this.retrieveValueMethod = 'rpc'; + if (settings.retrieveValueMethod && settings.retrieveValueMethod.length) { + this.retrieveValueMethod = settings.retrieveValueMethod; + } + this.valueKey = 'value'; + if (settings.valueKey && settings.valueKey.length) { + this.valueKey = settings.valueKey; + } + this.parseValueFunction = (data) => !!data; + if (settings.parseValueFunction && settings.parseValueFunction.length) { + try { + this.parseValueFunction = new Function('data', settings.parseValueFunction) as (data: any) => boolean; + } catch (e) { + this.parseValueFunction = (data) => !!data; + } + } + this.convertValueFunction = (value) => value; + if (settings.convertValueFunction && settings.convertValueFunction.length) { + try { + this.convertValueFunction = new Function('value', settings.convertValueFunction) as (value: any) => any; + } catch (e) { + this.convertValueFunction = (value) => value; + } + } + this.getValueMethod = 'getValue'; + if (settings.getValueMethod && settings.getValueMethod.length) { + this.getValueMethod = settings.getValueMethod; + } + this.setValueMethod = 'setValue'; + if (settings.setValueMethod && settings.setValueMethod.length) { + this.setValueMethod = settings.setValueMethod; + } + if (!rpcEnabled) { + this.onError('Target device is not set!'); + } else { + if (!this.isSimulated) { + if (this.retrieveValueMethod === 'rpc') { + this.rpcRequestValue(); + } else if (this.retrieveValueMethod === 'attribute' || this.retrieveValueMethod === 'timeseries') { + this.subscribeForValue(); + } + } + } + + } + + private resize() { + let width = this.switchContainer.width(); + let height = this.switchContainer.height(); + if (this.showOnOffLabels) { + height = height*2/3; + } + const ratio = width/height; + if (ratio > switchAspectRation) { + width = height*switchAspectRation; + } else { + height = width/switchAspectRation; + } + this.switchElement.css({width, height}); + this.matSlideToggle.css({width, height, minWidth: width}); + + if (this.showTitle) { + this.setFontSize(this.switchTitle, this.title, this.switchTitleContainer.height() * 2 / 3, this.switchTitleContainer.width()); + } + + if (this.showOnOffLabels) { + this.onoffContainer.css({width, height: this.switchContainer.height() / 3}); + this.setFontSize(this.onLabel, 'OFF', this.onoffContainer.height(), this.onoffContainer.width() / 2); + this.setFontSize(this.offLabel, 'OFF', this.onoffContainer.height(), this.onoffContainer.width() / 2); + } + + this.setFontSize(this.switchError, this.error, this.switchErrorContainer.height(), this.switchErrorContainer.width()); + } + + private setFontSize(element: JQuery, text: string, fontSize: number, maxWidth: number) { + let textWidth = this.measureTextWidth(text, fontSize); + while (textWidth > maxWidth) { + fontSize--; + textWidth = this.measureTextWidth(text, fontSize); + } + element.css({fontSize: fontSize+'px', lineHeight: fontSize+'px'}); + } + + private measureTextWidth(text: string, fontSize: number): number { + this.textMeasure.css({fontSize: fontSize+'px', lineHeight: fontSize+'px'}); + this.textMeasure.text(text); + return this.textMeasure.width(); + } + + private onError(error: string) { + this.error = error; + this.setFontSize(this.switchError, this.error, this.switchErrorContainer.height(), this.switchErrorContainer.width()); + this.ctx.detectChanges(); + } + + private setValue(value: boolean) { + this.value = value ? true : false; + } + + public onValue() { + this.rpcUpdateValue(this.value); + } + + private rpcRequestValue() { + this.error = ''; + this.ctx.controlApi.sendTwoWayCommand(this.getValueMethod, null, this.requestTimeout).subscribe( + (responseBody) => { + this.setValue(this.parseValueFunction(responseBody)); + }, + () => { + const errorText = this.ctx.defaultSubscription.rpcErrorText; + this.onError(errorText); + } + ); + } + + private rpcUpdateValue(value) { + if (this.executingUpdateValue) { + this.scheduledValue = value; + return; + } else { + this.scheduledValue = null; + this.rpcValue = value; + this.executingUpdateValue = true; + } + this.error = ''; + this.ctx.controlApi.sendOneWayCommand(this.setValueMethod, this.convertValueFunction(value), this.requestTimeout).subscribe( + () => { + this.executingUpdateValue = false; + if (this.scheduledValue != null && this.scheduledValue !== this.rpcValue) { + this.rpcUpdateValue(this.scheduledValue); + } + }, + () => { + this.executingUpdateValue = false; + const errorText = this.ctx.defaultSubscription.rpcErrorText; + this.onError(errorText); + } + ); + } + + private subscribeForValue() { + const valueSubscriptionInfo: SubscriptionInfo[] = [{ + type: DatasourceType.entity, + entityType: EntityType.DEVICE, + entityId: this.ctx.defaultSubscription.targetDeviceId + }]; + if (this.retrieveValueMethod === 'attribute') { + valueSubscriptionInfo[0].attributes = [ + {name: this.valueKey} + ]; + } else { + valueSubscriptionInfo[0].timeseries = [ + {name: this.valueKey} + ]; + } + const subscriptionOptions: WidgetSubscriptionOptions = { + callbacks: { + onDataUpdated: this.onDataUpdated.bind(this), + onDataUpdateError: this.onDataUpdateError.bind(this) + } + }; + this.ctx.subscriptionApi.createSubscriptionFromInfo ( + widgetType.latest, valueSubscriptionInfo, subscriptionOptions, false, true).subscribe( + (subscription) => { + this.valueSubscription = subscription; + } + ); + } + + private onDataUpdated(subscription: IWidgetSubscription, detectChanges: boolean) { + let value = false; + const data = subscription.data; + if (data.length) { + const keyData = data[0]; + if (keyData && keyData.data && keyData.data[0]) { + const attrValue = keyData.data[0][1]; + if (attrValue) { + let parsed = null; + try { + parsed = this.parseValueFunction(JSON.parse(attrValue)); + } catch (e){/**/} + value = !!parsed; + } + } + } + this.setValue(value); + if (detectChanges) { + this.ctx.detectChanges(); + } + } + + private onDataUpdateError(subscription: IWidgetSubscription, e: any) { + const exceptionData = this.utils.parseException(e); + let errorText = exceptionData.name; + if (exceptionData.message) { + errorText += ': ' + exceptionData.message; + } + this.onError(errorText); + } +} 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 3817481d85..248c2b48b5 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 @@ -25,6 +25,7 @@ import { SharedHomeComponentsModule } from '@home/components/shared-home-compone import { TimeseriesTableWidgetComponent } from '@home/components/widget/lib/timeseries-table-widget.component'; import { EntitiesHierarchyWidgetComponent } from '@home/components/widget/lib/entities-hierarchy-widget.component'; import { CustomDialogService } from '@home/components/widget/dialog/custom-dialog.service'; +import { RpcWidgetsModule } from '@home/components/widget/lib/rpc/rpc-widgets.module'; @NgModule({ declarations: @@ -39,13 +40,15 @@ import { CustomDialogService } from '@home/components/widget/dialog/custom-dialo imports: [ CommonModule, SharedModule, + RpcWidgetsModule, SharedHomeComponentsModule ], exports: [ EntitiesTableWidgetComponent, AlarmsTableWidgetComponent, TimeseriesTableWidgetComponent, - EntitiesHierarchyWidgetComponent + EntitiesHierarchyWidgetComponent, + RpcWidgetsModule ], providers: [ CustomDialogService From c36bfed38af39d23fdb53075d29f218fb2f409ad Mon Sep 17 00:00:00 2001 From: Vladyslav Date: Mon, 17 Feb 2020 17:39:32 +0200 Subject: [PATCH 116/133] Improved input widget (#2411) --- .../system/widget_bundles/input_widgets.json | 80 +++++++++---------- 1 file changed, 40 insertions(+), 40 deletions(-) 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 99824d1655..8e58b8bb03 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 @@ -11,11 +11,11 @@ "descriptor": { "type": "latest", "sizeX": 7.5, - "sizeY": 3.5, + "sizeY": 3, "resources": [], - "templateHtml": "
\n
\n
\n
\n \n \n \n
\n
{{requiredErrorMessage}}
\n
\n
\n
\n\n
\n \n check\n Update server attribute\n \n \n close\n Discard changes\n \n
\n
\n \n
\n No entity selected\n
\n
\n No attribute is selected\n
\n
\n Timeseries parameter cannot be used in this widget\n
\n
\n
", - "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.entity-title {\n font-weight: bold;\n font-size: 22px;\n padding-top: 12px;\n padding-bottom: 6px;\n color: #666;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .md-icon-button md-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.show-label label {\n display: block;\n}\n\nlabel {\n display: none;\n}\n\nmd-toast{\n min-width: 0;\n}\nmd-toast .md-toast-content {\n font-size: 14px!important;\n}", - "controllerScript": "let $scope;\r\nlet settings;\r\nlet attributeService;\r\nlet toast;\r\nlet utils;\r\nlet types;\r\n\r\nself.onInit = function() {\r\n\r\n $scope = self.ctx.$scope;\r\n attributeService = $scope.$injector.get('attributeService');\r\n toast = $scope.$injector.get('toast');\r\n utils = $scope.$injector.get('utils');\r\n types = $scope.$injector.get('types');\r\n settings = self.ctx.settings || {};\r\n $scope.settings = settings;\r\n $scope.isValidParameter = true;\r\n $scope.dataKeyDetected = false;\r\n $scope.requiredErrorMessage = settings.requiredErrorMessage || \"Entity attribute is required\";\r\n $scope.labelValue = settings.labelValue || \"Value\";\r\n\r\n if (self.ctx.datasources && self.ctx.datasources.length) {\r\n var datasource = self.ctx.datasources[0];\r\n if (datasource.type === 'entity') {\r\n if (datasource.entityType && datasource.entityId) {\r\n $scope.entityName = datasource.entityName;\r\n if (settings.widgetTitle && settings.widgetTitle.length) {\r\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\r\n } else {\r\n $scope.titleTemplate = self.ctx.widgetConfig.title;\r\n }\r\n\r\n $scope.entityDetected = true;\r\n }\r\n }\r\n if (datasource.dataKeys.length) {\r\n if (datasource.dataKeys[0].type != \"attribute\") {\r\n $scope.isValidParameter = false;\r\n } else {\r\n $scope.currentKey = datasource.dataKeys[0].name;\r\n $scope.dataKeyType = datasource.dataKeys[0].type;\r\n $scope.dataKeyDetected = true;\r\n }\r\n }\r\n }\r\n\r\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\r\n\r\n $scope.updateAttribute = function () {\r\n if ($scope.entityDetected) {\r\n var datasource = self.ctx.datasources[0];\r\n\r\n attributeService.saveEntityAttributes(\r\n datasource.entityType,\r\n datasource.entityId,\r\n types.attributesScope.server.value,\r\n [\r\n {\r\n key: $scope.currentKey,\r\n value: $scope.currentValue\r\n }\r\n ]\r\n ).then(\r\n function success() {\r\n $scope.originalValue = $scope.currentValue;\r\n if (settings.showResultMessage) {\r\n toast.showSuccess('Update successful', 1000, angular.element(self.ctx.$container), 'bottom left');\r\n }\r\n },\r\n function fail() {\r\n if (settings.showResultMessage) {\r\n toast.showError('Update failed', angular.element(self.ctx.$container), 'bottom left');\r\n }\r\n }\r\n );\r\n }\r\n };\r\n\r\n $scope.changeFocus = function () {\r\n if ($scope.currentValue === $scope.originalValue) {\r\n $scope.isFocused = false;\r\n }\r\n }\r\n}\r\n\r\nself.onDataUpdated = function() {\r\n\r\n try {\r\n if ($scope.dataKeyDetected) {\r\n if (!$scope.isFocused) {\r\n $scope.currentValue = $scope.originalValue = self.ctx.data[0].data[0][1];\r\n $scope.$digest();\r\n }\r\n }\r\n } catch (e) {\r\n console.log(e);\r\n }\r\n}\r\n\r\nself.onResize = function() {\r\n\r\n}\r\n\r\nself.typeParameters = function() {\r\n return {\r\n maxDatasources: 1,\r\n maxDataKeys: 1\r\n }\r\n}\r\n\r\nself.onDestroy = function() {\r\n\r\n}\r\n", + "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? labelValue : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n \n
\n \n \n
\n
\n \n
\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n
\n
\n
\n
", + "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}", + "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet utils;\nlet translate;\nlet http;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n http = $scope.$injector.get(self.ctx.servicesMap.get('http'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false; \n\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-timeseries-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || translate.instant('widgets.input-widgets.value');\n\n $scope.attributeUpdateFormGroup = $scope.fb.group(\n {currentValue: [undefined, [$scope.validators.required,\n $scope.validators.minLength(settings.minLength),\n $scope.validators.maxLength(settings.maxLength)]]}\n );\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"attribute\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n $scope.isFocused = false;\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n attributeService.saveEntityAttributes(\n datasource.entity.id,\n 'SERVER_SCOPE',\n [\n {\n key: $scope.currentKey,\n value: $scope.attributeUpdateFormGroup.get('currentValue').value\n }\n ]\n ).subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.attributeUpdateFormGroup.get('currentValue').value === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n}\n\nself.onDataUpdated = function() {\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue($scope.originalValue);\n self.ctx.detectChanges();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n }\n}\n\nself.onDestroy = function() {\n\n}", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showLabel\":{\n \"title\":\"Show label\",\n \"type\":\"boolean\",\n \"default\":true\n },\n \"labelValue\": {\n \"title\": \"Label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"maxLength\": {\n \"title\": \"Max length\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"minLength\": {\n \"title\": \"Min length\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\":true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showResultMessage\",\n \"showLabel\",\n \"labelValue\",\n \"requiredErrorMessage\",\n \"maxLength\",\n \"minLength\"\n ]\n}", "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.23592248334107624,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update server string attribute\",\"dropShadow\":true,\"enableFullscreen\":false,\"enableDataExport\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" @@ -29,9 +29,9 @@ "sizeX": 7.5, "sizeY": 3, "resources": [], - "templateHtml": "
\n
\n\n
\n
\n \n \n \n
\n
{{requiredErrorMessage}}
\n
\n
\n
\n\n
\n \n check\n Update server attribute\n \n \n close\n Discard changes\n \n
\n
\n\n
\n No entity selected\n
\n
\n No attribute is selected\n
\n
\n Timeseries parameter cannot be used in this widget\n
\n
\n
", - "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.entity-title {\n font-weight: bold;\n font-size: 22px;\n padding-top: 12px;\n padding-bottom: 6px;\n color: #666;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .md-icon-button md-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.show-label label {\n display: block;\n}\n\nlabel {\n display: none;\n}\n\nmd-toast{\n min-width: 0;\n}\nmd-toast .md-toast-content {\n font-size: 14px!important;\n}", - "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet toast;\nlet utils;\nlet types;\n\nself.onInit = function() {\n\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get('attributeService');\n toast = $scope.$injector.get('toast');\n utils = $scope.$injector.get('utils');\n types = $scope.$injector.get('types');\n settings = angular.copy(self.ctx.settings) || {};\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n $scope.requiredErrorMessage = settings.requiredErrorMessage || \"Entity attribute is required\";\n $scope.labelValue = settings.labelValue || \"Value\";\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type != \"attribute\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n attributeService.saveEntityAttributes(\n datasource.entityType,\n datasource.entityId,\n types.attributesScope.server.value,\n [\n {\n key: $scope.currentKey,\n value: $scope.currentValue\n }\n ]\n ).then(\n function success() {\n $scope.originalValue = $scope.currentValue;\n if (settings.showResultMessage) {\n toast.showSuccess('Update successful', 1000, angular.element(self.ctx.$container), 'bottom left');\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n toast.showError('Update failed', angular.element(self.ctx.$container), 'bottom left');\n }\n }\n );\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.currentValue === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n}\n\nself.onDataUpdated = function() {\n\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.currentValue = $scope.originalValue = self.ctx.data[0].data[0][1];\n correctValue($scope.currentValue);\n $scope.$digest();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nfunction correctValue(value) {\n if (typeof value !== \"number\") {\n $scope.currentValue = 0;\n }\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n }\n}\n\nself.onDestroy = function() {\n\n}\n", + "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? labelValue : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n \n
\n \n \n
\n
\n \n
\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n
\n
\n
\n
", + "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}", + "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet utils;\nlet translate;\nlet http;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n http = $scope.$injector.get(self.ctx.servicesMap.get('http'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false; \n\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-timeseries-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || translate.instant('widgets.input-widgets.value');\n\n $scope.attributeUpdateFormGroup = $scope.fb.group(\n {currentValue: [undefined, [$scope.validators.required,\n $scope.validators.min(settings.minValue),\n $scope.validators.max(settings.maxValue),\n $scope.validators.pattern(/^-?[0-9]+$/)]]}\n );\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n \n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"attribute\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n $scope.isFocused = false;\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n attributeService.saveEntityAttributes(\n datasource.entity.id,\n 'SERVER_SCOPE',\n [\n {\n key: $scope.currentKey,\n value: $scope.attributeUpdateFormGroup.get('currentValue').value\n }\n ]\n ).subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.attributeUpdateFormGroup.get('currentValue').value === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n}\n\nself.onDataUpdated = function() {\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue(correctValue($scope.originalValue));\n self.ctx.detectChanges();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nfunction correctValue(value) {\n if (typeof value !== \"number\") {\n return 0;\n }\n return value;\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n }\n}\n\nself.onDestroy = function() {\n\n}", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showLabel\":{\n \"title\":\"Show label\",\n \"type\":\"boolean\",\n \"default\":true\n },\n \"labelValue\": {\n \"title\": \"Label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"maxValue\": {\n \"title\": \"Max value\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"minValue\": {\n \"title\": \"Min value\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\":true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showResultMessage\",\n \"showLabel\",\n \"labelValue\",\n \"requiredErrorMessage\",\n \"maxValue\",\n \"minValue\"\n ]\n}", "dataKeySettingsSchema": "{}\n", "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\":{},\"title\":\"Update server integer attribute\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" @@ -45,9 +45,9 @@ "sizeX": 7.5, "sizeY": 3, "resources": [], - "templateHtml": "
\n
\n\n
\n
\n \n \n \n
\n
{{requiredErrorMessage}}
\n
\n
\n
\n\n
\n \n check\n Update server attribute\n \n \n close\n Discard changes\n \n
\n
\n\n
\n No entity selected\n
\n
\n No attribute is selected\n
\n
\n Timeseries parameter cannot be used in this widget\n
\n
\n
", - "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.entity-title {\n font-weight: bold;\n font-size: 22px;\n padding-top: 12px;\n padding-bottom: 6px;\n color: #666;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .md-icon-button md-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.show-label label {\n display: block;\n}\n\nlabel {\n display: none;\n}\n\nmd-toast{\n min-width: 0;\n}\nmd-toast .md-toast-content {\n font-size: 14px!important;\n}", - "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet toast;\nlet utils;\nlet types;\n\nself.onInit = function() {\n\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get('attributeService');\n toast = $scope.$injector.get('toast');\n utils = $scope.$injector.get('utils');\n types = $scope.$injector.get('types');\n settings = angular.copy(self.ctx.settings) || {};\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n $scope.requiredErrorMessage = settings.requiredErrorMessage || \"Entity attribute is required\";\n $scope.labelValue = settings.labelValue || \"Value\";\n \n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type != \"attribute\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n attributeService.saveEntityAttributes(\n datasource.entityType,\n datasource.entityId,\n types.attributesScope.server.value,\n [\n {\n key: $scope.currentKey,\n value: $scope.currentValue\n }\n ]\n ).then(\n function success() {\n $scope.originalValue = $scope.currentValue;\n if (settings.showResultMessage) {\n toast.showSuccess('Update successful', 1000, angular.element(self.ctx.$container), 'bottom left');\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n toast.showError('Update failed', angular.element(self.ctx.$container), 'bottom left');\n }\n }\n );\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.currentValue === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n}\n\nself.onDataUpdated = function() {\n\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.currentValue = $scope.originalValue = self.ctx.data[0].data[0][1];\n correctValue($scope.currentValue);\n $scope.$digest();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nfunction correctValue(value) {\n if (typeof value !== \"number\") {\n $scope.currentValue = 0;\n }\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n }\n}\n\nself.onDestroy = function() {\n\n}\n", + "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? labelValue : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n \n
\n \n \n
\n
\n \n
\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n
\n
\n
\n
", + "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}", + "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet utils;\nlet translate;\nlet http;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n http = $scope.$injector.get(self.ctx.servicesMap.get('http'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false; \n\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-timeseries-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || translate.instant('widgets.input-widgets.value');\n\n $scope.attributeUpdateFormGroup = $scope.fb.group(\n {currentValue: [undefined, [$scope.validators.required,\n $scope.validators.min(settings.minValue),\n $scope.validators.max(settings.maxValue)]]}\n );\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"attribute\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n $scope.isFocused = false;\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n attributeService.saveEntityAttributes(\n datasource.entity.id,\n 'SERVER_SCOPE',\n [\n {\n key: $scope.currentKey,\n value: $scope.attributeUpdateFormGroup.get('currentValue').value\n }\n ]\n ).subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.attributeUpdateFormGroup.get('currentValue').value === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n}\n\nself.onDataUpdated = function() {\n\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue(correctValue($scope.originalValue));\n self.ctx.detectChanges();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nfunction correctValue(value) {\n if (typeof value !== \"number\") {\n return 0;\n }\n return value;\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n }\n}\n\nself.onDestroy = function() {\n\n}", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showLabel\":{\n \"title\":\"Show label\",\n \"type\":\"boolean\",\n \"default\":true\n },\n \"labelValue\": {\n \"title\": \"Label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"maxValue\": {\n \"title\": \"Max value\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"minValue\": {\n \"title\": \"Min value\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\":true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showResultMessage\",\n \"showLabel\",\n \"labelValue\",\n \"requiredErrorMessage\",\n \"maxValue\",\n \"minValue\"\n ]\n}", "dataKeySettingsSchema": "{}\n", "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\":{},\"title\":\"Update server double attribute\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" @@ -61,9 +61,9 @@ "sizeX": 7.5, "sizeY": 3, "resources": [], - "templateHtml": "
\n
\n
\n
\n \n {{currentValue}}\n \n
\n
\n\n
\n
\n No attribute is selected\n
\n
\n Timeseries parameter cannot be used in this widget\n
\n
\n
", - "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.entity-title {\n font-weight: bold;\n font-size: 22px;\n padding-top: 12px;\n padding-bottom: 6px;\n color: #666;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .md-icon-button md-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n\nmd-toast{\n min-width: 0;\n}\nmd-toast .md-toast-content {\n font-size: 14px!important;\n}", - "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet toast;\nlet utils;\nlet types;\nlet map;\nlet mapReverse;\n\nself.onInit = function() {\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get('attributeService');\n toast = $scope.$injector.get('toast');\n utils = $scope.$injector.get('utils');\n types = $scope.$injector.get('types');\n settings = angular.copy(self.ctx.settings) || {};\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n $scope.message = 'No entity selected';\n\n settings.trueValue = settings.trueValue || true;\n settings.falseValue = settings.falseValue || false;\n\n map = {\"true\":settings.trueValue, \"false\": settings.falseValue};\n mapReverse = {[settings.trueValue]:true, [settings.falseValue]:false};\n $scope.checkboxValue = \"false\";\n $scope.currentValue = map[$scope.checkboxValue];\n\n $scope.changed = function () {\n $scope.currentValue = map[$scope.checkboxValue];\n $scope.updateAttribute();\n }\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type != \"attribute\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n attributeService.saveEntityAttributes(\n datasource.entityType,\n datasource.entityId,\n types.attributesScope.server.value,\n [\n {\n key: $scope.currentKey,\n value: mapReverse[$scope.currentValue] || false\n }\n ]\n ).then(\n function success() {\n $scope.originalValue = $scope.currentValue;\n if (settings.showResultMessage) {\n toast.showSuccess('Update successful', 1000, angular.element(self.ctx.$container), 'bottom left');\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n toast.showError('Update failed', angular.element(self.ctx.$container), 'bottom left');\n }\n }\n );\n }\n };\n}\n\nself.onDataUpdated = function() {\n try {\n if ($scope.dataKeyDetected) {\n $scope.checkboxValue = ($scope.originalValue = self.ctx.data[0].data[0][1]) || false;\n $scope.currentValue = map[$scope.checkboxValue];\n $scope.$digest();\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n }\n}\n\nself.onResize = function() {}\nself.onDestroy = function() {}\n", + "templateHtml": "
\r\n
\r\n
\r\n
\r\n
\r\n \r\n {{currentValue}}\r\n \r\n
\r\n
\r\n\r\n
\r\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\r\n
\r\n
\r\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\r\n
\r\n
\r\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\r\n
\r\n
\r\n
\r\n
", + "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}", + "controllerScript": "let settings;\nlet attributeService;\nlet utils;\nlet translate;\nlet http;\nlet $scope;\nlet map;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init();\n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n http = $scope.$injector.get(self.ctx.servicesMap.get('http'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n\n settings.trueValue = utils.defaultValue(utils.customTranslation(settings.trueValue, settings.trueValue), true);\n settings.falseValue = utils.defaultValue(utils.customTranslation(settings.falseValue, settings.falseValue), false);\n\n map = {\n true: settings.trueValue,\n false: settings.falseValue\n };\n \n $scope.checkboxValue = false;\n $scope.currentValue = map[$scope.checkboxValue];\n\n $scope.attributeUpdateFormGroup = $scope.fb.group({checkboxValue: [$scope.checkboxValue]});\n\n $scope.changed = function() {\n $scope.checkboxValue = $scope.attributeUpdateFormGroup.get('checkboxValue').value;\n $scope.currentValue = map[$scope.checkboxValue];\n $scope.updateAttribute();\n };\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"attribute\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function() {\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n attributeService.saveEntityAttributes(\n datasource.entity.id,\n 'SERVER_SCOPE',\n [\n {\n key: $scope.currentKey,\n value: $scope.checkboxValue\n }\n ]\n ).subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('checkboxValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n };\n}\n\nself.onDataUpdated = function() {\n try {\n $scope.checkboxValue = self.ctx.data[0].data[0][1] === 'true';\n $scope.currentValue = map[$scope.checkboxValue];\n $scope.attributeUpdateFormGroup.get('checkboxValue').patchValue($scope.checkboxValue);\n self.ctx.detectChanges();\n } catch (e) {\n console.log(e);\n }\n}\n\nself.onResize = function() {}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n }\n}\n\nself.onDestroy = function() {}", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"trueValue\": {\n \"title\": \"True value\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"falseValue\": {\n \"title\": \"False value\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\":true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showResultMessage\",\n \"trueValue\",\n \"falseValue\"\n ]\n}", "dataKeySettingsSchema": "{}\n", "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\":{},\"title\":\"Update server boolean attribute\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" @@ -75,11 +75,11 @@ "descriptor": { "type": "latest", "sizeX": 7.5, - "sizeY": 3.5, + "sizeY": 3, "resources": [], - "templateHtml": "
\n
\n\n
\n
\n \n \n \n
\n
{{requiredErrorMessage}}
\n
\n
\n
\n\n
\n \n check\n Update shared attribute\n \n \n close\n Discard changes\n \n
\n
\n\n
\n
\n No attribute is selected\n
\n
\n Timeseries parameter cannot be used in this widget\n
\n
\n
", - "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.entity-title {\n font-weight: bold;\n font-size: 22px;\n padding-top: 12px;\n padding-bottom: 6px;\n color: #666;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .md-icon-button md-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.show-label label {\n display: block;\n}\n\nlabel {\n display: none;\n}\n\nmd-toast{\n min-width: 0;\n}\nmd-toast .md-toast-content {\n font-size: 14px!important;\n}", - "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet toast;\nlet utils;\nlet types;\n\nself.onInit = function() {\n\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get('attributeService');\n toast = $scope.$injector.get('toast');\n utils = $scope.$injector.get('utils');\n types = $scope.$injector.get('types');\n settings = angular.copy(self.ctx.settings) || {};\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n $scope.message = 'No entity selected';\n $scope.requiredErrorMessage = settings.requiredErrorMessage || \"Entity attribute is required\";\n $scope.labelValue = settings.labelValue || \"Value\";\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType === \"DEVICE\") {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n } else {\n $scope.message = 'Selected entity cannot have shared attributes';\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type != \"attribute\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n attributeService.saveEntityAttributes(\n datasource.entityType,\n datasource.entityId,\n types.attributesScope.shared.value,\n [\n {\n key: $scope.currentKey,\n value: $scope.currentValue\n }\n ]\n ).then(\n function success() {\n $scope.originalValue = $scope.currentValue;\n if (settings.showResultMessage) {\n toast.showSuccess('Update successful', 1000, angular.element(self.ctx.$container), 'bottom left');\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n toast.showError('Update failed', angular.element(self.ctx.$container), 'bottom left');\n }\n }\n );\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.currentValue === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n}\n\nself.onDataUpdated = function() {\n\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.currentValue = $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.$digest();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n }\n}\n\nself.onDestroy = function() {\n\n}\n", + "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? labelValue : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n \n
\n \n \n
\n
\n \n
\n
\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n
\n
\n
\n
", + "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}", + "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet utils;\nlet translate;\nlet http;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n http = $scope.$injector.get(self.ctx.servicesMap.get('http'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false; \n $scope.message = translate.instant('widgets.input-widgets.no-entity-selected');\n \n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-timeseries-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || translate.instant('widgets.input-widgets.value');\n\n $scope.attributeUpdateFormGroup = $scope.fb.group(\n {currentValue: [undefined, [$scope.validators.required,\n $scope.validators.minLength(settings.minLength),\n $scope.validators.maxLength(settings.maxLength)]]}\n );\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType === 'DEVICE') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n \n $scope.entityDetected = true;\n }\n } else {\n $scope.message = translate.instant('widgets.input-widgets.not-allowed-entity');\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"attribute\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n $scope.isFocused = false;\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n attributeService.saveEntityAttributes(\n datasource.entity.id,\n 'SHARED_SCOPE',\n [\n {\n key: $scope.currentKey,\n value: $scope.attributeUpdateFormGroup.get('currentValue').value\n }\n ]\n ).subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.attributeUpdateFormGroup.get('currentValue').value === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n}\n\nself.onDataUpdated = function() {\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue($scope.originalValue);\n self.ctx.detectChanges();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n }\n}\n\nself.onDestroy = function() {\n\n}", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showLabel\":{\n \"title\":\"Show label\",\n \"type\":\"boolean\",\n \"default\":true\n },\n \"labelValue\": {\n \"title\": \"Label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"maxLength\": {\n \"title\": \"Max length\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"minLength\": {\n \"title\": \"Min length\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\":true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showResultMessage\",\n \"showLabel\",\n \"labelValue\",\n \"requiredErrorMessage\",\n \"maxLength\",\n \"minLength\"\n ]\n}", "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.23592248334107624,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update shared string attribute\",\"dropShadow\":true,\"enableFullscreen\":false,\"enableDataExport\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" @@ -93,9 +93,9 @@ "sizeX": 7.5, "sizeY": 3, "resources": [], - "templateHtml": "
\n
\n\n
\n
\n \n \n \n
\n
{{requiredErrorMessage}}
\n
\n
\n
\n\n
\n \n check\n Update shared attribute\n \n \n close\n Discard changes\n \n
\n
\n\n
\n
\n
\n No attribute is selected\n
\n
\n Timeseries parameter cannot be used in this widget\n
\n
\n
", - "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.entity-title {\n font-weight: bold;\n font-size: 22px;\n padding-top: 12px;\n padding-bottom: 6px;\n color: #666;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .md-icon-button md-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.show-label label {\n display: block;\n}\n\nlabel {\n display: none;\n}\n\nmd-toast{\n min-width: 0;\n}\nmd-toast .md-toast-content {\n font-size: 14px!important;\n}", - "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet toast;\nlet utils;\nlet types;\n\nself.onInit = function() {\n\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get('attributeService');\n toast = $scope.$injector.get('toast');\n utils = $scope.$injector.get('utils');\n types = $scope.$injector.get('types');\n settings = angular.copy(self.ctx.settings) || {};\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n $scope.requiredErrorMessage = settings.requiredErrorMessage || \"Entity attribute is required\";\n $scope.labelValue = settings.labelValue || \"Value\";\n $scope.message = 'No entity selected';\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType === \"DEVICE\") {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n } else {\n $scope.message = 'Selected entity cannot have share attribute';\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type != \"attribute\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n attributeService.saveEntityAttributes(\n datasource.entityType,\n datasource.entityId,\n types.attributesScope.shared.value,\n [\n {\n key: $scope.currentKey,\n value: $scope.currentValue\n }\n ]\n ).then(\n function success() {\n $scope.originalValue = $scope.currentValue;\n if (settings.showResultMessage) {\n toast.showSuccess('Update successful', 1000, angular.element(self.ctx.$container), 'bottom left');\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n toast.showError('Update failed', angular.element(self.ctx.$container), 'bottom left');\n }\n }\n );\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.currentValue === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n}\n\nself.onDataUpdated = function() {\n\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.currentValue = $scope.originalValue = self.ctx.data[0].data[0][1];\n correctValue($scope.currentValue);\n $scope.$digest();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nfunction correctValue(value) {\n if (typeof value !== \"number\") {\n $scope.currentValue = 0;\n }\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n }\n}\n\nself.onDestroy = function() {\n\n}\n", + "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? labelValue : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n \n
\n \n \n
\n
\n \n
\n
\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n
\n
\n
\n
", + "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}", + "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet utils;\nlet translate;\nlet http;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n http = $scope.$injector.get(self.ctx.servicesMap.get('http'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false; \n $scope.message = translate.instant('widgets.input-widgets.no-entity-selected');\n \n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-timeseries-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || translate.instant('widgets.input-widgets.value');\n\n $scope.attributeUpdateFormGroup = $scope.fb.group(\n {currentValue: [undefined, [$scope.validators.required,\n $scope.validators.min(settings.minValue),\n $scope.validators.max(settings.maxValue),\n $scope.validators.pattern(/^-?[0-9]+$/)]]}\n );\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType === 'DEVICE') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n \n $scope.entityDetected = true;\n }\n } else {\n $scope.message = translate.instant('widgets.input-widgets.not-allowed-entity');\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"attribute\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n $scope.isFocused = false;\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n attributeService.saveEntityAttributes(\n datasource.entity.id,\n 'SHARED_SCOPE',\n [\n {\n key: $scope.currentKey,\n value: $scope.attributeUpdateFormGroup.get('currentValue').value\n }\n ]\n ).subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.attributeUpdateFormGroup.get('currentValue').value === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n}\n\nself.onDataUpdated = function() {\n\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue(correctValue($scope.originalValue));\n self.ctx.detectChanges();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nfunction correctValue(value) {\n if (typeof value !== \"number\") {\n return 0;\n }\n return value;\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n }\n}\n\nself.onDestroy = function() {\n\n}", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showLabel\":{\n \"title\":\"Show label\",\n \"type\":\"boolean\",\n \"default\":true\n },\n \"labelValue\": {\n \"title\": \"Label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"maxValue\": {\n \"title\": \"Max value\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"minValue\": {\n \"title\": \"Min value\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\":true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showResultMessage\",\n \"showLabel\",\n \"labelValue\",\n \"requiredErrorMessage\",\n \"maxValue\",\n \"minValue\"\n ]\n}", "dataKeySettingsSchema": "{}\n", "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\":{},\"title\":\"Update shared integer attribute\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" @@ -109,9 +109,9 @@ "sizeX": 7.5, "sizeY": 3, "resources": [], - "templateHtml": "
\n
\n\n
\n
\n \n \n \n
\n
{{requiredErrorMessage}}
\n
\n
\n
\n\n
\n \n check\n Update shared attribute\n \n \n close\n Discard changes\n \n
\n
\n\n
\n
\n No attribute is selected\n
\n
\n Timeseries parameter cannot be used in this widget\n
\n
\n
", - "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.entity-title {\n font-weight: bold;\n font-size: 22px;\n padding-top: 12px;\n padding-bottom: 6px;\n color: #666;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .md-icon-button md-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.show-label label {\n display: block;\n}\n\nlabel {\n display: none;\n}\n\nmd-toast{\n min-width: 0;\n}\nmd-toast .md-toast-content {\n font-size: 14px!important;\n}", - "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet toast;\nlet utils;\nlet types;\n\nself.onInit = function() {\n\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get('attributeService');\n toast = $scope.$injector.get('toast');\n utils = $scope.$injector.get('utils');\n types = $scope.$injector.get('types');\n settings = angular.copy(self.ctx.settings) || {};\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n $scope.requiredErrorMessage = settings.requiredErrorMessage || \"Entity attribute is required\";\n $scope.labelValue = settings.labelValue || \"Value\";\n $scope.message = 'No entity selected';\n \n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType === \"DEVICE\") {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n } else {\n $scope.message = 'Selected entity cannot have shared attributes';\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type != \"attribute\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n attributeService.saveEntityAttributes(\n datasource.entityType,\n datasource.entityId,\n types.attributesScope.shared.value,\n [\n {\n key: $scope.currentKey,\n value: $scope.currentValue\n }\n ]\n ).then(\n function success() {\n $scope.originalValue = $scope.currentValue;\n if (settings.showResultMessage) {\n toast.showSuccess('Update successful', 1000, angular.element(self.ctx.$container), 'bottom left');\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n toast.showError('Update failed', angular.element(self.ctx.$container), 'bottom left');\n }\n }\n );\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.currentValue === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n}\n\nself.onDataUpdated = function() {\n\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.currentValue = $scope.originalValue = self.ctx.data[0].data[0][1];\n correctValue($scope.currentValue);\n $scope.$digest();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nfunction correctValue(value) {\n if (typeof value !== \"number\") {\n $scope.currentValue = 0;\n }\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n }\n}\n\nself.onDestroy = function() {\n\n}\n", + "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? labelValue : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n \n
\n \n \n
\n
\n \n
\n
\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\n
\n
\n
\n
", + "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}", + "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet utils;\nlet translate;\nlet http;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n http = $scope.$injector.get(self.ctx.servicesMap.get('http'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false; \n $scope.message = translate.instant('widgets.input-widgets.no-entity-selected');\n \n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-timeseries-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || translate.instant('widgets.input-widgets.value');\n\n $scope.attributeUpdateFormGroup = $scope.fb.group(\n {currentValue: [undefined, [$scope.validators.required,\n $scope.validators.min(settings.minValue),\n $scope.validators.max(settings.maxValue)]]}\n );\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType === 'DEVICE') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n \n $scope.entityDetected = true;\n }\n } else {\n $scope.message = translate.instant('widgets.input-widgets.not-allowed-entity');\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"attribute\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n $scope.isFocused = false;\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n attributeService.saveEntityAttributes(\n datasource.entity.id,\n 'SHARED_SCOPE',\n [\n {\n key: $scope.currentKey,\n value: $scope.attributeUpdateFormGroup.get('currentValue').value\n }\n ]\n ).subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.attributeUpdateFormGroup.get('currentValue').value === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n}\n\nself.onDataUpdated = function() {\n\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue(correctValue($scope.originalValue));\n self.ctx.detectChanges();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nfunction correctValue(value) {\n if (typeof value !== \"number\") {\n return 0;\n }\n return value;\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n }\n}\n\nself.onDestroy = function() {\n\n}", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showLabel\":{\n \"title\":\"Show label\",\n \"type\":\"boolean\",\n \"default\":true\n },\n \"labelValue\": {\n \"title\": \"Label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"maxValue\": {\n \"title\": \"Max value\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"minValue\": {\n \"title\": \"Min value\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\":true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showResultMessage\",\n \"showLabel\",\n \"labelValue\",\n \"requiredErrorMessage\",\n \"maxValue\",\n \"minValue\"\n ]\n}", "dataKeySettingsSchema": "{}\n", "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\":{},\"title\":\"Update shared double attribute\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" @@ -125,12 +125,12 @@ "sizeX": 7.5, "sizeY": 3, "resources": [], - "templateHtml": "
\n
\n
\n
\n \n {{currentValue}}\n \n
\n
\n\n
\n
\n No attribute is selected\n
\n
\n Timeseries parameter cannot be used in this widget\n
\n
\n
", - "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.entity-title {\n font-weight: bold;\n font-size: 22px;\n padding-top: 12px;\n padding-bottom: 6px;\n color: #666;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .md-icon-button md-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n\nmd-toast{\n min-width: 0;\n}\nmd-toast .md-toast-content {\n font-size: 14px!important;\n}", - "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet toast;\nlet utils;\nlet types;\nlet map;\nlet mapReverse;\n\nself.onInit = function() {\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get('attributeService');\n toast = $scope.$injector.get('toast');\n utils = $scope.$injector.get('utils');\n types = $scope.$injector.get('types');\n settings = angular.copy(self.ctx.settings) || {};\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n $scope.message = 'No entity selected';\n\n settings.trueValue = settings.trueValue || true;\n settings.falseValue = settings.falseValue || false;\n\n map = {\"true\":settings.trueValue, \"false\": settings.falseValue};\n mapReverse = {[settings.trueValue]:true, [settings.falseValue]:false};\n $scope.checkboxValue = \"false\";\n $scope.currentValue = map[$scope.checkboxValue];\n\n $scope.changed = function () {\n $scope.currentValue = map[$scope.checkboxValue];\n $scope.updateAttribute();\n }\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType === \"DEVICE\") {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n } else {\n $scope.message = 'Selected entity cannot have shared attributes';\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type != \"attribute\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n attributeService.saveEntityAttributes(\n datasource.entityType,\n datasource.entityId,\n types.attributesScope.shared.value,\n [\n {\n key: $scope.currentKey,\n value: mapReverse[$scope.currentValue] || false\n }\n ]\n ).then(\n function success() {\n $scope.originalValue = $scope.currentValue;\n if (settings.showResultMessage) {\n toast.showSuccess('Update successful', 1000, angular.element(self.ctx.$container), 'bottom left');\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n toast.showError('Update failed', angular.element(self.ctx.$container), 'bottom left');\n }\n }\n );\n }\n };\n}\n\nself.onDataUpdated = function() {\n try {\n $scope.checkboxValue = ($scope.originalValue = self.ctx.data[0].data[0][1]) || 'false';\n $scope.currentValue = map[$scope.checkboxValue];\n $scope.$digest();\n\n } catch (e) {\n console.log(e);\n }\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n }\n}\n\nself.onResize = function() {}\nself.onDestroy = function() {}\n", + "templateHtml": "
\r\n
\r\n
\r\n
\r\n
\r\n \r\n {{currentValue}}\r\n \r\n
\r\n
\r\n\r\n
\r\n
\r\n {{ 'widgets.input-widgets.no-attribute-selected' | translate }}\r\n
\r\n
\r\n {{ 'widgets.input-widgets.timeseries-not-allowed' | translate }}\r\n
\r\n
\r\n
\r\n
", + "templateCss": ".attribute-update-form {\r\n overflow: hidden;\r\n height: 100%;\r\n display: flex;\r\n flex-direction: column;\r\n}\r\n\r\n.attribute-update-form__grid {\r\n display: flex;\r\n}\r\n.grid__element:first-child {\r\n flex: 1;\r\n}\r\n\r\n.grid__element {\r\n display: flex;\r\n}\r\n\r\n.attribute-update-form .mat-button.mat-icon-button {\r\n margin: 0;\r\n}\r\n\r\n.attribute-update-form .mat-button.mat-icon-button {\r\n width: 32px;\r\n min-width: 32px;\r\n height: 32px;\r\n min-height: 32px;\r\n padding: 0 !important;\r\n margin: 0 !important;\r\n line-height: 20px;\r\n}\r\n\r\n.attribute-update-form .mat-icon-button mat-icon {\r\n width: 20px;\r\n min-width: 20px;\r\n height: 20px;\r\n min-height: 20px;\r\n font-size: 20px;\r\n}\r\n\r\n.tb-toast {\r\n font-size: 14px!important;\r\n}", + "controllerScript": "let settings;\nlet attributeService;\nlet utils;\nlet translate;\nlet http;\nlet $scope;\nlet map;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init();\n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n http = $scope.$injector.get(self.ctx.servicesMap.get('http'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n $scope.message = translate.instant('widgets.input-widgets.no-entity-selected');\n\n settings.trueValue = utils.defaultValue(utils.customTranslation(settings.trueValue, settings.trueValue), true);\n settings.falseValue = utils.defaultValue(utils.customTranslation(settings.falseValue, settings.falseValue), false);\n\n map = {\n true: settings.trueValue,\n false: settings.falseValue\n };\n \n $scope.checkboxValue = false;\n $scope.currentValue = map[$scope.checkboxValue];\n\n $scope.attributeUpdateFormGroup = $scope.fb.group({checkboxValue: [$scope.checkboxValue]});\n\n $scope.changed = function() {\n $scope.checkboxValue = $scope.attributeUpdateFormGroup.get('checkboxValue').value;\n $scope.currentValue = map[$scope.checkboxValue];\n $scope.updateAttribute();\n };\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType === 'DEVICE') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n \n $scope.entityDetected = true;\n }\n } else {\n $scope.message = translate.instant('widgets.input-widgets.not-allowed-entity');\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"attribute\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function() {\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n attributeService.saveEntityAttributes(\n datasource.entity.id,\n 'SHARED_SCOPE',\n [\n {\n key: $scope.currentKey,\n value: $scope.checkboxValue || false\n }\n ]\n ).subscribe(\n function success() {\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n };\n}\n\nself.onDataUpdated = function() {\n try {\n $scope.checkboxValue = self.ctx.data[0].data[0][1] === 'true';\n $scope.currentValue = map[$scope.checkboxValue];\n $scope.attributeUpdateFormGroup.get('checkboxValue').patchValue($scope.checkboxValue);\n self.ctx.detectChanges();\n } catch (e) {\n console.log(e);\n }\n}\n\nself.onResize = function() {}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n }\n}\n\nself.onDestroy = function() {}", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"trueValue\": {\n \"title\": \"True value\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"falseValue\": {\n \"title\": \"False value\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\":true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showResultMessage\",\n \"trueValue\",\n \"falseValue\"\n ]\n}", "dataKeySettingsSchema": "{}\n", - "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\":{\"trueValue\":\"active\",\"falseValue\":\"inactive\"},\"title\":\"Update shared boolean attribute\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" + "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\":{},\"title\":\"Update shared boolean attribute\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" } }, { @@ -141,9 +141,9 @@ "sizeX": 7.5, "sizeY": 3, "resources": [], - "templateHtml": "
\n
\n\n
\n
\n \n \n \n
\n
{{requiredErrorMessage}}
\n
\n
\n
\n\n
\n \n check\n Update string timeseries\n \n \n close\n Discard changes\n \n
\n
\n \n
\n No entity selected\n
\n
\n No timeseries is selected\n
\n
\n Attribute parameter cannot be used in this widget\n
\n
\n
", - "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.entity-title {\n font-weight: bold;\n font-size: 22px;\n padding-top: 12px;\n padding-bottom: 6px;\n color: #666;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .md-icon-button md-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.show-label label {\n display: block;\n}\n\nlabel {\n display: none;\n}\n\nmd-toast{\n min-width: 0;\n}\nmd-toast .md-toast-content {\n font-size: 14px!important;\n}", - "controllerScript": "let $scope;\r\nlet settings;\r\nlet attributeService;\r\nlet toast;\r\nlet utils;\r\nlet types;\r\nlet $q\r\nlet $http;\r\n\r\nself.onInit = function() {\r\n\r\n $scope = self.ctx.$scope;\r\n attributeService = $scope.$injector.get('attributeService');\r\n toast = $scope.$injector.get('toast');\r\n utils = $scope.$injector.get('utils');\r\n types = $scope.$injector.get('types');\r\n $q = $scope.$injector.get('$q');\r\n $http = $scope.$injector.get('$http');\r\n settings = self.ctx.settings || {};\r\n $scope.settings = settings;\r\n $scope.isValidParameter = true;\r\n $scope.dataKeyDetected = false;\r\n $scope.requiredErrorMessage = settings.requiredErrorMessage || \"Entity timeseries are required\";\r\n $scope.labelValue = settings.labelValue || \"Value\";\r\n\r\n if (self.ctx.datasources && self.ctx.datasources.length) {\r\n var datasource = self.ctx.datasources[0];\r\n if (datasource.type === 'entity') {\r\n if (datasource.entityType && datasource.entityId) {\r\n $scope.entityName = datasource.entityName;\r\n if (settings.widgetTitle && settings.widgetTitle.length) {\r\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\r\n } else {\r\n $scope.titleTemplate = self.ctx.widgetConfig.title;\r\n }\r\n\r\n $scope.entityDetected = true;\r\n }\r\n }\r\n if (datasource.dataKeys.length) {\r\n if (datasource.dataKeys[0].type != \"timeseries\") {\r\n $scope.isValidParameter = false;\r\n } else {\r\n $scope.currentKey = datasource.dataKeys[0].name;\r\n $scope.dataKeyType = datasource.dataKeys[0].type;\r\n $scope.dataKeyDetected = true;\r\n }\r\n }\r\n }\r\n\r\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\r\n\r\n $scope.updateAttribute = function () {\r\n if ($scope.entityDetected) {\r\n var datasource = self.ctx.datasources[0];\r\n\r\n saveEntityTimeseries(\r\n datasource.entityType,\r\n datasource.entityId,\r\n [\r\n {\r\n key: $scope.currentKey,\r\n value: $scope.currentValue\r\n }\r\n ]\r\n ).then(\r\n function success() {\r\n $scope.originalValue = $scope.currentValue;\r\n if (settings.showResultMessage) {\r\n toast.showSuccess('Update successful', 1000, angular.element(self.ctx.$container), 'bottom left');\r\n }\r\n },\r\n function fail() {\r\n if (settings.showResultMessage) {\r\n toast.showError('Update failed', angular.element(self.ctx.$container), 'bottom left');\r\n }\r\n }\r\n );\r\n }\r\n };\r\n\r\n $scope.changeFocus = function () {\r\n if ($scope.currentValue === $scope.originalValue) {\r\n $scope.isFocused = false;\r\n }\r\n }\r\n\r\n function saveEntityTimeseries(entityType, entityId, telemetries) {\r\n var deferred = $q.defer();\r\n var telemetriesData = {};\r\n for (var a = 0; a < telemetries.length; a++) {\r\n if (angular.isDefined(telemetries[a].value) && telemetries[a].value !== null) {\r\n telemetriesData[telemetries[a].key] = telemetries[a].value;\r\n }\r\n }\r\n if (Object.keys(telemetriesData).length) {\r\n var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/timeseries/scope';\r\n $http.post(url, telemetriesData).then(\r\n function(response) {\r\n deferred.resolve(response.data);\r\n },\r\n function() {\r\n deferred.reject();\r\n }\r\n );\r\n }\r\n return deferred.promise;\r\n }\r\n}\r\n\r\nself.onDataUpdated = function() {\r\n try {\r\n if ($scope.dataKeyDetected) {\r\n if (!$scope.isFocused) {\r\n $scope.currentValue = $scope.originalValue = self.ctx.data[0].data[0][1];\r\n $scope.$digest();\r\n }\r\n }\r\n } catch (e) {\r\n console.log(e);\r\n }\r\n}\r\n\r\nself.onResize = function() {\r\n\r\n}\r\n\r\nself.typeParameters = function() {\r\n return {\r\n maxDatasources: 1,\r\n maxDataKeys: 1\r\n }\r\n}\r\n\r\nself.onDestroy = function() {\r\n\r\n}\r\n", + "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? labelValue : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n \n
\n \n \n
\n
\n \n
\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-timeseries-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.attribute-not-allowed' | translate }}\n
\n
\n
\n
", + "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}", + "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet utils;\nlet translate;\nlet http;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n http = $scope.$injector.get(self.ctx.servicesMap.get('http'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-timeseries-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || translate.instant('widgets.input-widgets.value');\n\n $scope.attributeUpdateFormGroup = $scope.fb.group(\n {currentValue: [undefined, [$scope.validators.required,\n $scope.validators.minLength(settings.minLength),\n $scope.validators.maxLength(settings.maxLength)]]}\n );\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"timeseries\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n $scope.isFocused = false;\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n let observable = saveEntityTimeseries(\n datasource.entityType,\n datasource.entityId,\n [\n {\n key: $scope.currentKey,\n value: $scope.attributeUpdateFormGroup.get('currentValue').value\n }\n ]\n );\n if (observable) {\n observable.subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.attributeUpdateFormGroup.get('currentValue').value === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n\n function saveEntityTimeseries(entityType, entityId, telemetries) {\n var telemetriesData = {};\n for (var a = 0; a < telemetries.length; a++) {\n if (typeof telemetries[a].value !== 'undefined' && telemetries[a].value !== null) {\n telemetriesData[telemetries[a].key] = telemetries[a].value;\n }\n }\n if (Object.keys(telemetriesData).length) {\n var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/timeseries/scope';\n return http.post(url, telemetriesData);\n }\n return null;\n }\n}\n\nself.onDataUpdated = function() {\n\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue($scope.originalValue);\n self.ctx.detectChanges();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n }\n}\n\nself.onDestroy = function() {\n\n}\n", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showLabel\":{\n \"title\":\"Show label\",\n \"type\":\"boolean\",\n \"default\":true\n },\n \"labelValue\": {\n \"title\": \"Label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"maxLength\": {\n \"title\": \"Max length\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"minLength\": {\n \"title\": \"Min length\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\":true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showResultMessage\",\n \"showLabel\",\n \"labelValue\",\n \"requiredErrorMessage\",\n \"maxLength\",\n \"minLength\"\n ]\n}", "dataKeySettingsSchema": "{}\n", "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\":{},\"title\":\"Update string timeseries\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" @@ -157,12 +157,12 @@ "sizeX": 7.5, "sizeY": 3, "resources": [], - "templateHtml": "
\n
\n
\n
\n \n {{currentValue}}\n \n
\n
\n\n
\n
\n No timeseries is selected\n
\n
\n Attribute parameter cannot be used in this widget\n
\n
\n
", - "templateCss": ".attribute-update-form {\r\n overflow: hidden;\r\n height: 100%;\r\n display: flex;\r\n flex-direction: column;\r\n}\r\n\r\n.entity-title {\r\n font-weight: bold;\r\n font-size: 22px;\r\n padding-top: 12px;\r\n padding-bottom: 6px;\r\n color: #666;\r\n}\r\n\r\n.attribute-update-form__grid {\r\n display: flex;\r\n}\r\n.grid__element:first-child {\r\n flex: 1;\r\n}\r\n\r\n.grid__element {\r\n display: flex;\r\n}\r\n\r\n.attribute-update-form .md-button.md-icon-button {\r\n margin: 0;\r\n}\r\n\r\n.attribute-update-form .md-button.md-icon-button {\r\n width: 32px;\r\n min-width: 32px;\r\n height: 32px;\r\n min-height: 32px;\r\n padding: 0 !important;\r\n margin: 0 !important;\r\n line-height: 20px;\r\n}\r\n\r\n.attribute-update-form .md-icon-button md-icon {\r\n width: 20px;\r\n min-width: 20px;\r\n height: 20px;\r\n min-height: 20px;\r\n font-size: 20px;\r\n}\r\n\r\n\r\nmd-toast{\r\n min-width: 0;\r\n}\r\nmd-toast .md-toast-content {\r\n font-size: 14px!important;\r\n}", - "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet toast;\nlet utils;\nlet types;\nlet $q\nlet $http;\nlet map;\nlet mapReverse;\n\nself.onInit = function() {\n\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get('attributeService');\n toast = $scope.$injector.get('toast');\n utils = $scope.$injector.get('utils');\n types = $scope.$injector.get('types');\n $q = $scope.$injector.get('$q');\n $http = $scope.$injector.get('$http');\n settings = angular.copy(self.ctx.settings) || {};\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n $scope.message = 'No entity selected';\n\n settings.trueValue = settings.trueValue || true;\n settings.falseValue = settings.falseValue || false;\n \n map = {\"true\":settings.trueValue, \"false\": settings.falseValue};\n mapReverse = {[settings.trueValue]:\"true\", [settings.falseValue]:\"false\"};\n $scope.checkboxValue = \"false\";\n $scope.currentValue = map[$scope.checkboxValue];\n\n $scope.changed = function () {\n $scope.currentValue = map[$scope.checkboxValue];\n $scope.updateAttribute();\n }\n \n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type != \"timeseries\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n saveEntityTimeseries(\n datasource.entityType,\n datasource.entityId,\n [\n {\n key: $scope.currentKey,\n value: $scope.currentValue\n }\n ]\n ).then(\n function success() {\n $scope.originalValue = $scope.currentValue;\n if (settings.showResultMessage) {\n toast.showSuccess('Update successful', 1000, angular.element(self.ctx.$container), 'bottom left');\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n toast.showError('Update failed', angular.element(self.ctx.$container), 'bottom left');\n }\n }\n );\n }\n };\n\n function saveEntityTimeseries(entityType, entityId, telemetries) {\n var deferred = $q.defer();\n var telemetriesData = {};\n for (var a = 0; a < telemetries.length; a++) {\n if (angular.isDefined(telemetries[a].value) && telemetries[a].value !== null) {\n telemetriesData[telemetries[a].key] = telemetries[a].value;\n }\n }\n if (Object.keys(telemetriesData).length) {\n var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/timeseries/scope';\n $http.post(url, telemetriesData).then(\n function(response) {\n deferred.resolve(response.data);\n },\n function() {\n deferred.reject();\n }\n );\n }\n return deferred.promise;\n }\n}\n\nself.onDataUpdated = function() {\n\n try {\n $scope.checkboxValue = mapReverse[$scope.originalValue = self.ctx.data[0].data[0][1]] || 'false';\n $scope.currentValue = map[$scope.checkboxValue];\n $scope.$digest();\n } catch (e) {\n console.log(e);\n }\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n }\n}\n\nself.onDestroy = function() {\n\n}\n", + "templateHtml": "
\n
\n
\n
\n
\n \n {{currentValue}}\n \n
\n
\n\n
\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-timeseries-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.attribute-not-allowed' | translate }}\n
\n
\n
\n
", + "templateCss": ".attribute-update-form {\r\n overflow: hidden;\r\n height: 100%;\r\n display: flex;\r\n flex-direction: column;\r\n}\r\n\r\n.attribute-update-form__grid {\r\n display: flex;\r\n}\r\n.grid__element:first-child {\r\n flex: 1;\r\n}\r\n\r\n.grid__element {\r\n display: flex;\r\n}\r\n\r\n.attribute-update-form .mat-button.mat-icon-button {\r\n margin: 0;\r\n}\r\n\r\n.attribute-update-form .mat-button.mat-icon-button {\r\n width: 32px;\r\n min-width: 32px;\r\n height: 32px;\r\n min-height: 32px;\r\n padding: 0 !important;\r\n margin: 0 !important;\r\n line-height: 20px;\r\n}\r\n\r\n.attribute-update-form .mat-icon-button mat-icon {\r\n width: 20px;\r\n min-width: 20px;\r\n height: 20px;\r\n min-height: 20px;\r\n font-size: 20px;\r\n}\r\n\r\n.tb-toast {\r\n font-size: 14px!important;\r\n}", + "controllerScript": "let settings;\nlet attributeService;\nlet utils;\nlet translate;\nlet http;\nlet $scope;\nlet map;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init();\n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n http = $scope.$injector.get(self.ctx.servicesMap.get('http'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n\n settings.trueValue = utils.defaultValue(utils.customTranslation(settings.trueValue, settings.trueValue), true);\n settings.falseValue = utils.defaultValue(utils.customTranslation(settings.falseValue, settings.falseValue), false);\n\n map = {\n true: settings.trueValue,\n false: settings.falseValue\n };\n \n $scope.checkboxValue = false;\n $scope.currentValue = map[$scope.checkboxValue];\n\n $scope.attributeUpdateFormGroup = $scope.fb.group({checkboxValue: [$scope.checkboxValue]});\n\n $scope.changed = function() {\n $scope.checkboxValue = $scope.attributeUpdateFormGroup.get('checkboxValue').value;\n $scope.currentValue = map[$scope.checkboxValue];\n $scope.updateAttribute();\n };\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"timeseries\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function() {\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n let observable = saveEntityTimeseries(\n datasource.entityType,\n datasource.entityId,\n [{\n key: $scope.currentKey,\n value: $scope.checkboxValue\n }]\n );\n\n if (observable) {\n observable.subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('checkboxValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n }\n };\n\n function saveEntityTimeseries(entityType, entityId, telemetries) {\n var telemetriesData = {};\n for (var a = 0; a < telemetries.length; a++) {\n if (typeof telemetries[a].value !== 'undefined' && telemetries[a].value !== null) {\n telemetriesData[telemetries[a].key] = telemetries[a].value;\n }\n }\n if (Object.keys(telemetriesData).length) {\n var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/timeseries/scope';\n return http.post(url, telemetriesData);\n }\n return null;\n }\n}\n\nself.onDataUpdated = function() {\n try {\n $scope.checkboxValue = self.ctx.data[0].data[0][1] === 'true';\n $scope.currentValue = map[$scope.checkboxValue];\n $scope.attributeUpdateFormGroup.get('checkboxValue').patchValue($scope.checkboxValue);\n self.ctx.detectChanges();\n } catch (e) {\n console.log(e);\n }\n}\n\nself.onResize = function() {}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1\n }\n}\n\nself.onDestroy = function() {}", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"trueValue\": {\n \"title\": \"True value\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"falseValue\": {\n \"title\": \"False value\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\":true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showResultMessage\",\n \"trueValue\",\n \"falseValue\"\n ]\n}", "dataKeySettingsSchema": "{}\n", - "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\":{\"trueValue\":\"active\",\"falseValue\":\"inactive\"},\"title\":\"Update boolean timeseries\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" + "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\":{},\"title\":\"Update boolean timeseries\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" } }, { @@ -173,9 +173,9 @@ "sizeX": 7.5, "sizeY": 3, "resources": [], - "templateHtml": "
\n
\n\n
\n
\n \n \n \n
\n
{{requiredErrorMessage}}
\n
\n
\n
\n\n
\n \n check\n Update server attribute\n \n \n close\n Discard changes\n \n
\n
\n\n
\n No entity selected\n
\n
\n No timeseries is selected\n
\n
\n Attribute parameter can not be used in this widget\n
\n
\n
", - "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.entity-title {\n font-weight: bold;\n font-size: 22px;\n padding-top: 12px;\n padding-bottom: 6px;\n color: #666;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .md-button.md-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .md-icon-button md-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.show-label label {\n display: block;\n}\n\nlabel {\n display: none;\n}\n\nmd-toast{\n min-width: 0;\n}\nmd-toast .md-toast-content {\n font-size: 14px!important;\n}", - "controllerScript": "let $scope;\r\nlet settings;\r\nlet attributeService;\r\nlet toast;\r\nlet utils;\r\nlet types;\r\nlet $q;\r\nlet $http;\r\n\r\nself.onInit = function() {\r\n\r\n $scope = self.ctx.$scope;\r\n attributeService = $scope.$injector.get('attributeService');\r\n toast = $scope.$injector.get('toast');\r\n utils = $scope.$injector.get('utils');\r\n types = $scope.$injector.get('types');\r\n $q = $scope.$injector.get('$q');\r\n $http = $scope.$injector.get('$http');\r\n settings = angular.copy(self.ctx.settings) || {};\r\n $scope.settings = settings;\r\n $scope.isValidParameter = true;\r\n $scope.requiredErrorMessage = settings.requiredErrorMessage || \"Entity timeseries are required\";\r\n $scope.labelValue = settings.labelValue || \"Value\";\r\n\r\n if (self.ctx.datasources && self.ctx.datasources.length) {\r\n var datasource = self.ctx.datasources[0];\r\n if (datasource.type === 'entity') {\r\n if (datasource.entityType && datasource.entityId) {\r\n $scope.entityName = datasource.entityName;\r\n if (settings.widgetTitle && settings.widgetTitle.length) {\r\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\r\n } else {\r\n $scope.titleTemplate = self.ctx.widgetConfig.title;\r\n }\r\n\r\n $scope.entityDetected = true;\r\n }\r\n }\r\n if (datasource.dataKeys.length) {\r\n if (datasource.dataKeys[0].type != \"timeseries\") {\r\n $scope.isValidParameter = false;\r\n } else {\r\n $scope.currentKey = datasource.dataKeys[0].name;\r\n $scope.dataKeyType = datasource.dataKeys[0].type;\r\n $scope.dataKeyDetected = true;\r\n }\r\n }\r\n }\r\n\r\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\r\n\r\n $scope.updateAttribute = function () {\r\n if ($scope.entityDetected) {\r\n var datasource = self.ctx.datasources[0];\r\n\r\n saveEntityTimeseries(\r\n datasource.entityType,\r\n datasource.entityId,\r\n [\r\n {\r\n key: $scope.currentKey,\r\n value: $scope.currentValue\r\n }\r\n ]\r\n ).then(\r\n function success() {\r\n $scope.originalValue = $scope.currentValue;\r\n if (settings.showResultMessage) {\r\n toast.showSuccess('Update successful', 1000, angular.element(self.ctx.$container), 'bottom left');\r\n }\r\n },\r\n function fail() {\r\n if (settings.showResultMessage) {\r\n toast.showError('Update failed', angular.element(self.ctx.$container), 'bottom left');\r\n }\r\n }\r\n );\r\n }\r\n };\r\n\r\n $scope.changeFocus = function () {\r\n if ($scope.currentValue === $scope.originalValue) {\r\n $scope.isFocused = false;\r\n }\r\n }\r\n\r\n function saveEntityTimeseries(entityType, entityId, telemetries) {\r\n var deferred = $q.defer();\r\n var telemetriesData = {};\r\n for (var a = 0; a < telemetries.length; a++) {\r\n if (angular.isDefined(telemetries[a].value) && telemetries[a].value !== null) {\r\n telemetriesData[telemetries[a].key] = telemetries[a].value;\r\n }\r\n }\r\n if (Object.keys(telemetriesData).length) {\r\n var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/timeseries/scope';\r\n $http.post(url, telemetriesData).then(\r\n function(response) {\r\n deferred.resolve(response.data);\r\n },\r\n function() {\r\n deferred.reject();\r\n }\r\n );\r\n }\r\n return deferred.promise;\r\n }\r\n}\r\n\r\nself.onDataUpdated = function() {\r\n\r\n try {\r\n if ($scope.dataKeyDetected) {\r\n if (!$scope.isFocused) {\r\n $scope.currentValue = $scope.originalValue = self.ctx.data[0].data[0][1];\r\n correctValue($scope.currentValue);\r\n $scope.$digest();\r\n }\r\n }\r\n } catch (e) {\r\n console.log(e);\r\n }\r\n}\r\n\r\nfunction correctValue(value) {\r\n if (typeof value !== \"number\") {\r\n $scope.currentValue = 0;\r\n }\r\n}\r\n\r\nself.onResize = function() {\r\n\r\n}\r\n\r\nself.typeParameters = function() {\r\n return {\r\n maxDatasources: 1,\r\n maxDataKeys: 1\r\n }\r\n}\r\n\r\nself.onDestroy = function() {\r\n\r\n}\r\n", + "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? labelValue : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n \n
\n \n \n
\n
\n \n
\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-timeseries-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.attribute-not-allowed' | translate }}\n
\n
\n
\n
", + "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}", + "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet utils;\nlet translate;\nlet http;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n http = $scope.$injector.get(self.ctx.servicesMap.get('http'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-timeseries-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || translate.instant('widgets.input-widgets.value');\n\n $scope.attributeUpdateFormGroup = $scope.fb.group(\n {currentValue: [undefined, [$scope.validators.required,\n $scope.validators.min(settings.minValue),\n $scope.validators.max(settings.maxValue)]]}\n );\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"timeseries\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n $scope.isFocused = false;\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n let observable = saveEntityTimeseries(\n datasource.entityType,\n datasource.entityId,\n [\n {\n key: $scope.currentKey,\n value: $scope.attributeUpdateFormGroup.get('currentValue').value\n }\n ]\n );\n if (observable) {\n observable.subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.attributeUpdateFormGroup.get('currentValue').value === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n\n function saveEntityTimeseries(entityType, entityId, telemetries) {\n var telemetriesData = {};\n for (var a = 0; a < telemetries.length; a++) {\n if (typeof telemetries[a].value !== 'undefined' && telemetries[a].value !== null) {\n telemetriesData[telemetries[a].key] = telemetries[a].value;\n }\n }\n if (Object.keys(telemetriesData).length) {\n var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/timeseries/scope';\n return http.post(url, telemetriesData);\n }\n return null;\n }\n}\n\nself.onDataUpdated = function() {\n\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue(correctValue($scope.originalValue));\n self.ctx.detectChanges();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nfunction correctValue(value) {\n if (typeof value !== \"number\") {\n return 0;\n }\n return value;\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n dataKeyOptional: true\n }\n}\n\nself.onDestroy = function() {\n\n}", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showLabel\":{\n \"title\":\"Show label\",\n \"type\":\"boolean\",\n \"default\":true\n },\n \"labelValue\": {\n \"title\": \"Label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"maxValue\": {\n \"title\": \"Max value\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"minValue\": {\n \"title\": \"Min value\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\":true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showResultMessage\",\n \"showLabel\",\n \"labelValue\",\n \"requiredErrorMessage\",\n \"maxValue\",\n \"minValue\"\n ]\n}", "dataKeySettingsSchema": "{}\n", "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\":{},\"title\":\"Update double timeseries\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" @@ -189,13 +189,13 @@ "sizeX": 7.5, "sizeY": 3, "resources": [], - "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? labelValue : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n\n
\n \n \n
\n
\n\n
\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-timeseries-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.attribute-not-allowed' | translate }}\n
\n
\n
\n
", + "templateHtml": "
\n
\n
\n
\n
\n \n {{ settings.showLabel ? labelValue : '' }}\n \n \n {{requiredErrorMessage}}\n \n \n
\n\n
\n \n \n
\n
\n\n
\n {{ 'widgets.input-widgets.no-entity-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.no-timeseries-selected' | translate }}\n
\n
\n {{ 'widgets.input-widgets.attribute-not-allowed' | translate }}\n
\n
\n
\n
", "templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n margin: 0;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}\n", - "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet utils;\nlet translate;\nlet http;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n http = $scope.$injector.get(self.ctx.servicesMap.get('http'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-timeseries-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || translate.instant('widgets.input-widgets.value');\n\n $scope.attributeUpdateFormGroup = $scope.fb.group(\n {currentValue: [undefined, [$scope.validators.required,\n $scope.validators.min(settings.minValue),\n $scope.validators.max(settings.maxValue)]]}\n );\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"timeseries\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n $scope.isFocused = false;\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n let observable = saveEntityTimeseries(\n datasource.entityType,\n datasource.entityId,\n [\n {\n key: $scope.currentKey,\n value: $scope.attributeUpdateFormGroup.get('currentValue').value\n }\n ]\n );\n if (observable) {\n observable.subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.attributeUpdateFormGroup.get('currentValue').value === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n\n function saveEntityTimeseries(entityType, entityId, telemetries) {\n var telemetriesData = {};\n for (var a = 0; a < telemetries.length; a++) {\n if (typeof telemetries[a].value !== 'undefined' && telemetries[a].value !== null) {\n telemetriesData[telemetries[a].key] = telemetries[a].value;\n }\n }\n if (Object.keys(telemetriesData).length) {\n var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/timeseries/scope';\n return http.post(url, telemetriesData);\n }\n return null;\n }\n}\n\nself.onDataUpdated = function() {\n\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue(correctValue($scope.originalValue));\n self.ctx.detectChanges();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nfunction correctValue(value) {\n if (typeof value !== \"number\") {\n return 0;\n }\n return value;\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n dataKeyOptional: true\n }\n}\n\nself.onDestroy = function() {\n\n}\n", + "controllerScript": "let $scope;\nlet settings;\nlet attributeService;\nlet utils;\nlet translate;\nlet http;\n\nself.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges(true);\n });\n};\n\n\nfunction init() {\n\n $scope = self.ctx.$scope;\n attributeService = $scope.$injector.get(self.ctx.servicesMap.get('attributeService'));\n utils = $scope.$injector.get(self.ctx.servicesMap.get('utils'));\n translate = $scope.$injector.get(self.ctx.servicesMap.get('translate'));\n http = $scope.$injector.get(self.ctx.servicesMap.get('http'));\n $scope.toastTargetId = 'input-widget' + utils.guid();\n settings = utils.deepClone(self.ctx.settings) || {};\n settings.showLabel = utils.defaultValue(settings.showLabel, true);\n settings.showResultMessage = utils.defaultValue(settings.showResultMessage, true);\n $scope.settings = settings;\n $scope.isValidParameter = true;\n $scope.dataKeyDetected = false;\n $scope.requiredErrorMessage = utils.customTranslation(settings.requiredErrorMessage, settings.requiredErrorMessage) || translate.instant('widgets.input-widgets.entity-timeseries-required');\n $scope.labelValue = utils.customTranslation(settings.labelValue, settings.labelValue) || translate.instant('widgets.input-widgets.value');\n\n $scope.attributeUpdateFormGroup = $scope.fb.group(\n {currentValue: [undefined, [$scope.validators.required,\n $scope.validators.min(settings.minValue),\n $scope.validators.max(settings.maxValue),\n $scope.validators.pattern(/^-?[0-9]+$/)]]}\n );\n\n if (self.ctx.datasources && self.ctx.datasources.length) {\n var datasource = self.ctx.datasources[0];\n if (datasource.type === 'entity') {\n if (datasource.entityType && datasource.entityId) {\n $scope.entityName = datasource.entityName;\n if (settings.widgetTitle && settings.widgetTitle.length) {\n $scope.titleTemplate = utils.customTranslation(settings.widgetTitle, settings.widgetTitle);\n } else {\n $scope.titleTemplate = self.ctx.widgetConfig.title;\n }\n\n $scope.entityDetected = true;\n }\n }\n if (datasource.dataKeys.length) {\n if (datasource.dataKeys[0].type !== \"timeseries\") {\n $scope.isValidParameter = false;\n } else {\n $scope.currentKey = datasource.dataKeys[0].name;\n $scope.dataKeyType = datasource.dataKeys[0].type;\n $scope.dataKeyDetected = true;\n }\n }\n }\n\n self.ctx.widgetTitle = utils.createLabelFromDatasource(self.ctx.datasources[0], $scope.titleTemplate);\n\n $scope.updateAttribute = function () {\n $scope.isFocused = false;\n if ($scope.entityDetected) {\n var datasource = self.ctx.datasources[0];\n\n let observable = saveEntityTimeseries(\n datasource.entityType,\n datasource.entityId,\n [\n {\n key: $scope.currentKey,\n value: $scope.attributeUpdateFormGroup.get('currentValue').value\n }\n ]\n );\n if (observable) {\n observable.subscribe(\n function success() {\n $scope.originalValue = $scope.attributeUpdateFormGroup.get('currentValue').value;\n if (settings.showResultMessage) {\n $scope.showSuccessToast(translate.instant('widgets.input-widgets.update-successful'), 1000, 'bottom', 'left', $scope.toastTargetId);\n }\n },\n function fail() {\n if (settings.showResultMessage) {\n $scope.showErrorToast(translate.instant('widgets.input-widgets.update-failed'), 'bottom', 'left', $scope.toastTargetId);\n }\n }\n );\n }\n }\n };\n\n $scope.changeFocus = function () {\n if ($scope.attributeUpdateFormGroup.get('currentValue').value === $scope.originalValue) {\n $scope.isFocused = false;\n }\n }\n\n function saveEntityTimeseries(entityType, entityId, telemetries) {\n var telemetriesData = {};\n for (var a = 0; a < telemetries.length; a++) {\n if (typeof telemetries[a].value !== 'undefined' && telemetries[a].value !== null) {\n telemetriesData[telemetries[a].key] = telemetries[a].value;\n }\n }\n if (Object.keys(telemetriesData).length) {\n var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/timeseries/scope';\n return http.post(url, telemetriesData);\n }\n return null;\n }\n}\n\nself.onDataUpdated = function() {\n\n try {\n if ($scope.dataKeyDetected) {\n if (!$scope.isFocused) {\n $scope.originalValue = self.ctx.data[0].data[0][1];\n $scope.attributeUpdateFormGroup.get('currentValue').patchValue(correctValue($scope.originalValue));\n self.ctx.detectChanges();\n }\n }\n } catch (e) {\n console.log(e);\n }\n}\n\nfunction correctValue(value) {\n if (typeof value !== \"number\") {\n return 0;\n }\n return value;\n}\n\nself.onResize = function() {\n\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n dataKeyOptional: true\n }\n}\n\nself.onDestroy = function() {\n\n}\n", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showLabel\":{\n \"title\":\"Show label\",\n \"type\":\"boolean\",\n \"default\":true\n },\n \"labelValue\": {\n \"title\": \"Label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"maxValue\": {\n \"title\": \"Max value\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"minValue\": {\n \"title\": \"Min value\",\n \"type\": \"number\",\n \"default\": \"\"\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\":true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showResultMessage\",\n \"showLabel\",\n \"labelValue\",\n \"requiredErrorMessage\",\n \"maxValue\",\n \"minValue\"\n ]\n}", "dataKeySettingsSchema": "{}\n", "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\":{},\"title\":\"Update integer timeseries\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" } } ] -} \ No newline at end of file +} From dfb26cc033de8f4c4650940f342ab52de6a083e0 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 18 Feb 2020 17:25:17 +0200 Subject: [PATCH 117/133] Date range navigator widget. Minor improvements. --- .../data/json/system/widget_bundles/date.json | 4 +- .../system/widget_bundles/gpio_widgets.json | 4 +- ui-ngx/package-lock.json | 5772 +++++++++-------- ui-ngx/package.json | 1 + ui-ngx/src/app/core/services/time.service.ts | 9 +- .../src/app/core/settings/settings.utils.ts | 2 + .../dashboard/dashboard.component.ts | 14 +- .../date-range-navigator-panel.component.html | 36 + .../date-range-navigator-panel.component.scss | 33 + .../date-range-navigator.component.html | 56 + .../date-range-navigator.component.scss | 109 + .../date-range-navigator.component.ts | 307 + .../date-range-navigator.models.ts | 109 + .../widget/lib/rpc/knob.component.ts | 1 + .../widget/lib/rpc/led-indicator.component.ts | 8 +- .../widget/lib/rpc/round-switch.component.ts | 8 +- .../widget/lib/rpc/switch.component.ts | 8 +- .../timeseries-table-widget.component.scss | 1 + .../widget/widget-components.module.ts | 11 +- .../home/models/dashboard-component.models.ts | 2 +- .../src/app/shared/models/time/time.models.ts | 44 +- ui-ngx/src/app/shared/shared.module.ts | 3 + 22 files changed, 3753 insertions(+), 2789 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator-panel.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator-panel.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator.models.ts diff --git a/application/src/main/data/json/system/widget_bundles/date.json b/application/src/main/data/json/system/widget_bundles/date.json index cdde7bcad3..da05b4e53c 100644 --- a/application/src/main/data/json/system/widget_bundles/date.json +++ b/application/src/main/data/json/system/widget_bundles/date.json @@ -13,9 +13,9 @@ "sizeX": 5, "sizeY": 5.5, "resources": [], - "templateHtml": "", + "templateHtml": "", "templateCss": "", - "controllerScript": "self.onInit = function() {\n scope = self.ctx.$scope;\n scope.ctx = self.ctx;\n}", + "controllerScript": "self.onInit = function() {\n}\n", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"hidePicker\": {\n \"title\": \"Hide date range picker\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"onePanel\": {\n \"title\": \"Date range picker one panel\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"autoConfirm\": {\n \"title\": \"Date range picker auto confirm\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"showTemplate\": {\n \"title\": \"Date range picker show template\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"firstDayOfWeek\": {\n \"title\": \"First day of the week\",\n \"type\": \"number\",\n \"default\": 1\n },\n \"hideInterval\": {\n \"title\": \"Hide interval\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"initialInterval\": {\n\t\t\t\t\"title\": \"Initial interval\",\n\t\t\t\t\"type\": \"string\",\n\t\t\t\t\"default\": \"week\"\n\t\t\t},\n \"hideStepSize\": {\n \"title\": \"Hide step size\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"stepSize\": {\n\t\t\t\t\"title\": \"Initial step size\",\n\t\t\t\t\"type\": \"string\",\n\t\t\t\t\"default\": \"day\"\n\t\t\t},\n \"hideLabels\": {\n \"title\": \"Hide labels\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"useSessionStorage\": {\n \"title\": \"Use session storage\",\n \"type\": \"boolean\",\n \"default\": true\n }\n }\n },\n \"form\": [\n \"hidePicker\",\n\t\t\"onePanel\",\n\t\t\"autoConfirm\",\n\t\t\"showTemplate\",\n\t\t\"firstDayOfWeek\",\n \"hideInterval\",\n {\n\t\t\t\"key\": \"initialInterval\",\n\t\t\t\"type\": \"rc-select\",\n\t\t\t\"multiple\": false,\n\t\t\t\"items\": [\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"hour\",\n\t\t\t\t\t\"label\": \"Hour\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"day\",\n\t\t\t\t\t\"label\": \"Day\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"week\",\n\t\t\t\t\t\"label\": \"Week\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"twoWeeks\",\n\t\t\t\t\t\"label\": \"2 weeks\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"month\",\n\t\t\t\t\t\"label\": \"Month\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"threeMonths\",\n\t\t\t\t\t\"label\": \"3 months\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"sixMonths\",\n\t\t\t\t\t\"label\": \"6 months\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n \"hideStepSize\",\n {\n\t\t\t\"key\": \"stepSize\",\n\t\t\t\"type\": \"rc-select\",\n\t\t\t\"multiple\": false,\n\t\t\t\"items\": [\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"hour\",\n\t\t\t\t\t\"label\": \"Hour\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"day\",\n\t\t\t\t\t\"label\": \"Day\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"week\",\n\t\t\t\t\t\"label\": \"Week\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"twoWeeks\",\n\t\t\t\t\t\"label\": \"2 weeks\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"month\",\n\t\t\t\t\t\"label\": \"Month\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"threeMonths\",\n\t\t\t\t\t\"label\": \"3 months\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"sixMonths\",\n\t\t\t\t\t\"label\": \"6 months\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t\"hideLabels\",\n\t\t\"useSessionStorage\"\n ]\n}", "dataKeySettingsSchema": "{}\n", "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\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"defaultInterval\":\"week\",\"stepSize\":\"day\"},\"title\":\"Date-range-navigator\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" diff --git a/application/src/main/data/json/system/widget_bundles/gpio_widgets.json b/application/src/main/data/json/system/widget_bundles/gpio_widgets.json index 1cc9a1635d..5d55f4d485 100644 --- a/application/src/main/data/json/system/widget_bundles/gpio_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/gpio_widgets.json @@ -13,7 +13,7 @@ "sizeX": 4, "sizeY": 2, "resources": [], - "templateHtml": "
\n
\n
\n
\n {{ cell.label }}\n
\n {{cell.pin}}\n \n \n \n \n {{cell.pin}}\n
\n {{ cell.label }}\n
\n
\n \n \n \n
\n
\n
\n {{rpcErrorText}}\n \n
", + "templateHtml": "
\n
\n
\n
\n {{ cell.label }}\n
\n {{cell.pin}}\n \n \n \n \n {{cell.pin}}\n
\n {{ cell.label }}\n
\n
\n \n \n \n
\n
\n
\n {{rpcErrorText}}\n \n
", "templateCss": ".error {\n font-size: 14px !important;\n color: maroon;/*rgb(250,250,250);*/\n background-color: transparent;\n padding: 6px;\n}\n\n.error span {\n margin: auto;\n}\n\n.gpio-panel {\n padding-top: 10px;\n white-space: nowrap;\n}\n\n.switch-panel {\n margin: 0;\n height: 32px;\n width: 66px;\n min-width: 66px;\n}\n\n.switch-panel mat-slide-toggle {\n margin: 0;\n width: 36px;\n min-width: 36px;\n}\n\n.switch-panel.col-0 mat-slide-toggle {\n margin-left: 8px;\n margin-right: 4px;\n}\n\n.switch-panel.col-1 mat-slide-toggle {\n margin-left: 4px;\n margin-right: 8px;\n}\n\n.gpio-row {\n height: 32px;\n}\n\n.pin {\n margin-top: auto;\n margin-bottom: auto;\n color: white;\n font-size: 12px;\n width: 16px;\n min-width: 16px;\n}\n\n.switch-panel.col-0 .pin {\n margin-left: auto;\n padding-left: 2px;\n text-align: right;\n}\n\n.switch-panel.col-1 .pin {\n margin-right: auto;\n \n text-align: left;\n}\n\n.gpio-left-label {\n margin-right: 8px;\n}\n\n.gpio-right-label {\n margin-left: 8px;\n}", "controllerScript": "var namespace;\nvar cssParser = new cssjs();\n\nself.onInit = function() {\n var utils = self.ctx.$injector.get(self.ctx.servicesMap.get('utils'));\n namespace = 'gpio-control-' + utils.guid();\n cssParser.testMode = false;\n cssParser.cssPreviewNamespace = namespace;\n self.ctx.$container.addClass(namespace);\n self.ctx.ngZone.run(function() {\n init(); \n });\n}\n\nfunction init() {\n \n var i, gpio;\n var scope = self.ctx.$scope;\n var settings = self.ctx.settings;\n scope.gpioList = [];\n for (var g = 0; g < settings.gpioList.length; g++) {\n gpio = settings.gpioList[g];\n scope.gpioList.push(\n {\n row: gpio.row,\n col: gpio.col,\n pin: gpio.pin,\n label: gpio.label,\n enabled: false\n }\n );\n }\n\n scope.requestTimeout = settings.requestTimeout || 1000;\n\n scope.switchPanelBackgroundColor = settings.switchPanelBackgroundColor || tinycolor('green').lighten(2).toRgbString();\n\n scope.gpioStatusRequest = {\n method: \"getGpioStatus\",\n paramsBody: \"{}\"\n };\n \n if (settings.gpioStatusRequest) {\n scope.gpioStatusRequest.method = settings.gpioStatusRequest.method || scope.gpioStatusRequest.method;\n scope.gpioStatusRequest.paramsBody = settings.gpioStatusRequest.paramsBody || scope.gpioStatusRequest.paramsBody;\n }\n \n scope.gpioStatusChangeRequest = {\n method: \"setGpioStatus\",\n paramsBody: \"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"\n };\n \n if (settings.gpioStatusChangeRequest) {\n scope.gpioStatusChangeRequest.method = settings.gpioStatusChangeRequest.method || scope.gpioStatusChangeRequest.method;\n scope.gpioStatusChangeRequest.paramsBody = settings.gpioStatusChangeRequest.paramsBody || scope.gpioStatusChangeRequest.paramsBody;\n }\n \n scope.parseGpioStatusFunction = \"return body[pin] === true;\";\n \n if (settings.parseGpioStatusFunction && settings.parseGpioStatusFunction.length > 0) {\n scope.parseGpioStatusFunction = settings.parseGpioStatusFunction;\n }\n \n scope.parseGpioStatusFunction = new Function(\"body, pin\", scope.parseGpioStatusFunction);\n \n function requestGpioStatus() {\n self.ctx.controlApi.sendTwoWayCommand(scope.gpioStatusRequest.method, \n scope.gpioStatusRequest.paramsBody, \n scope.requestTimeout)\n .subscribe(\n function success(responseBody) {\n for (var g = 0; g < scope.gpioList.length; g++) {\n var gpio = scope.gpioList[g];\n var enabled = scope.parseGpioStatusFunction.apply(this, [responseBody, gpio.pin]);\n gpio.enabled = enabled; \n self.ctx.detectChanges();\n }\n }\n );\n }\n \n function changeGpioStatus(gpio) {\n var pin = gpio.pin + '';\n var enabled = !gpio.enabled;\n enabled = enabled === true ? 'true' : 'false';\n var paramsBody = scope.gpioStatusChangeRequest.paramsBody;\n var requestBody = JSON.parse(paramsBody.replace(\"\\\"{$pin}\\\"\", pin).replace(\"\\\"{$enabled}\\\"\", enabled));\n self.ctx.controlApi.sendTwoWayCommand(scope.gpioStatusChangeRequest.method, \n requestBody, scope.requestTimeout)\n .subscribe(\n function success(responseBody) {\n var enabled = scope.parseGpioStatusFunction.apply(this, [responseBody, gpio.pin]);\n gpio.enabled = enabled;\n self.ctx.detectChanges();\n }\n );\n }\n \n scope.gpioCells = {};\n var rowCount = 0;\n for (i = 0; i < scope.gpioList.length; i++) {\n gpio = scope.gpioList[i];\n scope.gpioCells[gpio.row+'_'+gpio.col] = gpio;\n rowCount = Math.max(rowCount, gpio.row+1);\n }\n \n scope.prefferedRowHeight = 32;\n scope.rows = [];\n for (i = 0; i < rowCount; i++) {\n var row = [];\n for (var c =0; c<2;c++) {\n if (scope.gpioCells[i+'_'+c]) {\n row[c] = scope.gpioCells[i+'_'+c];\n } else {\n row[c] = null;\n }\n }\n scope.rows.push(row);\n }\n\n scope.gpioClick = function($event, gpio) {\n if (scope.rpcEnabled && !scope.executingRpcRequest) {\n changeGpioStatus(gpio);\n }\n };\n \n scope.gpioToggleChange = function($event, gpio) {\n gpio.enabled = !$event.checked;\n $event.source.toggle();\n self.ctx.detectChanges();\n }\n \n if (scope.rpcEnabled) {\n requestGpioStatus(); \n }\n \n self.onResize();\n}\n\nself.onResize = function() {\n var scope = self.ctx.$scope;\n var rowCount = scope.rows.length;\n var prefferedRowHeight = (self.ctx.height - 35)/rowCount;\n prefferedRowHeight = Math.min(32, prefferedRowHeight);\n prefferedRowHeight = Math.max(12, prefferedRowHeight);\n scope.prefferedRowHeight = prefferedRowHeight;\n var ratio = prefferedRowHeight/32;\n \n var css = '.mat-slide-toggle .mat-slide-toggle-bar {\\n' +\n ' height: ' + 14*ratio+'px;\\n'+\n ' width: ' + 36*ratio+'px;\\n'+\n '}\\n';\n css += '.mat-slide-toggle .mat-slide-toggle-thumb-container {\\n' +\n ' height: ' + 20*ratio+'px;\\n'+\n ' width: ' + 20*ratio+'px;\\n'+\n '}\\n';\n css += '.mat-slide-toggle .mat-slide-toggle-thumb {\\n' +\n ' height: ' + 20*ratio+'px;\\n'+\n ' width: ' + 20*ratio+'px;\\n'+\n '}\\n';\n css += '.mat-slide-toggle .mat-slide-toggle-ripple {\\n' +\n ' height: ' + 40*ratio+'px;\\n'+\n ' width: ' + 40*ratio+'px;\\n'+\n ' top: calc(50% - '+20*ratio+'px);\\n'+\n ' left: calc(50% - '+20*ratio+'px);\\n'+\n '}\\n';\n css += '.gpio-left-label, .gpio-right-label {\\n' +\n ' font-size: ' + 16*ratio+'px;\\n'+\n '}\\n';\n var pinsFontSize = Math.max(9, 12*ratio);\n css += '.pin {\\n' +\n ' font-size: ' + pinsFontSize+'px;\\n'+\n '}\\n';\n\n cssParser.createStyleElement(namespace, css);\n \n self.ctx.detectChanges();\n}\n\nself.onDestroy = function() {\n}\n", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"gpioList\": {\n \"title\": \"Gpio switches\",\n \"type\": \"array\",\n \"minItems\" : 1,\n \"items\": {\n \"title\": \"Gpio switch\",\n \"type\": \"object\",\n \"properties\": {\n \"pin\": {\n \"title\": \"Pin\",\n \"type\": \"number\"\n },\n \"label\": {\n \"title\": \"Label\",\n \"type\": \"string\"\n },\n \"row\": {\n \"title\": \"Row\",\n \"type\": \"number\"\n },\n \"col\": {\n \"title\": \"Column\",\n \"type\": \"number\"\n }\n },\n \"required\": [\"pin\", \"label\", \"row\", \"col\"]\n }\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"switchPanelBackgroundColor\": {\n \"title\": \"Switches panel background color\",\n \"type\": \"string\",\n \"default\": \"#008a00\"\n },\n \"gpioStatusRequest\": {\n \"title\": \"GPIO status request\",\n \"type\": \"object\",\n \"properties\": {\n \"method\": {\n \"title\": \"Method name\",\n \"type\": \"string\",\n \"default\": \"getGpioStatus\"\n },\n \"paramsBody\": {\n \"title\": \"Method body\",\n \"type\": \"string\",\n \"default\": \"{}\"\n }\n },\n \"required\": [\"method\", \"paramsBody\"]\n },\n \"gpioStatusChangeRequest\": {\n \"title\": \"GPIO status change request\",\n \"type\": \"object\",\n \"properties\": {\n \"method\": {\n \"title\": \"Method name\",\n \"type\": \"string\",\n \"default\": \"setGpioStatus\"\n },\n \"paramsBody\": {\n \"title\": \"Method body\",\n \"type\": \"string\",\n \"default\": \"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"\n }\n },\n \"required\": [\"method\", \"paramsBody\"]\n },\n \"parseGpioStatusFunction\": {\n \"title\": \"Parse gpio status function\",\n \"type\": \"string\",\n \"default\": \"return body[pin] === true;\"\n } \n },\n \"required\": [\"gpioList\", \n \"requestTimeout\",\n \"switchPanelBackgroundColor\",\n \"gpioStatusRequest\",\n \"gpioStatusChangeRequest\",\n \"parseGpioStatusFunction\"]\n },\n \"form\": [\n \"gpioList\",\n \"requestTimeout\",\n {\n \"key\": \"switchPanelBackgroundColor\",\n \"type\": \"color\"\n },\n {\n \"key\": \"gpioStatusRequest\",\n \"items\": [\n \"gpioStatusRequest.method\",\n {\n \"key\": \"gpioStatusRequest.paramsBody\",\n \"type\": \"json\"\n }\n ]\n },\n {\n \"key\": \"gpioStatusChangeRequest\",\n \"items\": [\n \"gpioStatusChangeRequest.method\",\n {\n \"key\": \"gpioStatusChangeRequest.paramsBody\",\n \"type\": \"json\"\n }\n ]\n },\n {\n \"key\": \"parseGpioStatusFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", @@ -45,7 +45,7 @@ "sizeX": 6, "sizeY": 10.5, "resources": [], - "templateHtml": "
\n
\n
\n
\n {{ cell.label }}\n
\n {{cell.pin}}\n \n \n \n \n {{cell.pin}}\n
\n {{ cell.label }}\n
\n
\n \n \n \n
\n
\n
\n {{rpcErrorText}}\n \n
", + "templateHtml": "
\n
\n
\n
\n {{ cell.label }}\n
\n {{cell.pin}}\n \n \n \n \n {{cell.pin}}\n
\n {{ cell.label }}\n
\n
\n \n \n \n
\n
\n
\n {{rpcErrorText}}\n \n
", "templateCss": ".error {\n font-size: 14px !important;\n color: maroon;/*rgb(250,250,250);*/\n background-color: transparent;\n padding: 6px;\n}\n\n.error span {\n margin: auto;\n}\n\n.gpio-panel {\n padding-top: 10px;\n white-space: nowrap;\n}\n\n.switch-panel {\n margin: 0;\n height: 32px;\n width: 66px;\n min-width: 66px;\n}\n\n.switch-panel mat-slide-toggle {\n margin: 0;\n width: 36px;\n min-width: 36px;\n}\n\n.switch-panel.col-0 mat-slide-toggle {\n margin-left: 8px;\n margin-right: 4px;\n}\n\n.switch-panel.col-1 mat-slide-toggle {\n margin-left: 4px;\n margin-right: 8px;\n}\n\n.gpio-row {\n height: 32px;\n}\n\n.pin {\n margin-top: auto;\n margin-bottom: auto;\n color: white;\n font-size: 12px;\n width: 16px;\n min-width: 16px;\n}\n\n.switch-panel.col-0 .pin {\n margin-left: auto;\n padding-left: 2px;\n text-align: right;\n}\n\n.switch-panel.col-1 .pin {\n margin-right: auto;\n \n text-align: left;\n}\n\n.gpio-left-label {\n margin-right: 8px;\n}\n\n.gpio-right-label {\n margin-left: 8px;\n}", "controllerScript": "var namespace;\nvar cssParser = new cssjs();\n\nself.onInit = function() {\n var utils = self.ctx.$injector.get(self.ctx.servicesMap.get('utils'));\n namespace = 'gpio-control-' + utils.guid();\n cssParser.testMode = false;\n cssParser.cssPreviewNamespace = namespace;\n self.ctx.$container.addClass(namespace);\n self.ctx.ngZone.run(function() {\n init(); \n });\n}\n\nfunction init() {\n \n var i, gpio;\n var scope = self.ctx.$scope;\n var settings = self.ctx.settings;\n scope.gpioList = [];\n for (var g = 0; g < settings.gpioList.length; g++) {\n gpio = settings.gpioList[g];\n scope.gpioList.push(\n {\n row: gpio.row,\n col: gpio.col,\n pin: gpio.pin,\n label: gpio.label,\n enabled: false\n }\n );\n }\n\n scope.requestTimeout = settings.requestTimeout || 1000;\n\n scope.switchPanelBackgroundColor = settings.switchPanelBackgroundColor || tinycolor('green').lighten(2).toRgbString();\n\n scope.gpioStatusRequest = {\n method: \"getGpioStatus\",\n paramsBody: \"{}\"\n };\n \n if (settings.gpioStatusRequest) {\n scope.gpioStatusRequest.method = settings.gpioStatusRequest.method || scope.gpioStatusRequest.method;\n scope.gpioStatusRequest.paramsBody = settings.gpioStatusRequest.paramsBody || scope.gpioStatusRequest.paramsBody;\n }\n \n scope.gpioStatusChangeRequest = {\n method: \"setGpioStatus\",\n paramsBody: \"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"\n };\n \n if (settings.gpioStatusChangeRequest) {\n scope.gpioStatusChangeRequest.method = settings.gpioStatusChangeRequest.method || scope.gpioStatusChangeRequest.method;\n scope.gpioStatusChangeRequest.paramsBody = settings.gpioStatusChangeRequest.paramsBody || scope.gpioStatusChangeRequest.paramsBody;\n }\n \n scope.parseGpioStatusFunction = \"return body[pin] === true;\";\n \n if (settings.parseGpioStatusFunction && settings.parseGpioStatusFunction.length > 0) {\n scope.parseGpioStatusFunction = settings.parseGpioStatusFunction;\n }\n \n scope.parseGpioStatusFunction = new Function(\"body, pin\", scope.parseGpioStatusFunction);\n \n function requestGpioStatus() {\n self.ctx.controlApi.sendTwoWayCommand(scope.gpioStatusRequest.method, \n scope.gpioStatusRequest.paramsBody, \n scope.requestTimeout)\n .subscribe(\n function success(responseBody) {\n for (var g = 0; g < scope.gpioList.length; g++) {\n var gpio = scope.gpioList[g];\n var enabled = scope.parseGpioStatusFunction.apply(this, [responseBody, gpio.pin]);\n gpio.enabled = enabled; \n self.ctx.detectChanges();\n }\n }\n );\n }\n \n function changeGpioStatus(gpio) {\n var pin = gpio.pin + '';\n var enabled = !gpio.enabled;\n enabled = enabled === true ? 'true' : 'false';\n var paramsBody = scope.gpioStatusChangeRequest.paramsBody;\n var requestBody = JSON.parse(paramsBody.replace(\"\\\"{$pin}\\\"\", pin).replace(\"\\\"{$enabled}\\\"\", enabled));\n self.ctx.controlApi.sendTwoWayCommand(scope.gpioStatusChangeRequest.method, \n requestBody, scope.requestTimeout)\n .subscribe(\n function success(responseBody) {\n var enabled = scope.parseGpioStatusFunction.apply(this, [responseBody, gpio.pin]);\n gpio.enabled = enabled;\n self.ctx.detectChanges();\n }\n );\n }\n \n scope.gpioCells = {};\n var rowCount = 0;\n for (i = 0; i < scope.gpioList.length; i++) {\n gpio = scope.gpioList[i];\n scope.gpioCells[gpio.row+'_'+gpio.col] = gpio;\n rowCount = Math.max(rowCount, gpio.row+1);\n }\n \n scope.prefferedRowHeight = 32;\n scope.rows = [];\n for (i = 0; i < rowCount; i++) {\n var row = [];\n for (var c =0; c<2;c++) {\n if (scope.gpioCells[i+'_'+c]) {\n row[c] = scope.gpioCells[i+'_'+c];\n } else {\n row[c] = null;\n }\n }\n scope.rows.push(row);\n }\n\n scope.gpioClick = function($event, gpio) {\n if (scope.rpcEnabled && !scope.executingRpcRequest) {\n changeGpioStatus(gpio);\n }\n };\n \n scope.gpioToggleChange = function($event, gpio) {\n gpio.enabled = !$event.checked;\n $event.source.toggle();\n self.ctx.detectChanges();\n }\n \n if (scope.rpcEnabled) {\n requestGpioStatus(); \n }\n \n self.onResize();\n}\n\nself.onResize = function() {\n var scope = self.ctx.$scope;\n var rowCount = scope.rows.length;\n var prefferedRowHeight = (self.ctx.height - 35)/rowCount;\n prefferedRowHeight = Math.min(32, prefferedRowHeight);\n prefferedRowHeight = Math.max(12, prefferedRowHeight);\n scope.prefferedRowHeight = prefferedRowHeight;\n var ratio = prefferedRowHeight/32;\n \n var css = '.mat-slide-toggle .mat-slide-toggle-bar {\\n' +\n ' height: ' + 14*ratio+'px;\\n'+\n ' width: ' + 36*ratio+'px;\\n'+\n '}\\n';\n css += '.mat-slide-toggle .mat-slide-toggle-thumb-container {\\n' +\n ' height: ' + 20*ratio+'px;\\n'+\n ' width: ' + 20*ratio+'px;\\n'+\n '}\\n';\n css += '.mat-slide-toggle .mat-slide-toggle-thumb {\\n' +\n ' height: ' + 20*ratio+'px;\\n'+\n ' width: ' + 20*ratio+'px;\\n'+\n '}\\n';\n css += '.mat-slide-toggle .mat-slide-toggle-ripple {\\n' +\n ' height: ' + 40*ratio+'px;\\n'+\n ' width: ' + 40*ratio+'px;\\n'+\n ' top: calc(50% - '+20*ratio+'px);\\n'+\n ' left: calc(50% - '+20*ratio+'px);\\n'+\n '}\\n';\n css += '.gpio-left-label, .gpio-right-label {\\n' +\n ' font-size: ' + 16*ratio+'px;\\n'+\n '}\\n';\n var pinsFontSize = Math.max(9, 12*ratio);\n css += '.pin {\\n' +\n ' font-size: ' + pinsFontSize+'px;\\n'+\n '}\\n';\n\n cssParser.createStyleElement(namespace, css);\n \n self.ctx.detectChanges();\n}\n\nself.onDestroy = function() {\n}\n", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"gpioList\": {\n \"title\": \"Gpio switches\",\n \"type\": \"array\",\n \"minItems\" : 1,\n \"items\": {\n \"title\": \"Gpio switch\",\n \"type\": \"object\",\n \"properties\": {\n \"pin\": {\n \"title\": \"Pin\",\n \"type\": \"number\"\n },\n \"label\": {\n \"title\": \"Label\",\n \"type\": \"string\"\n },\n \"row\": {\n \"title\": \"Row\",\n \"type\": \"number\"\n },\n \"col\": {\n \"title\": \"Column\",\n \"type\": \"number\"\n }\n },\n \"required\": [\"pin\", \"label\", \"row\", \"col\"]\n }\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"switchPanelBackgroundColor\": {\n \"title\": \"Switches panel background color\",\n \"type\": \"string\",\n \"default\": \"#008a00\"\n },\n \"gpioStatusRequest\": {\n \"title\": \"GPIO status request\",\n \"type\": \"object\",\n \"properties\": {\n \"method\": {\n \"title\": \"Method name\",\n \"type\": \"string\",\n \"default\": \"getGpioStatus\"\n },\n \"paramsBody\": {\n \"title\": \"Method body\",\n \"type\": \"string\",\n \"default\": \"{}\"\n }\n },\n \"required\": [\"method\", \"paramsBody\"]\n },\n \"gpioStatusChangeRequest\": {\n \"title\": \"GPIO status change request\",\n \"type\": \"object\",\n \"properties\": {\n \"method\": {\n \"title\": \"Method name\",\n \"type\": \"string\",\n \"default\": \"setGpioStatus\"\n },\n \"paramsBody\": {\n \"title\": \"Method body\",\n \"type\": \"string\",\n \"default\": \"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"\n }\n },\n \"required\": [\"method\", \"paramsBody\"]\n },\n \"parseGpioStatusFunction\": {\n \"title\": \"Parse gpio status function\",\n \"type\": \"string\",\n \"default\": \"return body[pin] === true;\"\n } \n },\n \"required\": [\"gpioList\", \n \"requestTimeout\",\n \"switchPanelBackgroundColor\",\n \"gpioStatusRequest\",\n \"gpioStatusChangeRequest\",\n \"parseGpioStatusFunction\"]\n },\n \"form\": [\n \"gpioList\",\n \"requestTimeout\",\n {\n \"key\": \"switchPanelBackgroundColor\",\n \"type\": \"color\"\n },\n {\n \"key\": \"gpioStatusRequest\",\n \"items\": [\n \"gpioStatusRequest.method\",\n {\n \"key\": \"gpioStatusRequest.paramsBody\",\n \"type\": \"json\"\n }\n ]\n },\n {\n \"key\": \"gpioStatusChangeRequest\",\n \"items\": [\n \"gpioStatusChangeRequest.method\",\n {\n \"key\": \"gpioStatusChangeRequest.paramsBody\",\n \"type\": \"json\"\n }\n ]\n },\n {\n \"key\": \"parseGpioStatusFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", diff --git a/ui-ngx/package-lock.json b/ui-ngx/package-lock.json index 46da932a41..d926ee5c35 100644 --- a/ui-ngx/package-lock.json +++ b/ui-ngx/package-lock.json @@ -19,12 +19,12 @@ } }, "@angular-devkit/architect": { - "version": "0.900.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.900.1.tgz", - "integrity": "sha512-zzB3J0fXFoYeJpgF5tsmZ7byygzjJn1IPiXBdnbNqcMbil1OPOhq+KdD4ZFPyXNwBQ3w02kOwPdNqB++jbPmlQ==", + "version": "0.900.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.900.2.tgz", + "integrity": "sha512-uClqp4QEY/m6CB7SsNZGdVNTEgMzkI1Fkt0TOdE9huN1iCi/0+h3nQb+NZ1vBqD2afg9EqDwIPu2KCU0p1BR2A==", "dev": true, "requires": { - "@angular-devkit/core": "9.0.1", + "@angular-devkit/core": "9.0.2", "rxjs": "6.5.3" }, "dependencies": { @@ -40,19 +40,19 @@ } }, "@angular-devkit/build-angular": { - "version": "0.900.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.900.1.tgz", - "integrity": "sha512-e1/EiNI9UAKJxI9+7KA59A15Rkx2QA86evb9iUuwxWGvIsTsN/sg/oXUZA//nTUQTAht+qWJp3I2amd/nyQZLQ==", + "version": "0.900.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.900.2.tgz", + "integrity": "sha512-w1FHd+Ub0YO1/Xlz+SrSxbFWbJVW0jmR++fABWreh04XHGtC8kqsqP6VY8DUYBO9PcD5JyB5uG9TBxVLVR/G/w==", "dev": true, "requires": { - "@angular-devkit/architect": "0.900.1", - "@angular-devkit/build-optimizer": "0.900.1", - "@angular-devkit/build-webpack": "0.900.1", - "@angular-devkit/core": "9.0.1", + "@angular-devkit/architect": "0.900.2", + "@angular-devkit/build-optimizer": "0.900.2", + "@angular-devkit/build-webpack": "0.900.2", + "@angular-devkit/core": "9.0.2", "@babel/core": "7.7.7", "@babel/generator": "7.7.7", "@babel/preset-env": "7.7.7", - "@ngtools/webpack": "9.0.1", + "@ngtools/webpack": "9.0.2", "ajv": "6.10.2", "autoprefixer": "9.7.1", "babel-loader": "8.0.6", @@ -108,35 +108,16 @@ "worker-plugin": "3.2.0" }, "dependencies": { - "@babel/generator": { - "version": "7.7.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.7.7.tgz", - "integrity": "sha512-/AOIBpHh/JU1l0ZFS4kiRCBnLi6OTHzh0RPk3h9isBxkkqELtQNFi1Vr/tiG9p1yfoUdKVwISuXWQR+hwwM4VQ==", - "dev": true, - "requires": { - "@babel/types": "^7.7.4", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", + "ajv": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", + "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", "dev": true, "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, "core-js": { @@ -145,6 +126,12 @@ "integrity": "sha512-AHPTNKzyB+YwgDWoSOCaid9PUSEF6781vsfiK8qUz62zRR448/XgK2NtCbpiUGizbep8Lrpt0Du19PpGGZvw3Q==", "dev": true }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, "glob": { "version": "7.1.5", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.5.tgz", @@ -165,15 +152,6 @@ "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", "dev": true }, - "rimraf": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.0.tgz", - "integrity": "sha512-NDGVxTsjqfunkds7CqsOiEnxln4Bo7Nddl3XhS4pXg5OzwkLqJ971ZVAAnB+DDLnF76N+VnDEiBHaVV8I06SUg==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, "rxjs": { "version": "6.5.3", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz", @@ -198,9 +176,9 @@ } }, "@angular-devkit/build-optimizer": { - "version": "0.900.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.900.1.tgz", - "integrity": "sha512-EnIU+ogiJrUPf8+fuPE5xQ+j/qUZDZ/SmLs8XAOmvoOBpZ0vPNedrHBHCxmV+ACbCxHGmIKQ/ZL29XUYVasteg==", + "version": "0.900.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.900.2.tgz", + "integrity": "sha512-4jcjYKjGvUj1Q4vqQSUU0JT1LXOh7qC7vWCK+bbAsW77wAavtbKFt2mDjB2DMIRFzt9lSULi0Z+JVOD9KUzk2g==", "dev": true, "requires": { "loader-utils": "1.2.3", @@ -225,13 +203,13 @@ } }, "@angular-devkit/build-webpack": { - "version": "0.900.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.900.1.tgz", - "integrity": "sha512-GwV+jht42S2XZZbvy07mXqZ5us9ppbIi/gCL5SiUh+xtSdZGbfE6RoFZXmeOuxBn9FY0vUMTFtKCK5Mx8O3WYg==", + "version": "0.900.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.900.2.tgz", + "integrity": "sha512-DiHUSO352NV9OcXB8cZY8gLijrUg0SIbPwrKUTjx1prZMJKa+MqWDpwhleVsM1VRyUH3qMTzhaUFmw+hqdR0BQ==", "dev": true, "requires": { - "@angular-devkit/architect": "0.900.1", - "@angular-devkit/core": "9.0.1", + "@angular-devkit/architect": "0.900.2", + "@angular-devkit/core": "9.0.2", "rxjs": "6.5.3" }, "dependencies": { @@ -247,9 +225,9 @@ } }, "@angular-devkit/core": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-9.0.1.tgz", - "integrity": "sha512-HboJI/x+SJD9clSOAMjHRv0eXAGRAdEaqJGmjDfdFMP2wznfsBiC6cgcHC17oM4jRWFhmWMR8Omc7CjLZJawJg==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-9.0.2.tgz", + "integrity": "sha512-lEmfYs7+oHmXEQ3y97QGm73zs7i6chpx+ZSaBUvMM9oCKj/lytcn+diVG+t4hMavH6TK0lai7DO1rAbYkbmdrA==", "dev": true, "requires": { "ajv": "6.10.2", @@ -259,6 +237,30 @@ "source-map": "0.7.3" }, "dependencies": { + "ajv": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", + "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, "rxjs": { "version": "6.5.3", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz", @@ -277,12 +279,12 @@ } }, "@angular-devkit/schematics": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-9.0.1.tgz", - "integrity": "sha512-Cuub9eJm1TWygKTOowRbxMASA8QWeHWzNEU2V3TqUF1Tqy/iPf4cpuMijkFysXjTn2bi2HA9t26AwQkwymbliA==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-9.0.2.tgz", + "integrity": "sha512-+MiSBWErz8hxcbyHioCQtTnFpbqaoCEQEknK0vCb15fFEY2Hi3u2TXK59QNKsqn8w+Mye5dHYhwmpsAC8Wcgtw==", "dev": true, "requires": { - "@angular-devkit/core": "9.0.1", + "@angular-devkit/core": "9.0.2", "ora": "4.0.2", "rxjs": "6.5.3" }, @@ -299,9 +301,9 @@ } }, "@angular/animations": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-9.0.0.tgz", - "integrity": "sha512-jB8+SC3vMztW5zt5UYVmtVwqIWE33UyEjbP5JPba3I3bLRK5E059LcJmN1rSdJHItgIAdG9Y1I0WJ6aiSFyp4Q==" + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-9.0.1.tgz", + "integrity": "sha512-R0FLhAfylFIiRArhtLOUokOAVtWCH20ocRXo6E8HHOc3fbaUS9ci3rIbFZQkaAv9RgZfKewrcV6Wa3TY905w5g==" }, "@angular/cdk": { "version": "9.0.0", @@ -312,16 +314,16 @@ } }, "@angular/cli": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-9.0.1.tgz", - "integrity": "sha512-/nykTIqZq1plxaXVoMzAqjnExGhkYoSoq88AE4Mb31d6n/SW2DFh62C3hze+atI6YLqeFaPhYuA5zG+z3oOXbQ==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-9.0.2.tgz", + "integrity": "sha512-ih3bnvav94MXI9YpwJ4AaETfUGwzc+S2jg4vkfYMuBeWO8kJ7Ma4f2ZriIwWyfHWHlBLHDF6OjAVdisBKPpQag==", "dev": true, "requires": { - "@angular-devkit/architect": "0.900.1", - "@angular-devkit/core": "9.0.1", - "@angular-devkit/schematics": "9.0.1", - "@schematics/angular": "9.0.1", - "@schematics/update": "0.900.1", + "@angular-devkit/architect": "0.900.2", + "@angular-devkit/core": "9.0.2", + "@angular-devkit/schematics": "9.0.2", + "@schematics/angular": "9.0.2", + "@schematics/update": "0.900.2", "@yarnpkg/lockfile": "1.1.0", "ansi-colors": "4.1.1", "debug": "^4.1.1", @@ -354,21 +356,6 @@ "ms": "^2.1.1" } }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "rimraf": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.0.tgz", - "integrity": "sha512-NDGVxTsjqfunkds7CqsOiEnxln4Bo7Nddl3XhS4pXg5OzwkLqJ971ZVAAnB+DDLnF76N+VnDEiBHaVV8I06SUg==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -378,19 +365,19 @@ } }, "@angular/common": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-9.0.0.tgz", - "integrity": "sha512-ZMmEClGtUNJwV5CBlqcSHPIsNyz6WU/GvKWFzJ5VZc68oeg1e7lqfNMNIC47TjyolNJ7VSpNlyrKjzfdBlmqVw==" + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-9.0.1.tgz", + "integrity": "sha512-40jbKdCb4xi6NTzLt1kE0V/X7JxCLLo8eUEr3Z34Z9Ljnd4LC+/CkuThPdQJ3HW1Z8r5SWXj+rES+sn75YNVmA==" }, "@angular/compiler": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-9.0.0.tgz", - "integrity": "sha512-ctjwuntPfZZT2mNj2NDIVu51t9cvbhl/16epc5xEwyzyDt76pX9UgwvY+MbXrf/C/FWwdtmNtfP698BKI+9leQ==" + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-9.0.1.tgz", + "integrity": "sha512-ldamsPzIx+FLT/IYBqwsFL6qbP3BDgvPQa4Y3F/gFXDsoe+VTY5qwJfhr2iLbtF+fYomwOgY2kSL42BVQL873Q==" }, "@angular/compiler-cli": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-9.0.0.tgz", - "integrity": "sha512-6L3swd3Z2ceAapmioml6z7yu3bYC2aVm3/rgK7eCoZtPcevuvTpGnXcFSVvNgByV51GntgInThPbMx0xY23Rvw==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-9.0.1.tgz", + "integrity": "sha512-HxJAXr1TWoqVzR7pRe89UjWnu3ESJzo+gjWWtv1NtDMwUKQ2JHWmC3yp/U0URprA03Ii8lXlrZWBjps04ZIlAg==", "dev": true, "requires": { "canonical-path": "1.0.0", @@ -412,12 +399,27 @@ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, "emoji-regex": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", "dev": true }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, "fs-extra": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.2.tgz", @@ -435,10 +437,44 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", + "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, "require-main-filename": { @@ -505,9 +541,9 @@ } }, "@angular/core": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-9.0.0.tgz", - "integrity": "sha512-6Pxgsrf0qF9iFFqmIcWmjJGkkCaCm6V5QNnxMy2KloO3SDq6QuMVRbN9RtC8Urmo25LP+eZ6ZgYqFYpdD8Hd9w==" + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-9.0.1.tgz", + "integrity": "sha512-q/3VLGM98euB/ZluSuMqvWyQb563iabRcVkC/DrHqCQMadV1ZpvuOgf8Gm092d8GY/iC4CGlTsN0wiVapMxplQ==" }, "@angular/flex-layout": { "version": "9.0.0-beta.29", @@ -515,14 +551,14 @@ "integrity": "sha512-93sxR+kYfYMOdnlWL0Q77FZ428gg8XnBu0YZm6GsCdkw/vLggIT/G1ZAqHlCPIODt6pxmCJ5KXh4ShvniIYDsA==" }, "@angular/forms": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-9.0.0.tgz", - "integrity": "sha512-SIYJc0Rgaihow1t+iiwSFGEvvRgssgUuxwIYbMfCp1Sx513K+JX9nVFXqU+dcGj/eF1u5wwYwbvlVyuMQLzmXg==" + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-9.0.1.tgz", + "integrity": "sha512-yzzlCslWp7IiFSYjSGNqexPmnKn9xhpT8FKzxNT0qEpQ+SieQ7apsjvMfR3TCip0Nnfus2qTh3kz1ZCaawAcjQ==" }, "@angular/language-service": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-9.0.0.tgz", - "integrity": "sha512-tOMtXY8DFpTWMF77BOTXZmMMtqvdy6fbyOkJSccn6VatcPrNXOs5rKur+KNwdSlK+djjss6Y+LA8fQAvjNvUqw==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-9.0.1.tgz", + "integrity": "sha512-e/8CGATX7C0ElwBk6QjCfWk7A6lwikrBR1cesNu1kNwneZkiIeIel1jklbDUT0NFr4C2/FdBu2Z3GbvDeCO8Vw==", "dev": true }, "@angular/material": { @@ -531,19 +567,19 @@ "integrity": "sha512-QxN2rmR5mvg2YE1NoIGWLpbnmcJq0iFidzy6odzvN17+XkoCJBZ65IdYsHrJgfwGpoIy6bywuixrDHHcSh9I5w==" }, "@angular/platform-browser": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-9.0.0.tgz", - "integrity": "sha512-2PR/o57HjZvKEnAF8ODeqxmeC90oth9dLTMrJNoI5MET0IeErKeI/9Sl5cLQuXC+lSVN5rOMCvDb74VWSno5yw==" + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-9.0.1.tgz", + "integrity": "sha512-0o2aRxbQ3xZ/ZeLXajDqhrRK6vcICzdJ7GKvPgZxdohnnJ7JN1qp8U7J4aEotPqfSAde/aD2JvoDDtKZ0XIDWg==" }, "@angular/platform-browser-dynamic": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-9.0.0.tgz", - "integrity": "sha512-F1kbEpmDottTemRPEOAz2Te5ABVJ7wypfzBllxqXbdxPHvYLfL8db2dXyiGqABQ3ZFHPLNilrkUTy0sbuuU4OA==" + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-9.0.1.tgz", + "integrity": "sha512-DslT339T+TBt4jUlXMblPR4IghXtykB+jQctm02G4AJUlvMa4b798N1oM6sD5F8NmBMa6beZ2dcRJ07f75LVBA==" }, "@angular/router": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-9.0.0.tgz", - "integrity": "sha512-yyOcStpgN5t8wGRNO85mo0jplXkntP+v2tmSxNx45pahqmofSFm+QCEFa2zHQuMr7NoiGERhd0Tae7NDCCjtjA==" + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-9.0.1.tgz", + "integrity": "sha512-pHLDooNvXEUtjYANWtJ7fMxG9l2mDJgPphOi/S6c27U5yNf0NVk+Qh3kuuNi2hQQ5RaR4jdRyCQePD2H4g2+/g==" }, "@auth0/angular-jwt": { "version": "4.0.0", @@ -554,12 +590,12 @@ } }, "@babel/code-frame": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", - "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", "dev": true, "requires": { - "@babel/highlight": "^7.0.0" + "@babel/highlight": "^7.8.3" } }, "@babel/core": { @@ -584,125 +620,6 @@ "source-map": "^0.5.0" }, "dependencies": { - "@babel/generator": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", - "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", - "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", - "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", - "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/highlight": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", - "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", - "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", - "dev": true - }, - "@babel/template": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", - "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.8.3" - } - } - } - }, - "@babel/traverse": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", - "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.4", - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.8.4", - "@babel/types": "^7.8.3", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.8.3" - } - } - } - }, - "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -721,18 +638,6 @@ "minimist": "^1.2.0" } }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -742,23 +647,17 @@ } }, "@babel/generator": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.7.2.tgz", - "integrity": "sha512-WthSArvAjYLz4TcbKOi88me+KmDJdKSlfwwN8CnUYn9jBkzhq0ZEPuBfkAWIvjJ3AdEV1Cf/+eSQTnp3IDJKlQ==", + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.7.7.tgz", + "integrity": "sha512-/AOIBpHh/JU1l0ZFS4kiRCBnLi6OTHzh0RPk3h9isBxkkqELtQNFi1Vr/tiG9p1yfoUdKVwISuXWQR+hwwM4VQ==", "dev": true, "requires": { - "@babel/types": "^7.7.2", + "@babel/types": "^7.7.4", "jsesc": "^2.5.1", "lodash": "^4.17.13", "source-map": "^0.5.0" }, "dependencies": { - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -774,19 +673,6 @@ "dev": true, "requires": { "@babel/types": "^7.8.3" - }, - "dependencies": { - "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - } } }, "@babel/helper-builder-binary-assignment-operator-visitor": { @@ -797,19 +683,6 @@ "requires": { "@babel/helper-explode-assignable-expression": "^7.8.3", "@babel/types": "^7.8.3" - }, - "dependencies": { - "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - } } }, "@babel/helper-call-delegate": { @@ -821,135 +694,6 @@ "@babel/helper-hoist-variables": "^7.8.3", "@babel/traverse": "^7.8.3", "@babel/types": "^7.8.3" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.8.3" - } - }, - "@babel/generator": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", - "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", - "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", - "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", - "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/highlight": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", - "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", - "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", - "dev": true - }, - "@babel/template": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", - "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/traverse": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", - "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.4", - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.8.4", - "@babel/types": "^7.8.3", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" - } - }, - "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } } }, "@babel/helper-create-regexp-features-plugin": { @@ -971,76 +715,6 @@ "@babel/helper-function-name": "^7.8.3", "@babel/types": "^7.8.3", "lodash": "^4.17.13" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.8.3" - } - }, - "@babel/helper-function-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", - "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", - "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/highlight": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", - "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", - "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", - "dev": true - }, - "@babel/template": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", - "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - } } }, "@babel/helper-explode-assignable-expression": { @@ -1051,155 +725,26 @@ "requires": { "@babel/traverse": "^7.8.3", "@babel/types": "^7.8.3" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.8.3" - } - }, - "@babel/generator": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", - "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", - "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", - "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", - "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/highlight": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", - "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", - "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", - "dev": true - }, - "@babel/template": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", - "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/traverse": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", - "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.4", - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.8.4", - "@babel/types": "^7.8.3", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" - } - }, - "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } } }, "@babel/helper-function-name": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.7.0.tgz", - "integrity": "sha512-tDsJgMUAP00Ugv8O2aGEua5I2apkaQO7lBGUq1ocwN3G23JE5Dcq0uh3GvFTChPa4b40AWiAsLvCZOA2rdnQ7Q==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", + "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.7.0", - "@babel/template": "^7.7.0", - "@babel/types": "^7.7.0" + "@babel/helper-get-function-arity": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/types": "^7.8.3" } }, "@babel/helper-get-function-arity": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.0.tgz", - "integrity": "sha512-tLdojOTz4vWcEnHWHCuPN5P85JLZWbm5Fx5ZsMEMPhF3Uoe3O7awrbM2nQ04bDOUToH/2tH/ezKEOR8zEYzqyw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", + "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", "dev": true, "requires": { - "@babel/types": "^7.7.0" + "@babel/types": "^7.8.3" } }, "@babel/helper-hoist-variables": { @@ -1209,19 +754,6 @@ "dev": true, "requires": { "@babel/types": "^7.8.3" - }, - "dependencies": { - "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - } } }, "@babel/helper-member-expression-to-functions": { @@ -1231,19 +763,6 @@ "dev": true, "requires": { "@babel/types": "^7.8.3" - }, - "dependencies": { - "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - } } }, "@babel/helper-module-imports": { @@ -1253,19 +772,6 @@ "dev": true, "requires": { "@babel/types": "^7.8.3" - }, - "dependencies": { - "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - } } }, "@babel/helper-module-transforms": { @@ -1280,65 +786,6 @@ "@babel/template": "^7.8.3", "@babel/types": "^7.8.3", "lodash": "^4.17.13" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.8.3" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", - "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/highlight": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", - "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", - "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", - "dev": true - }, - "@babel/template": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", - "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - } } }, "@babel/helper-optimise-call-expression": { @@ -1348,19 +795,6 @@ "dev": true, "requires": { "@babel/types": "^7.8.3" - }, - "dependencies": { - "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - } } }, "@babel/helper-plugin-utils": { @@ -1389,135 +823,6 @@ "@babel/template": "^7.8.3", "@babel/traverse": "^7.8.3", "@babel/types": "^7.8.3" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.8.3" - } - }, - "@babel/generator": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", - "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", - "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", - "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", - "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/highlight": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", - "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", - "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", - "dev": true - }, - "@babel/template": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", - "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/traverse": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", - "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.4", - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.8.4", - "@babel/types": "^7.8.3", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" - } - }, - "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } } }, "@babel/helper-replace-supers": { @@ -1530,135 +835,6 @@ "@babel/helper-optimise-call-expression": "^7.8.3", "@babel/traverse": "^7.8.3", "@babel/types": "^7.8.3" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.8.3" - } - }, - "@babel/generator": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", - "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", - "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", - "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", - "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/highlight": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", - "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", - "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", - "dev": true - }, - "@babel/template": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", - "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/traverse": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", - "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.4", - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.8.4", - "@babel/types": "^7.8.3", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" - } - }, - "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } } }, "@babel/helper-simple-access": { @@ -1669,65 +845,15 @@ "requires": { "@babel/template": "^7.8.3", "@babel/types": "^7.8.3" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.8.3" - } - }, - "@babel/highlight": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", - "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", - "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", - "dev": true - }, - "@babel/template": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", - "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - } } }, "@babel/helper-split-export-declaration": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.0.tgz", - "integrity": "sha512-HgYSI8rH08neWlAH3CcdkFg9qX9YsZysZI5GD8LjhQib/mM0jGOZOVkoUiiV2Hu978fRtjtsGsW6w0pKHUWtqA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", + "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", "dev": true, "requires": { - "@babel/types": "^7.7.0" + "@babel/types": "^7.8.3" } }, "@babel/helper-wrap-function": { @@ -1740,135 +866,6 @@ "@babel/template": "^7.8.3", "@babel/traverse": "^7.8.3", "@babel/types": "^7.8.3" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.8.3" - } - }, - "@babel/generator": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", - "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", - "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", - "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", - "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/highlight": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", - "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", - "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", - "dev": true - }, - "@babel/template": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", - "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/traverse": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", - "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.4", - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.8.4", - "@babel/types": "^7.8.3", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" - } - }, - "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } } }, "@babel/helpers": { @@ -1880,141 +877,12 @@ "@babel/template": "^7.8.3", "@babel/traverse": "^7.8.4", "@babel/types": "^7.8.3" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.8.3" - } - }, - "@babel/generator": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", - "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", - "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", - "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", - "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/highlight": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", - "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", - "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", - "dev": true - }, - "@babel/template": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", - "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/traverse": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", - "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.4", - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.8.4", - "@babel/types": "^7.8.3", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" - } - }, - "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } } }, "@babel/highlight": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz", - "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", + "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", "dev": true, "requires": { "chalk": "^2.0.0", @@ -2023,9 +891,9 @@ } }, "@babel/parser": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.3.tgz", - "integrity": "sha512-bqv+iCo9i+uLVbI0ILzKkvMorqxouI+GbV13ivcARXn9NNEabi2IEz912IgNpT/60BNXac5dgcfjb94NjsF33A==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", + "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", "dev": true }, "@babel/plugin-proposal-async-generator-functions": { @@ -2196,85 +1064,6 @@ "@babel/helper-replace-supers": "^7.8.3", "@babel/helper-split-export-declaration": "^7.8.3", "globals": "^11.1.0" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.8.3" - } - }, - "@babel/helper-function-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", - "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", - "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", - "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/highlight": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", - "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", - "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", - "dev": true - }, - "@babel/template": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", - "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - } } }, "@babel/plugin-transform-computed-properties": { @@ -2341,76 +1130,6 @@ "requires": { "@babel/helper-function-name": "^7.8.3", "@babel/helper-plugin-utils": "^7.8.3" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.8.3" - } - }, - "@babel/helper-function-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", - "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", - "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/highlight": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", - "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", - "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", - "dev": true - }, - "@babel/template": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", - "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - } } }, "@babel/plugin-transform-literals": { @@ -2513,28 +1232,6 @@ "@babel/helper-call-delegate": "^7.8.3", "@babel/helper-get-function-arity": "^7.8.3", "@babel/helper-plugin-utils": "^7.8.3" - }, - "dependencies": { - "@babel/helper-get-function-arity": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", - "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - } } }, "@babel/plugin-transform-property-literals": { @@ -2678,19 +1375,6 @@ "invariant": "^2.2.2", "js-levenshtein": "^1.1.3", "semver": "^5.5.0" - }, - "dependencies": { - "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - } } }, "@babel/runtime": { @@ -2702,33 +1386,45 @@ } }, "@babel/template": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.0.tgz", - "integrity": "sha512-OKcwSYOW1mhWbnTBgQY5lvg1Fxg+VyfQGjcBduZFljfc044J5iDlnDSfhQ867O17XHiSCxYHUxHg2b7ryitbUQ==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", + "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.7.0", - "@babel/types": "^7.7.0" + "@babel/code-frame": "^7.8.3", + "@babel/parser": "^7.8.3", + "@babel/types": "^7.8.3" } }, "@babel/traverse": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.7.2.tgz", - "integrity": "sha512-TM01cXib2+rgIZrGJOLaHV/iZUAxf4A0dt5auY6KNZ+cm6aschuJGqKJM3ROTt3raPUdIDk9siAufIFEleRwtw==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", + "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", "dev": true, "requires": { - "@babel/code-frame": "^7.5.5", - "@babel/generator": "^7.7.2", - "@babel/helper-function-name": "^7.7.0", - "@babel/helper-split-export-declaration": "^7.7.0", - "@babel/parser": "^7.7.2", - "@babel/types": "^7.7.2", + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.8.4", + "@babel/helper-function-name": "^7.8.3", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/parser": "^7.8.4", + "@babel/types": "^7.8.3", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.13" }, "dependencies": { + "@babel/generator": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", + "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", + "dev": true, + "requires": { + "@babel/types": "^7.8.3", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + } + }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -2738,57 +1434,36 @@ "ms": "^2.1.1" } }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "dev": true } } }, "@babel/types": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.2.tgz", - "integrity": "sha512-YTf6PXoh3+eZgRCBzzP25Bugd2ngmpQVrk7kXX0i5N9BO7TFBtIgZYs7WtxtOGs8e6A4ZI7ECkbBCEHeXocvOA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", + "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", "dev": true, "requires": { "esutils": "^2.0.2", "lodash": "^4.17.13", "to-fast-properties": "^2.0.0" - }, - "dependencies": { - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - } } }, "@date-io/core": { - "version": "1.3.13", - "resolved": "https://registry.npmjs.org/@date-io/core/-/core-1.3.13.tgz", - "integrity": "sha512-AlEKV7TxjeK+jxWVKcCFrfYAk8spX9aCyiToFIiLPtfQbsjmRGLIhb5VZgptQcJdHtLXo7+m0DuurwFgUToQuA==" + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@date-io/core/-/core-2.4.0.tgz", + "integrity": "sha512-XUr4TSwFmthcCn5QYnGqobbnBqOsSyCggRfvieMQHPSz5zei8KYpw4xlvFFQfu/MI3CmCHDjWMkVaPy/uFIDNA==" }, "@date-io/date-fns": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@date-io/date-fns/-/date-fns-2.3.0.tgz", - "integrity": "sha512-WlDNt3xPjhK/7pcnDU0En9qk6dF0yu2ve7qiDT466YHHxm++C3jsaJgODZ+avLdnhwxNJPnp8LwABsh2vtYcLQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@date-io/date-fns/-/date-fns-2.4.0.tgz", + "integrity": "sha512-DYlfSiTs6GuPmbAmJ9ws7aaOd8f89lVBBqU+71w4Qxxv9DW2qQvBWwOkvCJ5qSp4ae4+PtCzy6JKQDKsdgBaJg==", "requires": { - "@date-io/core": "^2.3.0" - }, - "dependencies": { - "@date-io/core": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@date-io/core/-/core-2.3.0.tgz", - "integrity": "sha512-aOmiF9tDtRCGuzGfBILKsd+zigEnGMBqXWNlr4ZDA6Y8r4JxTjIpvlCZrmtDP/0x2T+og28uhwBjJbCGFzdiCA==" - } + "@date-io/core": "^2.4.0" } }, "@emotion/hash": { @@ -2817,25 +1492,24 @@ "dev": true }, "@mat-datetimepicker/core": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@mat-datetimepicker/core/-/core-4.0.0.tgz", - "integrity": "sha512-tOjRezVBZ1Jo1xw9L3oIZveVutvopoEfgKHeEfeazNJYdz4RWJ4Lanw4xqFhh811Hi5n5NXN7eRDcB86AJZcxw==" + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@mat-datetimepicker/core/-/core-4.0.1.tgz", + "integrity": "sha512-xu6UHgyHNBBsOOJ5LiAXLdRNbHJko3OhJWvcMHoac3TPEXoWmMGsx9hxzokwgbmdgEUT04KE11BA+XwQ78qeBA==" }, "@material-ui/core": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.9.2.tgz", - "integrity": "sha512-fSf/yBuE5GR7dA+FiQAAGY7HrCN/8RaYApi9tx3IKMiJIJkRCHk+E2lktgJZ+QRsaqCACLo2lwhU2CW5aeO0UQ==", + "version": "4.9.3", + "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.9.3.tgz", + "integrity": "sha512-EQrhN7XHdCfBCMiCTHmiHSn/H1tj2qCt5W/kSDhr1gaYAtBTTiQ29j2xR1NcHT1siVQqAtgIkPephqbTEgQLwA==", "requires": { "@babel/runtime": "^7.4.4", "@material-ui/styles": "^4.9.0", - "@material-ui/system": "^4.9.1", + "@material-ui/system": "^4.9.3", "@material-ui/types": "^5.0.0", "@material-ui/utils": "^4.7.1", "@types/react-transition-group": "^4.2.0", "clsx": "^1.0.2", "convert-css-length": "^2.0.1", "hoist-non-react-statics": "^3.3.2", - "normalize-scroll-left": "^0.2.0", "popper.js": "^1.14.1", "prop-types": "^15.7.2", "react-is": "^16.8.0", @@ -2861,6 +1535,13 @@ "clsx": "^1.0.2", "react-transition-group": "^4.0.0", "rifm": "^0.7.0" + }, + "dependencies": { + "@date-io/core": { + "version": "1.3.13", + "resolved": "https://registry.npmjs.org/@date-io/core/-/core-1.3.13.tgz", + "integrity": "sha512-AlEKV7TxjeK+jxWVKcCFrfYAk8spX9aCyiToFIiLPtfQbsjmRGLIhb5VZgptQcJdHtLXo7+m0DuurwFgUToQuA==" + } } }, "@material-ui/styles": { @@ -2887,9 +1568,9 @@ } }, "@material-ui/system": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.9.1.tgz", - "integrity": "sha512-CLrJK2aKNWNwruGVTRf+rLz96P4jmozpY2UaCE6hBTa1oGsQ396YXOQQABQ4c0igawmdyf5iQb0zs9j5zsAf1w==", + "version": "4.9.3", + "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.9.3.tgz", + "integrity": "sha512-DBGsTKYrLlFpHG8BUp0X6ZpvaOzef+GhSwn/8DwVTXUdHitphaPQoL9xucrI8X9MTBo//El+7nylko7lo7eJIw==", "requires": { "@babel/runtime": "^7.4.4", "@material-ui/utils": "^4.7.1", @@ -2927,12 +1608,12 @@ "integrity": "sha512-PWZmiOZE0J56GFfZpuzKLb7w0K2c6OXZSp/eWDeAvtdHFD4/Nas1i4TXtiWWMWWnSZeNs0hNIg4nFJXi2EddJQ==" }, "@ngtools/webpack": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-9.0.1.tgz", - "integrity": "sha512-SG1MDVSC7pIuaX1QYTh94k/YJa6w2OR2RNbghkDXToDzDv6bKnTQYoJPyXk+gwfDTVD4V5z2dKSNbxFzWleFpg==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-9.0.2.tgz", + "integrity": "sha512-RR18eMe4/k7y3KZ5Y3GTVQNOnJ8Jbe0Xs0q8IMNcGqldbUR878MmIl9PCc6J79stE/7WiNFQtq1L68RQi3i9/A==", "dev": true, "requires": { - "@angular-devkit/core": "9.0.1", + "@angular-devkit/core": "9.0.2", "enhanced-resolve": "4.1.1", "rxjs": "6.5.3", "webpack-sources": "1.4.3" @@ -2958,11 +1639,11 @@ } }, "@ngx-translate/core": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@ngx-translate/core/-/core-12.0.0.tgz", - "integrity": "sha512-hxuaLEqxlZ3IWBupyAoRXAhMZHCmaCg58XpY5+vevJmDhMEFJUEKdQyWVOKcf3+6PkoIFcuKJCeHa5C3Hb65gA==", + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/@ngx-translate/core/-/core-12.1.1.tgz", + "integrity": "sha512-uikMmTog1XAKr5198qeTntVl3Don1Z6Gr0kfznOjbwq+732kpDFeNIRGF3DqH8Nj4zaWHPa/Z3E6K3vuWqq9UQ==", "requires": { - "tslib": "^1.9.0" + "semantic-release": "^8.2.3" } }, "@ngx-translate/http-loader": { @@ -2974,23 +1655,23 @@ } }, "@schematics/angular": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-9.0.1.tgz", - "integrity": "sha512-lQ8Qc697ef2jvEf1+tElAUsbOnbUAMo3dnOUVw9RlYO90pHeG3/OdWBMH1kjn3jbjuKuvCVZH3voJUUcLDx6eg==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-9.0.2.tgz", + "integrity": "sha512-H6ZyxLYoIN68bbNKnUjBCPtB0fcwnpIyTkqXQHa3B4HITcU3uee4PLAl3xCkTS2NGv8m/0eWND9zt5wryGf8PQ==", "dev": true, "requires": { - "@angular-devkit/core": "9.0.1", - "@angular-devkit/schematics": "9.0.1" + "@angular-devkit/core": "9.0.2", + "@angular-devkit/schematics": "9.0.2" } }, "@schematics/update": { - "version": "0.900.1", - "resolved": "https://registry.npmjs.org/@schematics/update/-/update-0.900.1.tgz", - "integrity": "sha512-p2xfctTtT5kMAaCTBENxi69m5IhsvdTwwwokb9zVHJYAC6D1K//q1bl30mTe6U2YE3hSPWND2S14ahXw8PyN8g==", + "version": "0.900.2", + "resolved": "https://registry.npmjs.org/@schematics/update/-/update-0.900.2.tgz", + "integrity": "sha512-CRDb2pax8DtSyO32b2D5uKlQZ+s5h9feD4oEMvm2zU7z/4wcoTNC0T9ols20aHYeNhKYD9FuMI3KQWlgMTPgbw==", "dev": true, "requires": { - "@angular-devkit/core": "9.0.1", - "@angular-devkit/schematics": "9.0.1", + "@angular-devkit/core": "9.0.2", + "@angular-devkit/schematics": "9.0.2", "@yarnpkg/lockfile": "1.1.0", "ini": "1.3.5", "npm-package-arg": "^7.0.0", @@ -3000,6 +1681,24 @@ "semver-intersect": "1.4.0" }, "dependencies": { + "hosted-git-info": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.2.tgz", + "integrity": "sha512-ezZMWtHXm7Eb7Rq4Mwnx2vs79WUx2QmRg3+ZqeGroKzfDO+EprOcgRPYghsOP9JuYBfK18VojmRTGCg8Ma+ktw==", + "dev": true, + "requires": { + "lru-cache": "^5.1.1" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, "npm-package-arg": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-7.0.0.tgz", @@ -3034,9 +1733,74 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true } } }, + "@semantic-release/commit-analyzer": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-3.0.7.tgz", + "integrity": "sha512-bxCvvDsZeQp6Fvev8CdAV4pu9rEt8NOuLIFS0E8RLjKRnqQVL/fGAwpQWnRQ5hc08UZroguBNEENWpKBubWmKQ==", + "requires": { + "@semantic-release/error": "^2.0.0", + "conventional-changelog-angular": "^1.4.0", + "conventional-commits-parser": "^2.0.0", + "import-from": "^2.1.0", + "lodash": "^4.17.4", + "pify": "^3.0.0" + } + }, + "@semantic-release/condition-travis": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@semantic-release/condition-travis/-/condition-travis-6.2.1.tgz", + "integrity": "sha512-0g8O/OObhqjAotztjPjMJ43I6oX05z/Ffdu6HMsScDgLX1+QQzq5w0HnUxjmiXUGNwqi5M6s7BF65ZKHbGOytQ==", + "requires": { + "@semantic-release/error": "^2.0.0", + "github": "^12.0.0", + "parse-github-repo-url": "^1.4.1", + "semver": "^5.0.3", + "travis-deploy-once": "^3.0.0" + } + }, + "@semantic-release/error": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-2.2.0.tgz", + "integrity": "sha512-9Tj/qn+y2j+sjCI3Jd+qseGtHjOAeg7dU2/lVcqIQ9TV3QDaDXDYXcoOHU+7o2Hwh8L8ymL4gfuO7KxDs3q2zg==" + }, + "@semantic-release/last-release-npm": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@semantic-release/last-release-npm/-/last-release-npm-2.0.2.tgz", + "integrity": "sha512-ge5AWWtcrEd6GeG4tsv8gx774G9aPNrrornjBBqNDqN7Eg/xI914ftcnmSgnsbmKcqmq4g+QElIJhNMvsfpOkQ==", + "requires": { + "@semantic-release/error": "^2.0.0", + "npm-registry-client": "^8.4.0", + "npmlog": "^4.0.0" + } + }, + "@semantic-release/release-notes-generator": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-4.0.5.tgz", + "integrity": "sha512-LznqPnifl8jtQkV5ZXnQbqMLx9QROoX5d/FLteLyKy3AznLSubm5rH0LndKvf9ZI8XU8jwgLYu+wbLAEIR2PPg==", + "requires": { + "@semantic-release/error": "^2.0.0", + "conventional-changelog-angular": "^1.4.0", + "conventional-changelog-core": "^1.9.0", + "get-stream": "^3.0.0", + "import-from": "^2.1.0", + "lodash": "^4.17.4", + "pify": "^3.0.0" + } + }, + "@sindresorhus/is": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz", + "integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==" + }, "@types/canvas-gauges": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/@types/canvas-gauges/-/canvas-gauges-2.1.2.tgz", @@ -3081,9 +1845,9 @@ } }, "@types/jasmine": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.5.3.tgz", - "integrity": "sha512-LRJ21f/BO4QNZ3YDaMP0OEurOfE77x8mi8MfEnUsei5IKfmZL0GKl7juhABMdUIJHhVS9OCLotKHfsFNAuJ+DA==", + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.5.4.tgz", + "integrity": "sha512-Uc/obv/lRh1t6RMOV6wkiAfYSZ0AOSTJqVl0Th8bHFcDhw4rQ0L60sxVnmOJj+RXbVboAE1Fd/mBclQWARRAsQ==", "dev": true }, "@types/jasminewd2": { @@ -3130,9 +1894,9 @@ "integrity": "sha512-13gmo3M2qVvjQrWNseqM3+cR6S2Ss3grbR2NZltgMq94wOwqJYQdgn8qzwDshzgXqMlSUtyPZjysImmktu22ew==" }, "@types/node": { - "version": "13.7.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.7.0.tgz", - "integrity": "sha512-GnZbirvmqZUzMgkFn70c74OQpTTUcCzlhQliTzYjQMqg+hVKcDnxdL19Ne3UdYzdMA/+W3eb646FWn/ZaT1NfQ==", + "version": "13.7.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.7.1.tgz", + "integrity": "sha512-Zq8gcQGmn4txQEJeiXo/KiLpon8TzAl0kmKH4zdWctPj05nWwp1ClMdAVEloqrQKfaC48PNLdgN/aVaLqUrluA==", "dev": true }, "@types/prop-types": { @@ -3141,9 +1905,9 @@ "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==" }, "@types/q": { - "version": "0.0.32", - "resolved": "https://registry.npmjs.org/@types/q/-/q-0.0.32.tgz", - "integrity": "sha1-vShOV8hPEyXacCur/IKlMoGQwMU=", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz", + "integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==", "dev": true }, "@types/raphael": { @@ -3153,9 +1917,9 @@ "dev": true }, "@types/react": { - "version": "16.9.19", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.19.tgz", - "integrity": "sha512-LJV97//H+zqKWMms0kvxaKYJDG05U2TtQB3chRLF8MPNs+MQh/H1aGlyDUxjaHvu08EAGerdX2z4LTBc7ns77A==", + "version": "16.9.20", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.20.tgz", + "integrity": "sha512-jRrWBr25zzEVNa4QbESKLPluvrZ3W6Odfwrfe2F5vzbrDuNvlpnHa/xbZcXg8RH5D4CE181J5VxrRrLvzRH+5A==", "requires": { "@types/prop-types": "*", "csstype": "^2.2.0" @@ -3179,9 +1943,9 @@ } }, "@types/selenium-webdriver": { - "version": "3.0.16", - "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-3.0.16.tgz", - "integrity": "sha512-lMC2G0ItF2xv4UCiwbJGbnJlIuUixHrioOhNGHSCsYCJ8l4t9hMCUimCytvFv7qy6AfSzRxhRHoGa+UqaqwyeA==", + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-3.0.17.tgz", + "integrity": "sha512-tGomyEuzSC1H28y2zlW6XPCaDaXFaD6soTdb4GNdmte2qfHtrKqhy0ZFs4r/1hpazCfEZqeTSRLvSasmEx89uw==", "dev": true }, "@types/sizzle": { @@ -3427,7 +2191,6 @@ "version": "1.3.5", "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", - "dev": true, "requires": { "jsonparse": "^1.2.0", "through": ">=2.2.7 <3" @@ -3468,9 +2231,9 @@ } }, "adm-zip": { - "version": "0.4.13", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.13.tgz", - "integrity": "sha512-fERNJX8sOXfel6qCBCMPvZLzENBEhZTzKqg6vrOW5pvoEaQuJhRU4ndTAh6lHOxn1I6jnz2NHra56ZODM751uw==", + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.14.tgz", + "integrity": "sha512-/9aQCnQHF+0IiCl0qhXoK7qs//SwYE7zX8lsr/DNk1BRAHYxeLZPL4pguwK29gUEqasYQjqPtEpDRSWEkdHn9g==", "dev": true }, "after": { @@ -3483,7 +2246,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", - "dev": true, "requires": { "es6-promisify": "^5.0.0" } @@ -3505,15 +2267,22 @@ "requires": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" + }, + "dependencies": { + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + } } }, "ajv": { - "version": "6.10.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", - "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", - "dev": true, + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.11.0.tgz", + "integrity": "sha512-nCprB/0syFYy9fVYU1ox1l2KN8S9I+tziH8D4zdZuLT3N6RMlGSGt5FSTpAiHB/Whv8Qs1cWHma1aMKZyaHRKA==", "requires": { - "fast-deep-equal": "^2.0.1", + "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" @@ -3538,9 +2307,9 @@ "dev": true }, "angular-gridster2": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/angular-gridster2/-/angular-gridster2-9.0.0.tgz", - "integrity": "sha512-0/nREskZEZMzSS9AdQmPHdncb4txIXzHnSgylIJ5esEPJI4dkB4A5bd9shUdwH6u4kPZInkHO3W0hHJOTSilUQ==" + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/angular-gridster2/-/angular-gridster2-9.0.1.tgz", + "integrity": "sha512-4mapsgxJFMbDGTFAubhfl1vgmgsPd9RcWaL7/ciBNOrgOZfECI3E/topsiEZBmToeustrvkIj5Jlz+niEJlfkw==" }, "angular2-hotkeys": { "version": "2.1.5", @@ -3575,14 +2344,12 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "requires": { "color-convert": "^1.9.0" } @@ -3615,8 +2382,16 @@ "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } }, "arg": { "version": "4.1.3", @@ -3660,12 +2435,22 @@ "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", "dev": true }, + "array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=" + }, "array-flatten": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", "dev": true }, + "array-ify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", + "integrity": "sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4=" + }, "array-union": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", @@ -3696,8 +2481,7 @@ "arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "dev": true + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" }, "asap": { "version": "2.0.6", @@ -3709,7 +2493,6 @@ "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "dev": true, "requires": { "safer-buffer": "~2.1.0" } @@ -3755,8 +2538,7 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" }, "assign-symbols": { "version": "1.0.0", @@ -3771,13 +2553,9 @@ "dev": true }, "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "dev": true, - "requires": { - "lodash": "^4.17.14" - } + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" }, "async-each": { "version": "1.0.3", @@ -3794,8 +2572,7 @@ "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "atob": { "version": "2.1.2", @@ -3826,14 +2603,12 @@ "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" }, "aws4": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", - "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==", - "dev": true + "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==" }, "axobject-query": { "version": "2.0.2", @@ -3866,6 +2641,12 @@ "make-dir": "^2.0.0", "pkg-dir": "^3.0.0" } + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true } } }, @@ -3992,7 +2773,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "dev": true, "requires": { "tweetnacl": "^0.14.3" } @@ -4041,20 +2821,12 @@ "dev": true, "requires": { "minimist": "^1.2.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } } }, "bluebird": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.1.tgz", - "integrity": "sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg==", + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "dev": true }, "bn.js": { @@ -4087,6 +2859,21 @@ "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", "dev": true }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, "qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", @@ -4282,8 +3069,7 @@ "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" }, "buffer-indexof": { "version": "1.1.1", @@ -4312,8 +3098,7 @@ "builtins": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", - "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=", - "dev": true + "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=" }, "bytes": { "version": "3.0.0", @@ -4356,6 +3141,25 @@ "yallist": "^3.0.2" } }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "ssri": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-7.1.0.tgz", + "integrity": "sha512-77/WrDZUWocK0mvA5NTRQyveUf+wsrIc6vyrxpS8tVvYBcX215QbafrJR3KtkpskIzoFLqqNuuYQvxaMjXJ/0g==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1", + "minipass": "^3.1.1" + } + }, "yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -4381,6 +3185,27 @@ "unset-value": "^1.0.0" } }, + "cacheable-request": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-2.1.4.tgz", + "integrity": "sha1-DYCIAbY0KtM8kd+dC0TcCbkeXD0=", + "requires": { + "clone-response": "1.0.2", + "get-stream": "3.0.0", + "http-cache-semantics": "3.8.1", + "keyv": "3.0.0", + "lowercase-keys": "1.0.0", + "normalize-url": "2.0.1", + "responselike": "1.0.2" + }, + "dependencies": { + "lowercase-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz", + "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=" + } + } + }, "caller-callsite": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", @@ -4412,10 +3237,19 @@ "dev": true }, "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" + }, + "camelcase-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", + "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=", + "requires": { + "camelcase": "^4.1.0", + "map-obj": "^2.0.0", + "quick-lru": "^1.0.0" + } }, "caniuse-api": { "version": "3.0.0", @@ -4449,14 +3283,12 @@ "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -4470,19 +3302,19 @@ "dev": true }, "chokidar": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", - "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.1.tgz", + "integrity": "sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg==", "dev": true, "requires": { "anymatch": "~3.1.1", "braces": "~3.0.2", - "fsevents": "~2.1.1", + "fsevents": "~2.1.2", "glob-parent": "~5.1.0", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", - "readdirp": "~3.2.0" + "readdirp": "~3.3.0" }, "dependencies": { "glob-parent": { @@ -4497,9 +3329,9 @@ } }, "chownr": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz", - "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", "dev": true }, "chrome-trace-event": { @@ -4610,6 +3442,22 @@ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", @@ -4622,10 +3470,9 @@ } }, "clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", - "dev": true + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" }, "clone-deep": { "version": "4.0.1", @@ -4638,6 +3485,14 @@ "shallow-clone": "^3.0.0" } }, + "clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "requires": { + "mimic-response": "^1.0.0" + } + }, "clsx": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.0.tgz", @@ -4652,21 +3507,12 @@ "@types/q": "^1.5.1", "chalk": "^2.4.1", "q": "^1.1.2" - }, - "dependencies": { - "@types/q": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz", - "integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==", - "dev": true - } } }, "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "codelyzer": { "version": "5.2.1", @@ -4723,7 +3569,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "requires": { "color-name": "1.1.3" } @@ -4731,8 +3576,7 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "color-string": { "version": "1.5.3", @@ -4754,7 +3598,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "requires": { "delayed-stream": "~1.0.0" } @@ -4770,10 +3613,19 @@ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, - "compare-versions": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.5.1.tgz", - "integrity": "sha512-9fGPIB7C6AyM18CJJBHt5EnCZDG3oiTJYy0NjfIAGjKpzv0tkxWko7TNQHF5ymqm7IH03tqmeuBxtvD+Izh6mg==", + "compare-func": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-1.3.2.tgz", + "integrity": "sha1-md0LpFfh+bxyKxLAjsM+6rMfpkg=", + "requires": { + "array-ify": "^1.0.0", + "dot-prop": "^3.0.0" + } + }, + "compare-versions": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz", + "integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==", "dev": true }, "compass-sass-mixins": { @@ -4819,14 +3671,6 @@ "dev": true, "requires": { "mime-db": ">= 1.43.0 < 2" - }, - "dependencies": { - "mime-db": { - "version": "1.43.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", - "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==", - "dev": true - } } }, "compression": { @@ -4842,6 +3686,23 @@ "on-headers": "~1.0.2", "safe-buffer": "5.1.2", "vary": "~1.1.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } } }, "compression-webpack-plugin": { @@ -4858,76 +3719,6 @@ "webpack-sources": "^1.0.1" }, "dependencies": { - "cacache": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-13.0.1.tgz", - "integrity": "sha512-5ZvAxd05HDDU+y9BVvcqYu2LLXmPnQ0hW62h32g4xBTgL/MppR4/04NHfj/ycM2y6lmTnbw6HVi+1eN0Psba6w==", - "dev": true, - "requires": { - "chownr": "^1.1.2", - "figgy-pudding": "^3.5.1", - "fs-minipass": "^2.0.0", - "glob": "^7.1.4", - "graceful-fs": "^4.2.2", - "infer-owner": "^1.0.4", - "lru-cache": "^5.1.1", - "minipass": "^3.0.0", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.2", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "p-map": "^3.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^2.7.1", - "ssri": "^7.0.0", - "unique-filename": "^1.1.1" - } - }, - "fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, - "requires": { - "minipass": "^3.0.0" - } - }, - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "requires": { - "yallist": "^3.0.2" - }, - "dependencies": { - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - } - } - }, - "minipass": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.1.tgz", - "integrity": "sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "p-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", - "dev": true, - "requires": { - "aggregate-error": "^3.0.0" - } - }, "schema-utils": { "version": "2.6.4", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.4.tgz", @@ -4937,22 +3728,6 @@ "ajv": "^6.10.2", "ajv-keywords": "^3.4.1" } - }, - "ssri": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-7.1.0.tgz", - "integrity": "sha512-77/WrDZUWocK0mvA5NTRQyveUf+wsrIc6vyrxpS8tVvYBcX215QbafrJR3KtkpskIzoFLqqNuuYQvxaMjXJ/0g==", - "dev": true, - "requires": { - "figgy-pudding": "^3.5.1", - "minipass": "^3.1.1" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true } } }, @@ -4965,7 +3740,6 @@ "version": "1.6.2", "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, "requires": { "buffer-from": "^1.0.0", "inherits": "^2.0.3", @@ -4982,51 +3756,370 @@ "proto-list": "~1.2.1" } }, - "connect": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", - "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", - "dev": true, + "connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "requires": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "connect-history-api-fallback": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", + "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", + "dev": true + }, + "console-browserify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, + "constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", + "dev": true + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true + }, + "conventional-changelog-angular": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-1.6.6.tgz", + "integrity": "sha512-suQnFSqCxRwyBxY68pYTsFkG0taIdinHLNEAX5ivtw8bCRnIgnpvcHmlR/yjUyZIrNPYAoXlY1WiEKWgSE4BNg==", + "requires": { + "compare-func": "^1.3.1", + "q": "^1.5.1" + } + }, + "conventional-changelog-core": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-1.9.5.tgz", + "integrity": "sha1-XbdWba18DLddr0f7spdve/mSjB0=", + "requires": { + "conventional-changelog-writer": "^2.0.3", + "conventional-commits-parser": "^2.1.0", + "dateformat": "^1.0.12", + "get-pkg-repo": "^1.0.0", + "git-raw-commits": "^1.3.0", + "git-remote-origin-url": "^2.0.0", + "git-semver-tags": "^1.2.3", + "lodash": "^4.0.0", + "normalize-package-data": "^2.3.5", + "q": "^1.4.1", + "read-pkg": "^1.1.0", + "read-pkg-up": "^1.0.1", + "through2": "^2.0.0" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "requires": { + "error-ex": "^1.2.0" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "requires": { + "is-utf8": "^0.2.0" + } + } + } + }, + "conventional-changelog-writer": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-2.0.3.tgz", + "integrity": "sha512-2E1h7UXL0fhRO5h0CxDZ5EBc5sfBZEQePvuZ+gPvApiRrICUyNDy/NQIP+2TBd4wKZQf2Zm7TxbzXHG5HkPIbA==", + "requires": { + "compare-func": "^1.3.1", + "conventional-commits-filter": "^1.1.1", + "dateformat": "^1.0.11", + "handlebars": "^4.0.2", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.0.0", + "meow": "^3.3.0", + "semver": "^5.0.1", + "split": "^1.0.0", + "through2": "^2.0.0" + }, + "dependencies": { + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=" + }, + "camelcase-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "requires": { + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "requires": { + "repeating": "^2.0.0" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + } + }, + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=" + }, + "meow": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "requires": { + "camelcase-keys": "^2.0.0", + "decamelize": "^1.1.2", + "loud-rejection": "^1.0.0", + "map-obj": "^1.0.1", + "minimist": "^1.1.3", + "normalize-package-data": "^2.3.4", + "object-assign": "^4.0.1", + "read-pkg-up": "^1.0.1", + "redent": "^1.0.0", + "trim-newlines": "^1.0.0" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "requires": { + "error-ex": "^1.2.0" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + } + }, + "redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "requires": { + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "requires": { + "is-utf8": "^0.2.0" + } + }, + "strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "requires": { + "get-stdin": "^4.0.1" + } + }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=" + } + } + }, + "conventional-commits-filter": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-1.1.6.tgz", + "integrity": "sha512-KcDgtCRKJCQhyk6VLT7zR+ZOyCnerfemE/CsR3iQpzRRFbLEs0Y6rwk3mpDvtOh04X223z+1xyJ582Stfct/0Q==", "requires": { - "debug": "2.6.9", - "finalhandler": "1.1.2", - "parseurl": "~1.3.3", - "utils-merge": "1.0.1" + "is-subset": "^0.1.1", + "modify-values": "^1.0.0" } }, - "connect-history-api-fallback": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", - "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", - "dev": true - }, - "console-browserify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", - "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", - "dev": true - }, - "constants-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", - "dev": true - }, - "content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", - "dev": true, + "conventional-commits-parser": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-2.1.7.tgz", + "integrity": "sha512-BoMaddIEJ6B4QVMSDu9IkVImlGOSGA1I2BQyOZHeLQ6qVOJLcLKn97+fL6dGbzWEiqDzfH4OkcveULmeq2MHFQ==", "requires": { - "safe-buffer": "5.1.2" + "JSONStream": "^1.0.4", + "is-text-path": "^1.0.0", + "lodash": "^4.2.1", + "meow": "^4.0.0", + "split2": "^2.0.0", + "through2": "^2.0.0", + "trim-off-newlines": "^1.0.0" } }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "dev": true - }, "convert-css-length": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/convert-css-length/-/convert-css-length-2.0.1.tgz", @@ -5065,6 +4158,17 @@ "mkdirp": "^0.5.1", "rimraf": "^2.5.4", "run-queue": "^1.0.0" + }, + "dependencies": { + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } } }, "copy-descriptor": { @@ -5136,6 +4240,30 @@ "yallist": "^3.0.2" } }, + "p-limit": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", + "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, "ssri": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", @@ -5179,8 +4307,7 @@ "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "cosmiconfig": { "version": "5.2.1", @@ -5257,14 +4384,11 @@ } }, "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", + "lru-cache": "^4.0.1", "shebang-command": "^1.2.0", "which": "^1.2.9" } @@ -5363,6 +4487,12 @@ "regexpu-core": "^1.0.0" }, "dependencies": { + "cssesc": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz", + "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=", + "dev": true + }, "jsesc": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", @@ -5407,12 +4537,6 @@ "source-map": "^0.6.1" } }, - "css-unit-converter": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.1.tgz", - "integrity": "sha1-2bkoGtz9jO2TW9urqDeGiX9k6ZY=", - "dev": true - }, "css-vendor": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.7.tgz", @@ -5438,9 +4562,9 @@ } }, "cssesc": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz", - "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true }, "cssnano": { @@ -5530,9 +4654,17 @@ } }, "csstype": { - "version": "2.6.8", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.8.tgz", - "integrity": "sha512-msVS9qTuMT5zwAGCVm4mxfrZ18BNc6Csd0oJAtiFMZ1FAx1CCvy2+5MDmYoix63LM/6NDbNtodCiGYGmFgO0dA==" + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.9.tgz", + "integrity": "sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q==" + }, + "currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "requires": { + "array-find-index": "^1.0.1" + } }, "custom-event": { "version": "1.0.1", @@ -5552,11 +4684,18 @@ "integrity": "sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug==", "dev": true }, + "dargs": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/dargs/-/dargs-4.1.0.tgz", + "integrity": "sha1-A6nbtLXC8Tm/FK5T8LiipqhvThc=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, "requires": { "assert-plus": "^1.0.0" } @@ -5572,13 +4711,168 @@ "integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==", "dev": true }, + "dateformat": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz", + "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=", + "requires": { + "get-stdin": "^4.0.1", + "meow": "^3.3.0" + }, + "dependencies": { + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=" + }, + "camelcase-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "requires": { + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "requires": { + "repeating": "^2.0.0" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + } + }, + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=" + }, + "meow": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "requires": { + "camelcase-keys": "^2.0.0", + "decamelize": "^1.1.2", + "loud-rejection": "^1.0.0", + "map-obj": "^1.0.1", + "minimist": "^1.1.3", + "normalize-package-data": "^2.3.4", + "object-assign": "^4.0.1", + "read-pkg-up": "^1.0.1", + "redent": "^1.0.0", + "trim-newlines": "^1.0.0" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "requires": { + "error-ex": "^1.2.0" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + } + }, + "redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "requires": { + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "requires": { + "is-utf8": "^0.2.0" + } + }, + "strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "requires": { + "get-stdin": "^4.0.1" + } + }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=" + } + } + }, "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } }, "debuglog": { @@ -5590,14 +4884,36 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, + "decamelize-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", + "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", + "requires": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "dependencies": { + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=" + } + } }, "decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" + }, + "decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "requires": { + "mimic-response": "^1.0.0" + } }, "deep-equal": { "version": "2.0.1", @@ -5639,6 +4955,45 @@ "requires": { "execa": "^1.0.0", "ip-regex": "^2.1.0" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + } } }, "default-require-extensions": { @@ -5656,13 +5011,6 @@ "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", "requires": { "clone": "^1.0.2" - }, - "dependencies": { - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" - } } }, "define-properties": { @@ -5755,14 +5103,28 @@ "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", "dev": true + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } } } }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, "delegate": { "version": "3.2.0", @@ -5770,6 +5132,11 @@ "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==", "optional": true }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -5949,14 +5316,23 @@ } }, "dot-prop": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", - "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", - "dev": true, + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-3.0.0.tgz", + "integrity": "sha1-G3CK8JSknJoOfbyteQq6U52sEXc=", "requires": { "is-obj": "^1.0.0" } }, + "dotenv": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-4.0.0.tgz", + "integrity": "sha1-hk7xN5rO1Vzm+V3r7NzhefegzR0=" + }, + "duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" + }, "duplexify": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", @@ -5973,7 +5349,6 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "dev": true, "requires": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" @@ -5997,9 +5372,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.346", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.346.tgz", - "integrity": "sha512-Yy4jF5hJd57BWmGPt0KjaXc25AmWZeQK75kdr4zIzksWVtiT6DwaNtvTb9dt+LkQKwUpvBfCyyPsXXtbY/5GYw==", + "version": "1.3.354", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.354.tgz", + "integrity": "sha512-24YMkNiZWOUeF6YeoscWfIGP0oMx+lJpU/miwI+lcu7plIDpyZn8Gx0lx0qTDlzGoz7hx+lpyD8QkbkX5L2Pqw==", "dev": true }, "elliptic": { @@ -6081,6 +5456,23 @@ "requires": { "ms": "2.0.0" } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "ws": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0", + "safe-buffer": "~5.1.0", + "ultron": "~1.1.0" + } } } }, @@ -6117,6 +5509,23 @@ "requires": { "ms": "2.0.0" } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "ws": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0", + "safe-buffer": "~5.1.0", + "ultron": "~1.1.0" + } } } }, @@ -6175,7 +5584,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, "requires": { "is-arrayish": "^0.2.1" } @@ -6196,16 +5604,6 @@ "object.assign": "^4.1.0", "string.prototype.trimleft": "^2.1.1", "string.prototype.trimright": "^2.1.1" - }, - "dependencies": { - "is-regex": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", - "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", - "requires": { - "has": "^1.0.3" - } - } } }, "es-get-iterator": { @@ -6242,14 +5640,12 @@ "es6-promise": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", - "dev": true + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" }, "es6-promisify": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", - "dev": true, "requires": { "es6-promise": "^4.0.3" } @@ -6263,8 +5659,7 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "eslint-scope": { "version": "4.0.3", @@ -6346,13 +5741,12 @@ } }, "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.8.0.tgz", + "integrity": "sha1-2NdrvBtVIX7RkP1t1J08d07PyNo=", "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", "is-stream": "^1.1.0", "npm-run-path": "^2.0.0", "p-finally": "^1.0.0", @@ -6381,6 +5775,15 @@ "to-regex": "^3.0.1" }, "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, "define-property": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", @@ -6398,6 +5801,12 @@ "requires": { "is-extendable": "^0.1.0" } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true } } }, @@ -6445,6 +5854,21 @@ "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", "dev": true }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, "qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", @@ -6456,8 +5880,7 @@ "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, "extend-shallow": { "version": "3.0.2", @@ -6559,20 +5982,17 @@ "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", + "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==" }, "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", - "dev": true + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "fastparse": { "version": "1.1.2", @@ -6596,9 +6016,9 @@ "dev": true }, "figures": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.1.0.tgz", - "integrity": "sha512-ravh8VRXqHuMvZt/d8GblBeqDMkdJMBdv/2KntFH+ra5MXkO7nxNKpzQ3n6QD/2da1kH0aWmNISdvhM7gl2gVg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", "dev": true, "requires": { "escape-string-regexp": "^1.0.5" @@ -6673,6 +6093,23 @@ "parseurl": "~1.3.3", "statuses": "~1.5.0", "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } } }, "find-cache-dir": { @@ -6706,14 +6143,23 @@ } }, "make-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.0.tgz", - "integrity": "sha512-grNJDhb8b1Jm1qeqW5R/O63wUo4UXo2v2HMic6YT9i/HBlF93S8jkMgH7yugvY9ABDShH4VZMn8I+U8+fCNegw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.2.tgz", + "integrity": "sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w==", "dev": true, "requires": { "semver": "^6.0.0" } }, + "p-limit": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", + "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, "p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", @@ -6723,6 +6169,12 @@ "p-limit": "^2.2.0" } }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -6747,12 +6199,11 @@ } }, "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "requires": { - "locate-path": "^3.0.0" + "locate-path": "^2.0.0" } }, "flatted": { @@ -6780,29 +6231,11 @@ } }, "follow-redirects": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.9.0.tgz", - "integrity": "sha512-CRcPzsSIbXyVDl0QI01muNDu69S8trU4jArW9LpOt2WtC6LyUJetcIrmfHsRBx7/Jb6GHJUiuqyYxPooFfNt6A==", - "dev": true, + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.2.6.tgz", + "integrity": "sha512-FrMqZ/FONtHnbqO651UPpfRUVukIEwJhXMfdr/JWAmrDbeYBu773b1J6gdWDyRIj4hvvzQEHoEOTrdR8o6KLYA==", "requires": { - "debug": "^3.0.0" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } + "debug": "^3.1.0" } }, "font-awesome": { @@ -6816,17 +6249,20 @@ "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", "dev": true }, + "foreachasync": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/foreachasync/-/foreachasync-3.0.0.tgz", + "integrity": "sha1-VQKYfchxS+M5IJfzLgBxyd7gfPY=" + }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" }, "form-data": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", @@ -6858,17 +6294,15 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", - "dev": true, "requires": { "inherits": "^2.0.1", "readable-stream": "^2.0.0" } }, "fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", - "dev": true, + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", + "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", "requires": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", @@ -6913,6 +6347,21 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, "genfun": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/genfun/-/genfun-5.0.0.tgz", @@ -6925,15 +6374,175 @@ "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", "dev": true }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, + "get-pkg-repo": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-pkg-repo/-/get-pkg-repo-1.4.0.tgz", + "integrity": "sha1-xztInAbYDMVTbCyFP54FIyBWly0=", "requires": { - "pump": "^3.0.0" + "hosted-git-info": "^2.1.4", + "meow": "^3.3.0", + "normalize-package-data": "^2.3.0", + "parse-github-repo-url": "^1.3.0", + "through2": "^2.0.0" + }, + "dependencies": { + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=" + }, + "camelcase-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "requires": { + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "requires": { + "repeating": "^2.0.0" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + } + }, + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=" + }, + "meow": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "requires": { + "camelcase-keys": "^2.0.0", + "decamelize": "^1.1.2", + "loud-rejection": "^1.0.0", + "map-obj": "^1.0.1", + "minimist": "^1.1.3", + "normalize-package-data": "^2.3.4", + "object-assign": "^4.0.1", + "read-pkg-up": "^1.0.1", + "redent": "^1.0.0", + "trim-newlines": "^1.0.0" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "requires": { + "error-ex": "^1.2.0" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + } + }, + "redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "requires": { + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "requires": { + "is-utf8": "^0.2.0" + } + }, + "strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "requires": { + "get-stdin": "^4.0.1" + } + }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=" + } } }, + "get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=" + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" + }, "get-value": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", @@ -6944,11 +6553,86 @@ "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, "requires": { "assert-plus": "^1.0.0" } }, + "git-head": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/git-head/-/git-head-1.20.1.tgz", + "integrity": "sha1-A20WpLN0lJ5OPa8VgnkDaG08zVI=", + "requires": { + "git-refs": "^1.1.3" + } + }, + "git-raw-commits": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-1.3.6.tgz", + "integrity": "sha512-svsK26tQ8vEKnMshTDatSIQSMDdz8CxIIqKsvPqbtV23Etmw6VNaFAitu8zwZ0VrOne7FztwPyRLxK7/DIUTQg==", + "requires": { + "dargs": "^4.0.1", + "lodash.template": "^4.0.2", + "meow": "^4.0.0", + "split2": "^2.0.0", + "through2": "^2.0.0" + } + }, + "git-refs": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/git-refs/-/git-refs-1.1.3.tgz", + "integrity": "sha1-gwl8s6klhcSkkm7FTiGC354g6J0=", + "requires": { + "path-object": "^2.3.0", + "slash": "^1.0.0", + "walk": "^2.3.9" + } + }, + "git-remote-origin-url": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz", + "integrity": "sha1-UoJlna4hBxRaERJhEq0yFuxfpl8=", + "requires": { + "gitconfiglocal": "^1.0.0", + "pify": "^2.3.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + } + } + }, + "git-semver-tags": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/git-semver-tags/-/git-semver-tags-1.3.6.tgz", + "integrity": "sha512-2jHlJnln4D/ECk9FxGEBh3k44wgYdWjWDtMmJPaecjoRmxKo3Y1Lh8GMYuOPu04CHw86NTAODchYjC5pnpMQig==", + "requires": { + "meow": "^4.0.0", + "semver": "^5.5.0" + } + }, + "gitconfiglocal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz", + "integrity": "sha1-QdBF84UaXqiPA/JMocYXgRRGS5s=", + "requires": { + "ini": "^1.3.2" + } + }, + "github": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/github/-/github-12.1.0.tgz", + "integrity": "sha512-HhWjhd/OATC4Hjj7xfGjGRtwWzo/fzTc55EkvsRatI9G6Vp47mVcdBIt1lQ56A9Qit/yVQRX1+M9jbWlcJvgug==", + "requires": { + "dotenv": "^4.0.0", + "follow-redirects": "1.2.6", + "https-proxy-agent": "^2.1.0", + "lodash": "^4.17.4", + "mime": "^2.0.3", + "netrc": "^0.1.4" + } + }, "glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", @@ -7001,14 +6685,6 @@ "ignore": "^3.3.5", "pify": "^3.0.0", "slash": "^1.0.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - } } }, "good-listener": { @@ -7020,11 +6696,34 @@ "delegate": "^3.1.2" } }, + "got": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/got/-/got-8.3.2.tgz", + "integrity": "sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw==", + "requires": { + "@sindresorhus/is": "^0.7.0", + "cacheable-request": "^2.1.1", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "into-stream": "^3.1.0", + "is-retry-allowed": "^1.1.0", + "isurl": "^1.0.0-alpha5", + "lowercase-keys": "^1.0.0", + "mimic-response": "^1.0.0", + "p-cancelable": "^0.4.0", + "p-timeout": "^2.0.1", + "pify": "^3.0.0", + "safe-buffer": "^5.1.1", + "timed-out": "^4.0.1", + "url-parse-lax": "^3.0.0", + "url-to-options": "^1.0.1" + } + }, "graceful-fs": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true + "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==" }, "hammerjs": { "version": "2.0.8", @@ -7038,10 +6737,9 @@ "dev": true }, "handlebars": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.3.tgz", - "integrity": "sha512-3yPecJoJHK/4c6aZhSvxOyG4vJKDshV36VHp0iVCDVh7o9w2vwi3NSnL2MMPj3YdduqaBcu7cGbggJQM0br9xA==", - "dev": true, + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.3.tgz", + "integrity": "sha512-SRGwSYuNfx8DwHD/6InAPzD6RgeruWLT+B8e8a7gGs8FWgHzlExpTFMEq2IA6QpAfOClpKHy6+8IqTjeBCu6Kg==", "requires": { "neo-async": "^2.6.0", "optimist": "^0.6.1", @@ -7052,14 +6750,12 @@ "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" }, "har-validator": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", - "dev": true, "requires": { "ajv": "^6.5.5", "har-schema": "^2.0.0" @@ -7108,14 +6804,31 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "has-symbol-support-x": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz", + "integrity": "sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==" }, "has-symbols": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" }, + "has-to-string-tag-x": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz", + "integrity": "sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==", + "requires": { + "has-symbol-support-x": "^1.4.1" + } + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, "has-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", @@ -7199,46 +6912,25 @@ "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", "dev": true, - "requires": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "requires": { - "react-is": "^16.7.0" - } - }, - "hosted-git-info": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.2.tgz", - "integrity": "sha512-ezZMWtHXm7Eb7Rq4Mwnx2vs79WUx2QmRg3+ZqeGroKzfDO+EprOcgRPYghsOP9JuYBfK18VojmRTGCg8Ma+ktw==", - "dev": true, - "requires": { - "lru-cache": "^5.1.1" - }, - "dependencies": { - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "requires": { - "yallist": "^3.0.2" - } - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - } + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "react-is": "^16.7.0" } }, + "hosted-git-info": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz", + "integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==" + }, "hpack.js": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", @@ -7275,11 +6967,16 @@ "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=", "dev": true }, + "html-escaper": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.0.tgz", + "integrity": "sha512-a4u9BeERWGu/S8JiWEAQcdrg9v4QArtP9keViQjGMdff20fBdd8waotXaNmODqBe6uZ3Nafi7K/ho4gCQHV3Ig==", + "dev": true + }, "http-cache-semantics": { "version": "3.8.1", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", - "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==", - "dev": true + "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==" }, "http-deceiver": { "version": "1.2.7", @@ -7343,6 +7040,12 @@ "requires": { "ms": "2.0.0" } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true } } }, @@ -7362,7 +7065,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, "requires": { "assert-plus": "^1.0.0", "jsprim": "^1.2.2", @@ -7379,27 +7081,9 @@ "version": "2.2.4", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", - "dev": true, "requires": { "agent-base": "^4.3.0", "debug": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } } }, "humanize-ms": { @@ -7488,7 +7172,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz", "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=", - "dev": true, "requires": { "resolve-from": "^3.0.0" } @@ -7510,10 +7193,9 @@ "dev": true }, "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=" }, "indexes-of": { "version": "1.0.1", @@ -7636,6 +7318,15 @@ "ipaddr.js": "^1.9.0" } }, + "into-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-3.1.0.tgz", + "integrity": "sha1-lvsKk2wSur1v8XUqF9BWFqvQlMY=", + "requires": { + "from2": "^2.1.1", + "p-is-promise": "^1.1.0" + } + }, "invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -7703,8 +7394,7 @@ "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" }, "is-bigint": { "version": "1.0.0", @@ -7771,9 +7461,9 @@ } }, "is-date-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", - "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" }, "is-descriptor": { "version": "0.1.6", @@ -7812,11 +7502,18 @@ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true }, + "is-finite": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", + "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==" + }, "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } }, "is-glob": { "version": "4.0.1", @@ -7857,8 +7554,12 @@ "is-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", - "dev": true + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" + }, + "is-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz", + "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA=" }, "is-path-cwd": { "version": "2.2.0", @@ -7887,8 +7588,7 @@ "is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", - "dev": true + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" }, "is-plain-object": { "version": "2.0.4", @@ -7919,6 +7619,11 @@ "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", "dev": true }, + "is-retry-allowed": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", + "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==" + }, "is-set": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.1.tgz", @@ -7927,14 +7632,18 @@ "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" }, "is-string": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==" }, + "is-subset": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz", + "integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=" + }, "is-svg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz", @@ -7952,11 +7661,23 @@ "has-symbols": "^1.0.1" } }, + "is-text-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", + "integrity": "sha1-Thqg+1G/vLPpJogAE5cgLBd1tm4=", + "requires": { + "text-extensions": "^1.0.0" + } + }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" }, "is-weakmap": { "version": "2.0.1", @@ -7983,8 +7704,7 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isbinaryfile": { "version": "3.0.3", @@ -7998,8 +7718,7 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "isobject": { "version": "3.0.1", @@ -8010,8 +7729,7 @@ "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, "istanbul-api": { "version": "2.1.6", @@ -8102,138 +7820,11 @@ "semver": "^6.3.0" }, "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.8.3" - } - }, - "@babel/generator": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", - "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", - "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", - "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", - "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/highlight": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", - "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", - "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", - "dev": true - }, - "@babel/template": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", - "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/traverse": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", - "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.4", - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.8.4", - "@babel/types": "^7.8.3", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" - } - }, - "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true } } }, @@ -8293,21 +7884,33 @@ "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", "dev": true }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } } } }, "istanbul-reports": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.6.tgz", - "integrity": "sha512-SKi4rnMyLBKe0Jy2uUdx28h8oG7ph2PPuQPvIAh31d+Ci+lSiEu4C+h3oBPuJ9+mPKhOyW0M8gY4U5NM1WLeXA==", + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.7.tgz", + "integrity": "sha512-uu1F/L1o5Y6LzPVSVZXNOoD/KXpJue9aeLRd0sM9uMXfZvzomB0WxVamWb5ue8kA2vVWEmW7EG+A5n3f1kqHKg==", "dev": true, "requires": { - "handlebars": "^4.1.2" + "html-escaper": "^2.0.0" + } + }, + "isurl": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz", + "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==", + "requires": { + "has-to-string-tag-x": "^1.2.0", + "is-object": "^1.0.1" } }, "jasmine": { @@ -8382,9 +7985,9 @@ "integrity": "sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw==" }, "jquery.terminal": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/jquery.terminal/-/jquery.terminal-2.12.0.tgz", - "integrity": "sha512-y6uBR6OB0cViNH/AkEJRhr56H8db8MlTO7Vn0TuDZhl78R5+DnT5jMlQLKxZEQVhUWtZQMXZIlQt0Xr6Zf63BQ==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/jquery.terminal/-/jquery.terminal-2.14.0.tgz", + "integrity": "sha512-OLEpsVZs4+JZ9jiaXp4TPKKKHfPdSP3RXfwrctmRUviwS8Zi89Jx0NvAsct0qLIOfkw9bPVD/u7q30mVYz0OzA==", "requires": { "@types/jquery": "^3.3.29", "jquery": "~3", @@ -8428,8 +8031,7 @@ "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, "jsesc": { "version": "2.5.2", @@ -8437,17 +8039,20 @@ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true }, + "json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=" + }, "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" }, "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" }, "json-schema-defaults": { "version": "0.4.0", @@ -8460,14 +8065,12 @@ "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, "json3": { "version": "3.3.3", @@ -8482,21 +8085,12 @@ "dev": true, "requires": { "minimist": "^1.2.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } } }, "jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "dev": true, "requires": { "graceful-fs": "^4.1.6" } @@ -8504,14 +8098,12 @@ "jsonparse": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", - "dev": true + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=" }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "dev": true, "requires": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", @@ -8597,9 +8189,9 @@ } }, "jstree": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/jstree/-/jstree-3.3.8.tgz", - "integrity": "sha512-0/nhGxVLSGfGQyVg+q59ocqSEKWRDKHoA8wNrcOIvlzCCw19tzvcMNGJ19hf+U0b7fycABowkny7fQPcLgUwwA==", + "version": "3.3.9", + "resolved": "https://registry.npmjs.org/jstree/-/jstree-3.3.9.tgz", + "integrity": "sha512-jRIbhg+BHrIs1Wm6oiJt3oKTVBE6sWS0PCp2/RlkIUqsLUPWUYgV3q8LfKoi1/E+YMzGtP6BuK4okk+0mwfmhQ==", "requires": { "jquery": ">=1.9.1" } @@ -8658,11 +8250,14 @@ "useragent": "2.3.0" }, "dependencies": { - "mime": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", - "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", - "dev": true + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } } } }, @@ -8686,9 +8281,9 @@ } }, "karma-jasmine": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-3.1.0.tgz", - "integrity": "sha512-IVGbC8gap5x5NNCEOsAE77ic8rZtHDt6wmO0fFC5yT5FeB8qKnGTeud2mtKyQ41xl7vZkZ7ZxKr4wMGR6tWN+A==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-3.1.1.tgz", + "integrity": "sha512-pxBmv5K7IkBRLsFSTOpgiK/HzicQT3mfFF+oHAC7nxMfYKhaYFgxOa5qjnHW4sL5rUnmdkSajoudOnnOdPyW4Q==", "dev": true, "requires": { "jasmine-core": "^3.5.0" @@ -8709,6 +8304,14 @@ "source-map-support": "^0.5.5" } }, + "keyv": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.0.0.tgz", + "integrity": "sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA==", + "requires": { + "json-buffer": "3.0.0" + } + }, "killable": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", @@ -8745,6 +8348,21 @@ "promise": "^7.1.1", "request": "^2.83.0", "source-map": "~0.6.0" + }, + "dependencies": { + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "optional": true + } } }, "less-loader": { @@ -8756,6 +8374,20 @@ "clone": "^2.1.1", "loader-utils": "^1.1.0", "pify": "^4.0.1" + }, + "dependencies": { + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + } } }, "license-webpack-plugin": { @@ -8777,6 +8409,17 @@ "immediate": "~3.0.5" } }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, "loader-runner": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", @@ -8795,12 +8438,11 @@ } }, "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", "requires": { - "p-locate": "^3.0.0", + "p-locate": "^2.0.0", "path-exists": "^3.0.0" } }, @@ -8809,6 +8451,60 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, + "lodash._baseassign": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", + "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", + "requires": { + "lodash._basecopy": "^3.0.0", + "lodash.keys": "^3.0.0" + } + }, + "lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=" + }, + "lodash._bindcallback": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", + "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=" + }, + "lodash._createassigner": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz", + "integrity": "sha1-g4pbri/aymOsIt7o4Z+k5taXCxE=", + "requires": { + "lodash._bindcallback": "^3.0.0", + "lodash._isiterateecall": "^3.0.0", + "lodash.restparam": "^3.0.0" + } + }, + "lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=" + }, + "lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=" + }, + "lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=" + }, + "lodash.assign": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-3.2.0.tgz", + "integrity": "sha1-POnwI0tLIiPilrj6CsH+6OvKZPo=", + "requires": { + "lodash._baseassign": "^3.0.0", + "lodash._createassigner": "^3.0.0", + "lodash.keys": "^3.0.0" + } + }, "lodash.clonedeep": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", @@ -8820,17 +8516,59 @@ "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=" + }, + "lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=" + }, "lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" }, + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "requires": { + "lodash._getnative": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" + } + }, "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", "dev": true }, + "lodash.restparam": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", + "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=" + }, + "lodash.template": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", + "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", + "requires": { + "lodash._reinterpolate": "^3.0.0", + "lodash.templatesettings": "^4.0.0" + } + }, + "lodash.templatesettings": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", + "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", + "requires": { + "lodash._reinterpolate": "^3.0.0" + } + }, "lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", @@ -8865,21 +8603,15 @@ "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "ms": "^2.1.1" + } } } }, "loglevel": { - "version": "1.6.6", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.6.tgz", - "integrity": "sha512-Sgr5lbboAUBo3eXCSPL4/KoVz3ROKquOjcctxmHIt+vol2DrqTQe3SwkKKuYhEiWB5kYa13YyopJ69deJ1irzQ==", + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.7.tgz", + "integrity": "sha512-cY2eLFrQSAfVPhCgH1s7JI73tMbg9YC3v3+ZHVW67sBS7UxWzNEk/ZBbSfLykBWHp33dqqtOv82gjhKEi81T/A==", "dev": true }, "loose-envify": { @@ -8890,6 +8622,20 @@ "js-tokens": "^3.0.0 || ^4.0.0" } }, + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "requires": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + } + }, + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" + }, "lru-cache": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", @@ -8916,6 +8662,14 @@ "requires": { "pify": "^4.0.1", "semver": "^5.6.0" + }, + "dependencies": { + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + } } }, "make-error": { @@ -8975,6 +8729,15 @@ "yallist": "^3.0.2" } }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, "ssri": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", @@ -8998,14 +8761,6 @@ "integrity": "sha512-xTYd4JVHpSCW+aqDof6w/MebaMVNTVYBZhbB/vi513xXdiPT92JMVCo0Jq8W2UZnzYRFeVbQiQ+I25l13JuKvA==", "requires": { "minimist": "^1.2.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "optional": true - } } }, "mamacro": { @@ -9029,6 +8784,11 @@ "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", "dev": true }, + "map-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", + "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=" + }, "map-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", @@ -9075,6 +8835,14 @@ "map-age-cleaner": "^0.1.1", "mimic-fn": "^2.0.0", "p-is-promise": "^2.0.0" + }, + "dependencies": { + "p-is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", + "dev": true + } } }, "memory-fs": { @@ -9087,6 +8855,22 @@ "readable-stream": "^2.0.1" } }, + "meow": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-4.0.1.tgz", + "integrity": "sha512-xcSBHD5Z86zaOc+781KrupuHAzeGXSLtiAOmBsiLDiPSaYSB6hdew2ng9EBAnZ62jagG9MHAOdxpDi/lWBFJ/A==", + "requires": { + "camelcase-keys": "^4.0.0", + "decamelize-keys": "^1.0.0", + "loud-rejection": "^1.0.0", + "minimist": "^1.1.3", + "minimist-options": "^3.0.1", + "normalize-package-data": "^2.3.4", + "read-pkg-up": "^3.0.0", + "redent": "^2.0.0", + "trim-newlines": "^2.0.0" + } + }, "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -9250,24 +9034,21 @@ } }, "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", + "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==" }, "mime-db": { - "version": "1.42.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.42.0.tgz", - "integrity": "sha512-UbfJCR4UAVRNgMpfImz05smAXK7+c+ZntjaA26ANtkXLlOe947Aag5zdIcKQULAiF9Cq4WxBi9jUs5zkA84bYQ==", - "dev": true + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", + "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==" }, "mime-types": { - "version": "2.1.25", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.25.tgz", - "integrity": "sha512-5KhStqB5xpTAeGqKBAMgwaYMnQik7teQN4IAzC7npDv6kzeU6prfkR67bc87J1kWMPGkoaZSq1npmexMgkmEVg==", - "dev": true, + "version": "2.1.26", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", + "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", "requires": { - "mime-db": "1.42.0" + "mime-db": "1.43.0" } }, "mimic-fn": { @@ -9276,6 +9057,11 @@ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true }, + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" + }, "mini-css-extract-plugin": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.8.0.tgz", @@ -9299,6 +9085,31 @@ "query-string": "^4.1.0", "sort-keys": "^1.0.0" } + }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", + "dev": true + }, + "query-string": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", + "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", + "dev": true, + "requires": { + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" + } + }, + "sort-keys": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", + "dev": true, + "requires": { + "is-plain-obj": "^1.0.0" + } } } }, @@ -9323,9 +9134,18 @@ } }, "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + }, + "minimist-options": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz", + "integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==", + "requires": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0" + } }, "minipass": { "version": "3.1.1", @@ -9351,23 +9171,6 @@ "dev": true, "requires": { "minipass": "^3.0.0" - }, - "dependencies": { - "minipass": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.1.tgz", - "integrity": "sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } } }, "minipass-flush": { @@ -9377,23 +9180,6 @@ "dev": true, "requires": { "minipass": "^3.0.0" - }, - "dependencies": { - "minipass": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.1.tgz", - "integrity": "sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } } }, "minipass-pipeline": { @@ -9403,23 +9189,6 @@ "dev": true, "requires": { "minipass": "^3.0.0" - }, - "dependencies": { - "minipass": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.1.tgz", - "integrity": "sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } } }, "minizlib": { @@ -9494,17 +9263,29 @@ "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "requires": { "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + } } }, + "modify-values": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", + "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==" + }, "moment": { "version": "2.24.0", "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" }, "mousetrap": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/mousetrap/-/mousetrap-1.6.3.tgz", - "integrity": "sha512-bd+nzwhhs9ifsUrC2tWaSgm24/oo2c83zaRyZQF06hYA6sANfsXHtnZ19AbbbDXCDzeH5nZBSQ4NvCjgD62tJA==" + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/mousetrap/-/mousetrap-1.6.5.tgz", + "integrity": "sha512-QNo4kEepaIBwiT8CDhP98umTetp+JNfQYBWvC1pc6/OAibuXtRcxZ58Qz8skvEHYvURne/7R8T5VoOI7rDsEUA==" }, "move-concurrently": { "version": "1.0.1", @@ -9518,13 +9299,23 @@ "mkdirp": "^0.5.1", "rimraf": "^2.5.4", "run-queue": "^1.0.3" + }, + "dependencies": { + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } } }, "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "multicast-dns": { "version": "6.2.3", @@ -9583,8 +9374,17 @@ "neo-async": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", - "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", - "dev": true + "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==" + }, + "nerf-dart": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/nerf-dart/-/nerf-dart-1.0.0.tgz", + "integrity": "sha1-5tq3/r9a2Bbqgc9cYpxaDr3nLBo=" + }, + "netrc": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/netrc/-/netrc-0.1.4.tgz", + "integrity": "sha1-a+lPysqNd63gqWcNxGCRTJRHJEQ=" }, "ngrx-store-freeze": { "version": "0.2.4", @@ -9596,9 +9396,9 @@ } }, "ngx-clipboard": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/ngx-clipboard/-/ngx-clipboard-12.3.0.tgz", - "integrity": "sha512-ToSsuDv9I1L0g+TcthePcZ4B859/MpoarlHVr2KnHWy3pR8SxfJlNyP2i9STYRQkJ5bSEg65RFErW4tx52lHYQ==", + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/ngx-clipboard/-/ngx-clipboard-12.3.1.tgz", + "integrity": "sha512-qrUmkfYXEBQZQ4wNKno8yw5eOSxtdkzCPFFPil/XVVc93MNIKUme8aLbS3TEMB4PGsxopjbuTAzV7fptiSOIww==", "requires": { "ngx-window-token": "^2.0.0", "tslib": "^1.9.0" @@ -9609,8 +9409,16 @@ "resolved": "https://registry.npmjs.org/ngx-color-picker/-/ngx-color-picker-9.0.0.tgz", "integrity": "sha512-fe5KS89gzzBBB+keugAQtneBOGso44EW8cxJa8j/4c7pELBGw1Xgu/duINuwlwVDalN9R5GIFWeMdNaBvBPHyA==" }, + "ngx-daterangepicker-material": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ngx-daterangepicker-material/-/ngx-daterangepicker-material-2.2.0.tgz", + "integrity": "sha512-rw1UdRPeWoM52fgeYJYox+gkWEDt3+swk2qB/tb8tLYJJa2nW3DectHWGNQ+ysWp9SD9bhi8i0V8ARd0XjjqvA==", + "requires": { + "tslib": "^1.9.0" + } + }, "ngx-flowchart": { - "version": "git://github.com/thingsboard/ngx-flowchart.git#671b505b2484806a4a1c376344d0a12e5716679a", + "version": "git://github.com/thingsboard/ngx-flowchart.git#684c71fa3f15f99c5c22c8a6cf69a12d0b12be6e", "from": "git://github.com/thingsboard/ngx-flowchart.git#master", "requires": { "tslib": "^1.10.0" @@ -9697,9 +9505,9 @@ } }, "node-releases": { - "version": "1.1.48", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.48.tgz", - "integrity": "sha512-Hr8BbmUl1ujAST0K0snItzEA5zkJTQup8VNTKNfT6Zw8vTJkIiagUPNfxHmgDOyfFYNfKAul40sD0UEYTvwebw==", + "version": "1.1.49", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.49.tgz", + "integrity": "sha512-xH8t0LS0disN0mtRCh+eByxFPie+msJUBL/lJDBuap53QGiYPa9joh83K4pCZgWJ+2L4b9h88vCVdXQ60NO2bg==", "dev": true, "requires": { "semver": "^6.3.0" @@ -9726,20 +9534,11 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, "requires": { "hosted-git-info": "^2.1.4", "resolve": "^1.10.0", "semver": "2 || 3 || 4 || 5", "validate-npm-package-license": "^3.0.1" - }, - "dependencies": { - "hosted-git-info": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz", - "integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==", - "dev": true - } } }, "normalize-path": { @@ -9754,16 +9553,15 @@ "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", "dev": true }, - "normalize-scroll-left": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/normalize-scroll-left/-/normalize-scroll-left-0.2.0.tgz", - "integrity": "sha512-t5oCENZJl8TGusJKoCJm7+asaSsPuNmK6+iEjrZ5TyBj2f02brCRsd4c83hwtu+e5d4LCSBZ0uoDlMjBo+A8yA==" - }, "normalize-url": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", - "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==", - "dev": true + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-2.0.1.tgz", + "integrity": "sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==", + "requires": { + "prepend-http": "^2.0.0", + "query-string": "^5.0.1", + "sort-keys": "^2.0.0" + } }, "npm-bundled": { "version": "1.1.1", @@ -9784,20 +9582,11 @@ "version": "6.1.1", "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.1.tgz", "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==", - "dev": true, "requires": { "hosted-git-info": "^2.7.1", "osenv": "^0.1.5", "semver": "^5.6.0", "validate-npm-package-name": "^3.0.0" - }, - "dependencies": { - "hosted-git-info": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz", - "integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==", - "dev": true - } } }, "npm-packlist": { @@ -9822,10 +9611,29 @@ "semver": "^5.4.1" } }, + "npm-registry-client": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/npm-registry-client/-/npm-registry-client-8.6.0.tgz", + "integrity": "sha512-Qs6P6nnopig+Y8gbzpeN/dkt+n7IyVd8f45NTMotGk6Qo7GfBmzwYx6jRLoOOgKiMnaQfYxsuyQlD8Mc3guBhg==", + "requires": { + "concat-stream": "^1.5.2", + "graceful-fs": "^4.1.6", + "normalize-package-data": "~1.0.1 || ^2.0.0", + "npm-package-arg": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0", + "npmlog": "2 || ^3.1.0 || ^4.0.0", + "once": "^1.3.3", + "request": "^2.74.0", + "retry": "^0.10.0", + "safe-buffer": "^5.1.1", + "semver": "2 >=2.2.1 || 3.x || 4 || 5", + "slide": "^1.1.3", + "ssri": "^5.2.4" + } + }, "npm-registry-fetch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-4.0.2.tgz", - "integrity": "sha512-Z0IFtPEozNdeZRPh3aHHxdG+ZRpzcbQaJLthsm3VhNf6DScicTFRHZzK82u8RsJUsUHkX+QH/zcB/5pmd20H4A==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-4.0.3.tgz", + "integrity": "sha512-WGvUx0lkKFhu9MbiGFuT9nG2NpfQ+4dCJwRwwtK2HK5izJEvwDxMeUyqbuMS7N/OkpVCqDorV6rO5E4V9F8lJw==", "dev": true, "requires": { "JSONStream": "^1.3.4", @@ -9864,11 +9672,61 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, "requires": { "path-key": "^2.0.0" } }, + "npmconf": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/npmconf/-/npmconf-2.1.3.tgz", + "integrity": "sha512-iTK+HI68GceCoGOHAQiJ/ik1iDfI7S+cgyG8A+PP18IU3X83kRhQIRhAUNj4Bp2JMx6Zrt5kCiozYa9uGWTjhA==", + "requires": { + "config-chain": "~1.1.8", + "inherits": "~2.0.0", + "ini": "^1.2.0", + "mkdirp": "^0.5.0", + "nopt": "~3.0.1", + "once": "~1.3.0", + "osenv": "^0.1.0", + "safe-buffer": "^5.1.1", + "semver": "2 || 3 || 4", + "uid-number": "0.0.5" + }, + "dependencies": { + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "requires": { + "abbrev": "1" + } + }, + "once": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", + "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=", + "requires": { + "wrappy": "1" + } + }, + "semver": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", + "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=" + } + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, "nth-check": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", @@ -9887,14 +9745,12 @@ "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" }, "object-assign": { "version": "4.1.1", @@ -10077,10 +9933,16 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "dev": true, "requires": { "minimist": "~0.0.1", "wordwrap": "~0.0.2" + }, + "dependencies": { + "minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" + } } }, "ora": { @@ -10144,6 +10006,45 @@ "execa": "^1.0.0", "lcid": "^2.0.0", "mem": "^4.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + } } }, "os-tmpdir": { @@ -10160,6 +10061,11 @@ "os-tmpdir": "^1.0.0" } }, + "p-cancelable": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz", + "integrity": "sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==" + }, "p-defer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", @@ -10169,31 +10075,27 @@ "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" }, "p-is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", - "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", - "dev": true + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", + "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=" }, "p-limit": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", - "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", - "dev": true, + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", "requires": { - "p-try": "^2.0.0" + "p-try": "^1.0.0" } }, "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "requires": { - "p-limit": "^2.0.0" + "p-limit": "^1.1.0" } }, "p-map": { @@ -10205,20 +10107,40 @@ "aggregate-error": "^3.0.0" } }, + "p-reduce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-1.0.0.tgz", + "integrity": "sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=" + }, "p-retry": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-3.0.1.tgz", - "integrity": "sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w==", - "dev": true, + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-1.0.0.tgz", + "integrity": "sha1-OSczKkt9cCabU1UVEX/FR9oaaWg=", + "requires": { + "retry": "^0.10.0" + } + }, + "p-series": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-series/-/p-series-1.1.0.tgz", + "integrity": "sha512-356covArc9UCfj2twY/sxCJKGMzzO+pJJtucizsPC6aS1xKSTBc9PQrQhvFR3+7F+fa2KBKdJjdIcv6NEWDcIQ==", + "requires": { + "@sindresorhus/is": "^0.7.0", + "p-reduce": "^1.0.0" + } + }, + "p-timeout": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz", + "integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==", "requires": { - "retry": "^0.12.0" + "p-finally": "^1.0.0" } }, "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" }, "pacote": { "version": "9.5.8", @@ -10280,6 +10202,15 @@ "y18n": "^4.0.0" } }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, "lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -10299,6 +10230,15 @@ "yallist": "^3.0.0" } }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, "ssri": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", @@ -10347,11 +10287,15 @@ "safe-buffer": "^5.1.1" } }, + "parse-github-repo-url": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/parse-github-repo-url/-/parse-github-repo-url-1.4.1.tgz", + "integrity": "sha1-nn2LslKmy2ukJZUGC3v23z28H1A=" + }, "parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "dev": true, "requires": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" @@ -10408,8 +10352,7 @@ "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" }, "path-is-absolute": { "version": "1.0.1", @@ -10425,14 +10368,21 @@ "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + }, + "path-object": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/path-object/-/path-object-2.3.0.tgz", + "integrity": "sha1-A+RmU+XDdcYK8cq92UvGRIpdkRA=", + "requires": { + "core-util-is": "^1.0.1", + "lodash.assign": "^3.0.0" + } }, "path-parse": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" }, "path-to-regexp": { "version": "0.1.7", @@ -10444,17 +10394,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "requires": { - "pify": "^3.0.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - } + "requires": { + "pify": "^3.0.0" } }, "pbkdf2": { @@ -10476,28 +10417,25 @@ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "picomatch": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.1.1.tgz", - "integrity": "sha512-OYMyqkKzK7blWO/+XZYP6w8hH0LDvkBvdvKukti+7kqYFCiEAk+gI3DWnryapc0Dau05ugGTy0foQ6mqn4AHYA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.1.tgz", + "integrity": "sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA==", "dev": true }, "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" }, "pinkie": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" }, "pinkie-promise": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true, "requires": { "pinkie": "^2.0.0" } @@ -10509,6 +10447,51 @@ "dev": true, "requires": { "find-up": "^3.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", + "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + } } }, "popper.js": { @@ -10527,20 +10510,14 @@ "mkdirp": "^0.5.1" }, "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", "dev": true, "requires": { - "ms": "^2.1.1" + "lodash": "^4.17.14" } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true } } }, @@ -10573,22 +10550,35 @@ } }, "postcss-calc": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.1.tgz", - "integrity": "sha512-oXqx0m6tb4N3JGdmeMSc/i91KppbYsFZKdH0xMOqK8V1rJlzrKlTdokz8ozUXLVejydRN6u2IddxpcijRj2FqQ==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.2.tgz", + "integrity": "sha512-rofZFHUg6ZIrvRwPeFktv06GdbDYLcGqh9EwiMutZg+a0oePCCw1zHOEiji6LCpyRcjTREtPASuUqeAvYlEVvQ==", "dev": true, "requires": { - "css-unit-converter": "^1.1.1", - "postcss": "^7.0.5", - "postcss-selector-parser": "^5.0.0-rc.4", - "postcss-value-parser": "^3.3.1" + "postcss": "^7.0.27", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.0.2" }, "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true + "postcss": { + "version": "7.0.27", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", + "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } } } }, @@ -10743,13 +10733,28 @@ "vendors": "^1.0.0" }, "dependencies": { + "dot-prop": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", + "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", + "dev": true, + "requires": { + "is-obj": "^2.0.0" + } + }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true + }, "postcss-selector-parser": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz", - "integrity": "sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", + "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", "dev": true, "requires": { - "dot-prop": "^4.1.1", + "dot-prop": "^5.2.0", "indexes-of": "^1.0.1", "uniq": "^1.0.1" } @@ -10828,13 +10833,28 @@ "postcss-selector-parser": "^3.0.0" }, "dependencies": { + "dot-prop": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", + "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", + "dev": true, + "requires": { + "is-obj": "^2.0.0" + } + }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true + }, "postcss-selector-parser": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz", - "integrity": "sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", + "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", "dev": true, "requires": { - "dot-prop": "^4.1.1", + "dot-prop": "^5.2.0", "indexes-of": "^1.0.1", "uniq": "^1.0.1" } @@ -10978,6 +10998,12 @@ "postcss-value-parser": "^3.0.0" }, "dependencies": { + "normalize-url": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", + "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==", + "dev": true + }, "postcss-value-parser": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", @@ -11056,22 +11082,14 @@ } }, "postcss-selector-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", - "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz", + "integrity": "sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==", "dev": true, "requires": { - "cssesc": "^2.0.0", + "cssesc": "^3.0.0", "indexes-of": "^1.0.1", "uniq": "^1.0.1" - }, - "dependencies": { - "cssesc": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", - "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==", - "dev": true - } } }, "postcss-svgo": { @@ -11112,10 +11130,9 @@ "dev": true }, "prepend-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", - "dev": true + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=" }, "prismjs": { "version": "1.19.0", @@ -11140,8 +11157,7 @@ "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "promise": { "version": "7.3.1", @@ -11167,14 +11183,6 @@ "requires": { "err-code": "^1.0.0", "retry": "^0.10.0" - }, - "dependencies": { - "retry": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz", - "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=", - "dev": true - } } }, "prop-types": { @@ -11224,6 +11232,12 @@ "webdriver-manager": "^12.0.6" }, "dependencies": { + "@types/q": { + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/@types/q/-/q-0.0.32.tgz", + "integrity": "sha1-vShOV8hPEyXacCur/IKlMoGQwMU=", + "dev": true + }, "ansi-styles": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", @@ -11296,18 +11310,27 @@ "path-is-inside": "^1.0.1" } }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true }, + "q": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz", + "integrity": "sha1-VXBbzZPF82c1MMLCy8DCs63cKG4=", + "dev": true + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -11374,8 +11397,7 @@ "psl": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz", - "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==", - "dev": true + "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==" }, "public-encrypt": { "version": "4.0.3", @@ -11430,10 +11452,9 @@ "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" }, "q": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz", - "integrity": "sha1-VXBbzZPF82c1MMLCy8DCs63cKG4=", - "dev": true + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" }, "qjobs": { "version": "1.2.0", @@ -11444,15 +11465,14 @@ "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, "query-string": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", - "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", - "dev": true, + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", + "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", "requires": { + "decode-uri-component": "^0.2.0", "object-assign": "^4.1.0", "strict-uri-encode": "^1.0.0" } @@ -11474,6 +11494,11 @@ "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==", "dev": true }, + "quick-lru": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz", + "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=" + }, "raf": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", @@ -11742,11 +11767,29 @@ "util-promisify": "^2.1.0" } }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^3.0.0" + } + }, "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -11770,12 +11813,21 @@ } }, "readdirp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", - "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.3.0.tgz", + "integrity": "sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ==", "dev": true, "requires": { - "picomatch": "^2.0.4" + "picomatch": "^2.0.7" + } + }, + "redent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", + "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", + "requires": { + "indent-string": "^3.0.0", + "strip-indent": "^2.0.0" } }, "reflect-metadata": { @@ -11853,9 +11905,9 @@ "dev": true }, "regjsparser": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.2.tgz", - "integrity": "sha512-E9ghzUtoLwDekPT0DYCp+c4h+bvuUpe6rRHCTYn6eGoqj1LgKXxT6I0Il4WbjhQkOghzi/V+y03bPKvbllL93Q==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.3.tgz", + "integrity": "sha512-8uZvYbnfAtEm9Ab8NTb3hdLwL4g/LQzEYP7Xs27T96abJCCE2d6r3cPZPQEsLKy0vRSGVNG+/zVGtLr86HQduA==", "dev": true, "requires": { "jsesc": "~0.5.0" @@ -11887,11 +11939,18 @@ "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", "dev": true }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "requires": { + "is-finite": "^1.0.0" + } + }, "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", - "dev": true, + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", "requires": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", @@ -11900,7 +11959,7 @@ "extend": "~3.0.2", "forever-agent": "~0.6.1", "form-data": "~2.3.2", - "har-validator": "~5.1.0", + "har-validator": "~5.1.3", "http-signature": "~1.2.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", @@ -11910,7 +11969,7 @@ "performance-now": "^2.1.0", "qs": "~6.5.2", "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", + "tough-cookie": "~2.5.0", "tunnel-agent": "^0.6.0", "uuid": "^3.3.2" } @@ -11927,6 +11986,11 @@ "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", "dev": true }, + "require-relative": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz", + "integrity": "sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=" + }, "requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -11939,10 +12003,9 @@ "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" }, "resolve": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", - "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", - "dev": true, + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", + "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", "requires": { "path-parse": "^1.0.6" } @@ -11959,8 +12022,7 @@ "resolve-from": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", - "dev": true + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" }, "resolve-url": { "version": "0.2.1", @@ -11968,6 +12030,14 @@ "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", "dev": true }, + "responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "requires": { + "lowercase-keys": "^1.0.0" + } + }, "restore-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", @@ -11985,10 +12055,9 @@ "dev": true }, "retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", - "dev": true + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz", + "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=" }, "rfdc": { "version": "1.1.4", @@ -12017,9 +12086,9 @@ } }, "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.0.tgz", + "integrity": "sha512-NDGVxTsjqfunkds7CqsOiEnxln4Bo7Nddl3XhS4pXg5OzwkLqJ971ZVAAnB+DDLnF76N+VnDEiBHaVV8I06SUg==", "dev": true, "requires": { "glob": "^7.1.3" @@ -12075,8 +12144,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-regex": { "version": "1.1.0", @@ -12090,8 +12158,7 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sass": { "version": "1.23.3", @@ -12163,13 +12230,6 @@ "integrity": "sha1-ueU5g8xV/y29e2Xj2+CF2dEoXyo=", "requires": { "async": "^1.5.0" - }, - "dependencies": { - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" - } } }, "schema-utils": { @@ -12184,9 +12244,9 @@ } }, "screenfull": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/screenfull/-/screenfull-5.0.1.tgz", - "integrity": "sha512-NgQH4KKh2V3zlj2u90l7TUcSFxr9qL/64QEvhAvCN/fu1YS39YLTBKIqZqiS3STj3QD8sN6XnsK/8jk3hRq4WA==" + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/screenfull/-/screenfull-5.0.2.tgz", + "integrity": "sha512-cCF2b+L/mnEiORLN5xSAz6H3t18i2oHh9BA8+CQlAh5DRw2+NFAGQJOSYbcGw8B2k04g/lVvFcfZ83b3ysH5UQ==" }, "select": { "version": "1.1.2", @@ -12212,6 +12272,15 @@ "xml2js": "^0.4.17" }, "dependencies": { + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, "tmp": { "version": "0.0.30", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.30.tgz", @@ -12232,6 +12301,32 @@ "node-forge": "0.9.0" } }, + "semantic-release": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-8.2.3.tgz", + "integrity": "sha512-L0EKspX5xaPKhrrZWwbdlydwk+uXQG+SadYlaQjYQJjaBSfptd+jL6FRu3LspZoLxvKF8yQWe4sYAySHcp69aw==", + "requires": { + "@semantic-release/commit-analyzer": "^3.0.1", + "@semantic-release/condition-travis": "^6.0.0", + "@semantic-release/error": "^2.0.0", + "@semantic-release/last-release-npm": "^2.0.0", + "@semantic-release/release-notes-generator": "^4.0.0", + "execa": "^0.8.0", + "fs-extra": "^4.0.2", + "git-head": "^1.2.1", + "github": "^12.0.0", + "lodash": "^4.0.0", + "nerf-dart": "^1.0.0", + "nopt": "^4.0.0", + "normalize-package-data": "^2.3.4", + "npmconf": "^2.1.2", + "npmlog": "^4.0.0", + "p-series": "^1.0.0", + "parse-github-repo-url": "^1.3.0", + "require-relative": "^0.8.7", + "semver": "^5.4.1" + } + }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", @@ -12276,6 +12371,29 @@ "statuses": "~1.5.0" }, "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, "ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", @@ -12305,6 +12423,15 @@ "parseurl": "~1.3.2" }, "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, "http-errors": { "version": "1.6.3", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", @@ -12323,6 +12450,12 @@ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, "setprototypeof": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", @@ -12346,8 +12479,7 @@ "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, "set-immediate-shim": { "version": "1.0.1", @@ -12418,7 +12550,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, "requires": { "shebang-regex": "^1.0.0" } @@ -12426,8 +12557,7 @@ "shebang-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" }, "side-channel": { "version": "1.0.2", @@ -12446,8 +12576,7 @@ "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, "simple-swizzle": { "version": "0.2.2", @@ -12469,8 +12598,12 @@ "slash": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", - "dev": true + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=" + }, + "slide": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", + "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=" }, "smart-buffer": { "version": "4.1.0", @@ -12494,6 +12627,15 @@ "use": "^3.1.0" }, "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, "define-property": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", @@ -12512,6 +12654,12 @@ "is-extendable": "^0.1.0" } }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -12613,6 +12761,12 @@ "requires": { "ms": "2.0.0" } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true } } }, @@ -12658,6 +12812,12 @@ "requires": { "ms": "2.0.0" } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true } } }, @@ -12692,6 +12852,12 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true } } }, @@ -12719,15 +12885,6 @@ "url-parse": "^1.4.3" }, "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, "faye-websocket": { "version": "0.11.3", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", @@ -12736,12 +12893,6 @@ "requires": { "websocket-driver": ">=0.5.1" } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true } } }, @@ -12777,10 +12928,9 @@ } }, "sort-keys": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", - "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", - "dev": true, + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz", + "integrity": "sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg=", "requires": { "is-plain-obj": "^1.0.0" } @@ -12794,8 +12944,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, "source-map-loader": { "version": "0.2.4", @@ -12805,6 +12954,17 @@ "requires": { "async": "^2.5.0", "loader-utils": "^1.1.0" + }, + "dependencies": { + "async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + } } }, "source-map-resolve": { @@ -12846,7 +13006,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", - "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" @@ -12855,14 +13014,12 @@ "spdx-exceptions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", - "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", - "dev": true + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==" }, "spdx-expression-parse": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", - "dev": true, "requires": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" @@ -12871,8 +13028,7 @@ "spdx-license-ids": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", - "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", - "dev": true + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==" }, "spdy": { "version": "4.0.1", @@ -12895,12 +13051,6 @@ "requires": { "ms": "^2.1.1" } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true } } }, @@ -12927,16 +13077,10 @@ "ms": "^2.1.1" } }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "readable-stream": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.5.0.tgz", - "integrity": "sha512-gSz026xs2LfxBPudDuI41V1lka8cxg64E66SGe78zJlsUofOg/yqwezdIcdfwik6B4h8LFmWPA9ef9X3FiNFLA==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "dev": true, "requires": { "inherits": "^2.0.3", @@ -12955,6 +13099,14 @@ "chalk": "^2.0.1" } }, + "split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "requires": { + "through": "2" + } + }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -12969,6 +13121,14 @@ "resolved": "https://registry.npmjs.org/split.js/-/split.js-1.5.11.tgz", "integrity": "sha512-ec0sAbWnaMGpNHWo1ZgIlF3Mx7GzSyaO0GlcEBZGIFZQwYPPkbDV6JRpDmpzIshVig7USREuEPudy0ygQaskXg==" }, + "split2": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz", + "integrity": "sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw==", + "requires": { + "through2": "^2.0.2" + } + }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -12978,7 +13138,6 @@ "version": "1.16.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "dev": true, "requires": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -12992,13 +13151,11 @@ } }, "ssri": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-7.1.0.tgz", - "integrity": "sha512-77/WrDZUWocK0mvA5NTRQyveUf+wsrIc6vyrxpS8tVvYBcX215QbafrJR3KtkpskIzoFLqqNuuYQvxaMjXJ/0g==", - "dev": true, + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-5.3.0.tgz", + "integrity": "sha512-XRSIPqLij52MtgoQavH/x/dU1qVKtWUAAZeOHsR9c2Ddi4XerFy3mc1alf+dLJKl9EUIm/Ht+EowFkTUOA6GAQ==", "requires": { - "figgy-pudding": "^3.5.1", - "minipass": "^3.1.1" + "safe-buffer": "^5.1.1" } }, "stable": { @@ -13095,54 +13252,32 @@ "lodash": "^4.17.14" } }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", "dev": true, "requires": { - "ms": "^2.1.1" + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true } } }, "strict-uri-encode": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", - "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", - "dev": true + "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=" }, "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } }, "string.prototype.trimleft": { @@ -13167,7 +13302,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, "requires": { "safe-buffer": "~5.1.0" } @@ -13176,7 +13310,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, "requires": { "ansi-regex": "^2.0.0" } @@ -13184,14 +13317,17 @@ "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" }, "strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" + }, + "strip-indent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", + "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=" }, "style-loader": { "version": "1.0.0", @@ -13226,13 +13362,28 @@ "postcss-selector-parser": "^3.0.0" }, "dependencies": { + "dot-prop": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", + "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", + "dev": true, + "requires": { + "is-obj": "^2.0.0" + } + }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true + }, "postcss-selector-parser": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz", - "integrity": "sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", + "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", "dev": true, "requires": { - "dot-prop": "^4.1.1", + "dot-prop": "^5.2.0", "indexes-of": "^1.0.1", "uniq": "^1.0.1" } @@ -13264,6 +13415,12 @@ "ms": "2.0.0" } }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -13293,7 +13450,6 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, "requires": { "has-flag": "^3.0.0" } @@ -13453,9 +13609,9 @@ } }, "make-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.0.tgz", - "integrity": "sha512-grNJDhb8b1Jm1qeqW5R/O63wUo4UXo2v2HMic6YT9i/HBlF93S8jkMgH7yugvY9ABDShH4VZMn8I+U8+fCNegw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.2.tgz", + "integrity": "sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w==", "dev": true, "requires": { "semver": "^6.0.0" @@ -13479,6 +13635,12 @@ "p-limit": "^2.2.0" } }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -13521,17 +13683,20 @@ } } }, + "text-extensions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz", + "integrity": "sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==" + }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, "through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, "requires": { "readable-stream": "~2.3.6", "xtend": "~4.0.1" @@ -13543,6 +13708,11 @@ "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", "dev": true }, + "timed-out": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=" + }, "timers-browserify": { "version": "2.0.11", "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.11.tgz", @@ -13654,29 +13824,49 @@ "integrity": "sha512-W4tY3LG2eyPY2VQZRH3JcsNuRl3jPCEGmKBPOMTP/05E3+1kOJjASzPRRkcpP+uf9vqX7+896ivU86f6B8Esgw==" }, "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", - "dev": true, + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" + "psl": "^1.1.28", + "punycode": "^2.1.1" }, "dependencies": { "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" } } }, + "travis-deploy-once": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/travis-deploy-once/-/travis-deploy-once-3.3.0.tgz", + "integrity": "sha512-N0l+sdYHMQbJmQWfjmxkKRJrMWJ+52Ve1dhGDiPhGXFiWO0A0zSeTxiFujz0rSOLOl9OhsZ9EMlJh2lNrgGugA==", + "requires": { + "chalk": "^2.1.0", + "got": "^8.0.1", + "p-retry": "^1.0.0", + "semver": "^5.4.1", + "url-join": "^2.0.2" + } + }, "tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", "dev": true }, + "trim-newlines": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", + "integrity": "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=" + }, + "trim-off-newlines": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz", + "integrity": "sha1-n5up2e+odkw4dpi8v+sshI8RrbM=" + }, "ts-node": { "version": "8.6.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.6.2.tgz", @@ -13735,7 +13925,6 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, "requires": { "safe-buffer": "^5.0.1" } @@ -13748,8 +13937,7 @@ "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, "type-fest": { "version": "0.8.1", @@ -13770,8 +13958,7 @@ "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, "typeface-roboto": { "version": "0.0.75", @@ -13785,16 +13972,20 @@ "dev": true }, "uglify-js": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.7.2.tgz", - "integrity": "sha512-uhRwZcANNWVLrxLfNFEdltoPNhECUR3lc+UdJoG9CBpMcSnKyWA94tc3eAujB1GcMY5Uwq8ZMp4qWpxWYDQmaA==", - "dev": true, + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.7.7.tgz", + "integrity": "sha512-FeSU+hi7ULYy6mn8PKio/tXsdSXN35lm4KgV2asx00kzrLU9Pi3oAslcJT70Jdj7PHX29gGUPOT6+lXGBbemhA==", "optional": true, "requires": { "commander": "~2.20.3", "source-map": "~0.6.1" } }, + "uid-number": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.5.tgz", + "integrity": "sha1-Wj2yPvXb1VuB/ODsmirG/M3ruB4=" + }, "ultron": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", @@ -13880,30 +14071,12 @@ "debug": "^3.0.0", "request": "^2.88.0", "uuid": "^3.0.0" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } } }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" }, "unpipe": { "version": "1.0.0", @@ -13967,7 +14140,6 @@ "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "dev": true, "requires": { "punycode": "^2.1.0" }, @@ -13975,8 +14147,7 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" } } }, @@ -13995,6 +14166,11 @@ "querystring": "0.2.0" } }, + "url-join": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-2.0.5.tgz", + "integrity": "sha1-WvIvGMBSoACkjXuCxenC4v7tpyg=" + }, "url-parse": { "version": "1.4.7", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz", @@ -14005,6 +14181,19 @@ "requires-port": "^1.0.0" } }, + "url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", + "requires": { + "prepend-http": "^2.0.0" + } + }, + "url-to-options": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz", + "integrity": "sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k=" + }, "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", @@ -14041,8 +14230,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "util-promisify": { "version": "2.1.0", @@ -14074,14 +14262,12 @@ "uuid": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" }, "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, "requires": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" @@ -14091,7 +14277,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=", - "dev": true, "requires": { "builtins": "^1.0.3" } @@ -14112,7 +14297,6 @@ "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, "requires": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", @@ -14131,6 +14315,14 @@ "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", "dev": true }, + "walk": { + "version": "2.3.14", + "resolved": "https://registry.npmjs.org/walk/-/walk-2.3.14.tgz", + "integrity": "sha512-5skcWAUmySj6hkBdH6B6+3ddMjVQYH5Qy9QGbPmN8kVmLteXk+yVXg+yfk1nbX30EYakahLrr8iPcCxJQSCBeg==", + "requires": { + "foreachasync": "^3.0.0" + } + }, "warning": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", @@ -14967,6 +15159,15 @@ "readable-stream": "^2.0.1" } }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, "ssri": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", @@ -15023,12 +15224,6 @@ "errno": "^0.1.3", "readable-stream": "^2.0.1" } - }, - "mime": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", - "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", - "dev": true } } }, @@ -15759,11 +15954,14 @@ "is-buffer": "^1.1.5" } }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "p-retry": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-3.0.1.tgz", + "integrity": "sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w==", + "dev": true, + "requires": { + "retry": "^0.12.0" + } }, "readdirp": { "version": "2.2.1", @@ -15776,6 +15974,12 @@ "readable-stream": "^2.0.2" } }, + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", + "dev": true + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -15800,15 +16004,6 @@ "is-number": "^3.0.0", "repeat-string": "^1.6.1" } - }, - "ws": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", - "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", - "dev": true, - "requires": { - "async-limiter": "~1.0.0" - } } } }, @@ -15877,7 +16072,6 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, "requires": { "isexe": "^2.0.0" } @@ -15911,11 +16105,18 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "requires": { + "string-width": "^1.0.2 || 2" + } + }, "wordwrap": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", - "dev": true + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" }, "worker-farm": { "version": "1.7.0", @@ -15943,28 +16144,6 @@ "requires": { "string-width": "^1.0.1", "strip-ansi": "^3.0.1" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - } } }, "wrappy": { @@ -15973,14 +16152,12 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "ws": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", + "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", "dev": true, "requires": { - "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0", - "ultron": "~1.1.0" + "async-limiter": "~1.0.0" } }, "xml2js": { @@ -15991,14 +16168,6 @@ "requires": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" - }, - "dependencies": { - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true - } } }, "xmlbuilder": { @@ -16016,8 +16185,7 @@ "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" }, "y18n": { "version": "4.0.0", @@ -16048,6 +16216,82 @@ "which-module": "^2.0.0", "y18n": "^3.2.1 || ^4.0.0", "yargs-parser": "^11.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", + "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } } }, "yargs-parser": { @@ -16058,6 +16302,14 @@ "requires": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + } } }, "yeast": { diff --git a/ui-ngx/package.json b/ui-ngx/package.json index 1e4b937cde..bfc5afae99 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -62,6 +62,7 @@ "moment": "^2.24.0", "ngx-clipboard": "^12.3.0", "ngx-color-picker": "^9.0.0", + "ngx-daterangepicker-material": "^2.2.0", "ngx-flowchart": "git://github.com/thingsboard/ngx-flowchart.git#master", "ngx-hm-carousel": "^1.7.2", "ngx-translate-messageformat-compiler": "^4.5.0", diff --git a/ui-ngx/src/app/core/services/time.service.ts b/ui-ngx/src/app/core/services/time.service.ts index 30b15c3b08..ee5d278203 100644 --- a/ui-ngx/src/app/core/services/time.service.ts +++ b/ui-ngx/src/app/core/services/time.service.ts @@ -20,6 +20,7 @@ import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { defaultHttpOptions } from '@core/http/http-utils'; import { map } from 'rxjs/operators'; +import { isDefined } from '@core/utils'; export interface TimeInterval { name: string; @@ -70,10 +71,16 @@ export class TimeService { } public boundMinInterval(min: number): number { + if (isDefined(min)) { + min = Math.floor(min / 1000) * 1000; + } return this.toBound(min, MIN_INTERVAL, MAX_INTERVAL, MIN_INTERVAL); } public boundMaxInterval(max: number): number { + if (isDefined(max)) { + max = Math.floor(max / 1000) * 1000; + } return this.toBound(max, MIN_INTERVAL, MAX_INTERVAL, MAX_INTERVAL); } @@ -137,7 +144,7 @@ export class TimeService { } private toBound(value: number, min: number, max: number, defValue: number): number { - if (typeof value !== 'undefined') { + if (isDefined(value)) { value = Math.max(value, min); value = Math.min(value, max); return value; diff --git a/ui-ngx/src/app/core/settings/settings.utils.ts b/ui-ngx/src/app/core/settings/settings.utils.ts index c5798ac793..df6e1943bc 100644 --- a/ui-ngx/src/app/core/settings/settings.utils.ts +++ b/ui-ngx/src/app/core/settings/settings.utils.ts @@ -16,6 +16,7 @@ import { environment as env } from '@env/environment'; import { TranslateService } from '@ngx-translate/core'; +import * as _moment from 'moment'; export function updateUserLang(translate: TranslateService, userLang: string) { let targetLang = userLang; @@ -33,6 +34,7 @@ export function updateUserLang(translate: TranslateService, userLang: string) { console.log(`Detected supported lang: ${detectedSupportedLang}`); } translate.use(detectedSupportedLang); + _moment.locale([detectedSupportedLang]); } function detectSupportedLang(targetLang: string): string { diff --git a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts index 5f2a7962b3..433c72f053 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts @@ -19,7 +19,8 @@ import { Component, DoCheck, Input, - IterableDiffers, KeyValueDiffers, + IterableDiffers, + KeyValueDiffers, NgZone, OnChanges, OnDestroy, @@ -34,7 +35,7 @@ import { AuthUser } from '@shared/models/user.model'; import { getCurrentAuthUser } from '@core/auth/auth.selectors'; import { Timewindow, toHistoryTimewindow } from '@shared/models/time/time.models'; import { TimeService } from '@core/services/time.service'; -import { GridsterComponent, GridsterComponentInterface, GridsterConfig } from 'angular-gridster2'; +import { GridsterComponent, GridsterConfig } from 'angular-gridster2'; import { DashboardCallbacks, DashboardWidget, @@ -51,6 +52,7 @@ import { IAliasController, IStateController } from '@app/core/api/widget-api.mod import { Widget, WidgetPosition } from '@app/shared/models/widget.models'; import { MatMenuTrigger } from '@angular/material/menu'; import { SafeStyle } from '@angular/platform-browser'; +import { distinct } from 'rxjs/operators'; @Component({ selector: 'tb-dashboard', @@ -126,7 +128,9 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo dashboardTimewindowChangedSubject: Subject = new ReplaySubject(); - dashboardTimewindowChanged = this.dashboardTimewindowChangedSubject.asObservable(); + dashboardTimewindowChanged = this.dashboardTimewindowChangedSubject.asObservable().pipe( + distinct() + ); originalDashboardTimewindow: Timewindow; @@ -291,9 +295,9 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo addResizeListener(this.gridster.el, this.gridsterResizeListener); } - onUpdateTimewindow(startTimeMs: number, endTimeMs: number, interval?: number): void { + onUpdateTimewindow(startTimeMs: number, endTimeMs: number, interval?: number, persist?: boolean): void { this.ngZone.run(() => { - if (!this.originalDashboardTimewindow) { + if (!this.originalDashboardTimewindow && !persist) { this.originalDashboardTimewindow = deepClone(this.dashboardTimewindow); } this.dashboardTimewindow = toHistoryTimewindow(this.dashboardTimewindow, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator-panel.component.html new file mode 100644 index 0000000000..1f9d462b23 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator-panel.component.html @@ -0,0 +1,36 @@ + +
+ + + + + +
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator-panel.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator-panel.component.scss new file mode 100644 index 0000000000..f22ced2dbf --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator-panel.component.scss @@ -0,0 +1,33 @@ +/** + * Copyright © 2016-2019 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-date-range-navigator-panel { + overflow: auto; + background: #fff; + border-radius: 4px; + box-shadow: + 0 7px 8px -4px rgba(0, 0, 0, .2), + 0 13px 19px 2px rgba(0, 0, 0, .14), + 0 5px 24px 4px rgba(0, 0, 0, .12); + + .mat-content { + overflow: hidden; + background-color: #fff; + } + + .mat-padding { + padding: 16px; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator.component.html new file mode 100644 index 0000000000..026e1cc763 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator.component.html @@ -0,0 +1,56 @@ + +
+ + widgets.date-range-navigator.localizationMap.Date picker + + + {{advancedModel.chosenLabel}} + + + + + widgets.date-range-navigator.localizationMap.Interval + + + {{'widgets.date-range-navigator.localizationMap.' + customInterval.label | translate}} + + + {{'widgets.date-range-navigator.localizationMap.' + date.value.label | translate}} + + + + +
+ + + widgets.date-range-navigator.localizationMap.Step size + + + {{'widgets.date-range-navigator.localizationMap.' + date.value.label | translate}} + + + + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator.component.scss new file mode 100644 index 0000000000..20caf01dc5 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator.component.scss @@ -0,0 +1,109 @@ +/** + * Copyright © 2016-2019 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 { + display: flex; + height: 100%; + + .date-range-navigator { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-evenly; + width: 100%; + margin: auto; + + .drn__element { + display: flex; + flex-direction: row; + align-items: center; + max-width: 100%; + height: 60px; + margin: 4px 0; + } + + .navigation { + md-input-container { + margin: 0; + } + } + + .picker { + .picker__wrapper { + position: relative; + max-width: 100%; + padding: 2px; + + > label { + position: absolute; + right: -3px; + bottom: 100%; + left: 0; + padding-left: 3px; + color: #787878; + transform: scale(.75); + transform-origin: left bottom; + } + } + + .md-select-value { + min-width: 225px; + border-color: #e1e1e1; + + .md-select-icon { + color: #757575; + } + } + } + + &.short-mode { + display: block; + width: 90%; + + .drn__element { + width: 100%; + + md-input-container { + flex: 1; + } + } + + .picker { + .picker__wrapper { + width: 100%; + } + + .md-select-value { + min-width: initial; + } + } + + &.labels-hidden { + .drn__element { + margin: 0; + } + } + } + + &.long-mode { + &.labels-hidden { + .drn__element { + height: 36px; + } + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator.component.ts new file mode 100644 index 0000000000..6498086050 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator.component.ts @@ -0,0 +1,307 @@ +/// +/// Copyright © 2016-2019 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, + InjectionToken, + Input, + OnDestroy, + OnInit, + ViewChild, + ViewContainerRef, + ViewEncapsulation +} from '@angular/core'; +import { PageComponent } from '@shared/components/page.component'; +import { WidgetContext } from '@home/models/widget-component.models'; +import { UtilsService } from '@core/services/utils.service'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { + cloneDateRangeNavigatorModel, + DateIntervalEntry, + dateIntervalsMap, + DateRangeNavigatorModel, + DateRangeNavigatorSettings, + getFormattedDate +} from '@home/components/widget/lib/date-range-navigator/date-range-navigator.models'; +import { DatePipe, KeyValue } from '@angular/common'; +import * as _moment from 'moment'; +import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; +import { ComponentPortal, PortalInjector } from '@angular/cdk/portal'; +import { MatSelect } from '@angular/material/select'; +import { Subscription } from 'rxjs'; +import { HistoryWindowType, TimewindowType } from '@shared/models/time/time.models'; +import { isDefined } from '@core/utils'; + +@Component({ + selector: 'tb-date-range-navigator-widget', + templateUrl: './date-range-navigator.component.html', + styleUrls: ['./date-range-navigator.component.scss'] +}) +export class DateRangeNavigatorWidgetComponent extends PageComponent implements OnInit, OnDestroy { + + @Input() + ctx: WidgetContext; + + @ViewChild('datePicker', {static: true}) datePickerSelect: MatSelect; + + settings: DateRangeNavigatorSettings; + + datesMap = dateIntervalsMap; + + advancedModel: DateRangeNavigatorModel = {}; + + selectedDateInterval: number; + customInterval: DateIntervalEntry; + selectedStepSize: number; + + private firstUpdate = true; + private dashboardTimewindowChangedSubscription: Subscription; + + originalOrder = (a: KeyValue, b: KeyValue): number => { + return 0; + }; + + constructor(private utils: UtilsService, + private overlay: Overlay, + private viewContainerRef: ViewContainerRef, + protected store: Store) { + super(store); + } + + ngOnInit(): void { + this.dashboardTimewindowChangedSubscription = this.ctx.dashboard.dashboardTimewindowChanged.subscribe(() => { + this.widgetContextTimewindowSync(); + }); + this.settings = this.ctx.settings; + this.settings.useSessionStorage = isDefined(this.settings.useSessionStorage) ? this.settings.useSessionStorage : true; + let selection; + if (this.settings.useSessionStorage) { + selection = this.readFromStorage('date-range'); + } + if (selection) { + this.advancedModel = { + chosenLabel: selection.name, + startDate: _moment(selection.start), + endDate: _moment(selection.end) + }; + } else { + const end = new Date(); + end.setHours(23, 59, 59, 999); + this.advancedModel = { + startDate: _moment((end.getTime() + 1) - this.datesMap[this.settings.initialInterval || 'week'].ts), + endDate: _moment(end.getTime()) + }; + this.advancedModel.chosenLabel = getFormattedDate(this.advancedModel); + } + this.selectedStepSize = this.datesMap[this.settings.stepSize || 'day'].ts; + this.widgetContextTimewindowSync(); + } + + ngOnDestroy(): void { + if (this.dashboardTimewindowChangedSubscription) { + this.dashboardTimewindowChangedSubscription.unsubscribe(); + this.dashboardTimewindowChangedSubscription = null; + } + } + + openNavigatorPanel($event: Event) { + if ($event) { + $event.stopPropagation(); + } + this.datePickerSelect.close(); + const target = $event.target || $event.srcElement || $event.currentTarget; + const config = new OverlayConfig(); + config.backdropClass = 'cdk-overlay-transparent-backdrop'; + config.hasBackdrop = true; + config.panelClass = 'tb-date-range-navigator-panel'; + const connectedPosition: ConnectedPosition = { + originX: 'end', + originY: 'bottom', + overlayX: 'end', + overlayY: 'top' + }; + config.positionStrategy = this.overlay.position().flexibleConnectedTo(target as HTMLElement) + .withPositions([connectedPosition]); + + const overlayRef = this.overlay.create(config); + overlayRef.backdropClick().subscribe(() => { + overlayRef.dispose(); + }); + const injectionTokens = new WeakMap([ + [DATE_RANGE_NAVIGATOR_PANEL_DATA, { + model: cloneDateRangeNavigatorModel(this.advancedModel), + settings: this.settings, + onChange: model => { + this.advancedModel = model; + this.triggerChange(); + } + } as DateRangeNavigatorPanelData], + [OverlayRef, overlayRef] + ]); + const injector = new PortalInjector(this.viewContainerRef.injector, injectionTokens); + overlayRef.attach(new ComponentPortal(DateRangeNavigatorPanelComponent, + this.viewContainerRef, injector)); + this.ctx.detectChanges(); + } + + private widgetContextTimewindowSync() { + if (!this.firstUpdate) { + this.updateAdvancedModel(); + } + this.updateDateInterval(); + if (this.settings.useSessionStorage) { + this.updateStorageDate(); + } + if (this.firstUpdate) { + this.firstUpdate = false; + this.updateTimewindow(this.advancedModel.startDate.valueOf(), this.advancedModel.endDate.valueOf()); + } + this.ctx.detectChanges(); + } + + private updateAdvancedModel() { + const timewindow = this.ctx.dashboardTimewindow; + if (timewindow.selectedTab === TimewindowType.HISTORY && timewindow.history.historyType === HistoryWindowType.FIXED) { + const fixedTimewindow = timewindow.history.fixedTimewindow; + this.advancedModel.startDate = _moment(fixedTimewindow.startTimeMs); + this.advancedModel.endDate = _moment(fixedTimewindow.endTimeMs); + this.advancedModel.chosenLabel = getFormattedDate(this.advancedModel); + } + } + + private updateTimewindow(startTime: number, endTime: number) { + this.ctx.dashboard.onUpdateTimewindow(startTime, endTime, 10, true); + } + + private updateDateInterval() { + const interval = this.advancedModel.endDate.valueOf() - this.advancedModel.startDate.valueOf(); + + for (const key of Object.keys(this.datesMap)) { + if (Object.prototype.hasOwnProperty.call(this.datesMap, key)) { + if (this.datesMap[key].ts === interval || this.datesMap[key].ts === interval + 1 || this.datesMap[key].ts === interval - 1) { + this.selectedDateInterval = this.datesMap[key].ts; + this.customInterval = undefined; + return; + } + } + } + + this.selectedDateInterval = interval; + this.customInterval = {ts: interval, label: 'Custom interval'}; + } + + triggerChange() { + this.updateTimewindow(this.advancedModel.startDate.valueOf(), this.advancedModel.endDate.valueOf()); + } + + changeInterval() { + const endTime = this.ctx.dashboard.dashboardTimewindow.history ? + this.ctx.dashboard.dashboardTimewindow.history.fixedTimewindow.endTimeMs : + this.advancedModel.endDate.valueOf(); + this.updateTimewindow(endTime - this.selectedDateInterval / 2, endTime + this.selectedDateInterval / 2); + } + + goBack() { + this.step(-1); + } + + goForth() { + this.step(1); + } + + private step(direction: number) { + const startTime = this.ctx.dashboard.dashboardTimewindow.history ? + this.ctx.dashboard.dashboardTimewindow.history.fixedTimewindow.startTimeMs : + this.advancedModel.startDate.valueOf(); + const endTime = this.ctx.dashboard.dashboardTimewindow.history ? + this.ctx.dashboard.dashboardTimewindow.history.fixedTimewindow.endTimeMs : + this.advancedModel.endDate.valueOf(); + this.updateTimewindow(startTime + this.selectedStepSize * direction, endTime + this.selectedStepSize * direction); + } + + private readFromStorage(itemKey: string): any { + if (window.sessionStorage.getItem(itemKey)) { + const selection = JSON.parse(window.sessionStorage.getItem(itemKey)); + selection.start = new Date(parseInt(selection.start, 10)); + selection.end = new Date(parseInt(selection.end, 10)); + return selection; + } + return undefined; + } + + private updateStorageDate() { + this.saveIntoStorage('date-range', { + start: this.advancedModel.startDate.valueOf(), + end: this.advancedModel.endDate.valueOf(), + name: this.advancedModel.chosenLabel + }); + } + + private saveIntoStorage(keyName: string, selection: any) { + if (selection) { + window.sessionStorage.setItem(keyName, JSON.stringify(selection)); + } + } +} + +const DATE_RANGE_NAVIGATOR_PANEL_DATA = new InjectionToken('DateRangeNavigatorPanelData'); + +export interface DateRangeNavigatorPanelData { + model: DateRangeNavigatorModel; + settings: DateRangeNavigatorSettings; + onChange: (model: DateRangeNavigatorModel) => void; +} + +@Component({ + selector: 'tb-date-range-navigator-panel', + templateUrl: './date-range-navigator-panel.component.html', + styleUrls: ['./date-range-navigator-panel.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class DateRangeNavigatorPanelComponent { + + settings: DateRangeNavigatorSettings; + + model: DateRangeNavigatorModel; + + locale: any = {}; + + constructor(@Inject(DATE_RANGE_NAVIGATOR_PANEL_DATA) public data: DateRangeNavigatorPanelData, + private overlayRef: OverlayRef) { + this.model = data.model; + this.settings = data.settings; + this.locale.firstDay = this.settings.firstDayOfWeek || 1; + this.locale.daysOfWeek = _moment.weekdaysMin(); + this.locale.monthNames = _moment.monthsShort(); + } + + choosedDate($event) { + this.model = $event; + this.model.chosenLabel = getFormattedDate(this.model); + if (this.settings.autoConfirm) { + this.data.onChange(this.model); + this.overlayRef.dispose(); + } + } + + apply() { + this.data.onChange(this.model); + this.overlayRef.dispose(); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator.models.ts new file mode 100644 index 0000000000..adfeba027d --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator.models.ts @@ -0,0 +1,109 @@ +/// +/// Copyright © 2016-2019 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 * as _moment from 'moment'; + +export type DateRangeInterval = 'hour' | 'day' | 'week' | 'twoWeeks' | 'month' | 'threeMonths' | 'sixMonths'; + +export interface DateRangeNavigatorSettings { + hidePicker: boolean; + onePanel: boolean; + autoConfirm: boolean; + showTemplate: boolean; + firstDayOfWeek: number; + hideInterval: boolean; + initialInterval: DateRangeInterval; + hideStepSize: boolean; + stepSize: DateRangeInterval; + hideLabels: boolean; + useSessionStorage: boolean; +} + +export interface DateIntervalEntry { + ts: number; + label: string; +} + +export interface DateRangeNavigatorModel { + chosenLabel?: string; + startDate?: _moment.Moment; + endDate?: _moment.Moment; +} + +export function cloneDateRangeNavigatorModel(model: DateRangeNavigatorModel): DateRangeNavigatorModel { + const cloned: DateRangeNavigatorModel = {}; + cloned.chosenLabel = model.chosenLabel; + cloned.startDate = model.startDate ? model.startDate.clone() : undefined; + cloned.endDate = model.endDate ? model.endDate.clone() : undefined; + return cloned; +} + +export function getFormattedDate(model: DateRangeNavigatorModel): string { + let template: string; + + const startDate = model.startDate; + const endDate = model.endDate; + + if (startDate.diff(endDate, 'days') === 0) { + template = startDate.format('DD MMM YYYY'); // datePipe.transform(startDate, 'dd MMM yyyy'); + } else { + let startDateFormat = 'DD'; + if (startDate.month() !== endDate.month() || startDate.year() !== endDate.year()) { + startDateFormat += ' MMM'; + } + if (startDate.year() !== endDate.year()) { + startDateFormat += ' YYYY'; + } + template = startDate.format(startDateFormat) + ' - ' + endDate.format('DD MMM YYYY'); + } + return template; +} + +const hour = 3600000; +const day = 86400000; +const week = 604800000; +const month = 2629743000; + +export const dateIntervalsMap: {[key: string]: DateIntervalEntry} = { + hour: { + ts: hour, + label: 'Hour' + }, + day: { + ts: day, + label: 'Day' + }, + week: { + ts: week, + label: 'Week' + }, + twoWeeks: { + ts: week * 2, + label: '2 weeks' + }, + month: { + ts: month, + label: 'Month' + }, + threeMonths: { + ts: month * 3, + label: '3 months' + }, + sixMonths: { + ts: month * 6, + label: '6 months' + } +}; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/knob.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/knob.component.ts index 4c21f188b0..ea28ded9c4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/knob.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/knob.component.ts @@ -359,6 +359,7 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { this.updateColor(this.canvasBar.getValueColor()); this.value = this.formatValue(value); this.checkValueSize(); + this.ctx.detectChanges(); } private updateColor(color: string) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/led-indicator.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/led-indicator.component.ts index c307018976..763e5e24b8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/led-indicator.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/led-indicator.component.ts @@ -97,8 +97,12 @@ export class LedIndicatorComponent extends PageComponent implements OnInit, OnDe private subscriptionOptions: WidgetSubscriptionOptions = { callbacks: { - onDataUpdated: this.onDataUpdated.bind(this), - onDataUpdateError: this.onDataUpdateError.bind(this), + onDataUpdated: (subscription, detectChanges) => this.ctx.ngZone.run(() => { + this.onDataUpdated(subscription, detectChanges); + }), + onDataUpdateError: (subscription, e) => this.ctx.ngZone.run(() => { + this.onDataUpdateError(subscription, e); + }), dataLoading: () => {} } }; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/round-switch.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/round-switch.component.ts index 3be714fd2b..ce1850f7db 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/round-switch.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/round-switch.component.ts @@ -292,8 +292,12 @@ export class RoundSwitchComponent extends PageComponent implements OnInit, OnDes } const subscriptionOptions: WidgetSubscriptionOptions = { callbacks: { - onDataUpdated: this.onDataUpdated.bind(this), - onDataUpdateError: this.onDataUpdateError.bind(this) + onDataUpdated: (subscription, detectChanges) => this.ctx.ngZone.run(() => { + this.onDataUpdated(subscription, detectChanges); + }), + onDataUpdateError: (subscription, e) => this.ctx.ngZone.run(() => { + this.onDataUpdateError(subscription, e); + }) } }; this.ctx.subscriptionApi.createSubscriptionFromInfo ( diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/switch.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/switch.component.ts index b91e32bf4d..a6eaa56c8c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/switch.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/switch.component.ts @@ -309,8 +309,12 @@ export class SwitchComponent extends PageComponent implements OnInit, OnDestroy } const subscriptionOptions: WidgetSubscriptionOptions = { callbacks: { - onDataUpdated: this.onDataUpdated.bind(this), - onDataUpdateError: this.onDataUpdateError.bind(this) + onDataUpdated: (subscription, detectChanges) => this.ctx.ngZone.run(() => { + this.onDataUpdated(subscription, detectChanges); + }), + onDataUpdateError: (subscription, e) => this.ctx.ngZone.run(() => { + this.onDataUpdateError(subscription, e); + }) } }; this.ctx.subscriptionApi.createSubscriptionFromInfo ( diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.scss index c2992a2984..35061b94ac 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.scss @@ -36,6 +36,7 @@ .tb-table-widget { .mat-tab-group { height: 100%; + overflow: hidden; } .mat-tab-body-wrapper { height: 100%; 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 248c2b48b5..ee8bb5518f 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 @@ -26,6 +26,10 @@ import { TimeseriesTableWidgetComponent } from '@home/components/widget/lib/time import { EntitiesHierarchyWidgetComponent } from '@home/components/widget/lib/entities-hierarchy-widget.component'; import { CustomDialogService } from '@home/components/widget/dialog/custom-dialog.service'; import { RpcWidgetsModule } from '@home/components/widget/lib/rpc/rpc-widgets.module'; +import { + DateRangeNavigatorPanelComponent, + DateRangeNavigatorWidgetComponent +} from '@home/components/widget/lib/date-range-navigator/date-range-navigator.component'; @NgModule({ declarations: @@ -35,7 +39,9 @@ import { RpcWidgetsModule } from '@home/components/widget/lib/rpc/rpc-widgets.mo EntitiesTableWidgetComponent, AlarmsTableWidgetComponent, TimeseriesTableWidgetComponent, - EntitiesHierarchyWidgetComponent + EntitiesHierarchyWidgetComponent, + DateRangeNavigatorWidgetComponent, + DateRangeNavigatorPanelComponent ], imports: [ CommonModule, @@ -48,7 +54,8 @@ import { RpcWidgetsModule } from '@home/components/widget/lib/rpc/rpc-widgets.mo AlarmsTableWidgetComponent, TimeseriesTableWidgetComponent, EntitiesHierarchyWidgetComponent, - RpcWidgetsModule + RpcWidgetsModule, + DateRangeNavigatorWidgetComponent ], providers: [ CustomDialogService 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 6cfb596f44..76db07d5cc 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 @@ -66,7 +66,7 @@ export interface IDashboardComponent { dashboardTimewindowChanged: Observable; aliasController: IAliasController; stateController: IStateController; - onUpdateTimewindow(startTimeMs: number, endTimeMs: number, interval?: number): void; + onUpdateTimewindow(startTimeMs: number, endTimeMs: number, interval?: number, persist?: boolean): void; onResetTimewindow(): void; resetHighlight(): void; highlightWidget(widgetId: string, delay?: number); diff --git a/ui-ngx/src/app/shared/models/time/time.models.ts b/ui-ngx/src/app/shared/models/time/time.models.ts index fec2262e0e..7de0629451 100644 --- a/ui-ngx/src/app/shared/models/time/time.models.ts +++ b/ui-ngx/src/app/shared/models/time/time.models.ts @@ -15,7 +15,7 @@ /// import { TimeService } from '@core/services/time.service'; -import { deepClone, isDefined } from '@app/core/utils'; +import { deepClone, isDefined, isUndefined } from '@app/core/utils'; export const SECOND = 1000; export const MINUTE = 60 * SECOND; @@ -140,22 +140,36 @@ export function defaultTimewindow(timeService: TimeService): Timewindow { export function initModelFromDefaultTimewindow(value: Timewindow, timeService: TimeService): Timewindow { const model = defaultTimewindow(timeService); if (value) { - if (value.realtime) { - model.selectedTab = TimewindowType.REALTIME; + if (isUndefined(value.selectedTab)) { + if (value.realtime) { + model.selectedTab = TimewindowType.REALTIME; + } else { + model.selectedTab = TimewindowType.HISTORY; + } + } else { + model.selectedTab = value.selectedTab; + } + if (model.selectedTab === TimewindowType.REALTIME) { if (isDefined(value.realtime.interval)) { model.realtime.interval = value.realtime.interval; } model.realtime.timewindowMs = value.realtime.timewindowMs; } else { - model.selectedTab = TimewindowType.HISTORY; if (isDefined(value.history.interval)) { model.history.interval = value.history.interval; } - if (isDefined(value.history.timewindowMs)) { - model.history.historyType = HistoryWindowType.LAST_INTERVAL; + if (isUndefined(value.history.historyType)) { + if (isDefined(value.history.timewindowMs)) { + model.history.historyType = HistoryWindowType.LAST_INTERVAL; + } else { + model.history.historyType = HistoryWindowType.FIXED; + } + } else { + model.history.historyType = value.history.historyType; + } + if (model.history.historyType === HistoryWindowType.LAST_INTERVAL) { model.history.timewindowMs = value.history.timewindowMs; } else { - model.history.historyType = HistoryWindowType.FIXED; model.history.fixedTimewindow.startTimeMs = value.history.fixedTimewindow.startTimeMs; model.history.fixedTimewindow.endTimeMs = value.history.fixedTimewindow.endTimeMs; } @@ -189,7 +203,9 @@ export function toHistoryTimewindow(timewindow: Timewindow, startTimeMs: number, limit = timeService.getMaxDatapointsLimit(); } const historyTimewindow: Timewindow = { + selectedTab: TimewindowType.HISTORY, history: { + historyType: HistoryWindowType.FIXED, fixedTimewindow: { startTimeMs, endTimeMs @@ -226,7 +242,11 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num limit: timewindow.aggregation.limit || timeService.getMaxDatapointsLimit() }; } - if (isDefined(timewindow.realtime)) { + let selectedTab = timewindow.selectedTab; + if (isUndefined(selectedTab)) { + selectedTab = isDefined(timewindow.realtime) ? TimewindowType.REALTIME : TimewindowType.HISTORY; + } + if (selectedTab === TimewindowType.REALTIME) { subscriptionTimewindow.realtimeWindowMs = timewindow.realtime.timewindowMs; subscriptionTimewindow.aggregation.interval = timeService.boundIntervalToTimewindow(subscriptionTimewindow.realtimeWindowMs, timewindow.realtime.interval, @@ -238,8 +258,12 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num subscriptionTimewindow.startTs -= startDiff; aggTimewindow += subscriptionTimewindow.aggregation.interval; } - } else if (isDefined(timewindow.history)) { - if (isDefined(timewindow.history.timewindowMs)) { + } else { + let historyType = timewindow.history.historyType; + if (isUndefined(historyType)) { + historyType = isDefined(timewindow.history.timewindowMs) ? HistoryWindowType.LAST_INTERVAL : HistoryWindowType.FIXED; + } + if (historyType === HistoryWindowType.LAST_INTERVAL) { const currentTime = Date.now(); subscriptionTimewindow.fixedWindow = { startTimeMs: currentTime - timewindow.history.timewindowMs, diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index 9bf97a2707..829865a086 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -53,6 +53,7 @@ import { MatTabsModule } from '@angular/material/tabs'; import { MatToolbarModule } from '@angular/material/toolbar'; import { MatTooltipModule } from '@angular/material/tooltip'; import { MatDatetimepickerModule, MatNativeDatetimeModule } from '@mat-datetimepicker/core'; +import { NgxDaterangepickerMd } from 'ngx-daterangepicker-material'; import { GridsterModule } from 'angular-gridster2'; import { FlexLayoutModule } from '@angular/flex-layout'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; @@ -232,6 +233,7 @@ import { LedLightComponent } from '@shared/components/led-light.component'; MatDatepickerModule, MatNativeDatetimeModule, MatDatetimepickerModule, + NgxDaterangepickerMd.forRoot(), MatSliderModule, MatExpansionModule, MatStepperModule, @@ -318,6 +320,7 @@ import { LedLightComponent } from '@shared/components/led-light.component'; MatDatepickerModule, MatNativeDatetimeModule, MatDatetimepickerModule, + NgxDaterangepickerMd, MatSliderModule, MatExpansionModule, MatStepperModule, From 3aa5048861b73386be4aead69f8ebacaf5edaa5a Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 18 Feb 2020 18:12:28 +0200 Subject: [PATCH 118/133] Minor fixes --- .../home/components/widget/lib/rpc/led-indicator.component.scss | 2 +- .../home/components/widget/lib/rpc/round-switch.component.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/led-indicator.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/led-indicator.component.scss index 5dc4e28814..03dfda199a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/led-indicator.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/led-indicator.component.scss @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@import "~compass-sass-mixins/lib/compass"; +@import "node_modules/compass-sass-mixins/lib/compass"; $error-height: 14px !default; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/round-switch.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/round-switch.component.scss index 99cbe764eb..e632bef3a3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/round-switch.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/round-switch.component.scss @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@import "~compass-sass-mixins/lib/compass"; +@import "node_modules/compass-sass-mixins/lib/compass"; $error-height: 14px !default; From f56161efc51ca3dcb9835a1eb7dcedc7e53aca18 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 18 Feb 2020 18:26:14 +0200 Subject: [PATCH 119/133] Minor refactoring --- .../widget/lib/canvas-digital-gauge.ts | 41 +++++++++++++++---- ui-ngx/src/typings/canvas-gauges.typings.d.ts | 27 ------------ ui-ngx/tsconfig.json | 3 +- 3 files changed, 34 insertions(+), 37 deletions(-) delete mode 100644 ui-ngx/src/typings/canvas-gauges.typings.d.ts diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/canvas-digital-gauge.ts b/ui-ngx/src/app/modules/home/components/widget/lib/canvas-digital-gauge.ts index 336dc7de2f..a58f928b2b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/canvas-digital-gauge.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/canvas-digital-gauge.ts @@ -166,6 +166,31 @@ interface FontHeightInfo { descent?: number; } +export class Drawings { + static font(options: CanvasGauges.GenericOptions, target: string, baseSize: number): string { + return options['font' + target + 'Style'] + ' ' + + options['font' + target + 'Weight'] + ' ' + + options['font' + target + 'Size'] * baseSize + 'px ' + + options['font' + target]; + } + static normalizedValue(options: CanvasGauges.GenericOptions): {normal: number, indented: number} { + const value = options.value; + const min = options.minValue; + const max = options.maxValue; + const dt = (max - min) * 0.01; + return { + normal: value < min ? min : value > max ? max : value, + indented: value < min ? min - dt : value > max ? max + dt : value + }; + } + static verifyError(err: any) { + if (err instanceof DOMException && (err as any).result === 0x8053000b) { + return ; // ignore it + } + throw err; + } +} + export class CanvasDigitalGauge extends BaseGauge { static heightCache: {[key: string]: FontHeightInfo} = {}; @@ -350,7 +375,7 @@ export class CanvasDigitalGauge extends BaseGauge { valueChanged = true; } - const progress = (CanvasGauges.drawings.normalizedValue(options).normal - options.minValue) / + const progress = (Drawings.normalizedValue(options).normal - options.minValue) / (options.maxValue - options.minValue); const fixedProgress = progress.toFixed(3); @@ -385,7 +410,7 @@ export class CanvasDigitalGauge extends BaseGauge { super.draw(); } catch (err) { - CanvasGauges.drawings.verifyError(err); + Drawings.verifyError(err); } return this; } @@ -395,7 +420,7 @@ export class CanvasDigitalGauge extends BaseGauge { let color = this.contextProgressClone.currentColor; const options = this.options as CanvasDigitalGaugeOptions; if (!color) { - const progress = (CanvasGauges.drawings.normalizedValue(options).normal - options.minValue) / + const progress = (Drawings.normalizedValue(options).normal - options.minValue) / (options.maxValue - options.minValue); if (options.neonGlowBrightness) { color = getProgressColor(progress, options.neonColorsRange); @@ -539,7 +564,7 @@ function barDimensions(context: DigitalGaugeCanvasRenderingContext2D, bd.barLeft = bd.origBaseX + options.fontMinMaxSize/3 * bd.fontSizeFactor; bd.barRight = bd.origBaseX + w + /*bd.width*/ - options.fontMinMaxSize/3 * bd.fontSizeFactor; } else { - context.font = CanvasGauges.drawings.font(options, 'MinMax', bd.fontSizeFactor); + context.font = Drawings.font(options, 'MinMax', bd.fontSizeFactor); const minTextWidth = context.measureText(options.minValue+'').width; const maxTextWidth = context.measureText(options.maxValue+'').width; const maxW = Math.max(minTextWidth, maxTextWidth); @@ -682,7 +707,7 @@ function drawDigitalTitle(context: DigitalGaugeCanvasRenderingContext2D, options context.save(); context.textAlign = 'center'; - context.font = CanvasGauges.drawings.font(options, 'Title', fontSizeFactor); + context.font = Drawings.font(options, 'Title', fontSizeFactor); context.lineWidth = 0; drawText(context, options, 'Title', options.title.toUpperCase(), textX, textY); } @@ -698,7 +723,7 @@ function drawDigitalLabel(context: DigitalGaugeCanvasRenderingContext2D, options context.save(); context.textAlign = 'center'; - context.font = CanvasGauges.drawings.font(options, 'Label', fontSizeFactor); + context.font = Drawings.font(options, 'Label', fontSizeFactor); context.lineWidth = 0; drawText(context, options, 'Label', options.label.toUpperCase(), textX, textY); } @@ -712,7 +737,7 @@ function drawDigitalMinMax(context: DigitalGaugeCanvasRenderingContext2D, option context.save(); context.textAlign = fontMinMaxAlign; context.textBaseline = fontMinMaxBaseline; - context.font = CanvasGauges.drawings.font(options, 'MinMax', fontSizeFactor); + context.font = Drawings.font(options, 'MinMax', fontSizeFactor); context.lineWidth = 0; drawText(context, options, 'MinMax', options.minValue+'', minX, minY); drawText(context, options, 'MinMax', options.maxValue+'', maxX, maxY); @@ -751,7 +776,7 @@ function drawDigitalValue(context: DigitalGaugeCanvasRenderingContext2D, options context.save(); context.textAlign = 'center'; context.textBaseline = fontValueBaseline; - context.font = CanvasGauges.drawings.font(options, 'Value', fontSizeFactor); + context.font = Drawings.font(options, 'Value', fontSizeFactor); context.lineWidth = 0; drawText(context, options, 'Value', text, textX, textY); } diff --git a/ui-ngx/src/typings/canvas-gauges.typings.d.ts b/ui-ngx/src/typings/canvas-gauges.typings.d.ts deleted file mode 100644 index a221d528ef..0000000000 --- a/ui-ngx/src/typings/canvas-gauges.typings.d.ts +++ /dev/null @@ -1,27 +0,0 @@ -/// -/// Copyright © 2016-2019 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. -/// - - -// tslint:disable-next-line:no-namespace -declare namespace CanvasGauges { - const drawings: Drawings; -} - -interface Drawings { - font(options: CanvasGauges.GenericOptions, target: string, baseSize: number): string; - normalizedValue(options: CanvasGauges.GenericOptions): {normal: number, indented: number}; - verifyError(err: any); -} diff --git a/ui-ngx/tsconfig.json b/ui-ngx/tsconfig.json index c2f74dcebe..a35b8a413f 100644 --- a/ui-ngx/tsconfig.json +++ b/ui-ngx/tsconfig.json @@ -19,8 +19,7 @@ "src/typings/jquery.typings.d.ts", "src/typings/jquery.flot.typings.d.ts", "src/typings/jquery.jstree.typings.d.ts", - "src/typings/split.js.typings.d.ts", - "src/typings/canvas-gauges.typings.d.ts" + "src/typings/split.js.typings.d.ts" ], "paths": { "@app/*": ["src/app/*"], From f2448f55e755bac24bcbe7e2856864f844f525c8 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 18 Feb 2020 18:32:15 +0200 Subject: [PATCH 120/133] Minor refactoring --- ui-ngx/src/app/shared/shared.module.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index 829865a086..4a8dca7e4f 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -21,7 +21,7 @@ import { LogoComponent } from '@shared/components/logo.component'; import { TbSnackBarComponent, ToastDirective } from '@shared/components/toast.directive'; import { BreadcrumbComponent } from '@shared/components/breadcrumb.component'; import { NgxFlowModule, FlowInjectionToken } from '@flowjs/ngx-flow'; -import { NgxFlowchartModule } from 'ngx-flowchart/dist/ngx-flowchart'; +import { NgxFlowchartModule } from 'ngx-flowchart/projects/ngx-flowchart/src/public-api'; import Flow from '@flowjs/flow.js'; import { MatAutocompleteModule } from '@angular/material/autocomplete'; From 51da3b3deadce5a215844444e110b8833b3f72a3 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 18 Feb 2020 19:01:21 +0200 Subject: [PATCH 121/133] Update --- ui-ngx/src/app/shared/shared.module.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index 4a8dca7e4f..829865a086 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -21,7 +21,7 @@ import { LogoComponent } from '@shared/components/logo.component'; import { TbSnackBarComponent, ToastDirective } from '@shared/components/toast.directive'; import { BreadcrumbComponent } from '@shared/components/breadcrumb.component'; import { NgxFlowModule, FlowInjectionToken } from '@flowjs/ngx-flow'; -import { NgxFlowchartModule } from 'ngx-flowchart/projects/ngx-flowchart/src/public-api'; +import { NgxFlowchartModule } from 'ngx-flowchart/dist/ngx-flowchart'; import Flow from '@flowjs/flow.js'; import { MatAutocompleteModule } from '@angular/material/autocomplete'; From fd1a069214fea9c3f4b8fb0b0ba42893c6a26c65 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 19 Feb 2020 13:39:50 +0200 Subject: [PATCH 122/133] Update versions --- ui-ngx/angular.json | 4 +- ui-ngx/package-lock.json | 38 ++++----- ui-ngx/package.json | 79 +++++++++--------- ui-ngx/proxy.conf.json | 14 ---- .../shared/components/nav-tree.component.scss | 65 +++++++++++++- .../assets/jstree/{32px.png => tb32px.png} | Bin .../assets/jstree/{40px.png => tb40px.png} | Bin 7 files changed, 122 insertions(+), 78 deletions(-) delete mode 100644 ui-ngx/proxy.conf.json rename ui-ngx/src/assets/jstree/{32px.png => tb32px.png} (100%) rename ui-ngx/src/assets/jstree/{40px.png => tb40px.png} (100%) diff --git a/ui-ngx/angular.json b/ui-ngx/angular.json index 2031867edc..f6cf3125d5 100644 --- a/ui-ngx/angular.json +++ b/ui-ngx/angular.json @@ -107,8 +107,8 @@ "budgets": [ { "type": "initial", - "maximumWarning": "5mb", - "maximumError": "10mb" + "maximumWarning": "9mb", + "maximumError": "12mb" } ] } diff --git a/ui-ngx/package-lock.json b/ui-ngx/package-lock.json index d926ee5c35..33f404b76c 100644 --- a/ui-ngx/package-lock.json +++ b/ui-ngx/package-lock.json @@ -1845,9 +1845,9 @@ } }, "@types/jasmine": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.5.4.tgz", - "integrity": "sha512-Uc/obv/lRh1t6RMOV6wkiAfYSZ0AOSTJqVl0Th8bHFcDhw4rQ0L60sxVnmOJj+RXbVboAE1Fd/mBclQWARRAsQ==", + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.5.5.tgz", + "integrity": "sha512-LlhwGivHkUV8ehNmaXjGGXopLm91G9ORIRcjw7Ya47jVAIGudewFZM2PdPXBvueZfRWwYzLt083wiPfKRXrSlg==", "dev": true }, "@types/jasminewd2": { @@ -1894,9 +1894,9 @@ "integrity": "sha512-13gmo3M2qVvjQrWNseqM3+cR6S2Ss3grbR2NZltgMq94wOwqJYQdgn8qzwDshzgXqMlSUtyPZjysImmktu22ew==" }, "@types/node": { - "version": "13.7.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.7.1.tgz", - "integrity": "sha512-Zq8gcQGmn4txQEJeiXo/KiLpon8TzAl0kmKH4zdWctPj05nWwp1ClMdAVEloqrQKfaC48PNLdgN/aVaLqUrluA==", + "version": "13.7.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.7.2.tgz", + "integrity": "sha512-uvilvAQbdJvnSBFcKJ2td4016urcGvsiR+N4dHGU87ml8O2Vl6l+ErOi9w0kXSPiwJ1AYlIW+0pDXDWWMOiWbw==", "dev": true }, "@types/prop-types": { @@ -5372,9 +5372,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.354", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.354.tgz", - "integrity": "sha512-24YMkNiZWOUeF6YeoscWfIGP0oMx+lJpU/miwI+lcu7plIDpyZn8Gx0lx0qTDlzGoz7hx+lpyD8QkbkX5L2Pqw==", + "version": "1.3.355", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.355.tgz", + "integrity": "sha512-zKO/wS+2ChI/jz9WAo647xSW8t2RmgRLFdbUb/77cORkUTargO+SCj4ctTHjBn2VeNFrsLgDT7IuDVrd3F8mLQ==", "dev": true }, "elliptic": { @@ -8673,9 +8673,9 @@ } }, "make-error": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", - "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, "make-fetch-happen": { @@ -9418,7 +9418,7 @@ } }, "ngx-flowchart": { - "version": "git://github.com/thingsboard/ngx-flowchart.git#684c71fa3f15f99c5c22c8a6cf69a12d0b12be6e", + "version": "git://github.com/thingsboard/ngx-flowchart.git#97a77477ca8579becf0e3a07866046b4536fe30a", "from": "git://github.com/thingsboard/ngx-flowchart.git#master", "requires": { "tslib": "^1.10.0" @@ -11124,9 +11124,9 @@ } }, "postcss-value-parser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.2.tgz", - "integrity": "sha512-LmeoohTpp/K4UiyQCwuGWlONxXamGzCMtFxLq4W1nZVGIQLYvMCJx3yAF9qyyuFpflABI9yVdtJAqbihOsCsJQ==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.3.tgz", + "integrity": "sha512-N7h4pG+Nnu5BEIzyeaaIYWs0LI5XC40OrRh5L60z0QjFsqGWcHcbkBvpe1WYpcIS9yQ8sOi/vIPt1ejQCrMVrg==", "dev": true }, "prepend-http": { @@ -13972,9 +13972,9 @@ "dev": true }, "uglify-js": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.7.7.tgz", - "integrity": "sha512-FeSU+hi7ULYy6mn8PKio/tXsdSXN35lm4KgV2asx00kzrLU9Pi3oAslcJT70Jdj7PHX29gGUPOT6+lXGBbemhA==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.8.0.tgz", + "integrity": "sha512-ugNSTT8ierCsDHso2jkBHXYrU8Y5/fY2ZUprfrJUiD7YpuFvV4jODLFmb3h4btQjqr5Nh4TX4XtgDfCU1WdioQ==", "optional": true, "requires": { "commander": "~2.20.3", diff --git a/ui-ngx/package.json b/ui-ngx/package.json index bfc5afae99..a8375a169d 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -13,33 +13,33 @@ }, "private": true, "dependencies": { - "@angular/animations": "~9.0.0", - "@angular/cdk": "~9.0.0", - "@angular/common": "~9.0.0", - "@angular/compiler": "~9.0.0", - "@angular/core": "~9.0.0", + "@angular/animations": "^9.0.1", + "@angular/cdk": "^9.0.0", + "@angular/common": "^9.0.1", + "@angular/compiler": "^9.0.1", + "@angular/core": "^9.0.1", "@angular/flex-layout": "^9.0.0-beta.29", - "@angular/forms": "~9.0.0", + "@angular/forms": "^9.0.1", "@angular/material": "^9.0.0", - "@angular/platform-browser": "~9.0.0", - "@angular/platform-browser-dynamic": "~9.0.0", - "@angular/router": "~9.0.0", + "@angular/platform-browser": "^9.0.1", + "@angular/platform-browser-dynamic": "^9.0.1", + "@angular/router": "^9.0.1", "@auth0/angular-jwt": "^4.0.0", - "@date-io/date-fns": "^2.3.0", + "@date-io/date-fns": "^2.4.0", "@flowjs/flow.js": "^2.14.0", "@flowjs/ngx-flow": "^0.4.3", - "@mat-datetimepicker/core": "^4.0.0", - "@material-ui/core": "^4.9.2", + "@mat-datetimepicker/core": "^4.0.1", + "@material-ui/core": "^4.9.3", "@material-ui/icons": "^4.9.1", "@material-ui/pickers": "^3.2.10", "@ngrx/effects": "^8.6.0", "@ngrx/store": "^8.6.0", "@ngrx/store-devtools": "^8.6.0", "@ngx-share/core": "^7.1.4", - "@ngx-translate/core": "^12.0.0", + "@ngx-translate/core": "^12.1.1", "@ngx-translate/http-loader": "^4.0.0", "ace-builds": "^1.4.8", - "angular-gridster2": "^9.0.0", + "angular-gridster2": "^9.0.1", "angular2-hotkeys": "^2.1.5", "base64-js": "^1.3.1", "canvas-gauges": "^2.1.5", @@ -52,15 +52,15 @@ "font-awesome": "^4.7.0", "javascript-detect-element-resize": "^0.5.3", "jquery": "^3.4.1", - "jquery.terminal": "^2.12.0", + "jquery.terminal": "^2.14.0", "js-beautify": "^1.10.3", "json-schema-defaults": "^0.4.0", - "jstree": "^3.3.8", + "jstree": "^3.3.9", "jstree-bootstrap-theme": "^1.0.1", "material-design-icons": "^3.0.1", "messageformat": "^2.3.0", "moment": "^2.24.0", - "ngx-clipboard": "^12.3.0", + "ngx-clipboard": "^12.3.1", "ngx-color-picker": "^9.0.0", "ngx-daterangepicker-material": "^2.2.0", "ngx-flowchart": "git://github.com/thingsboard/ngx-flowchart.git#master", @@ -77,7 +77,7 @@ "reactcss": "^1.2.3", "rxjs": "^6.5.4", "schema-inspector": "1.6.8", - "screenfull": "^5.0.1", + "screenfull": "^5.0.2", "split.js": "^1.5.11", "systemjs": "0.21.5", "tinycolor2": "^1.4.1", @@ -89,40 +89,37 @@ }, "devDependencies": { "@angular-builders/custom-webpack": "^9.0.0-beta.7", - "@angular-devkit/build-angular": "~0.900.1", - "@angular/cli": "^9.0.1", - "@angular/compiler-cli": "~9.0.0", - "@angular/language-service": "~9.0.0", + "@angular-devkit/build-angular": "^0.900.2", + "@angular/cli": "^9.0.2", + "@angular/compiler-cli": "^9.0.1", + "@angular/language-service": "^9.0.1", "@types/canvas-gauges": "^2.1.2", - "@types/flot": "0.0.31", - "@types/jasmine": "^3.5.3", - "@types/jasminewd2": "~2.0.8", + "@types/flot": "^0.0.31", + "@types/jasmine": "^3.5.5", + "@types/jasminewd2": "^2.0.8", "@types/jquery": "^3.3.32", "@types/js-beautify": "^1.8.1", "@types/jstree": "^3.3.39", - "@types/node": "^13.7.0", + "@types/node": "^13.7.2", "@types/raphael": "^2.1.30", - "@types/react": "^16.9.19", + "@types/react": "^16.9.20", "@types/react-dom": "^16.9.5", "@types/tinycolor2": "^1.4.2", - "@types/tooltipster": "0.0.29", - "codelyzer": "^5.1.2", + "@types/tooltipster": "^0.0.29", + "codelyzer": "^5.2.1", "compression-webpack-plugin": "^3.1.0", "directory-tree": "^2.2.4", - "jasmine-core": "~3.5.0", - "jasmine-spec-reporter": "~4.2.1", - "karma": "~4.4.1", - "karma-chrome-launcher": "~3.1.0", - "karma-coverage-istanbul-reporter": "~2.1.1", - "karma-jasmine": "~3.1.0", + "jasmine-core": "^3.5.0", + "jasmine-spec-reporter": "^4.2.1", + "karma": "^4.4.1", + "karma-chrome-launcher": "^3.1.0", + "karma-coverage-istanbul-reporter": "^2.1.1", + "karma-jasmine": "^3.1.1", "karma-jasmine-html-reporter": "^1.5.2", "ngrx-store-freeze": "^0.2.4", "protractor": "^5.4.3", - "ts-node": "~8.6.2", - "tslint": "~6.0.0", - "typescript": "~3.7.5" - }, - "resolutions": { - "serialize-javascript": "^2.1.1" + "ts-node": "^8.6.2", + "tslint": "^6.0.0", + "typescript": "^3.7.5" } } diff --git a/ui-ngx/proxy.conf.json b/ui-ngx/proxy.conf.json deleted file mode 100644 index a558aee24e..0000000000 --- a/ui-ngx/proxy.conf.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "/api": { - "target": "http://localhost:8080", - "secure": false - }, - "/static/rulenode": { - "target": "http://localhost:8080", - "secure": false - }, - "/api/ws": { - "target": "ws://localhost:8080", - "ws": true - } -} diff --git a/ui-ngx/src/app/shared/components/nav-tree.component.scss b/ui-ngx/src/app/shared/components/nav-tree.component.scss index e57cca30f7..46290a277d 100644 --- a/ui-ngx/src/app/shared/components/nav-tree.component.scss +++ b/ui-ngx/src/app/shared/components/nav-tree.component.scss @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + .tb-nav-tree-container { padding: 15px; font-family: Roboto, "Helvetica Neue", sans-serif; @@ -20,7 +21,7 @@ &.jstree-proton, &.jstree-proton-small, &.jstree-proton-large { .jstree-node, .jstree-icon { - background-image: url("../../../assets/jstree/32px.png"); + background-image: url("../../../assets/jstree/tb32px.png"); } .jstree-last { @@ -43,6 +44,14 @@ .jstree-anchor { font-size: 16px; } + + .jstree-file { + background: url("../../../assets/jstree/tb32px.png") -101px -69px no-repeat; + } + + .jstree-folder { + background: url("../../../assets/jstree/tb32px.png") -261px -5px no-repeat; + } } &.jstree-proton-small { @@ -56,6 +65,14 @@ .jstree-anchor { font-size: 14px; } + + .jstree-file { + background: url("../../../assets/jstree/tb32px.png") -103px -71px no-repeat; + } + + .jstree-folder { + background: url("../../../assets/jstree/tb32px.png") -263px -7px no-repeat; + } } &.jstree-proton-large { @@ -69,6 +86,14 @@ .jstree-anchor { font-size: 20px; } + + .jstree-file { + background: url("../../../assets/jstree/tb32px.png") -96px -64px no-repeat; + } + + .jstree-folder { + background: url("../../../assets/jstree/tb32px.png") -256px 0px no-repeat; + } } a { @@ -120,6 +145,15 @@ } } +#jstree-dnd { + &.jstree-proton, &.jstree-proton-small, &.jstree-proton-large { + .jstree-ok, + .jstree-er { + background-image: url("../../../assets/jstree/tb32px.png"); + } + } +} + @media (max-width: 768px) { .tb-nav-tree-container { &.jstree-proton-responsive { @@ -128,7 +162,7 @@ .jstree-node > .jstree-ocl, .jstree-themeicon, .jstree-checkbox { - background-image: url("../../../assets/jstree/40px.png"); + background-image: url("../../../assets/jstree/tb40px.png"); background-size: 120px 240px; } @@ -215,6 +249,15 @@ background-position: -40px -40px; } + .jstree-file { + background: url("../../../assets/jstree/tb40px.png") 0 -160px no-repeat; + background-size: 120px 240px; + } + .jstree-folder { + background: url("../../../assets/jstree/tb40px.png") -40px -40px no-repeat; + background-size: 120px 240px; + } + .jstree-checkbox, .jstree-checkbox:hover { background-position: -40px -80px; @@ -289,6 +332,24 @@ } } +@media (max-width: 768px) { + #jstree-dnd { + &.jstree-dnd-responsive { + .jstree-ok, + .jstree-er { + background-image: url("../../../assets/jstree/tb40px.png"); + background-size: 120px 240px; + } + .jstree-ok { + background-position: 0 -200px; + } + .jstree-er { + background-position: -40px -200px; + } + } + } +} + .tb-nav-tree .mat-button.tb-active { font-weight: 500; background-color: rgba(255, 255, 255, .15); diff --git a/ui-ngx/src/assets/jstree/32px.png b/ui-ngx/src/assets/jstree/tb32px.png similarity index 100% rename from ui-ngx/src/assets/jstree/32px.png rename to ui-ngx/src/assets/jstree/tb32px.png diff --git a/ui-ngx/src/assets/jstree/40px.png b/ui-ngx/src/assets/jstree/tb40px.png similarity index 100% rename from ui-ngx/src/assets/jstree/40px.png rename to ui-ngx/src/assets/jstree/tb40px.png From f01049bfb1e869544677a1af5557a59c59fd77e7 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 19 Feb 2020 13:48:53 +0200 Subject: [PATCH 123/133] Update ngrx config --- ui-ngx/src/app/core/core.module.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ui-ngx/src/app/core/core.module.ts b/ui-ngx/src/app/core/core.module.ts index 8e6f550172..482a4ee438 100644 --- a/ui-ngx/src/app/core/core.module.ts +++ b/ui-ngx/src/app/core/core.module.ts @@ -72,7 +72,15 @@ export function HttpLoaderFactory(http: HttpClient) { HotkeyModule.forRoot(), // ngrx - StoreModule.forRoot(reducers, { metaReducers }), + StoreModule.forRoot(reducers, + { metaReducers, + runtimeChecks: { + strictStateImmutability: true, + strictActionImmutability: true, + strictStateSerializability: true, + strictActionSerializability: true + }} + ), EffectsModule.forRoot(effects), env.production ? [] From 5ca3c497c29131ffa25750160742312d3e0f6e9a Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 19 Feb 2020 14:31:15 +0200 Subject: [PATCH 124/133] Update package.lock --- ui-ngx/package-lock.json | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/ui-ngx/package-lock.json b/ui-ngx/package-lock.json index 33f404b76c..db25e430bf 100644 --- a/ui-ngx/package-lock.json +++ b/ui-ngx/package-lock.json @@ -2798,16 +2798,6 @@ "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", "dev": true }, - "bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "dev": true, - "optional": true, - "requires": { - "file-uri-to-path": "1.0.0" - } - }, "blob": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", @@ -6054,13 +6044,6 @@ "tslib": "^1.9.0" } }, - "file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "dev": true, - "optional": true - }, "fileset": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz", @@ -9339,13 +9322,6 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, - "nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", - "dev": true, - "optional": true - }, "nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", @@ -14435,8 +14411,6 @@ "dev": true, "optional": true, "requires": { - "bindings": "^1.5.0", - "nan": "^2.12.1", "node-pre-gyp": "*" }, "dependencies": { @@ -15370,8 +15344,6 @@ "dev": true, "optional": true, "requires": { - "bindings": "^1.5.0", - "nan": "^2.12.1", "node-pre-gyp": "*" }, "dependencies": { From 3e2a87135fa27624bcb45e31a6d04bc7a1fb420d Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 20 Feb 2020 10:26:43 +0200 Subject: [PATCH 125/133] Prepare for merge with master --- application/build.gradle | 2 +- application/pom.xml | 4 ++-- application/src/main/assembly/windows.xml | 2 +- application/src/main/conf/logback.xml | 2 +- application/src/main/conf/thingsboard.conf | 2 +- .../main/data/upgrade/1.3.0/schema_update.cql | 2 +- .../main/data/upgrade/1.3.1/schema_update.sql | 2 +- .../main/data/upgrade/1.4.0/schema_update.cql | 2 +- .../main/data/upgrade/1.4.0/schema_update.sql | 2 +- .../main/data/upgrade/2.0.0/schema_update.cql | 2 +- .../main/data/upgrade/2.0.0/schema_update.sql | 2 +- .../main/data/upgrade/2.1.1/schema_update.cql | 2 +- .../main/data/upgrade/2.1.1/schema_update.sql | 2 +- .../main/data/upgrade/2.1.2/schema_update.cql | 2 +- .../main/data/upgrade/2.1.2/schema_update.sql | 2 +- .../main/data/upgrade/2.2.0/schema_update.sql | 2 +- .../main/data/upgrade/2.3.1/schema_update.sql | 2 +- .../main/data/upgrade/2.4.0/schema_update.sql | 2 +- .../server/ThingsboardInstallApplication.java | 2 +- .../server/ThingsboardServerApplication.java | 2 +- .../server/actors/ActorSystemContext.java | 2 +- .../thingsboard/server/actors/app/AppActor.java | 2 +- .../server/actors/app/AppInitMsg.java | 2 +- .../server/actors/device/DeviceActor.java | 2 +- .../server/actors/device/DeviceActorCreator.java | 2 +- .../device/DeviceActorMessageProcessor.java | 2 +- .../device/DeviceActorToRuleEngineMsg.java | 2 +- .../server/actors/device/SessionInfo.java | 2 +- .../actors/device/SessionInfoMetaData.java | 2 +- .../actors/device/SessionTimeoutCheckMsg.java | 2 +- .../device/ToDeviceRpcRequestMetadata.java | 2 +- .../device/ToServerRpcRequestMetadata.java | 2 +- .../actors/rpc/BasicRpcSessionListener.java | 2 +- .../server/actors/rpc/RpcBroadcastMsg.java | 2 +- .../server/actors/rpc/RpcManagerActor.java | 2 +- .../server/actors/rpc/RpcSessionActor.java | 2 +- .../server/actors/rpc/RpcSessionClosedMsg.java | 2 +- .../actors/rpc/RpcSessionConnectedMsg.java | 2 +- .../actors/rpc/RpcSessionCreateRequestMsg.java | 2 +- .../actors/rpc/RpcSessionDisconnectedMsg.java | 2 +- .../server/actors/rpc/RpcSessionTellMsg.java | 2 +- .../server/actors/rpc/SessionActorInfo.java | 2 +- .../actors/ruleChain/DefaultTbContext.java | 2 +- .../ruleChain/RemoteToRuleChainTellNextMsg.java | 2 +- .../server/actors/ruleChain/RuleChainActor.java | 2 +- .../RuleChainActorMessageProcessor.java | 2 +- .../actors/ruleChain/RuleChainManagerActor.java | 2 +- .../ruleChain/RuleChainToRuleChainMsg.java | 2 +- .../actors/ruleChain/RuleChainToRuleNodeMsg.java | 2 +- .../server/actors/ruleChain/RuleNodeActor.java | 2 +- .../ruleChain/RuleNodeActorMessageProcessor.java | 2 +- .../server/actors/ruleChain/RuleNodeCtx.java | 2 +- .../actors/ruleChain/RuleNodeRelation.java | 2 +- .../RuleNodeToRuleChainTellNextMsg.java | 2 +- .../actors/ruleChain/RuleNodeToSelfErrorMsg.java | 2 +- .../actors/ruleChain/RuleNodeToSelfMsg.java | 2 +- .../server/actors/service/ActorService.java | 2 +- .../server/actors/service/ComponentActor.java | 2 +- .../server/actors/service/ContextAwareActor.java | 2 +- .../actors/service/ContextBasedCreator.java | 2 +- .../actors/service/DefaultActorService.java | 2 +- .../shared/AbstractContextAwareMsgProcessor.java | 2 +- .../actors/shared/ActorTerminationMsg.java | 2 +- .../actors/shared/ComponentMsgProcessor.java | 2 +- .../actors/shared/EntityActorsManager.java | 2 +- .../shared/rulechain/RuleChainManager.java | 2 +- .../shared/rulechain/SystemRuleChainManager.java | 2 +- .../shared/rulechain/TenantRuleChainManager.java | 2 +- .../server/actors/stats/StatsActor.java | 2 +- .../server/actors/stats/StatsPersistMsg.java | 2 +- .../server/actors/stats/StatsPersistTick.java | 2 +- .../server/actors/tenant/DebugTbRateLimits.java | 2 +- .../server/actors/tenant/TenantActor.java | 2 +- .../server/config/AuditLogLevelProperties.java | 2 +- .../thingsboard/server/config/JwtSettings.java | 2 +- .../server/config/MvcCorsProperties.java | 2 +- .../server/config/RateLimitProcessingFilter.java | 2 +- .../server/config/SchedulingConfiguration.java | 2 +- .../server/config/SwaggerConfiguration.java | 2 +- .../config/ThingsboardMessageConfiguration.java | 2 +- .../config/ThingsboardSecurityConfiguration.java | 2 +- .../org/thingsboard/server/config/WebConfig.java | 2 +- .../server/config/WebSocketConfiguration.java | 2 +- .../server/controller/AdminController.java | 2 +- .../server/controller/AlarmController.java | 2 +- .../server/controller/AssetController.java | 2 +- .../server/controller/AuditLogController.java | 2 +- .../server/controller/AuthController.java | 2 +- .../server/controller/BaseController.java | 2 +- .../ComponentDescriptorController.java | 2 +- .../server/controller/CustomerController.java | 2 +- .../server/controller/DashboardController.java | 2 +- .../server/controller/DeviceController.java | 2 +- .../controller/EntityRelationController.java | 2 +- .../server/controller/EntityViewController.java | 2 +- .../server/controller/EventController.java | 2 +- .../controller/HttpValidationCallback.java | 2 +- .../server/controller/RpcController.java | 2 +- .../server/controller/RuleChainController.java | 2 +- .../server/controller/TbUrlConstants.java | 2 +- .../server/controller/TelemetryController.java | 2 +- .../server/controller/TenantController.java | 2 +- .../server/controller/UserController.java | 2 +- .../server/controller/WidgetTypeController.java | 2 +- .../controller/WidgetsBundleController.java | 2 +- .../controller/claim/data/ClaimRequest.java | 2 +- .../controller/plugin/TbWebSocketHandler.java | 2 +- .../ThingsboardCredentialsExpiredResponse.java | 2 +- .../exception/ThingsboardErrorResponse.java | 2 +- .../ThingsboardErrorResponseHandler.java | 2 +- .../install/ThingsboardInstallConfiguration.java | 2 +- .../install/ThingsboardInstallException.java | 2 +- .../install/ThingsboardInstallService.java | 2 +- .../discovery/CurrentServerInstanceService.java | 2 +- .../cluster/discovery/DiscoveryService.java | 2 +- .../discovery/DiscoveryServiceListener.java | 2 +- .../cluster/discovery/DummyDiscoveryService.java | 2 +- .../cluster/discovery/ServerInstance.java | 2 +- .../cluster/discovery/ServerInstanceService.java | 2 +- .../cluster/discovery/ZkDiscoveryService.java | 2 +- .../cluster/routing/ClusterRoutingService.java | 2 +- .../routing/ConsistentClusterRoutingService.java | 2 +- .../cluster/routing/ConsistentHashCircle.java | 2 +- .../service/cluster/rpc/ClusterGrpcService.java | 2 +- .../service/cluster/rpc/ClusterRpcService.java | 2 +- .../server/service/cluster/rpc/GrpcSession.java | 2 +- .../service/cluster/rpc/GrpcSessionListener.java | 2 +- .../service/cluster/rpc/RpcMsgListener.java | 2 +- .../AnnotationComponentDiscoveryService.java | 2 +- .../component/ComponentDiscoveryService.java | 2 +- .../encoding/DataDecodingEncodingService.java | 2 +- .../service/encoding/ProtoWithFSTService.java | 2 +- .../environment/EnvironmentLogService.java | 2 +- .../executors/AbstractListeningExecutor.java | 2 +- .../ClusterRpcCallbackExecutorService.java | 2 +- .../executors/DbCallbackExecutorService.java | 2 +- .../executors/ExternalCallExecutorService.java | 2 +- .../executors/SharedEventLoopGroupService.java | 3 +-- .../CassandraAbstractDatabaseSchemaService.java | 2 +- .../install/CassandraDatabaseUpgradeService.java | 2 +- .../CassandraTsDatabaseSchemaService.java | 2 +- .../server/service/install/DatabaseHelper.java | 2 +- .../service/install/DatabaseSchemaService.java | 2 +- .../service/install/DatabaseUpgradeService.java | 2 +- .../install/DefaultSystemDataLoaderService.java | 2 +- .../install/EntityDatabaseSchemaService.java | 2 +- .../server/service/install/InstallScripts.java | 2 +- .../SqlAbstractDatabaseSchemaService.java | 2 +- .../install/SqlDatabaseUpgradeService.java | 2 +- .../install/SqlEntityDatabaseSchemaService.java | 2 +- .../install/SqlTsDatabaseSchemaService.java | 2 +- .../service/install/SystemDataLoaderService.java | 2 +- .../service/install/TsDatabaseSchemaService.java | 2 +- .../service/install/cql/CQLStatementsParser.java | 2 +- .../service/install/cql/CassandraDbHelper.java | 2 +- .../server/service/install/sql/SqlDbHelper.java | 2 +- .../install/update/DataUpdateService.java | 2 +- .../install/update/DefaultDataUpdateService.java | 2 +- .../service/install/update/PaginatedUpdater.java | 2 +- .../server/service/mail/DefaultMailService.java | 2 +- .../server/service/mail/MailExecutorService.java | 2 +- .../service/rpc/DefaultDeviceRpcService.java | 2 +- .../server/service/rpc/DeviceRpcService.java | 2 +- .../service/rpc/FromDeviceRpcResponse.java | 2 +- .../server/service/rpc/LocalRequestMetaData.java | 2 +- .../service/rpc/ToDeviceRpcRequestActorMsg.java | 2 +- .../service/rpc/ToServerRpcResponseActorMsg.java | 2 +- .../service/script/AbstractJsInvokeService.java | 2 +- .../script/AbstractNashornJsInvokeService.java | 2 +- .../server/service/script/JsExecutorService.java | 2 +- .../server/service/script/JsInvokeRequest.java | 2 +- .../server/service/script/JsInvokeResponse.java | 2 +- .../server/service/script/JsInvokeService.java | 2 +- .../server/service/script/JsScriptType.java | 2 +- .../service/script/NashornJsInvokeService.java | 2 +- .../service/script/RemoteJsInvokeService.java | 2 +- .../service/script/RemoteJsRequestEncoder.java | 2 +- .../service/script/RemoteJsResponseDecoder.java | 2 +- .../service/script/RuleNodeJsScriptEngine.java | 2 +- .../service/script/RuleNodeScriptFactory.java | 2 +- .../server/service/security/AccessValidator.java | 2 +- .../service/security/ValidationCallback.java | 2 +- .../service/security/ValidationResult.java | 2 +- .../service/security/ValidationResultCode.java | 2 +- .../auth/AbstractJwtAuthenticationToken.java | 2 +- .../security/auth/JwtAuthenticationToken.java | 2 +- .../auth/RefreshAuthenticationToken.java | 2 +- .../auth/jwt/JwtAuthenticationProvider.java | 2 +- .../JwtTokenAuthenticationProcessingFilter.java | 2 +- .../jwt/RefreshTokenAuthenticationProvider.java | 2 +- .../auth/jwt/RefreshTokenProcessingFilter.java | 2 +- .../auth/jwt/RefreshTokenRepository.java | 2 +- .../security/auth/jwt/RefreshTokenRequest.java | 2 +- .../auth/jwt/SkipPathRequestMatcher.java | 2 +- .../jwt/extractor/JwtHeaderTokenExtractor.java | 2 +- .../jwt/extractor/JwtQueryTokenExtractor.java | 2 +- .../auth/jwt/extractor/TokenExtractor.java | 2 +- .../service/security/auth/rest/LoginRequest.java | 2 +- .../security/auth/rest/PublicLoginRequest.java | 2 +- .../auth/rest/RestAuthenticationDetails.java | 3 +-- .../rest/RestAuthenticationDetailsSource.java | 3 +-- .../auth/rest/RestAuthenticationProvider.java | 2 +- .../RestAwareAuthenticationFailureHandler.java | 2 +- .../RestAwareAuthenticationSuccessHandler.java | 2 +- .../auth/rest/RestLoginProcessingFilter.java | 2 +- .../rest/RestPublicLoginProcessingFilter.java | 2 +- .../device/DefaultDeviceAuthService.java | 2 +- .../AuthMethodNotSupportedException.java | 2 +- .../exception/JwtExpiredTokenException.java | 2 +- .../exception/UserPasswordExpiredException.java | 2 +- .../service/security/model/SecuritySettings.java | 2 +- .../service/security/model/SecurityUser.java | 2 +- .../security/model/UserPasswordPolicy.java | 2 +- .../service/security/model/UserPrincipal.java | 2 +- .../security/model/token/AccessJwtToken.java | 2 +- .../service/security/model/token/JwtToken.java | 2 +- .../security/model/token/JwtTokenFactory.java | 2 +- .../security/model/token/RawAccessJwtToken.java | 2 +- .../security/permission/AbstractPermissions.java | 2 +- .../permission/AccessControlService.java | 2 +- .../permission/CustomerUserPermissions.java | 2 +- .../permission/DefaultAccessControlService.java | 2 +- .../service/security/permission/Operation.java | 2 +- .../security/permission/PermissionChecker.java | 2 +- .../service/security/permission/Permissions.java | 2 +- .../service/security/permission/Resource.java | 2 +- .../security/permission/SysAdminPermissions.java | 2 +- .../permission/TenantAdminPermissions.java | 2 +- .../system/DefaultSystemSecurityService.java | 2 +- .../security/system/SystemSecurityService.java | 2 +- .../DefaultDeviceSessionCacheService.java | 2 +- .../session/DeviceSessionCacheService.java | 2 +- .../service/state/DefaultDeviceStateService.java | 2 +- .../server/service/state/DeviceState.java | 2 +- .../server/service/state/DeviceStateData.java | 2 +- .../server/service/state/DeviceStateService.java | 2 +- .../server/service/telemetry/AttributeData.java | 2 +- .../DefaultTelemetrySubscriptionService.java | 2 +- .../DefaultTelemetryWebSocketService.java | 2 +- .../server/service/telemetry/SessionEvent.java | 2 +- .../service/telemetry/TelemetryFeature.java | 2 +- .../telemetry/TelemetrySubscriptionService.java | 2 +- .../telemetry/TelemetryWebSocketMsgEndpoint.java | 2 +- .../telemetry/TelemetryWebSocketService.java | 2 +- .../telemetry/TelemetryWebSocketSessionRef.java | 2 +- .../telemetry/TelemetryWebSocketTextMsg.java | 2 +- .../server/service/telemetry/TsData.java | 2 +- .../service/telemetry/WsSessionMetaData.java | 2 +- .../telemetry/cmd/AttributesSubscriptionCmd.java | 2 +- .../service/telemetry/cmd/GetHistoryCmd.java | 2 +- .../service/telemetry/cmd/SubscriptionCmd.java | 2 +- .../telemetry/cmd/TelemetryPluginCmd.java | 2 +- .../cmd/TelemetryPluginCmdsWrapper.java | 2 +- .../telemetry/cmd/TimeseriesSubscriptionCmd.java | 2 +- .../exception/AccessDeniedException.java | 2 +- .../exception/EntityNotFoundException.java | 2 +- .../exception/InternalErrorException.java | 2 +- .../exception/InvalidParametersException.java | 2 +- .../exception/ToErrorResponseEntity.java | 2 +- .../exception/UnauthorizedException.java | 2 +- .../exception/UncheckedApiException.java | 2 +- .../service/telemetry/sub/Subscription.java | 2 +- .../telemetry/sub/SubscriptionErrorCode.java | 2 +- .../service/telemetry/sub/SubscriptionState.java | 2 +- .../telemetry/sub/SubscriptionUpdate.java | 2 +- .../BaseRuleChainTransactionService.java | 2 +- .../service/transaction/TbTransactionTask.java | 2 +- .../transport/LocalTransportApiService.java | 2 +- .../service/transport/LocalTransportService.java | 2 +- .../RemoteRuleEngineTransportService.java | 2 +- .../transport/RemoteTransportApiService.java | 2 +- .../transport/RuleEngineTransportService.java | 2 +- .../transport/ToRuleEngineMsgDecoder.java | 2 +- .../service/transport/ToTransportMsgEncoder.java | 2 +- .../transport/TransportApiRequestDecoder.java | 2 +- .../transport/TransportApiResponseEncoder.java | 2 +- .../service/transport/TransportApiService.java | 2 +- .../msg/TransportToDeviceActorMsgWrapper.java | 2 +- .../service/update/DefaultUpdateService.java | 2 +- .../server/service/update/UpdateService.java | 2 +- .../service/update/model/UpdateMessage.java | 2 +- .../org/thingsboard/server/utils/MiscUtils.java | 2 +- application/src/main/proto/cluster.proto | 2 +- application/src/main/proto/jsinvoke.proto | 2 +- application/src/main/resources/actor-system.conf | 2 +- application/src/main/resources/logback.xml | 2 +- .../resources/templates/account.activated.vm | 2 +- .../src/main/resources/templates/activation.vm | 2 +- .../resources/templates/password.was.reset.vm | 2 +- .../main/resources/templates/reset.password.vm | 2 +- application/src/main/resources/templates/test.vm | 2 +- application/src/main/resources/thingsboard.yml | 2 +- application/src/main/scripts/install/install.sh | 2 +- .../src/main/scripts/install/install_dev_db.sh | 2 +- application/src/main/scripts/install/logback.xml | 2 +- application/src/main/scripts/install/upgrade.sh | 2 +- .../controller/AbstractControllerTest.java | 2 +- .../AbstractRuleEngineControllerTest.java | 2 +- .../controller/BaseAdminControllerTest.java | 2 +- .../controller/BaseAssetControllerTest.java | 2 +- .../controller/BaseAuditLogControllerTest.java | 2 +- .../controller/BaseAuthControllerTest.java | 2 +- .../BaseComponentDescriptorControllerTest.java | 2 +- .../controller/BaseCustomerControllerTest.java | 2 +- .../controller/BaseDashboardControllerTest.java | 2 +- .../controller/BaseDeviceControllerTest.java | 2 +- .../controller/BaseEntityViewControllerTest.java | 2 +- .../controller/BaseTenantControllerTest.java | 2 +- .../controller/BaseUserControllerTest.java | 2 +- .../controller/BaseWidgetTypeControllerTest.java | 2 +- .../BaseWidgetsBundleControllerTest.java | 2 +- .../controller/ControllerSqlTestSuite.java | 2 +- .../controller/sql/AdminControllerSqlTest.java | 2 +- .../controller/sql/AssetControllerSqlTest.java | 2 +- .../sql/AuditLogControllerSqlTest.java | 2 +- .../controller/sql/AuthControllerSqlTest.java | 2 +- .../ComponentDescriptorControllerSqlTest.java | 2 +- .../sql/CustomerControllerSqlTest.java | 2 +- .../sql/DashboardControllerSqlTest.java | 2 +- .../controller/sql/DeviceControllerSqlTest.java | 2 +- .../sql/EntityViewControllerSqlTest.java | 2 +- .../controller/sql/TenantControllerSqlTest.java | 2 +- .../controller/sql/UserControllerSqlTest.java | 2 +- .../sql/WidgetTypeControllerSqlTest.java | 2 +- .../sql/WidgetsBundleControllerSqlTest.java | 2 +- .../server/mqtt/DbConfigurationTestRule.java | 2 +- .../server/mqtt/MqttNoSqlTestSuite.java | 2 +- .../server/mqtt/MqttSqlTestSuite.java | 2 +- ...AbstractMqttServerSideRpcIntegrationTest.java | 2 +- .../MqttServerSideRpcNoSqlIntegrationTest.java | 2 +- .../sql/MqttServerSideRpcSqlIntegrationTest.java | 2 +- .../AbstractMqttTelemetryIntegrationTest.java | 2 +- .../nosql/MqttTelemetryNoSqlIntegrationTest.java | 2 +- .../sql/MqttTelemetrySqlIntegrationTest.java | 2 +- .../server/rules/RuleEngineSqlTestSuite.java | 2 +- .../AbstractRuleEngineFlowIntegrationTest.java | 2 +- .../sql/RuleEngineFlowSqlIntegrationTest.java | 2 +- ...stractRuleEngineLifecycleIntegrationTest.java | 2 +- .../RuleEngineLifecycleSqlIntegrationTest.java | 2 +- .../server/service/mail/TestMailService.java | 2 +- .../script/RuleNodeJsScriptEngineTest.java | 2 +- .../script/TestNashornJsInvokeService.java | 2 +- .../server/system/BaseHttpDeviceApiTest.java | 2 +- .../server/system/SystemSqlTestSuite.java | 2 +- .../server/system/sql/DeviceApiSqlTest.java | 2 +- common/dao-api/pom.xml | 4 ++-- .../server/dao/alarm/AlarmService.java | 2 +- .../server/dao/asset/AssetService.java | 2 +- .../server/dao/attributes/AttributesService.java | 2 +- .../server/dao/audit/AuditLogService.java | 2 +- .../dao/cassandra/AbstractCassandraCluster.java | 2 +- .../server/dao/cassandra/CassandraCluster.java | 2 +- .../dao/cassandra/CassandraInstallCluster.java | 2 +- .../dao/cassandra/CassandraQueryOptions.java | 2 +- .../dao/cassandra/CassandraSocketOptions.java | 2 +- .../component/ComponentDescriptorService.java | 2 +- .../server/dao/customer/CustomerService.java | 2 +- .../server/dao/dashboard/DashboardService.java | 2 +- .../dao/device/DeviceCredentialsService.java | 2 +- .../server/dao/device/DeviceService.java | 2 +- .../server/dao/entity/EntityService.java | 2 +- .../server/dao/entityview/EntityViewService.java | 2 +- .../server/dao/event/EventService.java | 2 +- .../server/dao/nosql/CassandraStatementTask.java | 2 +- .../server/dao/relation/RelationService.java | 2 +- .../server/dao/rule/RuleChainService.java | 2 +- .../dao/settings/AdminSettingsService.java | 2 +- .../server/dao/tenant/TenantService.java | 2 +- .../server/dao/timeseries/TimeseriesService.java | 2 +- .../thingsboard/server/dao/user/UserService.java | 2 +- .../thingsboard/server/dao/util/AsyncTask.java | 2 +- .../thingsboard/server/dao/util/NoSqlAnyDao.java | 2 +- .../thingsboard/server/dao/util/NoSqlDao.java | 2 +- .../thingsboard/server/dao/util/NoSqlTsDao.java | 2 +- .../org/thingsboard/server/dao/util/SqlDao.java | 2 +- .../thingsboard/server/dao/util/SqlTsDao.java | 2 +- .../server/dao/widget/WidgetTypeService.java | 2 +- .../server/dao/widget/WidgetsBundleService.java | 2 +- common/data/pom.xml | 4 ++-- .../server/common/data/AdminSettings.java | 2 +- .../thingsboard/server/common/data/BaseData.java | 2 +- .../server/common/data/CacheConstants.java | 2 +- .../server/common/data/ContactBased.java | 2 +- .../thingsboard/server/common/data/Customer.java | 2 +- .../server/common/data/Dashboard.java | 2 +- .../server/common/data/DashboardInfo.java | 2 +- .../server/common/data/DataConstants.java | 2 +- .../thingsboard/server/common/data/Device.java | 2 +- .../server/common/data/DeviceInfo.java | 2 +- .../server/common/data/EntityFieldsData.java | 2 +- .../server/common/data/EntitySubtype.java | 2 +- .../server/common/data/EntityType.java | 2 +- .../server/common/data/EntityView.java | 2 +- .../server/common/data/EntityViewInfo.java | 2 +- .../thingsboard/server/common/data/Event.java | 2 +- .../server/common/data/HasAdditionalInfo.java | 2 +- .../server/common/data/HasCustomerId.java | 2 +- .../thingsboard/server/common/data/HasName.java | 2 +- .../server/common/data/HasTenantId.java | 2 +- .../server/common/data/SearchTextBased.java | 2 +- .../data/SearchTextBasedWithAdditionalInfo.java | 2 +- .../server/common/data/ShortCustomerInfo.java | 2 +- .../thingsboard/server/common/data/Tenant.java | 2 +- .../server/common/data/UUIDConverter.java | 2 +- .../org/thingsboard/server/common/data/User.java | 2 +- .../server/common/data/alarm/Alarm.java | 2 +- .../server/common/data/alarm/AlarmId.java | 2 +- .../server/common/data/alarm/AlarmInfo.java | 2 +- .../server/common/data/alarm/AlarmQuery.java | 2 +- .../common/data/alarm/AlarmSearchStatus.java | 2 +- .../server/common/data/alarm/AlarmSeverity.java | 2 +- .../server/common/data/alarm/AlarmStatus.java | 2 +- .../server/common/data/asset/Asset.java | 2 +- .../server/common/data/asset/AssetInfo.java | 2 +- .../common/data/asset/AssetSearchQuery.java | 2 +- .../server/common/data/audit/ActionStatus.java | 2 +- .../server/common/data/audit/ActionType.java | 2 +- .../server/common/data/audit/AuditLog.java | 2 +- .../common/data/device/DeviceSearchQuery.java | 2 +- .../data/entityview/EntityViewSearchQuery.java | 2 +- .../data/exception/ThingsboardErrorCode.java | 2 +- .../data/exception/ThingsboardException.java | 2 +- .../server/common/data/id/AdminSettingsId.java | 2 +- .../server/common/data/id/AssetId.java | 2 +- .../server/common/data/id/AuditLogId.java | 2 +- .../common/data/id/ComponentDescriptorId.java | 2 +- .../server/common/data/id/CustomerId.java | 2 +- .../server/common/data/id/DashboardId.java | 2 +- .../common/data/id/DeviceCredentialsId.java | 2 +- .../server/common/data/id/DeviceId.java | 2 +- .../server/common/data/id/EntityId.java | 2 +- .../common/data/id/EntityIdDeserializer.java | 2 +- .../server/common/data/id/EntityIdFactory.java | 2 +- .../common/data/id/EntityIdSerializer.java | 2 +- .../server/common/data/id/EntityViewId.java | 2 +- .../server/common/data/id/EventId.java | 2 +- .../server/common/data/id/IdBased.java | 2 +- .../server/common/data/id/NodeId.java | 2 +- .../server/common/data/id/RuleChainId.java | 2 +- .../server/common/data/id/RuleNodeId.java | 2 +- .../server/common/data/id/TenantId.java | 2 +- .../server/common/data/id/UUIDBased.java | 2 +- .../server/common/data/id/UserCredentialsId.java | 2 +- .../server/common/data/id/UserId.java | 2 +- .../server/common/data/id/WidgetTypeId.java | 2 +- .../server/common/data/id/WidgetsBundleId.java | 2 +- .../server/common/data/kv/Aggregation.java | 2 +- .../server/common/data/kv/AttributeKey.java | 2 +- .../server/common/data/kv/AttributeKvEntry.java | 2 +- .../common/data/kv/BaseAttributeKvEntry.java | 2 +- .../common/data/kv/BaseDeleteTsKvQuery.java | 2 +- .../server/common/data/kv/BaseReadTsKvQuery.java | 2 +- .../server/common/data/kv/BaseTsKvQuery.java | 2 +- .../server/common/data/kv/BasicKvEntry.java | 2 +- .../server/common/data/kv/BasicTsKvEntry.java | 2 +- .../server/common/data/kv/BooleanDataEntry.java | 2 +- .../server/common/data/kv/DataType.java | 2 +- .../server/common/data/kv/DeleteTsKvQuery.java | 2 +- .../server/common/data/kv/DoubleDataEntry.java | 2 +- .../server/common/data/kv/KvEntry.java | 2 +- .../server/common/data/kv/LongDataEntry.java | 2 +- .../server/common/data/kv/ReadTsKvQuery.java | 2 +- .../server/common/data/kv/StringDataEntry.java | 2 +- .../server/common/data/kv/TsKvEntry.java | 2 +- .../server/common/data/kv/TsKvQuery.java | 2 +- .../data/objects/AttributesEntityView.java | 2 +- .../common/data/objects/TelemetryEntityView.java | 2 +- .../server/common/data/page/PageData.java | 2 +- .../common/data/page/PageDataIterable.java | 2 +- .../server/common/data/page/PageLink.java | 2 +- .../server/common/data/page/SortOrder.java | 2 +- .../server/common/data/page/TimePageLink.java | 2 +- .../common/data/plugin/ComponentDescriptor.java | 2 +- .../data/plugin/ComponentLifecycleEvent.java | 2 +- .../data/plugin/ComponentLifecycleState.java | 2 +- .../common/data/plugin/ComponentScope.java | 2 +- .../server/common/data/plugin/ComponentType.java | 2 +- .../common/data/relation/EntityRelation.java | 2 +- .../common/data/relation/EntityRelationInfo.java | 2 +- .../data/relation/EntityRelationsQuery.java | 2 +- .../data/relation/EntitySearchDirection.java | 2 +- .../common/data/relation/EntityTypeFilter.java | 2 +- .../common/data/relation/RelationTypeGroup.java | 2 +- .../data/relation/RelationsSearchParameters.java | 2 +- .../server/common/data/rpc/RpcRequest.java | 2 +- .../common/data/rpc/ToDeviceRpcRequestBody.java | 2 +- .../common/data/rule/NodeConnectionInfo.java | 2 +- .../server/common/data/rule/RuleChain.java | 2 +- .../data/rule/RuleChainConnectionInfo.java | 2 +- .../common/data/rule/RuleChainMetaData.java | 2 +- .../server/common/data/rule/RuleNode.java | 2 +- .../server/common/data/rule/RuleType.java | 2 +- .../server/common/data/rule/Scope.java | 2 +- .../server/common/data/security/Authority.java | 2 +- .../common/data/security/DeviceCredentials.java | 2 +- .../data/security/DeviceCredentialsFilter.java | 2 +- .../data/security/DeviceCredentialsType.java | 2 +- .../data/security/DeviceTokenCredentials.java | 2 +- .../data/security/DeviceX509Credentials.java | 2 +- .../common/data/security/UserCredentials.java | 2 +- .../server/common/data/widget/WidgetType.java | 2 +- .../server/common/data/widget/WidgetsBundle.java | 2 +- .../server/common/data/UUIDConverterTest.java | 2 +- common/message/pom.xml | 4 ++-- .../server/common/msg/EncryptionUtil.java | 2 +- .../thingsboard/server/common/msg/MsgType.java | 2 +- .../server/common/msg/TbActorMsg.java | 2 +- .../org/thingsboard/server/common/msg/TbMsg.java | 2 +- .../server/common/msg/TbMsgDataType.java | 2 +- .../server/common/msg/TbMsgMetaData.java | 2 +- .../server/common/msg/TbMsgTransactionData.java | 2 +- .../common/msg/aware/CustomerAwareMsg.java | 2 +- .../server/common/msg/aware/DeviceAwareMsg.java | 2 +- .../server/common/msg/aware/NodeAwareMsg.java | 2 +- .../common/msg/aware/RuleChainAwareMsg.java | 2 +- .../server/common/msg/aware/TenantAwareMsg.java | 2 +- .../common/msg/cluster/ClusterEventMsg.java | 2 +- .../common/msg/cluster/SendToClusterMsg.java | 2 +- .../server/common/msg/cluster/ServerAddress.java | 2 +- .../server/common/msg/cluster/ServerType.java | 2 +- .../server/common/msg/cluster/ToAllNodesMsg.java | 2 +- .../common/msg/core/ToServerRpcResponseMsg.java | 2 +- .../server/common/msg/kv/AttributesKVMsg.java | 2 +- .../common/msg/kv/BasicAttributeKVMsg.java | 2 +- .../common/msg/plugin/ComponentLifecycleMsg.java | 2 +- .../common/msg/rpc/ToDeviceRpcRequest.java | 2 +- .../server/common/msg/session/FeatureType.java | 2 +- .../common/msg/session/SessionContext.java | 2 +- .../common/msg/session/SessionMsgType.java | 2 +- .../session/ex/ProcessingTimeoutException.java | 2 +- .../msg/session/ex/SessionAuthException.java | 2 +- .../common/msg/session/ex/SessionException.java | 2 +- .../msg/system/ServiceToRuleEngineMsg.java | 2 +- .../DeviceActorClientSideRpcTimeoutMsg.java | 2 +- .../DeviceActorServerSideRpcTimeoutMsg.java | 2 +- .../server/common/msg/timeout/TimeoutMsg.java | 2 +- .../server/common/msg/tools/TbRateLimits.java | 2 +- .../common/msg/tools/TbRateLimitsException.java | 2 +- common/message/src/main/proto/tbmsg.proto | 2 +- common/pom.xml | 4 ++-- common/queue/pom.xml | 4 ++-- .../server/kafka/AbstractTbKafkaTemplate.java | 2 +- .../server/kafka/AsyncCallbackTemplate.java | 2 +- .../thingsboard/server/kafka/TBKafkaAdmin.java | 2 +- .../server/kafka/TBKafkaConsumerTemplate.java | 2 +- .../server/kafka/TBKafkaProducerTemplate.java | 2 +- .../thingsboard/server/kafka/TbKafkaDecoder.java | 2 +- .../thingsboard/server/kafka/TbKafkaEncoder.java | 2 +- .../server/kafka/TbKafkaEnricher.java | 2 +- .../thingsboard/server/kafka/TbKafkaHandler.java | 2 +- .../server/kafka/TbKafkaPartitioner.java | 2 +- .../server/kafka/TbKafkaProperty.java | 2 +- .../server/kafka/TbKafkaRequestIdExtractor.java | 2 +- .../server/kafka/TbKafkaRequestTemplate.java | 2 +- .../server/kafka/TbKafkaResponseTemplate.java | 2 +- .../server/kafka/TbKafkaSettings.java | 2 +- .../server/kafka/TbNodeIdProvider.java | 2 +- common/transport/coap/pom.xml | 4 ++-- .../transport/coap/CoapTransportContext.java | 2 +- .../transport/coap/CoapTransportResource.java | 2 +- .../transport/coap/CoapTransportService.java | 2 +- .../coap/adaptors/CoapTransportAdaptor.java | 2 +- .../transport/coap/adaptors/JsonCoapAdaptor.java | 2 +- .../transport/coap/client/DeviceEmulator.java | 2 +- common/transport/http/pom.xml | 4 ++-- .../transport/http/DeviceApiController.java | 2 +- .../transport/http/HttpTransportContext.java | 2 +- common/transport/mqtt/pom.xml | 4 ++-- .../transport/mqtt/MqttSslHandlerProvider.java | 2 +- .../server/transport/mqtt/MqttTopics.java | 2 +- .../transport/mqtt/MqttTransportContext.java | 2 +- .../transport/mqtt/MqttTransportHandler.java | 2 +- .../mqtt/MqttTransportServerInitializer.java | 2 +- .../transport/mqtt/MqttTransportService.java | 2 +- .../transport/mqtt/adaptors/JsonMqttAdaptor.java | 2 +- .../mqtt/adaptors/MqttTransportAdaptor.java | 2 +- .../transport/mqtt/session/DeviceSessionCtx.java | 2 +- .../mqtt/session/GatewayDeviceSessionCtx.java | 2 +- .../mqtt/session/GatewaySessionHandler.java | 2 +- .../session/MqttDeviceAwareSessionContext.java | 2 +- .../transport/mqtt/session/MqttTopicMatcher.java | 2 +- .../server/transport/mqtt/util/SslUtil.java | 2 +- common/transport/pom.xml | 4 ++-- common/transport/transport-api/pom.xml | 4 ++-- .../common/transport/SessionMsgListener.java | 2 +- .../common/transport/SessionMsgProcessor.java | 2 +- .../common/transport/TransportAdaptor.java | 2 +- .../common/transport/TransportContext.java | 2 +- .../common/transport/TransportService.java | 2 +- .../transport/TransportServiceCallback.java | 2 +- .../transport/adaptor/AdaptorException.java | 2 +- .../common/transport/adaptor/JsonConverter.java | 2 +- .../transport/adaptor/JsonConverterConfig.java | 2 +- .../common/transport/auth/DeviceAuthResult.java | 2 +- .../common/transport/auth/DeviceAuthService.java | 2 +- .../service/AbstractTransportService.java | 2 +- .../service/RemoteTransportService.java | 2 +- .../transport/service/SessionMetaData.java | 2 +- .../service/ToRuleEngineMsgEncoder.java | 2 +- .../service/ToTransportMsgResponseDecoder.java | 2 +- .../service/TransportApiRequestEncoder.java | 2 +- .../service/TransportApiResponseDecoder.java | 2 +- .../session/DeviceAwareSessionContext.java | 2 +- .../transport-api/src/main/proto/transport.proto | 2 +- common/util/pom.xml | 4 ++-- .../thingsboard/common/util/DonAsynchron.java | 2 +- dao/pom.xml | 4 ++-- .../java/org/thingsboard/server/dao/Dao.java | 2 +- .../java/org/thingsboard/server/dao/DaoUtil.java | 2 +- .../org/thingsboard/server/dao/JpaDaoConfig.java | 2 +- .../thingsboard/server/dao/alarm/AlarmDao.java | 2 +- .../server/dao/alarm/BaseAlarmService.java | 2 +- .../thingsboard/server/dao/asset/AssetDao.java | 2 +- .../server/dao/asset/AssetTypeFilter.java | 2 +- .../server/dao/asset/BaseAssetService.java | 2 +- .../server/dao/attributes/AttributesDao.java | 2 +- .../dao/attributes/BaseAttributesService.java | 2 +- .../server/dao/audit/AuditLogDao.java | 2 +- .../server/dao/audit/AuditLogLevelFilter.java | 2 +- .../server/dao/audit/AuditLogLevelMask.java | 2 +- .../server/dao/audit/AuditLogServiceImpl.java | 2 +- .../dao/audit/DummyAuditLogServiceImpl.java | 2 +- .../server/dao/audit/sink/AuditLogSink.java | 2 +- .../server/dao/audit/sink/DummyAuditLogSink.java | 2 +- .../audit/sink/ElasticsearchAuditLogSink.java | 2 +- .../thingsboard/server/dao/cache/CacheSpecs.java | 2 +- .../dao/cache/CaffeineCacheConfiguration.java | 2 +- .../PreviousDeviceCredentialsIdKeyGenerator.java | 2 +- .../dao/cache/TBRedisCacheConfiguration.java | 2 +- .../BaseComponentDescriptorService.java | 2 +- .../dao/component/ComponentDescriptorDao.java | 2 +- .../server/dao/customer/CustomerDao.java | 2 +- .../server/dao/customer/CustomerServiceImpl.java | 2 +- .../server/dao/dashboard/DashboardDao.java | 2 +- .../server/dao/dashboard/DashboardInfoDao.java | 2 +- .../dao/dashboard/DashboardServiceImpl.java | 2 +- .../server/dao/device/ClaimDevicesService.java | 2 +- .../dao/device/ClaimDevicesServiceImpl.java | 2 +- .../server/dao/device/DeviceCredentialsDao.java | 2 +- .../dao/device/DeviceCredentialsServiceImpl.java | 2 +- .../thingsboard/server/dao/device/DeviceDao.java | 2 +- .../server/dao/device/DeviceServiceImpl.java | 2 +- .../server/dao/device/claim/ClaimData.java | 2 +- .../server/dao/device/claim/ClaimResponse.java | 2 +- .../server/dao/device/claim/ClaimResult.java | 2 +- .../server/dao/entity/AbstractEntityService.java | 2 +- .../server/dao/entity/BaseEntityService.java | 2 +- .../server/dao/entityview/EntityViewDao.java | 2 +- .../dao/entityview/EntityViewServiceImpl.java | 2 +- .../server/dao/event/BaseEventService.java | 2 +- .../thingsboard/server/dao/event/EventDao.java | 2 +- .../dao/exception/BufferLimitException.java | 2 +- .../dao/exception/DataValidationException.java | 2 +- .../server/dao/exception/DatabaseException.java | 2 +- .../exception/IncorrectParameterException.java | 2 +- .../thingsboard/server/dao/model/BaseEntity.java | 2 +- .../server/dao/model/BaseSqlEntity.java | 2 +- .../server/dao/model/EntitySubtypeEntity.java | 2 +- .../server/dao/model/ModelConstants.java | 2 +- .../server/dao/model/SearchTextEntity.java | 2 +- .../org/thingsboard/server/dao/model/ToData.java | 2 +- .../dao/model/sql/AbstractAlarmEntity.java | 2 +- .../dao/model/sql/AbstractAssetEntity.java | 2 +- .../dao/model/sql/AbstractDeviceEntity.java | 2 +- .../dao/model/sql/AbstractEntityViewEntity.java | 2 +- .../dao/model/sql/AdminSettingsEntity.java | 2 +- .../server/dao/model/sql/AlarmEntity.java | 2 +- .../server/dao/model/sql/AlarmInfoEntity.java | 2 +- .../server/dao/model/sql/AssetEntity.java | 2 +- .../server/dao/model/sql/AssetInfoEntity.java | 2 +- .../dao/model/sql/AttributeKvCompositeKey.java | 2 +- .../server/dao/model/sql/AttributeKvEntity.java | 2 +- .../server/dao/model/sql/AuditLogEntity.java | 2 +- .../dao/model/sql/ComponentDescriptorEntity.java | 2 +- .../server/dao/model/sql/CustomerEntity.java | 2 +- .../server/dao/model/sql/DashboardEntity.java | 2 +- .../dao/model/sql/DashboardInfoEntity.java | 2 +- .../dao/model/sql/DeviceCredentialsEntity.java | 2 +- .../server/dao/model/sql/DeviceEntity.java | 2 +- .../server/dao/model/sql/DeviceInfoEntity.java | 2 +- .../server/dao/model/sql/EntityViewEntity.java | 2 +- .../dao/model/sql/EntityViewInfoEntity.java | 2 +- .../server/dao/model/sql/EventEntity.java | 2 +- .../dao/model/sql/RelationCompositeKey.java | 2 +- .../server/dao/model/sql/RelationEntity.java | 2 +- .../server/dao/model/sql/RuleChainEntity.java | 2 +- .../server/dao/model/sql/RuleNodeEntity.java | 2 +- .../server/dao/model/sql/TenantEntity.java | 2 +- .../server/dao/model/sql/TsKvCompositeKey.java | 2 +- .../server/dao/model/sql/TsKvEntity.java | 2 +- .../dao/model/sql/TsKvLatestCompositeKey.java | 2 +- .../server/dao/model/sql/TsKvLatestEntity.java | 2 +- .../dao/model/sql/UserCredentialsEntity.java | 2 +- .../server/dao/model/sql/UserEntity.java | 2 +- .../server/dao/model/sql/WidgetTypeEntity.java | 2 +- .../dao/model/sql/WidgetsBundleEntity.java | 2 +- .../model/type/ComponentLifecycleStateCodec.java | 2 +- .../server/dao/model/type/EntityTypeCodec.java | 2 +- .../server/dao/model/type/JsonCodec.java | 2 +- .../dao/model/wrapper/EntityResultSet.java | 2 +- .../dao/nosql/CassandraAbstractAsyncDao.java | 2 +- .../server/dao/nosql/CassandraAbstractDao.java | 2 +- .../dao/nosql/CassandraBufferedRateExecutor.java | 2 +- .../dao/nosql/RateLimitedResultSetFuture.java | 2 +- .../server/dao/nosql/TbResultSetFuture.java | 2 +- .../server/dao/relation/BaseRelationService.java | 2 +- .../server/dao/relation/RelationDao.java | 2 +- .../server/dao/rule/BaseRuleChainService.java | 2 +- .../server/dao/rule/RuleChainDao.java | 2 +- .../thingsboard/server/dao/rule/RuleNodeDao.java | 2 +- .../server/dao/service/DataValidator.java | 2 +- .../server/dao/service/PaginatedRemover.java | 2 +- .../server/dao/service/TimePaginatedRemover.java | 2 +- .../server/dao/service/Validator.java | 2 +- .../server/dao/settings/AdminSettingsDao.java | 2 +- .../dao/settings/AdminSettingsServiceImpl.java | 2 +- .../server/dao/sql/JpaAbstractDao.java | 2 +- .../JpaAbstractDaoListeningExecutorService.java | 2 +- .../server/dao/sql/JpaAbstractSearchTextDao.java | 2 +- .../server/dao/sql/JpaAbstractSearchTimeDao.java | 2 +- .../server/dao/sql/alarm/AlarmRepository.java | 2 +- .../server/dao/sql/alarm/JpaAlarmDao.java | 2 +- .../server/dao/sql/asset/AssetRepository.java | 2 +- .../server/dao/sql/asset/JpaAssetDao.java | 2 +- .../sql/attributes/AttributeKvRepository.java | 2 +- .../dao/sql/attributes/JpaAttributeDao.java | 2 +- .../server/dao/sql/audit/AuditLogRepository.java | 2 +- .../server/dao/sql/audit/JpaAuditLogDao.java | 2 +- .../component/ComponentDescriptorRepository.java | 2 +- .../component/JpaBaseComponentDescriptorDao.java | 2 +- .../dao/sql/customer/CustomerRepository.java | 2 +- .../server/dao/sql/customer/JpaCustomerDao.java | 2 +- .../sql/dashboard/DashboardInfoRepository.java | 2 +- .../dao/sql/dashboard/DashboardRepository.java | 2 +- .../dao/sql/dashboard/JpaDashboardDao.java | 2 +- .../dao/sql/dashboard/JpaDashboardInfoDao.java | 2 +- .../sql/device/DeviceCredentialsRepository.java | 2 +- .../server/dao/sql/device/DeviceRepository.java | 2 +- .../dao/sql/device/JpaDeviceCredentialsDao.java | 2 +- .../server/dao/sql/device/JpaDeviceDao.java | 2 +- .../dao/sql/entityview/EntityViewRepository.java | 2 +- .../dao/sql/entityview/JpaEntityViewDao.java | 2 +- .../server/dao/sql/event/EventRepository.java | 2 +- .../server/dao/sql/event/JpaBaseEventDao.java | 2 +- .../server/dao/sql/relation/JpaRelationDao.java | 2 +- .../dao/sql/relation/RelationRepository.java | 2 +- .../server/dao/sql/rule/JpaRuleChainDao.java | 2 +- .../server/dao/sql/rule/JpaRuleNodeDao.java | 2 +- .../server/dao/sql/rule/RuleChainRepository.java | 2 +- .../server/dao/sql/rule/RuleNodeRepository.java | 2 +- .../sql/settings/AdminSettingsRepository.java | 2 +- .../dao/sql/settings/JpaAdminSettingsDao.java | 2 +- .../server/dao/sql/tenant/JpaTenantDao.java | 2 +- .../server/dao/sql/tenant/TenantRepository.java | 2 +- .../dao/sql/timeseries/JpaTimeseriesDao.java | 2 +- .../dao/sql/timeseries/TsKvLatestRepository.java | 2 +- .../dao/sql/timeseries/TsKvRepository.java | 2 +- .../dao/sql/user/JpaUserCredentialsDao.java | 2 +- .../server/dao/sql/user/JpaUserDao.java | 2 +- .../dao/sql/user/UserCredentialsRepository.java | 2 +- .../server/dao/sql/user/UserRepository.java | 2 +- .../server/dao/sql/widget/JpaWidgetTypeDao.java | 2 +- .../dao/sql/widget/JpaWidgetsBundleDao.java | 2 +- .../dao/sql/widget/WidgetTypeRepository.java | 2 +- .../dao/sql/widget/WidgetsBundleRepository.java | 2 +- .../thingsboard/server/dao/tenant/TenantDao.java | 2 +- .../server/dao/tenant/TenantServiceImpl.java | 2 +- .../timeseries/AggregatePartitionsFunction.java | 2 +- .../dao/timeseries/BaseTimeseriesService.java | 2 +- .../timeseries/CassandraBaseTimeseriesDao.java | 2 +- .../server/dao/timeseries/QueryCursor.java | 2 +- .../dao/timeseries/SimpleListenableFuture.java | 2 +- .../server/dao/timeseries/TimeseriesDao.java | 2 +- .../dao/timeseries/TsInsertExecutorType.java | 2 +- .../server/dao/timeseries/TsKvQueryCursor.java | 2 +- .../server/dao/timeseries/TsPartitionDate.java | 2 +- .../server/dao/user/UserCredentialsDao.java | 2 +- .../org/thingsboard/server/dao/user/UserDao.java | 2 +- .../server/dao/user/UserServiceImpl.java | 2 +- .../dao/util/AbstractBufferedRateExecutor.java | 2 +- .../server/dao/util/AsyncRateLimiter.java | 2 +- .../server/dao/util/AsyncTaskContext.java | 2 +- .../server/dao/util/BufferedRateExecutor.java | 2 +- .../dao/util/TenantRateLimitException.java | 2 +- .../mapping/AbstractJsonSqlTypeDescriptor.java | 2 +- .../server/dao/util/mapping/JacksonUtil.java | 2 +- .../mapping/JsonStringSqlTypeDescriptor.java | 2 +- .../server/dao/util/mapping/JsonStringType.java | 2 +- .../dao/util/mapping/JsonTypeDescriptor.java | 2 +- .../server/dao/widget/WidgetTypeDao.java | 2 +- .../server/dao/widget/WidgetTypeServiceImpl.java | 2 +- .../server/dao/widget/WidgetsBundleDao.java | 2 +- .../dao/widget/WidgetsBundleServiceImpl.java | 2 +- dao/src/main/resources/cassandra/schema-ts.cql | 2 +- .../main/resources/sql/schema-entities-idx.sql | 2 +- dao/src/main/resources/sql/schema-entities.sql | 2 +- dao/src/main/resources/sql/schema-ts.sql | 2 +- .../server/dao/AbstractJpaDaoTest.java | 2 +- .../server/dao/CustomCassandraCQLUnit.java | 2 +- .../thingsboard/server/dao/CustomSqlUnit.java | 2 +- .../thingsboard/server/dao/JpaDaoTestSuite.java | 2 +- .../server/dao/JpaDbunitTestConfig.java | 2 +- .../server/dao/NoSqlDaoServiceTestSuite.java | 2 +- .../server/dao/SqlDaoServiceTestSuite.java | 2 +- .../nosql/RateLimitedResultSetFutureTest.java | 2 +- .../server/dao/service/AbstractServiceTest.java | 2 +- .../service/BaseAdminSettingsServiceTest.java | 2 +- .../server/dao/service/BaseAlarmServiceTest.java | 2 +- .../server/dao/service/BaseAssetServiceTest.java | 2 +- .../dao/service/BaseCustomerServiceTest.java | 2 +- .../dao/service/BaseDashboardServiceTest.java | 2 +- .../service/BaseDeviceCredentialsCacheTest.java | 2 +- .../BaseDeviceCredentialsServiceTest.java | 2 +- .../dao/service/BaseDeviceServiceTest.java | 2 +- .../dao/service/BaseRelationCacheTest.java | 2 +- .../dao/service/BaseRelationServiceTest.java | 2 +- .../dao/service/BaseRuleChainServiceTest.java | 2 +- .../dao/service/BaseTenantServiceTest.java | 2 +- .../server/dao/service/BaseUserServiceTest.java | 2 +- .../dao/service/BaseWidgetTypeServiceTest.java | 2 +- .../service/BaseWidgetsBundleServiceTest.java | 2 +- .../server/dao/service/DaoNoSqlTest.java | 2 +- .../server/dao/service/DaoSqlTest.java | 2 +- .../attributes/BaseAttributesServiceTest.java | 2 +- .../attributes/sql/AttributesServiceSqlTest.java | 2 +- .../dao/service/event/BaseEventServiceTest.java | 2 +- .../service/event/sql/EventServiceSqlTest.java | 2 +- .../service/sql/AdminSettingsServiceSqlTest.java | 2 +- .../dao/service/sql/AlarmServiceSqlTest.java | 2 +- .../dao/service/sql/AssetServiceSqlTest.java | 2 +- .../dao/service/sql/CustomerServiceSqlTest.java | 2 +- .../dao/service/sql/DashboardServiceSqlTest.java | 2 +- .../DeviceCredentialsCacheServiceSqlTest.java | 2 +- .../sql/DeviceCredentialsServiceSqlTest.java | 2 +- .../dao/service/sql/DeviceServiceSqlTest.java | 2 +- .../dao/service/sql/RelationCacheSqlTest.java | 2 +- .../dao/service/sql/RelationServiceSqlTest.java | 2 +- .../dao/service/sql/RuleChainServiceSqlTest.java | 2 +- .../dao/service/sql/TenantServiceSqlTest.java | 2 +- .../dao/service/sql/UserServiceSqlTest.java | 2 +- .../service/sql/WidgetTypeServiceSqlTest.java | 2 +- .../service/sql/WidgetsBundleServiceSqlTest.java | 2 +- .../timeseries/BaseTimeseriesServiceTest.java | 2 +- .../nosql/TimeseriesServiceNoSqlTest.java | 2 +- .../timeseries/sql/TimeseriesServiceSqlTest.java | 2 +- .../server/dao/sql/alarm/JpaAlarmDaoTest.java | 2 +- .../server/dao/sql/asset/JpaAssetDaoTest.java | 2 +- .../server/dao/sql/audit/JpaAuditLogDaoTest.java | 2 +- .../JpaBaseComponentDescriptorDaoTest.java | 2 +- .../dao/sql/customer/JpaCustomerDaoTest.java | 2 +- .../sql/dashboard/JpaDashboardInfoDaoTest.java | 2 +- .../sql/device/JpaDeviceCredentialsDaoTest.java | 2 +- .../server/dao/sql/device/JpaDeviceDaoTest.java | 2 +- .../dao/sql/event/JpaBaseEventDaoTest.java | 2 +- .../server/dao/sql/tenant/JpaTenantDaoTest.java | 2 +- .../dao/sql/user/JpaUserCredentialsDaoTest.java | 2 +- .../server/dao/sql/user/JpaUserDaoTest.java | 2 +- .../dao/sql/widget/JpaWidgetTypeDaoTest.java | 2 +- .../dao/sql/widget/JpaWidgetsBundleDaoTest.java | 2 +- docker/compose-utils.sh | 2 +- docker/docker-compose.cassandra.yml | 2 +- docker/docker-compose.postgres.volumes.yml | 2 +- docker/docker-compose.postgres.yml | 2 +- docker/docker-compose.yml | 2 +- docker/docker-install-tb.sh | 2 +- docker/docker-remove-services.sh | 2 +- docker/docker-start-services.sh | 2 +- docker/docker-stop-services.sh | 2 +- docker/docker-update-service.sh | 2 +- docker/docker-upgrade-tb.sh | 2 +- docker/tb-transports/coap/conf/logback.xml | 2 +- .../coap/conf/tb-coap-transport.conf | 2 +- docker/tb-transports/http/conf/logback.xml | 2 +- .../http/conf/tb-http-transport.conf | 2 +- docker/tb-transports/mqtt/conf/logback.xml | 2 +- .../mqtt/conf/tb-mqtt-transport.conf | 2 +- k8s/cassandra.yml | 2 +- k8s/database-setup.yml | 2 +- k8s/k8s-delete-all.sh | 2 +- k8s/k8s-delete-resources.sh | 2 +- k8s/k8s-deploy-resources.sh | 2 +- k8s/k8s-install-tb.sh | 2 +- k8s/k8s-upgrade-tb.sh | 2 +- k8s/postgres.yml | 2 +- k8s/tb-coap-transport-configmap.yml | 2 +- k8s/tb-http-transport-configmap.yml | 2 +- k8s/tb-mqtt-transport-configmap.yml | 2 +- k8s/tb-namespace.yml | 2 +- k8s/tb-node-cassandra-configmap.yml | 2 +- k8s/tb-node-configmap.yml | 2 +- k8s/tb-node-postgres-configmap.yml | 2 +- k8s/thingsboard.yml | 2 +- license-header-template.txt | 2 +- msa/black-box-tests/pom.xml | 4 ++-- .../server/msa/AbstractContainerTest.java | 2 +- .../server/msa/ContainerTestSuite.java | 2 +- .../server/msa/DockerComposeExecutor.java | 2 +- .../server/msa/ThingsBoardDbInstaller.java | 2 +- .../org/thingsboard/server/msa/WsClient.java | 2 +- .../server/msa/connectivity/HttpClientTest.java | 2 +- .../server/msa/connectivity/MqttClientTest.java | 2 +- .../server/msa/mapper/AttributesResponse.java | 2 +- .../server/msa/mapper/WsTelemetryResponse.java | 2 +- msa/js-executor/api/jsExecutor.js | 2 +- msa/js-executor/api/jsInvokeMessageProcessor.js | 2 +- msa/js-executor/api/utils.js | 2 +- msa/js-executor/build.gradle | 2 +- .../config/custom-environment-variables.yml | 2 +- msa/js-executor/config/default.yml | 2 +- msa/js-executor/config/logger.js | 2 +- msa/js-executor/config/tb-js-executor.conf | 2 +- msa/js-executor/docker/Dockerfile | 2 +- msa/js-executor/docker/start-js-executor.sh | 2 +- msa/js-executor/install.js | 2 +- msa/js-executor/pom.xml | 4 ++-- msa/js-executor/server.js | 2 +- msa/js-executor/src/main/assembly/windows.xml | 2 +- msa/pom.xml | 4 ++-- msa/tb-node/docker/Dockerfile | 2 +- msa/tb-node/docker/start-tb-node.sh | 2 +- msa/tb-node/pom.xml | 4 ++-- msa/tb/docker-cassandra/Dockerfile | 2 +- msa/tb/docker-cassandra/start-db.sh | 2 +- msa/tb/docker-cassandra/stop-db.sh | 2 +- msa/tb/docker-postgres/Dockerfile | 2 +- msa/tb/docker-postgres/start-db.sh | 2 +- msa/tb/docker-postgres/stop-db.sh | 2 +- msa/tb/docker-tb/Dockerfile | 2 +- msa/tb/docker-tb/start-db.sh | 2 +- msa/tb/docker-tb/stop-db.sh | 2 +- msa/tb/docker/install-tb.sh | 2 +- msa/tb/docker/logback.xml | 2 +- msa/tb/docker/start-tb.sh | 2 +- msa/tb/docker/thingsboard.conf | 2 +- msa/tb/docker/upgrade-tb.sh | 2 +- msa/tb/pom.xml | 4 ++-- msa/transport/coap/docker/Dockerfile | 2 +- .../coap/docker/start-tb-coap-transport.sh | 2 +- msa/transport/coap/pom.xml | 4 ++-- msa/transport/http/docker/Dockerfile | 2 +- .../http/docker/start-tb-http-transport.sh | 2 +- msa/transport/http/pom.xml | 4 ++-- msa/transport/mqtt/docker/Dockerfile | 2 +- .../mqtt/docker/start-tb-mqtt-transport.sh | 2 +- msa/transport/mqtt/pom.xml | 4 ++-- msa/transport/pom.xml | 4 ++-- msa/web-ui/build.gradle | 2 +- .../config/custom-environment-variables.yml | 2 +- msa/web-ui/config/default.yml | 2 +- msa/web-ui/config/logger.js | 2 +- msa/web-ui/config/tb-web-ui.conf | 2 +- msa/web-ui/docker/Dockerfile | 2 +- msa/web-ui/docker/start-web-ui.sh | 2 +- msa/web-ui/install.js | 2 +- msa/web-ui/pom.xml | 4 ++-- msa/web-ui/server.js | 2 +- msa/web-ui/src/main/assembly/windows.xml | 2 +- netty-mqtt/pom.xml | 6 +++--- .../thingsboard/mqtt/ChannelClosedException.java | 2 +- .../org/thingsboard/mqtt/MqttChannelHandler.java | 2 +- .../java/org/thingsboard/mqtt/MqttClient.java | 2 +- .../org/thingsboard/mqtt/MqttClientCallback.java | 2 +- .../org/thingsboard/mqtt/MqttClientConfig.java | 2 +- .../org/thingsboard/mqtt/MqttClientImpl.java | 2 +- .../org/thingsboard/mqtt/MqttConnectResult.java | 2 +- .../java/org/thingsboard/mqtt/MqttHandler.java | 2 +- .../mqtt/MqttIncomingQos2Publish.java | 2 +- .../java/org/thingsboard/mqtt/MqttLastWill.java | 2 +- .../org/thingsboard/mqtt/MqttPendingPublish.java | 2 +- .../mqtt/MqttPendingSubscription.java | 2 +- .../mqtt/MqttPendingUnsubscription.java | 2 +- .../org/thingsboard/mqtt/MqttPingHandler.java | 2 +- .../org/thingsboard/mqtt/MqttSubscription.java | 2 +- .../thingsboard/mqtt/RetransmissionHandler.java | 2 +- pom.xml | 4 ++-- rule-engine/pom.xml | 4 ++-- rule-engine/rule-engine-api/pom.xml | 4 ++-- .../rule/engine/api/EmptyNodeConfiguration.java | 2 +- .../rule/engine/api/ListeningExecutor.java | 2 +- .../thingsboard/rule/engine/api/MailService.java | 2 +- .../rule/engine/api/NodeConfiguration.java | 2 +- .../rule/engine/api/NodeDefinition.java | 2 +- .../thingsboard/rule/engine/api/RpcError.java | 2 +- .../engine/api/RuleChainTransactionService.java | 2 +- .../engine/api/RuleEngineDeviceRpcRequest.java | 2 +- .../engine/api/RuleEngineDeviceRpcResponse.java | 2 +- .../rule/engine/api/RuleEngineRpcService.java | 2 +- .../engine/api/RuleEngineTelemetryService.java | 2 +- .../thingsboard/rule/engine/api/RuleNode.java | 2 +- .../rule/engine/api/ScriptEngine.java | 2 +- .../thingsboard/rule/engine/api/TbContext.java | 2 +- .../org/thingsboard/rule/engine/api/TbNode.java | 2 +- .../rule/engine/api/TbNodeConfiguration.java | 2 +- .../rule/engine/api/TbNodeException.java | 2 +- .../thingsboard/rule/engine/api/TbNodeState.java | 2 +- .../rule/engine/api/TbRelationTypes.java | 2 +- .../rule/engine/api/msg/DeviceAttributes.java | 2 +- .../DeviceAttributesEventNotificationMsg.java | 2 +- .../DeviceCredentialsUpdateNotificationMsg.java | 2 +- .../rule/engine/api/msg/DeviceMetaData.java | 2 +- .../api/msg/DeviceNameOrTypeUpdateMsg.java | 2 +- .../api/msg/ToDeviceActorNotificationMsg.java | 2 +- .../rule/engine/api/util/TbNodeUtils.java | 2 +- rule-engine/rule-engine-components/pom.xml | 4 ++-- .../rule/engine/action/TbAbstractAlarmNode.java | 2 +- .../action/TbAbstractAlarmNodeConfiguration.java | 2 +- .../action/TbAbstractCustomerActionNode.java | 2 +- ...bAbstractCustomerActionNodeConfiguration.java | 2 +- .../action/TbAbstractRelationActionNode.java | 2 +- ...bAbstractRelationActionNodeConfiguration.java | 2 +- .../engine/action/TbAssignToCustomerNode.java | 2 +- .../TbAssignToCustomerNodeConfiguration.java | 2 +- .../rule/engine/action/TbClearAlarmNode.java | 2 +- .../action/TbClearAlarmNodeConfiguration.java | 2 +- .../action/TbCopyAttributesToEntityViewNode.java | 2 +- .../rule/engine/action/TbCreateAlarmNode.java | 2 +- .../action/TbCreateAlarmNodeConfiguration.java | 2 +- .../rule/engine/action/TbCreateRelationNode.java | 2 +- .../TbCreateRelationNodeConfiguration.java | 2 +- .../rule/engine/action/TbDeleteRelationNode.java | 2 +- .../TbDeleteRelationNodeConfiguration.java | 2 +- .../rule/engine/action/TbLogNode.java | 2 +- .../engine/action/TbLogNodeConfiguration.java | 2 +- .../rule/engine/action/TbMsgCountNode.java | 2 +- .../action/TbMsgCountNodeConfiguration.java | 2 +- .../action/TbSaveToCustomCassandraTableNode.java | 2 +- ...eToCustomCassandraTableNodeConfiguration.java | 2 +- .../action/TbUnassignFromCustomerNode.java | 2 +- .../TbUnassignFromCustomerNodeConfiguration.java | 2 +- .../rule/engine/aws/sns/TbSnsNode.java | 2 +- .../engine/aws/sns/TbSnsNodeConfiguration.java | 2 +- .../rule/engine/aws/sqs/TbSqsNode.java | 2 +- .../engine/aws/sqs/TbSqsNodeConfiguration.java | 2 +- .../rule/engine/data/DeviceRelationsQuery.java | 2 +- .../rule/engine/data/RelationsQuery.java | 2 +- .../rule/engine/debug/TbMsgGeneratorNode.java | 2 +- .../debug/TbMsgGeneratorNodeConfiguration.java | 2 +- .../rule/engine/delay/TbMsgDelayNode.java | 2 +- .../delay/TbMsgDelayNodeConfiguration.java | 2 +- .../rule/engine/filter/TbCheckMessageNode.java | 2 +- .../filter/TbCheckMessageNodeConfiguration.java | 2 +- .../rule/engine/filter/TbCheckRelationNode.java | 2 +- .../filter/TbCheckRelationNodeConfiguration.java | 2 +- .../rule/engine/filter/TbJsFilterNode.java | 2 +- .../filter/TbJsFilterNodeConfiguration.java | 2 +- .../rule/engine/filter/TbJsSwitchNode.java | 2 +- .../filter/TbJsSwitchNodeConfiguration.java | 2 +- .../rule/engine/filter/TbMsgTypeFilterNode.java | 2 +- .../filter/TbMsgTypeFilterNodeConfiguration.java | 2 +- .../rule/engine/filter/TbMsgTypeSwitchNode.java | 2 +- .../filter/TbOriginatorTypeFilterNode.java | 2 +- .../TbOriginatorTypeFilterNodeConfiguration.java | 2 +- .../filter/TbOriginatorTypeSwitchNode.java | 2 +- .../rule/engine/gcp/pubsub/TbPubSubNode.java | 2 +- .../gcp/pubsub/TbPubSubNodeConfiguration.java | 2 +- .../rule/engine/geo/AbstractGeofencingNode.java | 2 +- .../thingsboard/rule/engine/geo/Coordinates.java | 2 +- .../rule/engine/geo/EntityGeofencingState.java | 2 +- .../org/thingsboard/rule/engine/geo/GeoUtil.java | 2 +- .../thingsboard/rule/engine/geo/Perimeter.java | 2 +- .../rule/engine/geo/PerimeterType.java | 2 +- .../thingsboard/rule/engine/geo/RangeUnit.java | 2 +- .../engine/geo/TbGpsGeofencingActionNode.java | 2 +- .../TbGpsGeofencingActionNodeConfiguration.java | 2 +- .../engine/geo/TbGpsGeofencingFilterNode.java | 2 +- .../TbGpsGeofencingFilterNodeConfiguration.java | 2 +- .../rule/engine/kafka/TbKafkaNode.java | 2 +- .../engine/kafka/TbKafkaNodeConfiguration.java | 2 +- .../thingsboard/rule/engine/mail/EmailPojo.java | 2 +- .../rule/engine/mail/TbMsgToEmailNode.java | 2 +- .../mail/TbMsgToEmailNodeConfiguration.java | 2 +- .../rule/engine/mail/TbSendEmailNode.java | 2 +- .../mail/TbSendEmailNodeConfiguration.java | 2 +- .../metadata/TbAbstractGetAttributesNode.java | 2 +- .../metadata/TbAbstractGetEntityDetailsNode.java | 2 +- ...bstractGetEntityDetailsNodeConfiguration.java | 2 +- .../engine/metadata/TbEntityGetAttrNode.java | 2 +- .../engine/metadata/TbGetAttributesNode.java | 2 +- .../TbGetAttributesNodeConfiguration.java | 2 +- .../metadata/TbGetCustomerAttributeNode.java | 2 +- .../metadata/TbGetCustomerDetailsNode.java | 2 +- .../TbGetCustomerDetailsNodeConfiguration.java | 2 +- .../engine/metadata/TbGetDeviceAttrNode.java | 2 +- .../TbGetDeviceAttrNodeConfiguration.java | 2 +- .../TbGetEntityAttrNodeConfiguration.java | 2 +- .../TbGetOriginatorFieldsConfiguration.java | 2 +- .../metadata/TbGetOriginatorFieldsNode.java | 2 +- .../TbGetRelatedAttrNodeConfiguration.java | 2 +- .../metadata/TbGetRelatedAttributeNode.java | 2 +- .../rule/engine/metadata/TbGetTelemetryNode.java | 2 +- .../TbGetTelemetryNodeConfiguration.java | 2 +- .../metadata/TbGetTenantAttributeNode.java | 2 +- .../engine/metadata/TbGetTenantDetailsNode.java | 2 +- .../TbGetTenantDetailsNodeConfiguration.java | 2 +- .../thingsboard/rule/engine/mqtt/TbMqttNode.java | 2 +- .../engine/mqtt/TbMqttNodeConfiguration.java | 2 +- .../mqtt/credentials/AnonymousCredentials.java | 2 +- .../mqtt/credentials/BasicCredentials.java | 2 +- .../credentials/CertPemClientCredentials.java | 2 +- .../mqtt/credentials/MqttClientCredentials.java | 2 +- .../rule/engine/rabbitmq/TbRabbitMqNode.java | 2 +- .../rabbitmq/TbRabbitMqNodeConfiguration.java | 2 +- .../rule/engine/rest/TbRestApiCallNode.java | 2 +- .../rest/TbRestApiCallNodeConfiguration.java | 2 +- .../rule/engine/rpc/TbSendRPCReplyNode.java | 2 +- .../rule/engine/rpc/TbSendRPCRequestNode.java | 2 +- .../rpc/TbSendRpcReplyNodeConfiguration.java | 2 +- .../rpc/TbSendRpcRequestNodeConfiguration.java | 2 +- .../engine/telemetry/TbMsgAttributesNode.java | 2 +- .../TbMsgAttributesNodeConfiguration.java | 2 +- .../engine/telemetry/TbMsgTimeseriesNode.java | 2 +- .../TbMsgTimeseriesNodeConfiguration.java | 2 +- .../engine/telemetry/TelemetryNodeCallback.java | 2 +- .../transaction/TbSynchronizationBeginNode.java | 2 +- .../transaction/TbSynchronizationEndNode.java | 2 +- .../transform/TbAbstractTransformNode.java | 2 +- .../engine/transform/TbChangeOriginatorNode.java | 2 +- .../TbChangeOriginatorNodeConfiguration.java | 2 +- .../engine/transform/TbTransformMsgNode.java | 2 +- .../TbTransformMsgNodeConfiguration.java | 2 +- .../transform/TbTransformNodeConfiguration.java | 2 +- .../EntitiesAlarmOriginatorIdAsyncLoader.java | 2 +- .../util/EntitiesCustomerIdAsyncLoader.java | 2 +- .../engine/util/EntitiesFieldsAsyncLoader.java | 2 +- .../util/EntitiesRelatedDeviceIdAsyncLoader.java | 2 +- .../util/EntitiesRelatedEntityIdAsyncLoader.java | 2 +- .../engine/util/EntitiesTenantIdAsyncLoader.java | 2 +- .../rule/engine/util/EntityContainer.java | 2 +- .../rule/engine/util/EntityDetails.java | 2 +- .../rule/engine/action/TbAlarmNodeTest.java | 2 +- .../rule/engine/filter/TbJsFilterNodeTest.java | 2 +- .../rule/engine/filter/TbJsSwitchNodeTest.java | 2 +- .../rule/engine/mail/TbMsgToEmailNodeTest.java | 2 +- .../metadata/TbGetCustomerAttributeNodeTest.java | 2 +- .../transform/TbChangeOriginatorNodeTest.java | 2 +- .../engine/transform/TbTransformMsgNodeTest.java | 2 +- tools/pom.xml | 4 ++-- .../thingsboard/client/tools/MqttSslClient.java | 2 +- .../org/thingsboard/client/tools/RestClient.java | 2 +- tools/src/main/python/mqtt-send-telemetry.py | 2 +- tools/src/main/python/one-way-ssl-mqtt-client.py | 2 +- tools/src/main/python/simple-mqtt-client.py | 2 +- tools/src/main/python/two-way-ssl-mqtt-client.py | 2 +- tools/src/main/shell/client.keygen.sh | 2 +- tools/src/main/shell/server.keygen.sh | 2 +- transport/coap/build.gradle | 2 +- transport/coap/pom.xml | 4 ++-- transport/coap/src/main/assembly/windows.xml | 2 +- transport/coap/src/main/conf/logback.xml | 2 +- .../coap/src/main/conf/tb-coap-transport.conf | 2 +- .../ThingsboardCoapTransportApplication.java | 2 +- transport/coap/src/main/resources/logback.xml | 2 +- .../src/main/resources/tb-coap-transport.yml | 2 +- transport/http/build.gradle | 2 +- transport/http/pom.xml | 4 ++-- transport/http/src/main/assembly/windows.xml | 2 +- transport/http/src/main/conf/logback.xml | 2 +- .../http/src/main/conf/tb-http-transport.conf | 2 +- .../ThingsboardHttpTransportApplication.java | 2 +- transport/http/src/main/resources/logback.xml | 2 +- .../src/main/resources/tb-http-transport.yml | 2 +- transport/mqtt/build.gradle | 2 +- transport/mqtt/pom.xml | 4 ++-- transport/mqtt/src/main/assembly/windows.xml | 2 +- transport/mqtt/src/main/conf/logback.xml | 2 +- .../mqtt/src/main/conf/tb-mqtt-transport.conf | 2 +- .../ThingsboardMqttTransportApplication.java | 2 +- transport/mqtt/src/main/resources/logback.xml | 2 +- .../src/main/resources/tb-mqtt-transport.yml | 2 +- transport/pom.xml | 4 ++-- ui-ngx/.editorconfig | 2 +- ui-ngx/e2e/protractor.conf.js | 2 +- ui-ngx/e2e/src/app.e2e-spec.ts | 2 +- ui-ngx/e2e/src/app.po.ts | 2 +- ui-ngx/extra-webpack.config.js | 2 +- ui-ngx/pom.xml | 4 ++-- ui-ngx/proxy.conf.js | 3 +-- ui-ngx/src/app/app-routing.module.ts | 2 +- ui-ngx/src/app/app.component.html | 2 +- ui-ngx/src/app/app.component.scss | 2 +- ui-ngx/src/app/app.component.ts | 2 +- ui-ngx/src/app/app.module.ts | 2 +- ui-ngx/src/app/core/api/alias-controller.ts | 2 +- ui-ngx/src/app/core/api/data-aggregator.ts | 2 +- .../src/app/core/api/datasource-subcription.ts | 2 +- ui-ngx/src/app/core/api/datasource.service.ts | 2 +- ui-ngx/src/app/core/api/public-api.ts | 2 +- ui-ngx/src/app/core/api/widget-api.models.ts | 2 +- ui-ngx/src/app/core/api/widget-subscription.ts | 2 +- ui-ngx/src/app/core/auth/auth.actions.ts | 2 +- ui-ngx/src/app/core/auth/auth.models.ts | 2 +- ui-ngx/src/app/core/auth/auth.reducer.ts | 2 +- ui-ngx/src/app/core/auth/auth.selectors.ts | 2 +- ui-ngx/src/app/core/auth/auth.service.spec.ts | 2 +- ui-ngx/src/app/core/auth/auth.service.ts | 2 +- ui-ngx/src/app/core/core.module.ts | 2 +- ui-ngx/src/app/core/core.state.ts | 2 +- ui-ngx/src/app/core/css/css.js | 2 +- ui-ngx/src/app/core/guards/auth.guard.ts | 2 +- .../src/app/core/guards/confirm-on-exit.guard.ts | 2 +- ui-ngx/src/app/core/http/admin.service.ts | 2 +- ui-ngx/src/app/core/http/alarm.service.ts | 2 +- ui-ngx/src/app/core/http/asset.service.ts | 2 +- ui-ngx/src/app/core/http/attribute.service.ts | 2 +- ui-ngx/src/app/core/http/audit-log.service.ts | 2 +- .../core/http/component-descriptor.service.ts | 2 +- ui-ngx/src/app/core/http/customer.service.ts | 2 +- ui-ngx/src/app/core/http/dashboard.service.ts | 2 +- ui-ngx/src/app/core/http/device.service.ts | 2 +- .../src/app/core/http/entity-relation.service.ts | 2 +- ui-ngx/src/app/core/http/entity-view.service.ts | 2 +- ui-ngx/src/app/core/http/entity.service.ts | 2 +- ui-ngx/src/app/core/http/event.service.ts | 2 +- ui-ngx/src/app/core/http/http-utils.ts | 2 +- ui-ngx/src/app/core/http/public-api.ts | 2 +- ui-ngx/src/app/core/http/rule-chain.service.ts | 2 +- ui-ngx/src/app/core/http/tenant.service.ts | 2 +- ui-ngx/src/app/core/http/user.service.ts | 2 +- ui-ngx/src/app/core/http/widget.service.ts | 2 +- .../core/interceptors/global-http-interceptor.ts | 2 +- .../app/core/interceptors/interceptor-config.ts | 2 +- .../core/interceptors/interceptor-http-params.ts | 2 +- ui-ngx/src/app/core/interceptors/load.actions.ts | 2 +- ui-ngx/src/app/core/interceptors/load.models.ts | 2 +- ui-ngx/src/app/core/interceptors/load.reducer.ts | 2 +- .../src/app/core/interceptors/load.selectors.ts | 2 +- .../core/local-storage/local-storage.service.ts | 2 +- .../src/app/core/meta-reducers/debug.reducer.ts | 2 +- .../init-state-from-local-storage.reducer.ts | 2 +- .../core/notification/notification.actions.ts | 2 +- .../core/notification/notification.effects.ts | 2 +- .../app/core/notification/notification.models.ts | 2 +- .../core/notification/notification.reducer.ts | 2 +- ui-ngx/src/app/core/operator/enterZone.ts | 2 +- ui-ngx/src/app/core/public-api.ts | 2 +- ui-ngx/src/app/core/services/broadcast.models.ts | 2 +- .../src/app/core/services/broadcast.service.ts | 2 +- .../app/core/services/dashboard-utils.service.ts | 2 +- ui-ngx/src/app/core/services/dialog.service.ts | 2 +- .../dynamic-component-factory.service.ts | 2 +- .../src/app/core/services/item-buffer.service.ts | 2 +- ui-ngx/src/app/core/services/menu.models.ts | 2 +- ui-ngx/src/app/core/services/menu.service.ts | 2 +- .../app/core/services/notification.service.ts | 2 +- ui-ngx/src/app/core/services/public-api.ts | 2 +- ui-ngx/src/app/core/services/raf.service.ts | 2 +- .../src/app/core/services/resources.service.ts | 2 +- .../services/script/node-script-test.service.ts | 2 +- ui-ngx/src/app/core/services/time.service.ts | 2 +- ui-ngx/src/app/core/services/title.service.ts | 2 +- ui-ngx/src/app/core/services/utils.service.ts | 2 +- ui-ngx/src/app/core/services/window.service.ts | 2 +- ui-ngx/src/app/core/settings/settings.actions.ts | 2 +- ui-ngx/src/app/core/settings/settings.effects.ts | 2 +- ui-ngx/src/app/core/settings/settings.models.ts | 2 +- ui-ngx/src/app/core/settings/settings.reducer.ts | 2 +- .../src/app/core/settings/settings.selectors.ts | 2 +- ui-ngx/src/app/core/settings/settings.utils.ts | 2 +- .../core/translate/missing-translate-handler.ts | 2 +- .../core/translate/translate-default-compiler.ts | 2 +- ui-ngx/src/app/core/utils.ts | 2 +- .../app/core/ws/telemetry-websocket.service.ts | 2 +- .../modules/dashboard/dashboard-pages.module.ts | 2 +- .../dashboard/dashboard-pages.routing.module.ts | 2 +- .../dashboard/dashboard-routing.module.ts | 2 +- .../alarm/alarm-details-dialog.component.html | 2 +- .../alarm/alarm-details-dialog.component.ts | 2 +- .../home/components/alarm/alarm-table-config.ts | 2 +- .../alarm/alarm-table-header.component.html | 3 +-- .../alarm/alarm-table-header.component.scss | 2 +- .../alarm/alarm-table-header.component.ts | 2 +- .../components/alarm/alarm-table.component.html | 2 +- .../components/alarm/alarm-table.component.scss | 2 +- .../components/alarm/alarm-table.component.ts | 2 +- .../aliases-entity-select-panel.component.html | 2 +- .../aliases-entity-select-panel.component.scss | 2 +- .../aliases-entity-select-panel.component.ts | 2 +- .../alias/aliases-entity-select.component.html | 2 +- .../alias/aliases-entity-select.component.scss | 2 +- .../alias/aliases-entity-select.component.ts | 2 +- .../alias/entity-alias-dialog.component.html | 2 +- .../alias/entity-alias-dialog.component.scss | 2 +- .../alias/entity-alias-dialog.component.ts | 2 +- .../alias/entity-alias-select.component.html | 2 +- .../entity-alias-select.component.models.ts | 2 +- .../alias/entity-alias-select.component.scss | 2 +- .../alias/entity-alias-select.component.ts | 2 +- .../alias/entity-aliases-dialog.component.html | 2 +- .../alias/entity-aliases-dialog.component.scss | 3 +-- .../alias/entity-aliases-dialog.component.ts | 2 +- .../add-attribute-dialog.component.html | 2 +- .../attribute/add-attribute-dialog.component.ts | 2 +- ...add-widget-to-dashboard-dialog.component.html | 2 +- ...add-widget-to-dashboard-dialog.component.scss | 3 +-- .../add-widget-to-dashboard-dialog.component.ts | 2 +- .../attribute/attribute-table.component.html | 2 +- .../attribute/attribute-table.component.scss | 2 +- .../attribute/attribute-table.component.ts | 2 +- .../edit-attribute-value-panel.component.html | 2 +- .../edit-attribute-value-panel.component.scss | 2 +- .../edit-attribute-value-panel.component.ts | 2 +- .../audit-log-details-dialog.component.html | 2 +- .../audit-log-details-dialog.component.scss | 2 +- .../audit-log-details-dialog.component.ts | 2 +- .../audit-log/audit-log-table-config.ts | 2 +- .../audit-log/audit-log-table.component.html | 2 +- .../audit-log/audit-log-table.component.scss | 2 +- .../audit-log/audit-log-table.component.ts | 2 +- .../home/components/contact.component.html | 2 +- .../modules/home/components/contact.component.ts | 2 +- .../dashboard/dashboard.component.html | 2 +- .../dashboard/dashboard.component.scss | 2 +- .../components/dashboard/dashboard.component.ts | 2 +- .../home/components/dashboard/layout-button.scss | 2 +- .../select-target-layout-dialog.component.html | 2 +- .../select-target-layout-dialog.component.ts | 2 +- .../select-target-state-dialog.component.html | 2 +- .../select-target-state-dialog.component.ts | 2 +- .../home/components/details-panel.component.html | 2 +- .../home/components/details-panel.component.scss | 2 +- .../home/components/details-panel.component.ts | 2 +- .../entity/add-entity-dialog.component.html | 2 +- .../entity/add-entity-dialog.component.scss | 2 +- .../entity/add-entity-dialog.component.ts | 2 +- .../components/entity/contact-based.component.ts | 2 +- .../entity/entities-table.component.html | 2 +- .../entity/entities-table.component.scss | 2 +- .../entity/entities-table.component.ts | 2 +- .../entity/entity-details-panel.component.html | 2 +- .../entity/entity-details-panel.component.scss | 2 +- .../entity/entity-details-panel.component.ts | 2 +- .../entity/entity-filter-view.component.html | 2 +- .../entity/entity-filter-view.component.scss | 2 +- .../entity/entity-filter-view.component.ts | 2 +- .../entity/entity-filter.component.html | 2 +- .../entity/entity-filter.component.scss | 2 +- .../components/entity/entity-filter.component.ts | 2 +- .../entity/entity-table-header.component.ts | 2 +- .../components/entity/entity-tabs.component.ts | 2 +- .../home/components/entity/entity.component.ts | 2 +- .../event/event-content-dialog.component.html | 2 +- .../event/event-content-dialog.component.scss | 2 +- .../event/event-content-dialog.component.ts | 2 +- .../home/components/event/event-table-config.ts | 2 +- .../event/event-table-header.component.html | 3 +-- .../event/event-table-header.component.scss | 2 +- .../event/event-table-header.component.ts | 2 +- .../components/event/event-table.component.html | 2 +- .../components/event/event-table.component.scss | 2 +- .../components/event/event-table.component.ts | 2 +- .../home/components/home-components.module.ts | 2 +- .../import-dialog-csv.component.html | 2 +- .../import-dialog-csv.component.scss | 2 +- .../import-export/import-dialog-csv.component.ts | 2 +- .../import-export/import-dialog.component.html | 2 +- .../import-export/import-dialog.component.ts | 2 +- .../import-export/import-export.models.ts | 2 +- .../import-export/import-export.service.ts | 2 +- .../table-columns-assignment.component.html | 2 +- .../table-columns-assignment.component.scss | 2 +- .../table-columns-assignment.component.ts | 2 +- .../app/modules/home/components/public-api.ts | 2 +- .../relation/relation-dialog.component.html | 2 +- .../relation/relation-dialog.component.scss | 2 +- .../relation/relation-dialog.component.ts | 2 +- .../relation/relation-filters.component.html | 2 +- .../relation/relation-filters.component.scss | 2 +- .../relation/relation-filters.component.ts | 2 +- .../relation/relation-table.component.html | 2 +- .../relation/relation-table.component.scss | 2 +- .../relation/relation-table.component.ts | 2 +- .../components/shared-home-components.module.ts | 2 +- .../custom-action-pretty-editor.component.html | 2 +- .../custom-action-pretty-editor.component.scss | 2 +- .../custom-action-pretty-editor.component.ts | 2 +- ...m-action-pretty-resources-tabs.component.html | 2 +- ...m-action-pretty-resources-tabs.component.scss | 2 +- ...tom-action-pretty-resources-tabs.component.ts | 2 +- .../action/manage-widget-actions.component.html | 2 +- .../manage-widget-actions.component.models.ts | 2 +- .../action/manage-widget-actions.component.scss | 2 +- .../action/manage-widget-actions.component.ts | 2 +- .../action/widget-action-dialog.component.html | 2 +- .../action/widget-action-dialog.component.ts | 2 +- .../widget/data-key-config-dialog.component.html | 2 +- .../widget/data-key-config-dialog.component.scss | 2 +- .../widget/data-key-config-dialog.component.ts | 2 +- .../widget/data-key-config.component.html | 2 +- .../widget/data-key-config.component.scss | 2 +- .../widget/data-key-config.component.ts | 2 +- .../components/widget/data-keys.component.html | 2 +- .../widget/data-keys.component.models.ts | 2 +- .../components/widget/data-keys.component.scss | 3 +-- .../components/widget/data-keys.component.ts | 2 +- .../dialog/custom-dialog-container.component.ts | 2 +- .../widget/dialog/custom-dialog.component.ts | 2 +- .../widget/dialog/custom-dialog.service.ts | 2 +- .../widget/dynamic-widget.component.ts | 2 +- .../widget/legend-config-panel.component.html | 2 +- .../widget/legend-config-panel.component.scss | 2 +- .../widget/legend-config-panel.component.ts | 2 +- .../widget/legend-config.component.html | 2 +- .../components/widget/legend-config.component.ts | 2 +- .../home/components/widget/legend.component.html | 2 +- .../home/components/widget/legend.component.scss | 3 +-- .../home/components/widget/legend.component.ts | 2 +- .../lib/alarm-status-filter-panel.component.html | 2 +- .../lib/alarm-status-filter-panel.component.scss | 2 +- .../lib/alarm-status-filter-panel.component.ts | 2 +- .../lib/alarms-table-widget.component.html | 2 +- .../lib/alarms-table-widget.component.scss | 2 +- .../widget/lib/alarms-table-widget.component.ts | 2 +- .../widget/lib/analogue-compass.models.ts | 2 +- .../components/widget/lib/analogue-compass.ts | 2 +- .../widget/lib/analogue-gauge.models.ts | 2 +- .../widget/lib/analogue-linear-gauge.models.ts | 2 +- .../widget/lib/analogue-linear-gauge.ts | 2 +- .../widget/lib/analogue-radial-gauge.models.ts | 2 +- .../widget/lib/analogue-radial-gauge.ts | 2 +- .../widget/lib/canvas-digital-gauge.ts | 2 +- .../date-range-navigator-panel.component.html | 2 +- .../date-range-navigator-panel.component.scss | 2 +- .../date-range-navigator.component.html | 2 +- .../date-range-navigator.component.scss | 3 +-- .../date-range-navigator.component.ts | 2 +- .../date-range-navigator.models.ts | 2 +- .../widget/lib/digital-gauge.models.ts | 2 +- .../home/components/widget/lib/digital-gauge.ts | 2 +- .../lib/display-columns-panel.component.html | 2 +- .../lib/display-columns-panel.component.scss | 2 +- .../lib/display-columns-panel.component.ts | 2 +- .../lib/entities-hierarchy-widget.component.html | 2 +- .../lib/entities-hierarchy-widget.component.scss | 3 +-- .../lib/entities-hierarchy-widget.component.ts | 2 +- .../lib/entities-hierarchy-widget.models.ts | 2 +- .../lib/entities-table-widget.component.html | 2 +- .../lib/entities-table-widget.component.scss | 2 +- .../lib/entities-table-widget.component.ts | 2 +- .../components/widget/lib/flot-widget.models.ts | 2 +- .../home/components/widget/lib/flot-widget.ts | 2 +- .../widget/lib/rpc/knob.component.html | 2 +- .../widget/lib/rpc/knob.component.scss | 2 +- .../components/widget/lib/rpc/knob.component.ts | 2 +- .../widget/lib/rpc/led-indicator.component.html | 2 +- .../widget/lib/rpc/led-indicator.component.scss | 2 +- .../widget/lib/rpc/led-indicator.component.ts | 2 +- .../widget/lib/rpc/round-switch.component.html | 2 +- .../widget/lib/rpc/round-switch.component.scss | 2 +- .../widget/lib/rpc/round-switch.component.ts | 2 +- .../widget/lib/rpc/rpc-widgets.module.ts | 2 +- .../widget/lib/rpc/switch.component.html | 2 +- .../widget/lib/rpc/switch.component.scss | 3 +-- .../widget/lib/rpc/switch.component.ts | 2 +- .../components/widget/lib/settings.models.ts | 2 +- .../components/widget/lib/table-widget.models.ts | 2 +- .../home/components/widget/lib/table-widget.scss | 2 +- .../lib/timeseries-table-widget.component.html | 2 +- .../lib/timeseries-table-widget.component.scss | 2 +- .../lib/timeseries-table-widget.component.ts | 2 +- .../widget/widget-component.service.ts | 2 +- .../widget/widget-components.module.ts | 2 +- .../widget/widget-config.component.html | 2 +- .../widget/widget-config.component.models.ts | 2 +- .../widget/widget-config.component.scss | 3 +-- .../components/widget/widget-config.component.ts | 2 +- .../home/components/widget/widget.component.html | 2 +- .../home/components/widget/widget.component.scss | 3 +-- .../home/components/widget/widget.component.ts | 2 +- ...dd-entities-to-customer-dialog.component.html | 2 +- .../add-entities-to-customer-dialog.component.ts | 2 +- .../assign-to-customer-dialog.component.html | 2 +- .../assign-to-customer-dialog.component.ts | 2 +- .../modules/home/dialogs/home-dialogs.module.ts | 2 +- .../modules/home/dialogs/home-dialogs.service.ts | 2 +- .../src/app/modules/home/home-routing.module.ts | 2 +- ui-ngx/src/app/modules/home/home.component.html | 2 +- ui-ngx/src/app/modules/home/home.component.scss | 2 +- ui-ngx/src/app/modules/home/home.component.ts | 2 +- ui-ngx/src/app/modules/home/home.module.ts | 2 +- .../modules/home/menu/menu-link.component.html | 2 +- .../modules/home/menu/menu-link.component.scss | 2 +- .../app/modules/home/menu/menu-link.component.ts | 2 +- .../modules/home/menu/menu-toggle.component.html | 2 +- .../modules/home/menu/menu-toggle.component.scss | 2 +- .../modules/home/menu/menu-toggle.component.ts | 2 +- .../modules/home/menu/side-menu.component.html | 2 +- .../modules/home/menu/side-menu.component.scss | 2 +- .../app/modules/home/menu/side-menu.component.ts | 2 +- .../app/modules/home/models/contact.models.ts | 2 +- .../home/models/dashboard-component.models.ts | 2 +- .../models/datasource/attribute-datasource.ts | 2 +- .../home/models/datasource/entity-datasource.ts | 2 +- .../models/datasource/relation-datasource.ts | 2 +- .../entity/entities-table-config.models.ts | 2 +- .../models/entity/entity-component.models.ts | 2 +- .../home/models/searchable-component.models.ts | 2 +- .../home/models/widget-component.models.ts | 2 +- .../home/pages/admin/admin-routing.module.ts | 2 +- .../app/modules/home/pages/admin/admin.module.ts | 2 +- .../pages/admin/general-settings.component.html | 2 +- .../pages/admin/general-settings.component.scss | 2 +- .../pages/admin/general-settings.component.ts | 2 +- .../home/pages/admin/mail-server.component.html | 2 +- .../home/pages/admin/mail-server.component.scss | 2 +- .../home/pages/admin/mail-server.component.ts | 2 +- .../pages/admin/security-settings.component.html | 2 +- .../pages/admin/security-settings.component.scss | 2 +- .../pages/admin/security-settings.component.ts | 2 +- .../modules/home/pages/admin/settings-card.scss | 2 +- .../home/pages/asset/asset-routing.module.ts | 2 +- .../asset/asset-table-header.component.html | 2 +- .../asset/asset-table-header.component.scss | 2 +- .../pages/asset/asset-table-header.component.ts | 2 +- .../home/pages/asset/asset-tabs.component.html | 2 +- .../home/pages/asset/asset-tabs.component.ts | 2 +- .../home/pages/asset/asset.component.html | 2 +- .../home/pages/asset/asset.component.scss | 3 +-- .../modules/home/pages/asset/asset.component.ts | 2 +- .../app/modules/home/pages/asset/asset.module.ts | 2 +- .../pages/asset/assets-table-config.resolver.ts | 2 +- .../pages/audit-log/audit-log-routing.module.ts | 2 +- .../home/pages/audit-log/audit-log.module.ts | 2 +- .../pages/customer/customer-routing.module.ts | 2 +- .../pages/customer/customer-tabs.component.html | 2 +- .../pages/customer/customer-tabs.component.ts | 2 +- .../home/pages/customer/customer.component.html | 2 +- .../home/pages/customer/customer.component.ts | 2 +- .../home/pages/customer/customer.module.ts | 2 +- .../customer/customers-table-config.resolver.ts | 2 +- .../dashboard/add-widget-dialog.component.html | 2 +- .../dashboard/add-widget-dialog.component.ts | 2 +- .../dashboard/dashboard-form.component.html | 2 +- .../dashboard/dashboard-form.component.scss | 3 +-- .../pages/dashboard/dashboard-form.component.ts | 2 +- .../dashboard/dashboard-page.component.html | 2 +- .../dashboard/dashboard-page.component.scss | 3 +-- .../pages/dashboard/dashboard-page.component.ts | 2 +- .../pages/dashboard/dashboard-page.models.ts | 2 +- .../pages/dashboard/dashboard-routing.module.ts | 2 +- .../dashboard-settings-dialog.component.html | 2 +- .../dashboard-settings-dialog.component.ts | 2 +- .../dashboard/dashboard-tabs.component.html | 2 +- .../pages/dashboard/dashboard-tabs.component.ts | 2 +- .../dashboard/dashboard-toolbar.component.html | 2 +- .../dashboard/dashboard-toolbar.component.scss | 2 +- .../dashboard/dashboard-toolbar.component.ts | 2 +- .../dashboard-widget-select.component.html | 2 +- .../dashboard-widget-select.component.scss | 2 +- .../dashboard-widget-select.component.ts | 2 +- .../home/pages/dashboard/dashboard.module.ts | 2 +- .../dashboards-table-config.resolver.ts | 2 +- .../pages/dashboard/edit-widget.component.html | 2 +- .../pages/dashboard/edit-widget.component.ts | 2 +- .../layout/dashboard-layout.component.html | 2 +- .../layout/dashboard-layout.component.scss | 2 +- .../layout/dashboard-layout.component.ts | 2 +- .../home/pages/dashboard/layout/layout.models.ts | 2 +- ...anage-dashboard-layouts-dialog.component.html | 2 +- .../manage-dashboard-layouts-dialog.component.ts | 2 +- .../make-dashboard-public-dialog.component.html | 2 +- .../make-dashboard-public-dialog.component.ts | 2 +- ...age-dashboard-customers-dialog.component.html | 2 +- ...anage-dashboard-customers-dialog.component.ts | 2 +- .../states/dashboard-state-dialog.component.html | 2 +- .../states/dashboard-state-dialog.component.ts | 2 +- .../default-state-controller.component.html | 2 +- .../default-state-controller.component.scss | 2 +- .../states/default-state-controller.component.ts | 2 +- .../entity-state-controller.component.html | 3 +-- .../entity-state-controller.component.scss | 2 +- .../states/entity-state-controller.component.ts | 2 +- ...manage-dashboard-states-dialog.component.html | 2 +- ...e-dashboard-states-dialog.component.models.ts | 2 +- ...manage-dashboard-states-dialog.component.scss | 2 +- .../manage-dashboard-states-dialog.component.ts | 2 +- .../states/state-controller.component.ts | 2 +- .../dashboard/states/state-controller.models.ts | 2 +- .../states/states-component.directive.ts | 2 +- .../dashboard/states/states-controller.module.ts | 2 +- .../states/states-controller.service.ts | 2 +- .../device-credentials-dialog.component.html | 2 +- .../device-credentials-dialog.component.ts | 2 +- .../home/pages/device/device-routing.module.ts | 2 +- .../device/device-table-header.component.html | 2 +- .../device/device-table-header.component.scss | 2 +- .../device/device-table-header.component.ts | 2 +- .../home/pages/device/device-tabs.component.html | 2 +- .../home/pages/device/device-tabs.component.ts | 2 +- .../home/pages/device/device.component.html | 2 +- .../home/pages/device/device.component.scss | 3 +-- .../home/pages/device/device.component.ts | 2 +- .../modules/home/pages/device/device.module.ts | 2 +- .../device/devices-table-config.resolver.ts | 2 +- .../entity-view/entity-view-routing.module.ts | 2 +- .../entity-view-table-header.component.html | 2 +- .../entity-view-table-header.component.scss | 2 +- .../entity-view-table-header.component.ts | 2 +- .../entity-view/entity-view-tabs.component.html | 2 +- .../entity-view/entity-view-tabs.component.ts | 2 +- .../pages/entity-view/entity-view.component.html | 2 +- .../pages/entity-view/entity-view.component.scss | 3 +-- .../pages/entity-view/entity-view.component.ts | 2 +- .../home/pages/entity-view/entity-view.module.ts | 2 +- .../entity-views-table-config.resolver.ts | 2 +- .../home-links/home-links-routing.module.ts | 2 +- .../pages/home-links/home-links.component.html | 2 +- .../pages/home-links/home-links.component.scss | 2 +- .../pages/home-links/home-links.component.ts | 2 +- .../home/pages/home-links/home-links.module.ts | 2 +- .../app/modules/home/pages/home-pages.module.ts | 2 +- .../change-password-dialog.component.html | 2 +- .../change-password-dialog.component.scss | 2 +- .../profile/change-password-dialog.component.ts | 2 +- .../home/pages/profile/profile-routing.module.ts | 2 +- .../home/pages/profile/profile.component.html | 2 +- .../home/pages/profile/profile.component.scss | 2 +- .../home/pages/profile/profile.component.ts | 2 +- .../modules/home/pages/profile/profile.module.ts | 2 +- ui-ngx/src/app/modules/home/pages/public-api.ts | 2 +- .../add-rule-node-dialog.component.html | 2 +- .../add-rule-node-link-dialog.component.html | 2 +- .../add-rule-node-link-dialog.component.scss | 2 +- .../pages/rulechain/link-labels.component.html | 2 +- .../pages/rulechain/link-labels.component.ts | 2 +- .../home/pages/rulechain/rule-node-colors.scss | 3 +-- .../rulechain/rule-node-config.component.html | 2 +- .../rulechain/rule-node-config.component.scss | 2 +- .../rulechain/rule-node-config.component.ts | 2 +- .../rulechain/rule-node-details.component.html | 2 +- .../rulechain/rule-node-details.component.scss | 3 +-- .../rulechain/rule-node-details.component.ts | 2 +- .../rulechain/rule-node-link.component.html | 2 +- .../pages/rulechain/rule-node-link.component.ts | 2 +- .../rulechain/rulechain-page.component.html | 2 +- .../rulechain/rulechain-page.component.scss | 3 +-- .../pages/rulechain/rulechain-page.component.ts | 2 +- .../pages/rulechain/rulechain-page.models.ts | 2 +- .../pages/rulechain/rulechain-routing.module.ts | 2 +- .../rulechain/rulechain-tabs.component.html | 2 +- .../pages/rulechain/rulechain-tabs.component.ts | 2 +- .../pages/rulechain/rulechain.component.html | 2 +- .../pages/rulechain/rulechain.component.scss | 3 +-- .../home/pages/rulechain/rulechain.component.ts | 2 +- .../home/pages/rulechain/rulechain.module.ts | 2 +- .../rulechains-table-config.resolver.ts | 2 +- .../home/pages/rulechain/rulenode.component.html | 2 +- .../home/pages/rulechain/rulenode.component.scss | 3 +-- .../home/pages/rulechain/rulenode.component.ts | 2 +- .../home/pages/tenant/tenant-routing.module.ts | 2 +- .../home/pages/tenant/tenant-tabs.component.html | 2 +- .../home/pages/tenant/tenant-tabs.component.ts | 2 +- .../home/pages/tenant/tenant.component.html | 2 +- .../home/pages/tenant/tenant.component.ts | 2 +- .../modules/home/pages/tenant/tenant.module.ts | 2 +- .../tenant/tenants-table-config.resolver.ts | 2 +- .../user/activation-link-dialog.component.html | 2 +- .../user/activation-link-dialog.component.ts | 2 +- .../pages/user/add-user-dialog.component.html | 2 +- .../home/pages/user/add-user-dialog.component.ts | 2 +- .../home/pages/user/user-routing.module.ts | 2 +- .../home/pages/user/user-tabs.component.html | 2 +- .../home/pages/user/user-tabs.component.ts | 2 +- .../modules/home/pages/user/user.component.html | 2 +- .../modules/home/pages/user/user.component.scss | 2 +- .../modules/home/pages/user/user.component.ts | 2 +- .../app/modules/home/pages/user/user.module.ts | 2 +- .../pages/user/users-table-config.resolver.ts | 2 +- .../save-widget-type-as-dialog.component.html | 2 +- .../save-widget-type-as-dialog.component.ts | 2 +- .../select-widget-type-dialog.component.html | 2 +- .../select-widget-type-dialog.component.scss | 3 +-- .../select-widget-type-dialog.component.ts | 2 +- .../pages/widget/widget-editor.component.html | 2 +- .../pages/widget/widget-editor.component.scss | 3 +-- .../home/pages/widget/widget-editor.component.ts | 2 +- .../widget/widget-library-routing.module.ts | 2 +- .../pages/widget/widget-library.component.html | 2 +- .../pages/widget/widget-library.component.scss | 3 +-- .../pages/widget/widget-library.component.ts | 2 +- .../home/pages/widget/widget-library.module.ts | 2 +- .../pages/widget/widgets-bundle.component.html | 2 +- .../pages/widget/widgets-bundle.component.scss | 3 +-- .../pages/widget/widgets-bundle.component.ts | 2 +- .../widgets-bundles-table-config.resolver.ts | 2 +- ui-ngx/src/app/modules/home/public-api.ts | 2 +- .../app/modules/login/login-routing.module.ts | 2 +- ui-ngx/src/app/modules/login/login.module.ts | 2 +- .../pages/login/create-password.component.html | 2 +- .../pages/login/create-password.component.scss | 2 +- .../pages/login/create-password.component.ts | 2 +- .../login/pages/login/login.component.html | 2 +- .../login/pages/login/login.component.scss | 2 +- .../modules/login/pages/login/login.component.ts | 2 +- .../login/reset-password-request.component.html | 2 +- .../login/reset-password-request.component.scss | 2 +- .../login/reset-password-request.component.ts | 2 +- .../pages/login/reset-password.component.html | 2 +- .../pages/login/reset-password.component.scss | 2 +- .../pages/login/reset-password.component.ts | 2 +- .../animations/speed-dial-fab.animations.ts | 2 +- .../shared/components/breadcrumb.component.html | 2 +- .../shared/components/breadcrumb.component.scss | 2 +- .../shared/components/breadcrumb.component.ts | 2 +- ui-ngx/src/app/shared/components/breadcrumb.ts | 2 +- .../shared/components/cheatsheet.component.ts | 2 +- .../components/circular-progress.directive.ts | 2 +- .../shared/components/color-input.component.html | 2 +- .../shared/components/color-input.component.scss | 3 +-- .../shared/components/color-input.component.ts | 2 +- .../dashboard-autocomplete.component.html | 2 +- .../dashboard-autocomplete.component.ts | 2 +- .../dashboard-select-panel.component.html | 2 +- .../dashboard-select-panel.component.scss | 2 +- .../dashboard-select-panel.component.ts | 2 +- .../components/dashboard-select.component.html | 2 +- .../components/dashboard-select.component.scss | 2 +- .../components/dashboard-select.component.ts | 2 +- .../app/shared/components/dialog.component.ts | 2 +- .../dialog/alert-dialog.component.html | 2 +- .../dialog/alert-dialog.component.scss | 2 +- .../components/dialog/alert-dialog.component.ts | 2 +- .../dialog/color-picker-dialog.component.html | 2 +- .../dialog/color-picker-dialog.component.ts | 2 +- .../dialog/confirm-dialog.component.html | 2 +- .../dialog/confirm-dialog.component.scss | 2 +- .../dialog/confirm-dialog.component.ts | 2 +- .../dialog/material-icons-dialog.component.html | 2 +- .../dialog/material-icons-dialog.component.scss | 2 +- .../dialog/material-icons-dialog.component.ts | 2 +- .../node-script-test-dialog.component.html | 2 +- .../node-script-test-dialog.component.scss | 2 +- .../dialog/node-script-test-dialog.component.ts | 2 +- .../components/dialog/todo-dialog.component.html | 2 +- .../components/dialog/todo-dialog.component.scss | 2 +- .../components/dialog/todo-dialog.component.ts | 2 +- .../entity/entity-autocomplete.component.html | 2 +- .../entity/entity-autocomplete.component.ts | 2 +- .../entity/entity-keys-list.component.html | 2 +- .../entity/entity-keys-list.component.scss | 2 +- .../entity/entity-keys-list.component.ts | 2 +- .../entity/entity-list-select.component.html | 2 +- .../entity/entity-list-select.component.scss | 2 +- .../entity/entity-list-select.component.ts | 2 +- .../components/entity/entity-list.component.html | 2 +- .../components/entity/entity-list.component.scss | 2 +- .../components/entity/entity-list.component.ts | 2 +- .../entity/entity-select.component.html | 2 +- .../entity/entity-select.component.scss | 2 +- .../components/entity/entity-select.component.ts | 2 +- .../entity-subtype-autocomplete.component.html | 2 +- .../entity-subtype-autocomplete.component.ts | 2 +- .../entity/entity-subtype-list.component.html | 2 +- .../entity/entity-subtype-list.component.scss | 2 +- .../entity/entity-subtype-list.component.ts | 2 +- .../entity/entity-subtype-select.component.html | 2 +- .../entity/entity-subtype-select.component.scss | 2 +- .../entity/entity-subtype-select.component.ts | 2 +- .../entity/entity-type-list.component.html | 2 +- .../entity/entity-type-list.component.ts | 2 +- .../entity/entity-type-select.component.html | 2 +- .../entity/entity-type-select.component.scss | 2 +- .../entity/entity-type-select.component.ts | 2 +- .../shared/components/fab-toolbar.component.html | 2 +- .../shared/components/fab-toolbar.component.scss | 2 +- .../shared/components/fab-toolbar.component.ts | 2 +- .../shared/components/file-input.component.html | 2 +- .../shared/components/file-input.component.scss | 2 +- .../shared/components/file-input.component.ts | 2 +- .../components/footer-fab-buttons.component.html | 2 +- .../components/footer-fab-buttons.component.scss | 3 +-- .../components/footer-fab-buttons.component.ts | 2 +- .../app/shared/components/footer.component.html | 2 +- .../app/shared/components/footer.component.scss | 2 +- .../app/shared/components/footer.component.ts | 2 +- .../shared/components/fullscreen.directive.ts | 2 +- .../app/shared/components/help.component.html | 2 +- .../src/app/shared/components/help.component.ts | 2 +- .../app/shared/components/hotkeys.directive.ts | 2 +- .../shared/components/image-input.component.html | 2 +- .../shared/components/image-input.component.scss | 2 +- .../shared/components/image-input.component.ts | 2 +- .../app/shared/components/js-func.component.html | 2 +- .../app/shared/components/js-func.component.scss | 2 +- .../app/shared/components/js-func.component.ts | 2 +- .../components/json-content.component.html | 2 +- .../components/json-content.component.scss | 3 +-- .../shared/components/json-content.component.ts | 2 +- .../json-form/json-form-component.models.ts | 2 +- .../json-form/json-form.component.html | 2 +- .../json-form/json-form.component.scss | 2 +- .../components/json-form/json-form.component.ts | 2 +- .../json-form/react/json-form-utils.ts | 2 +- .../json-form/react/json-form.models.ts | 2 +- .../components/json-form/react/json-form.scss | 2 +- .../json-form/react/styles/thingsboardTheme.ts | 2 +- .../components/json-object-edit.component.html | 2 +- .../components/json-object-edit.component.scss | 3 +-- .../components/json-object-edit.component.ts | 2 +- .../app/shared/components/kv-map.component.html | 3 +-- .../app/shared/components/kv-map.component.scss | 2 +- .../app/shared/components/kv-map.component.ts | 2 +- .../shared/components/led-light.component.html | 2 +- .../app/shared/components/led-light.component.ts | 2 +- .../app/shared/components/logo.component.html | 2 +- .../app/shared/components/logo.component.scss | 2 +- .../src/app/shared/components/logo.component.ts | 2 +- .../components/mat-chip-draggable.directive.ts | 2 +- .../material-icon-select.component.html | 2 +- .../material-icon-select.component.scss | 2 +- .../components/material-icon-select.component.ts | 2 +- .../message-type-autocomplete.component.html | 2 +- .../message-type-autocomplete.component.ts | 2 +- .../shared/components/nav-tree.component.html | 2 +- .../shared/components/nav-tree.component.scss | 3 +-- .../app/shared/components/nav-tree.component.ts | 2 +- .../src/app/shared/components/page.component.ts | 2 +- ui-ngx/src/app/shared/components/public-api.ts | 2 +- .../relation-type-autocomplete.component.html | 2 +- .../relation-type-autocomplete.component.ts | 2 +- .../shared/components/snack-bar-component.html | 2 +- .../shared/components/snack-bar-component.scss | 2 +- .../components/socialshare-panel.component.html | 2 +- .../components/socialshare-panel.component.scss | 2 +- .../components/socialshare-panel.component.ts | 2 +- .../app/shared/components/tb-anchor.component.ts | 2 +- .../shared/components/tb-checkbox.component.html | 2 +- .../shared/components/tb-checkbox.component.ts | 2 +- .../app/shared/components/tb-error.component.ts | 2 +- .../time/datetime-period.component.html | 2 +- .../time/datetime-period.component.scss | 2 +- .../components/time/datetime-period.component.ts | 2 +- .../components/time/datetime.component.html | 2 +- .../components/time/datetime.component.scss | 2 +- .../shared/components/time/datetime.component.ts | 2 +- .../components/time/timeinterval.component.html | 2 +- .../components/time/timeinterval.component.scss | 2 +- .../components/time/timeinterval.component.ts | 2 +- .../time/timewindow-panel.component.html | 2 +- .../time/timewindow-panel.component.scss | 2 +- .../time/timewindow-panel.component.ts | 2 +- .../components/time/timewindow.component.html | 2 +- .../components/time/timewindow.component.scss | 2 +- .../components/time/timewindow.component.ts | 2 +- .../src/app/shared/components/toast.directive.ts | 2 +- .../shared/components/user-menu.component.html | 2 +- .../shared/components/user-menu.component.scss | 2 +- .../app/shared/components/user-menu.component.ts | 2 +- .../shared/components/value-input.component.html | 2 +- .../shared/components/value-input.component.scss | 3 +-- .../shared/components/value-input.component.ts | 2 +- .../widgets-bundle-select.component.html | 2 +- .../widgets-bundle-select.component.scss | 3 +-- .../widgets-bundle-select.component.ts | 2 +- ui-ngx/src/app/shared/models/alarm.models.ts | 2 +- ui-ngx/src/app/shared/models/alias.models.ts | 2 +- ui-ngx/src/app/shared/models/asset.models.ts | 2 +- ui-ngx/src/app/shared/models/audit-log.models.ts | 2 +- ui-ngx/src/app/shared/models/authority.enum.ts | 2 +- ui-ngx/src/app/shared/models/base-data.ts | 2 +- .../shared/models/component-descriptor.models.ts | 2 +- ui-ngx/src/app/shared/models/constants.ts | 2 +- .../src/app/shared/models/contact-based.model.ts | 2 +- ui-ngx/src/app/shared/models/customer.model.ts | 2 +- ui-ngx/src/app/shared/models/dashboard.models.ts | 2 +- ui-ngx/src/app/shared/models/device.models.ts | 2 +- .../src/app/shared/models/entity-type.models.ts | 16 ++++++++++++++++ .../src/app/shared/models/entity-view.models.ts | 2 +- ui-ngx/src/app/shared/models/entity.models.ts | 2 +- ui-ngx/src/app/shared/models/error.models.ts | 2 +- ui-ngx/src/app/shared/models/event.models.ts | 2 +- ui-ngx/src/app/shared/models/id/alarm-id.ts | 2 +- ui-ngx/src/app/shared/models/id/asset-id.ts | 2 +- ui-ngx/src/app/shared/models/id/audit-log-id.ts | 2 +- ui-ngx/src/app/shared/models/id/customer-id.ts | 2 +- ui-ngx/src/app/shared/models/id/dashboard-id.ts | 2 +- .../shared/models/id/device-credentials-id.ts | 2 +- ui-ngx/src/app/shared/models/id/device-id.ts | 2 +- ui-ngx/src/app/shared/models/id/entity-id.ts | 2 +- .../src/app/shared/models/id/entity-view-id.ts | 2 +- ui-ngx/src/app/shared/models/id/event-id.ts | 2 +- ui-ngx/src/app/shared/models/id/has-uuid.ts | 2 +- ui-ngx/src/app/shared/models/id/public-api.ts | 2 +- ui-ngx/src/app/shared/models/id/rule-chain-id.ts | 2 +- ui-ngx/src/app/shared/models/id/rule-node-id.ts | 2 +- ui-ngx/src/app/shared/models/id/tenant-id.ts | 2 +- ui-ngx/src/app/shared/models/id/user-id.ts | 2 +- .../src/app/shared/models/id/widget-type-id.ts | 2 +- .../app/shared/models/id/widgets-bundle-id.ts | 2 +- ui-ngx/src/app/shared/models/login.models.ts | 2 +- ui-ngx/src/app/shared/models/material.models.ts | 2 +- ui-ngx/src/app/shared/models/page/page-data.ts | 2 +- ui-ngx/src/app/shared/models/page/page-link.ts | 2 +- ui-ngx/src/app/shared/models/page/public-api.ts | 2 +- ui-ngx/src/app/shared/models/page/sort-order.ts | 2 +- ui-ngx/src/app/shared/models/public-api.ts | 2 +- ui-ngx/src/app/shared/models/relation.models.ts | 2 +- .../src/app/shared/models/rule-chain.models.ts | 2 +- ui-ngx/src/app/shared/models/rule-node.models.ts | 2 +- ui-ngx/src/app/shared/models/settings.models.ts | 2 +- .../shared/models/telemetry/telemetry.models.ts | 2 +- ui-ngx/src/app/shared/models/tenant.model.ts | 2 +- ui-ngx/src/app/shared/models/time/time.models.ts | 2 +- ui-ngx/src/app/shared/models/user.model.ts | 2 +- ui-ngx/src/app/shared/models/widget.models.ts | 2 +- .../app/shared/models/widgets-bundle.model.ts | 2 +- .../app/shared/models/window-message.model.ts | 2 +- ui-ngx/src/app/shared/pipe/enum-to-array.pipe.ts | 2 +- ui-ngx/src/app/shared/pipe/highlight.pipe.ts | 2 +- .../app/shared/pipe/keyboard-shortcut.pipe.ts | 2 +- .../pipe/milliseconds-to-time-string.pipe.ts | 2 +- ui-ngx/src/app/shared/pipe/nospace.pipe.ts | 2 +- ui-ngx/src/app/shared/pipe/public-api.ts | 2 +- ui-ngx/src/app/shared/pipe/truncate.pipe.ts | 2 +- ui-ngx/src/app/shared/public-api.ts | 2 +- ui-ngx/src/app/shared/shared.module.ts | 2 +- ui-ngx/src/environments/environment.prod.ts | 2 +- ui-ngx/src/environments/environment.ts | 2 +- ui-ngx/src/index.html | 2 +- ui-ngx/src/karma.conf.js | 2 +- ui-ngx/src/main.ts | 2 +- ui-ngx/src/polyfills.ts | 2 +- ui-ngx/src/scss/animations.scss | 2 +- ui-ngx/src/scss/constants.scss | 2 +- ui-ngx/src/scss/fonts.scss | 2 +- ui-ngx/src/scss/mixins.scss | 3 +-- ui-ngx/src/styles.scss | 2 +- ui-ngx/src/test.ts | 2 +- ui-ngx/src/theme.scss | 2 +- ui-ngx/src/typings/jquery.flot.typings.d.ts | 2 +- ui-ngx/src/typings/jquery.jstree.typings.d.ts | 2 +- ui-ngx/src/typings/jquery.typings.d.ts | 2 +- ui-ngx/src/typings/rawloader.typings.d.ts | 2 +- ui-ngx/src/typings/split.js.typings.d.ts | 2 +- ui-ngx/src/zone-flags.ts | 2 +- 1934 files changed, 1984 insertions(+), 2007 deletions(-) diff --git a/application/build.gradle b/application/build.gradle index 1d6017c964..1ea603039a 100644 --- a/application/build.gradle +++ b/application/build.gradle @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2019 The Thingsboard Authors + * Copyright © 2016-2020 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/application/pom.xml b/application/pom.xml index 24e8d681f7..5bd7c69e8b 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -1,6 +1,6 @@ - alarm.alarm-status - event.event-type -